From dbc2eb4771f985b9e6e87b0925bf62f897555272 Mon Sep 17 00:00:00 2001 From: Kyle Chang Date: Tue, 18 Sep 2018 23:15:36 -0400 Subject: [PATCH 0001/2815] Create ZoomIn Mod --- osu.Game.Rulesets.Osu/Mods/OsuModZoomIn.cs | 55 ++++++++++++++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 1 + 2 files changed, 56 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModZoomIn.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModZoomIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModZoomIn.cs new file mode 100644 index 0000000000..b531b9b3d0 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModZoomIn.cs @@ -0,0 +1,55 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using OpenTK; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModZoomIn : Mod, IApplicableToDrawableHitObjects + { + public override string Name => "Zoom In"; + public override string ShortenedName => "ZI"; + public override FontAwesome Icon => FontAwesome.fa_dot_circle_o; + public override ModType Type => ModType.Fun; + public override string Description => "Circles zoom in. No approach circles."; + public override double ScoreMultiplier => 1; + + public void ApplyToDrawableHitObjects(IEnumerable drawables) + { + foreach (var drawable in drawables) + { + drawable.ApplyCustomUpdateState += ApplyBounceState; + } + } + + protected void ApplyBounceState(DrawableHitObject drawable, ArmedState state) + { + if (!(drawable is DrawableOsuHitObject d)) + return; + + var h = (OsuHitObject)drawable.HitObject; + + double appearTime = h.StartTime - h.TimePreempt; + double moveDuration = h.TimePreempt; + + using (drawable.BeginAbsoluteSequence(appearTime, true)) + { + var origScale = drawable.Scale; + + drawable + .ScaleTo(0.0f) + .ScaleTo(origScale, moveDuration, Easing.InOutSine); + } + + // Hide approach circle + (drawable as DrawableHitCircle)?.ApproachCircle.Hide(); + } + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 6736d10dab..029ca7d43e 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -121,6 +121,7 @@ namespace osu.Game.Rulesets.Osu return new Mod[] { new OsuModTransform(), new OsuModWiggle(), + new OsuModZoomIn(), }; default: return new Mod[] { }; From 1e3599bddef4004561454f049b18439c607f93c8 Mon Sep 17 00:00:00 2001 From: Kyle Chang Date: Wed, 19 Sep 2018 23:56:25 -0400 Subject: [PATCH 0002/2815] Disable fade in --- osu.Game.Rulesets.Osu/Mods/OsuModZoomIn.cs | 51 ++++++++++++++++------ 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModZoomIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModZoomIn.cs index b531b9b3d0..8e73ece12c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModZoomIn.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModZoomIn.cs @@ -2,13 +2,13 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; +using System.Linq; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; -using OpenTK; namespace osu.Game.Rulesets.Osu.Mods { @@ -31,25 +31,48 @@ namespace osu.Game.Rulesets.Osu.Mods protected void ApplyBounceState(DrawableHitObject drawable, ArmedState state) { - if (!(drawable is DrawableOsuHitObject d)) - return; + if (!(drawable is DrawableOsuHitObject)) return; + if (state != ArmedState.Idle) return; var h = (OsuHitObject)drawable.HitObject; + var appearTime = h.StartTime - h.TimePreempt; + var moveDuration = h.TimePreempt; - double appearTime = h.StartTime - h.TimePreempt; - double moveDuration = h.TimePreempt; - - using (drawable.BeginAbsoluteSequence(appearTime, true)) + switch (drawable) { - var origScale = drawable.Scale; + case DrawableHitCircle circle: + foreach (var t in circle.Transforms.Where(t => t.TargetMember == "Alpha")) + circle.RemoveTransform(t); + using (circle.BeginAbsoluteSequence(appearTime, true)) + { + var origScale = drawable.Scale; - drawable - .ScaleTo(0.0f) - .ScaleTo(origScale, moveDuration, Easing.InOutSine); + circle + .ScaleTo(0) + .ScaleTo(origScale, moveDuration, Easing.OutSine) + .FadeTo(1); + } + + circle.ApproachCircle.Hide(); + + break; + + case DrawableSlider slider: + foreach (var t in slider.Transforms.Where(t => t.TargetMember == "Alpha")) + slider.RemoveTransform(t); + + using (slider.BeginAbsoluteSequence(appearTime, true)) + { + var origScale = slider.Scale; + + slider + .ScaleTo(0) + .ScaleTo(origScale, moveDuration, Easing.OutSine) + .FadeTo(1); + } + + break; } - - // Hide approach circle - (drawable as DrawableHitCircle)?.ApproachCircle.Hide(); } } } From 51d26fb648727a7db020cdd1367dbd86e13209ee Mon Sep 17 00:00:00 2001 From: Kyle Chang Date: Thu, 20 Sep 2018 19:06:37 -0400 Subject: [PATCH 0003/2815] Now spin in mod; circles spin in --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 1 + .../Mods/{OsuModZoomIn.cs => OsuModSpinIn.cs} | 44 +++++++++++++------ osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- 3 files changed, 32 insertions(+), 15 deletions(-) rename osu.Game.Rulesets.Osu/Mods/{OsuModZoomIn.cs => OsuModSpinIn.cs} (54%) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 4eff2a55c8..b3fb1b57a6 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -17,6 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Description => @"Play with no approach circles and fading circles/sliders."; public override double ScoreMultiplier => 1.06; + public override Type[] IncompatibleMods => new[] { typeof(OsuModSpinIn) }; private const double fade_in_duration_multiplier = 0.4; private const double fade_out_duration_multiplier = 0.3; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModZoomIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs similarity index 54% rename from osu.Game.Rulesets.Osu/Mods/OsuModZoomIn.cs rename to osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs index 8e73ece12c..3bb7d5933c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModZoomIn.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs @@ -1,35 +1,43 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; +using OpenTK; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModZoomIn : Mod, IApplicableToDrawableHitObjects + public class OsuModSpinIn : Mod, IApplicableToDrawableHitObjects { - public override string Name => "Zoom In"; - public override string ShortenedName => "ZI"; - public override FontAwesome Icon => FontAwesome.fa_dot_circle_o; + public override string Name => "Spin In"; + public override string ShortenedName => "SI"; + public override FontAwesome Icon => FontAwesome.fa_rotate_right; public override ModType Type => ModType.Fun; - public override string Description => "Circles zoom in. No approach circles."; + public override string Description => "Circle spin in. No approach circles."; public override double ScoreMultiplier => 1; + public override Type[] IncompatibleMods => new[] { typeof(OsuModHidden) }; + + private const int rotate_offset = 360; + private const float rotate_starting_width = 2.5f; + public void ApplyToDrawableHitObjects(IEnumerable drawables) { foreach (var drawable in drawables) { - drawable.ApplyCustomUpdateState += ApplyBounceState; + drawable.ApplyCustomUpdateState += ApplyZoomState; } } - protected void ApplyBounceState(DrawableHitObject drawable, ArmedState state) + protected void ApplyZoomState(DrawableHitObject drawable, ArmedState state) { if (!(drawable is DrawableOsuHitObject)) return; if (state != ArmedState.Idle) return; @@ -41,15 +49,21 @@ namespace osu.Game.Rulesets.Osu.Mods switch (drawable) { case DrawableHitCircle circle: - foreach (var t in circle.Transforms.Where(t => t.TargetMember == "Alpha")) - circle.RemoveTransform(t); + // Disable Fade + circle.Transforms + .Where(t => t.TargetMember == "Alpha") + .ForEach(t => circle.RemoveTransform(t)); + using (circle.BeginAbsoluteSequence(appearTime, true)) { var origScale = drawable.Scale; + var origRotate = circle.Rotation; circle - .ScaleTo(0) - .ScaleTo(origScale, moveDuration, Easing.OutSine) + .RotateTo(origRotate+rotate_offset) + .RotateTo(origRotate, moveDuration) + .ScaleTo(origScale * new Vector2(rotate_starting_width, 0)) + .ScaleTo(origScale, moveDuration) .FadeTo(1); } @@ -58,8 +72,10 @@ namespace osu.Game.Rulesets.Osu.Mods break; case DrawableSlider slider: - foreach (var t in slider.Transforms.Where(t => t.TargetMember == "Alpha")) - slider.RemoveTransform(t); + // Disable fade + slider.Transforms + .Where(t => t.TargetMember == "Alpha") + .ForEach(t => slider.RemoveTransform(t)); using (slider.BeginAbsoluteSequence(appearTime, true)) { @@ -67,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Mods slider .ScaleTo(0) - .ScaleTo(origScale, moveDuration, Easing.OutSine) + .ScaleTo(origScale, moveDuration) .FadeTo(1); } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 029ca7d43e..cebd9f1321 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Osu return new Mod[] { new OsuModTransform(), new OsuModWiggle(), - new OsuModZoomIn(), + new OsuModSpinIn(), }; default: return new Mod[] { }; From 58e989fb9d925b7b1d160f05be813ffbe90c27b1 Mon Sep 17 00:00:00 2001 From: Kyle Chang Date: Thu, 20 Sep 2018 21:08:31 -0400 Subject: [PATCH 0004/2815] Fix judgement backgrounds not appearing properly --- .../Objects/Drawables/DrawableOsuHitObject.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 10cd246172..5503a61b3b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -37,8 +37,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { double transformTime = HitObject.StartTime - HitObject.TimePreempt; - base.ApplyTransformsAt(transformTime, true); - base.ClearTransformsAfter(transformTime, true); + if (state == ArmedState.Idle) + { + base.ApplyTransformsAt(transformTime, true); + base.ClearTransformsAfter(transformTime, true); + } using (BeginAbsoluteSequence(transformTime, true)) { From ece4da0435e988c968bd4d917c7115fc39a2e032 Mon Sep 17 00:00:00 2001 From: Kyle Chang Date: Thu, 20 Sep 2018 23:49:15 -0400 Subject: [PATCH 0005/2815] Revert "Fix judgement backgrounds not appearing properly" This reverts commit 58e989f --- .../Objects/Drawables/DrawableOsuHitObject.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 5503a61b3b..10cd246172 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -37,11 +37,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { double transformTime = HitObject.StartTime - HitObject.TimePreempt; - if (state == ArmedState.Idle) - { - base.ApplyTransformsAt(transformTime, true); - base.ClearTransformsAfter(transformTime, true); - } + base.ApplyTransformsAt(transformTime, true); + base.ClearTransformsAfter(transformTime, true); using (BeginAbsoluteSequence(transformTime, true)) { From bfa430ad8ca04f683bc7ee7363a58660dd16d210 Mon Sep 17 00:00:00 2001 From: Kyle Chang Date: Thu, 20 Sep 2018 23:51:43 -0400 Subject: [PATCH 0006/2815] Fix judgement backgrounds without breaking other things --- osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs index 3bb7d5933c..e597567336 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string ShortenedName => "SI"; public override FontAwesome Icon => FontAwesome.fa_rotate_right; public override ModType Type => ModType.Fun; - public override string Description => "Circle spin in. No approach circles."; + public override string Description => "Circles spin in. No approach circles."; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModHidden) }; @@ -33,6 +33,7 @@ namespace osu.Game.Rulesets.Osu.Mods { foreach (var drawable in drawables) { + // Need to add custom update in order to disable fade drawable.ApplyCustomUpdateState += ApplyZoomState; } } @@ -43,8 +44,9 @@ namespace osu.Game.Rulesets.Osu.Mods if (state != ArmedState.Idle) return; var h = (OsuHitObject)drawable.HitObject; - var appearTime = h.StartTime - h.TimePreempt; - var moveDuration = h.TimePreempt; + + var appearTime = h.StartTime - h.TimePreempt + 1; + var moveDuration = h.TimePreempt - 1; switch (drawable) { @@ -63,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Mods .RotateTo(origRotate+rotate_offset) .RotateTo(origRotate, moveDuration) .ScaleTo(origScale * new Vector2(rotate_starting_width, 0)) - .ScaleTo(origScale, moveDuration) + .ScaleTo(origScale, moveDuration, Easing.InQuad) .FadeTo(1); } From 3cb33e53cc7df760a7295a20c74dfddb863cceb3 Mon Sep 17 00:00:00 2001 From: Kyle Chang Date: Fri, 21 Sep 2018 00:07:09 -0400 Subject: [PATCH 0007/2815] Hid approach circles after rewinding replays --- osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs index e597567336..348093ec9b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs @@ -69,7 +69,10 @@ namespace osu.Game.Rulesets.Osu.Mods .FadeTo(1); } - circle.ApproachCircle.Hide(); + using (circle.ApproachCircle.BeginAbsoluteSequence(appearTime, true)) + { + circle.ApproachCircle.Hide(); + } break; From 3cc75bac3443ac43cab7b62868c5397a2081784d Mon Sep 17 00:00:00 2001 From: Kyle Chang Date: Fri, 21 Sep 2018 20:26:01 -0400 Subject: [PATCH 0008/2815] Adjust easings --- osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs index 348093ec9b..102275043c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(OsuModHidden) }; private const int rotate_offset = 360; - private const float rotate_starting_width = 2.5f; + private const float rotate_starting_width = 2.0f; public void ApplyToDrawableHitObjects(IEnumerable drawables) @@ -63,9 +63,9 @@ namespace osu.Game.Rulesets.Osu.Mods circle .RotateTo(origRotate+rotate_offset) - .RotateTo(origRotate, moveDuration) + .RotateTo(origRotate, moveDuration, Easing.InOutSine) .ScaleTo(origScale * new Vector2(rotate_starting_width, 0)) - .ScaleTo(origScale, moveDuration, Easing.InQuad) + .ScaleTo(origScale, moveDuration, Easing.InOutSine) .FadeTo(1); } @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Mods slider .ScaleTo(0) - .ScaleTo(origScale, moveDuration) + .ScaleTo(origScale, moveDuration, Easing.InOutSine) .FadeTo(1); } From 1366b53a7150c56165a534dacea2eae9933e0a3b Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Tue, 9 Oct 2018 13:16:27 +0200 Subject: [PATCH 0009/2815] Added traceable mod + HideButApproachCircle function for DrawableHitCircle --- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 32 +++++++++++++++++++ .../Objects/Drawables/DrawableHitCircle.cs | 9 ++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 1 + 3 files changed, 42 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs new file mode 100644 index 0000000000..43eac55ffd --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.Mods +{ + internal class OsuModTraceable : Mod, IApplicableToDrawableHitObjects + { + public override string Name => "Traceable"; + public override string ShortenedName => "TC"; + public override FontAwesome Icon => FontAwesome.fa_snapchat_ghost; + public override ModType Type => ModType.Fun; + public override string Description => "Put your faith in the approach circles..."; + public override double ScoreMultiplier => 1; + + public void ApplyToDrawableHitObjects(IEnumerable drawables) + { + foreach (var drawable in drawables) + { + if (drawable is DrawableHitCircle c) + c.HideButApproachCircle(); + if (drawable is DrawableSlider s) + s.HeadCircle.HideButApproachCircle(); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 4bdddcef11..79cc88e474 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -76,6 +76,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } + public void HideButApproachCircle() + { + circle.Hide(); + circle.AlwaysPresent = true; + ring.Hide(); + number.Hide(); + glow.Hide(); + } + protected override void CheckForResult(bool userTriggered, double timeOffset) { if (!userTriggered) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 6736d10dab..0b6aee7881 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -121,6 +121,7 @@ namespace osu.Game.Rulesets.Osu return new Mod[] { new OsuModTransform(), new OsuModWiggle(), + new OsuModTraceable(), }; default: return new Mod[] { }; From 954bcd8c123dcad1112ed94bc07bf3a824adfc9d Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Tue, 9 Oct 2018 18:36:12 +0200 Subject: [PATCH 0010/2815] Treat non-DrawableHitCircle's similar to OsuModHidden --- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 61 +++++++++++++++++-- .../Objects/Drawables/DrawableHitCircle.cs | 2 + 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index 43eac55ffd..dfd398e7e4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -1,11 +1,14 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Collections.Generic; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Osu.Mods { @@ -21,11 +24,61 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableHitObjects(IEnumerable drawables) { foreach (var drawable in drawables) + drawable.ApplyCustomUpdateState += ApplyTraceableState; + } + + /* Similar to ApplyHiddenState, only different if drawable is DrawableHitCircle. + * If we'd use ApplyHiddenState instead but only on non-DrawableHitCircle's, then + * the nested object HeadCircle of DrawableSlider would still use ApplyHiddenState, + * thus treating the DrawableHitCircle with the hidden mod instead of the traceable mod. + */ + protected void ApplyTraceableState(DrawableHitObject drawable, ArmedState state) + { + if (!(drawable is DrawableOsuHitObject d)) + return; + + var h = d.HitObject; + + var fadeOutStartTime = h.StartTime - h.TimePreempt + h.TimeFadeIn; + + // new duration from completed fade in to end (before fading out) + var longFadeDuration = ((h as IHasEndTime)?.EndTime ?? h.StartTime) - fadeOutStartTime; + + switch (drawable) { - if (drawable is DrawableHitCircle c) - c.HideButApproachCircle(); - if (drawable is DrawableSlider s) - s.HeadCircle.HideButApproachCircle(); + case DrawableHitCircle circle: + // we only want to see the approach circle + using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) + circle.HideButApproachCircle(); + + // approach circle fades out quickly at StartTime + using (drawable.BeginAbsoluteSequence(h.StartTime, true)) + circle.ApproachCircle.FadeOut(50); + + break; + case DrawableSlider slider: + using (slider.BeginAbsoluteSequence(fadeOutStartTime, true)) + slider.Body.FadeOut(longFadeDuration, Easing.Out); + + break; + case DrawableSliderTick sliderTick: + // slider ticks fade out over up to one second + var tickFadeOutDuration = Math.Min(sliderTick.HitObject.TimePreempt - DrawableSliderTick.ANIM_DURATION, 1000); + + using (sliderTick.BeginAbsoluteSequence(sliderTick.HitObject.StartTime - tickFadeOutDuration, true)) + sliderTick.FadeOut(tickFadeOutDuration); + + break; + case DrawableSpinner spinner: + // hide elements we don't care about. + spinner.Disc.Hide(); + spinner.Ticks.Hide(); + spinner.Background.Hide(); + + using (spinner.BeginAbsoluteSequence(fadeOutStartTime + longFadeDuration, true)) + spinner.FadeOut(h.TimePreempt); + + break; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 79cc88e474..1644480aa4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -81,6 +81,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables circle.Hide(); circle.AlwaysPresent = true; ring.Hide(); + flash.Hide(); + explode.Hide(); number.Hide(); glow.Hide(); } From edb69463fd4318145ff97e2bb3a0219fc8f646dc Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Tue, 9 Oct 2018 18:52:34 +0200 Subject: [PATCH 0011/2815] Trimming whitespace from comment...... --- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index dfd398e7e4..a6d8d35125 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Mods } /* Similar to ApplyHiddenState, only different if drawable is DrawableHitCircle. - * If we'd use ApplyHiddenState instead but only on non-DrawableHitCircle's, then + * If we'd use ApplyHiddenState instead but only on non-DrawableHitCircle's, then * the nested object HeadCircle of DrawableSlider would still use ApplyHiddenState, * thus treating the DrawableHitCircle with the hidden mod instead of the traceable mod. */ From dea2acaea3f24b6fb33cf7f1746002e7151941f7 Mon Sep 17 00:00:00 2001 From: Paul Teng Date: Sun, 14 Oct 2018 11:18:10 -0400 Subject: [PATCH 0012/2815] Add new interface that allows restarts --- .../Rulesets/Mods/IApplicableForceRestart.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 osu.Game/Rulesets/Mods/IApplicableForceRestart.cs diff --git a/osu.Game/Rulesets/Mods/IApplicableForceRestart.cs b/osu.Game/Rulesets/Mods/IApplicableForceRestart.cs new file mode 100644 index 0000000000..2c9a6b6cb9 --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableForceRestart.cs @@ -0,0 +1,16 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Mods +{ + /// + /// Represents a mod which can override (and block) a fail. + /// + public interface IApplicableRestartOnFail : IApplicableMod + { + /// + /// Whether we allow restarting + /// + bool AllowRestart { get; } + } +} From fd774c6a09edf2abd7954e7fd4fc8703bd3d015e Mon Sep 17 00:00:00 2001 From: Paul Teng Date: Sun, 14 Oct 2018 11:18:52 -0400 Subject: [PATCH 0013/2815] Allow restarts in ModPerfect --- osu.Game/Rulesets/Mods/ModPerfect.cs | 4 +++- osu.Game/Screens/Play/Player.cs | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs index 802890866f..5d6065b4a2 100644 --- a/osu.Game/Rulesets/Mods/ModPerfect.cs +++ b/osu.Game/Rulesets/Mods/ModPerfect.cs @@ -6,13 +6,15 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mods { - public abstract class ModPerfect : ModSuddenDeath + public abstract class ModPerfect : ModSuddenDeath, IApplicableRestartOnFail { public override string Name => "Perfect"; public override string ShortenedName => "PF"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_perfect; public override string Description => "SS or quit."; + public bool AllowRestart => true; + protected override bool FailCondition(ScoreProcessor scoreProcessor) => scoreProcessor.Accuracy.Value != 1; } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b3cbeb3850..0577369b05 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -289,6 +289,12 @@ namespace osu.Game.Screens.Play if (Beatmap.Value.Mods.Value.OfType().Any(m => !m.AllowFail)) return false; + if (Beatmap.Value.Mods.Value.OfType().Any(m => m.AllowRestart)) + { + Restart(); + return false; + } + adjustableClock.Stop(); HasFailed = true; From 39c767af2d349e3219b20690208694a4eb7d778a Mon Sep 17 00:00:00 2001 From: Paul Teng Date: Sun, 14 Oct 2018 11:25:05 -0400 Subject: [PATCH 0014/2815] Update file name and update summary --- .../{IApplicableForceRestart.cs => IApplicableRestartOnFail.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game/Rulesets/Mods/{IApplicableForceRestart.cs => IApplicableRestartOnFail.cs} (86%) diff --git a/osu.Game/Rulesets/Mods/IApplicableForceRestart.cs b/osu.Game/Rulesets/Mods/IApplicableRestartOnFail.cs similarity index 86% rename from osu.Game/Rulesets/Mods/IApplicableForceRestart.cs rename to osu.Game/Rulesets/Mods/IApplicableRestartOnFail.cs index 2c9a6b6cb9..43b3f36624 100644 --- a/osu.Game/Rulesets/Mods/IApplicableForceRestart.cs +++ b/osu.Game/Rulesets/Mods/IApplicableRestartOnFail.cs @@ -4,7 +4,7 @@ namespace osu.Game.Rulesets.Mods { /// - /// Represents a mod which can override (and block) a fail. + /// Represents a mod which can request to restart on fail. /// public interface IApplicableRestartOnFail : IApplicableMod { From d7d83a27d40289d73e819984721a9b18a0fa94e6 Mon Sep 17 00:00:00 2001 From: Paul Teng Date: Wed, 24 Oct 2018 13:36:35 -0400 Subject: [PATCH 0015/2815] Add restart as mods settings --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 9ac2cabe9f..bcdd3acd10 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -84,6 +84,7 @@ namespace osu.Game.Configuration Set(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised); Set(OsuSetting.IncreaseFirstObjectVisibility, true); + Set(OsuSetting.RestartOnFail, false); // Update Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer); @@ -148,6 +149,7 @@ namespace osu.Game.Configuration BeatmapSkins, BeatmapHitsounds, IncreaseFirstObjectVisibility, + RestartOnFail, ScoreDisplayMode } } diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs index a9cefa81da..a3a0bf118b 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs @@ -20,6 +20,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay LabelText = "Increase visibility of first object with \"Hidden\" mod", Bindable = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility) }, + new SettingsCheckbox + { + LabelText = "Restart on fail with \"Perfect\" and \"Sudden Death\" mod", + Bindable = config.GetBindable(OsuSetting.RestartOnFail) + }, }; } } From 794afa988fe6e599ca9e13a1de130d7a765bf4c0 Mon Sep 17 00:00:00 2001 From: Paul Teng Date: Wed, 24 Oct 2018 13:37:27 -0400 Subject: [PATCH 0016/2815] Make both SD and PF auto-restart based on settings --- osu.Game/Rulesets/Mods/ModPerfect.cs | 4 +--- osu.Game/Rulesets/Mods/ModSuddenDeath.cs | 12 +++++++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs index 5d6065b4a2..802890866f 100644 --- a/osu.Game/Rulesets/Mods/ModPerfect.cs +++ b/osu.Game/Rulesets/Mods/ModPerfect.cs @@ -6,15 +6,13 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mods { - public abstract class ModPerfect : ModSuddenDeath, IApplicableRestartOnFail + public abstract class ModPerfect : ModSuddenDeath { public override string Name => "Perfect"; public override string ShortenedName => "PF"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_perfect; public override string Description => "SS or quit."; - public bool AllowRestart => true; - protected override bool FailCondition(ScoreProcessor scoreProcessor) => scoreProcessor.Accuracy.Value != 1; } } diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index 48f7d496a5..af7d9be785 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -3,11 +3,13 @@ using System; using osu.Game.Graphics; +using osu.Game.Configuration; using osu.Game.Rulesets.Scoring; +using osu.Framework.Configuration; namespace osu.Game.Rulesets.Mods { - public abstract class ModSuddenDeath : Mod, IApplicableToScoreProcessor + public abstract class ModSuddenDeath : Mod, IReadFromConfig, IApplicableToScoreProcessor, IApplicableRestartOnFail { public override string Name => "Sudden Death"; public override string ShortenedName => "SD"; @@ -18,11 +20,19 @@ namespace osu.Game.Rulesets.Mods public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) }; + protected Bindable RestartOnFail = new Bindable(); + public bool AllowRestart => RestartOnFail.Value; + public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { scoreProcessor.FailConditions += FailCondition; } + public void ReadFromConfig(OsuConfigManager config) + { + RestartOnFail = config.GetBindable(OsuSetting.RestartOnFail); + } + protected virtual bool FailCondition(ScoreProcessor scoreProcessor) => scoreProcessor.Combo.Value == 0; } } From cb9ec94dc2af3c511554b9a8a6ce0439164938c9 Mon Sep 17 00:00:00 2001 From: Paul Teng Date: Mon, 29 Oct 2018 08:19:38 -0400 Subject: [PATCH 0017/2815] Remove option from settings --- osu.Game/Configuration/OsuConfigManager.cs | 2 -- .../Settings/Sections/Gameplay/ModsSettings.cs | 5 ----- osu.Game/Rulesets/Mods/ModSuddenDeath.cs | 10 ++-------- 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index bcdd3acd10..9ac2cabe9f 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -84,7 +84,6 @@ namespace osu.Game.Configuration Set(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised); Set(OsuSetting.IncreaseFirstObjectVisibility, true); - Set(OsuSetting.RestartOnFail, false); // Update Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer); @@ -149,7 +148,6 @@ namespace osu.Game.Configuration BeatmapSkins, BeatmapHitsounds, IncreaseFirstObjectVisibility, - RestartOnFail, ScoreDisplayMode } } diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs index a3a0bf118b..a9cefa81da 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs @@ -20,11 +20,6 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay LabelText = "Increase visibility of first object with \"Hidden\" mod", Bindable = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility) }, - new SettingsCheckbox - { - LabelText = "Restart on fail with \"Perfect\" and \"Sudden Death\" mod", - Bindable = config.GetBindable(OsuSetting.RestartOnFail) - }, }; } } diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index af7d9be785..77e08dff86 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -9,7 +9,7 @@ using osu.Framework.Configuration; namespace osu.Game.Rulesets.Mods { - public abstract class ModSuddenDeath : Mod, IReadFromConfig, IApplicableToScoreProcessor, IApplicableRestartOnFail + public abstract class ModSuddenDeath : Mod, IApplicableToScoreProcessor, IApplicableRestartOnFail { public override string Name => "Sudden Death"; public override string ShortenedName => "SD"; @@ -20,19 +20,13 @@ namespace osu.Game.Rulesets.Mods public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) }; - protected Bindable RestartOnFail = new Bindable(); - public bool AllowRestart => RestartOnFail.Value; + public bool AllowRestart => true; public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { scoreProcessor.FailConditions += FailCondition; } - public void ReadFromConfig(OsuConfigManager config) - { - RestartOnFail = config.GetBindable(OsuSetting.RestartOnFail); - } - protected virtual bool FailCondition(ScoreProcessor scoreProcessor) => scoreProcessor.Combo.Value == 0; } } From 52b9a3f5e9a0096ff5529a223a317b8b489977ec Mon Sep 17 00:00:00 2001 From: Paul Teng Date: Mon, 29 Oct 2018 17:51:49 -0400 Subject: [PATCH 0018/2815] Remove unused using --- osu.Game/Rulesets/Mods/ModSuddenDeath.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index 77e08dff86..733fd6345a 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -3,9 +3,7 @@ using System; using osu.Game.Graphics; -using osu.Game.Configuration; using osu.Game.Rulesets.Scoring; -using osu.Framework.Configuration; namespace osu.Game.Rulesets.Mods { From 007dfedbb751fa0e4767740e39fcc621e73cdf54 Mon Sep 17 00:00:00 2001 From: Paul Teng Date: Tue, 30 Oct 2018 07:12:06 -0400 Subject: [PATCH 0019/2815] Combine RestartOnFail into FailOverride --- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 1 + .../Rulesets/Mods/IApplicableFailOverride.cs | 5 +++++ .../Rulesets/Mods/IApplicableRestartOnFail.cs | 16 ---------------- osu.Game/Rulesets/Mods/ModAutoplay.cs | 1 + osu.Game/Rulesets/Mods/ModNoFail.cs | 1 + osu.Game/Rulesets/Mods/ModSuddenDeath.cs | 5 +++-- osu.Game/Screens/Play/Player.cs | 2 +- 7 files changed, 12 insertions(+), 19 deletions(-) delete mode 100644 osu.Game/Rulesets/Mods/IApplicableRestartOnFail.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 8d27502b3c..2240425654 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray(); public bool AllowFail => false; + public bool RestartOnFail => false; public void Update(Playfield playfield) { diff --git a/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs b/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs index 6a4042a906..f650259373 100644 --- a/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs +++ b/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs @@ -12,5 +12,10 @@ namespace osu.Game.Rulesets.Mods /// Whether we should allow failing at the current point in time. /// bool AllowFail { get; } + + /// + /// Whether we want to restart on fail. Only used if AllowFail is true. + /// + bool RestartOnFail { get; } } } diff --git a/osu.Game/Rulesets/Mods/IApplicableRestartOnFail.cs b/osu.Game/Rulesets/Mods/IApplicableRestartOnFail.cs deleted file mode 100644 index 43b3f36624..0000000000 --- a/osu.Game/Rulesets/Mods/IApplicableRestartOnFail.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Mods -{ - /// - /// Represents a mod which can request to restart on fail. - /// - public interface IApplicableRestartOnFail : IApplicableMod - { - /// - /// Whether we allow restarting - /// - bool AllowRestart { get; } - } -} diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index 5c03cb9736..849eaeeb5a 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -30,6 +30,7 @@ namespace osu.Game.Rulesets.Mods public override string Description => "Watch a perfect automated play through the song."; public override double ScoreMultiplier => 1; public bool AllowFail => false; + public bool RestartOnFail => false; public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) }; } } diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs index 7510f62432..c30c6d712d 100644 --- a/osu.Game/Rulesets/Mods/ModNoFail.cs +++ b/osu.Game/Rulesets/Mods/ModNoFail.cs @@ -21,5 +21,6 @@ namespace osu.Game.Rulesets.Mods /// We never fail, 'yo. /// public bool AllowFail => false; + public bool RestartOnFail => false; } } diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index 733fd6345a..252df98f32 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -7,7 +7,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mods { - public abstract class ModSuddenDeath : Mod, IApplicableToScoreProcessor, IApplicableRestartOnFail + public abstract class ModSuddenDeath : Mod, IApplicableToScoreProcessor, IApplicableFailOverride { public override string Name => "Sudden Death"; public override string ShortenedName => "SD"; @@ -18,7 +18,8 @@ namespace osu.Game.Rulesets.Mods public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) }; - public bool AllowRestart => true; + public bool AllowFail => true; + public bool RestartOnFail => true; public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 0577369b05..863cfeda07 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -289,7 +289,7 @@ namespace osu.Game.Screens.Play if (Beatmap.Value.Mods.Value.OfType().Any(m => !m.AllowFail)) return false; - if (Beatmap.Value.Mods.Value.OfType().Any(m => m.AllowRestart)) + if (Beatmap.Value.Mods.Value.OfType().Any(m => m.RestartOnFail)) { Restart(); return false; From d8f97a32c779f40005c6328c925ec9c327a5b54a Mon Sep 17 00:00:00 2001 From: Paul Teng Date: Wed, 31 Oct 2018 07:03:37 -0400 Subject: [PATCH 0020/2815] Make sure restart on fail actually fails --- osu.Game/Screens/Play/Player.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 863cfeda07..b388387866 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -289,15 +289,16 @@ namespace osu.Game.Screens.Play if (Beatmap.Value.Mods.Value.OfType().Any(m => !m.AllowFail)) return false; - if (Beatmap.Value.Mods.Value.OfType().Any(m => m.RestartOnFail)) - { - Restart(); - return false; - } - adjustableClock.Stop(); HasFailed = true; + + if (Beatmap.Value.Mods.Value.OfType().Any(m => m.RestartOnFail)) + { + Restart(); + return true; + } + failOverlay.Retries = RestartCount; failOverlay.Show(); return true; From 5825890c31b6c74e604f21b90f1d9b481b1e1091 Mon Sep 17 00:00:00 2001 From: RORIdev Date: Wed, 3 Apr 2019 16:58:44 -0300 Subject: [PATCH 0021/2815] Implemented the 3 lives system into the Easy Mod. --- osu.Game/Screens/Play/Player.cs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 0eebefec86..f45c5d1028 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -51,6 +51,8 @@ namespace osu.Game.Screens.Play public int RestartCount; + public int Lives = 2; + [Resolved] private ScoreManager scoreManager { get; set; } @@ -323,8 +325,32 @@ namespace osu.Game.Screens.Play protected FailOverlay FailOverlay { get; private set; } + private bool onFail() { + + //issue #3372 + if (Beatmap.Value.Mods.Value.Any(x => x is ModEasy)) + { + + if (Lives != 0) + { + Lives--; + ScoreProcessor.Health.Value = 100; + return false; + } + else + { + GameplayClockContainer.Stop(); + HasFailed = true; + FailOverlay.Retries = RestartCount; + FailOverlay.Show(); + return true; + + } + + } + if (Beatmap.Value.Mods.Value.OfType().Any(m => !m.AllowFail)) return false; @@ -332,6 +358,8 @@ namespace osu.Game.Screens.Play HasFailed = true; + + // There is a chance that we could be in a paused state as the ruleset's internal clock (see FrameStabilityContainer) // could process an extra frame after the GameplayClock is stopped. // In such cases we want the fail state to precede a user triggered pause. From e9f4cdd5119d75e4c57a8cb40abbf0e46c314dcd Mon Sep 17 00:00:00 2001 From: RORIdev Date: Wed, 3 Apr 2019 17:18:42 -0300 Subject: [PATCH 0022/2815] Removed code duplication. --- osu.Game/Screens/Play/Player.cs | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index f45c5d1028..b71bb87bdb 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -325,14 +325,18 @@ namespace osu.Game.Screens.Play protected FailOverlay FailOverlay { get; private set; } - + private void Fail() + { + GameplayClockContainer.Stop(); + HasFailed = true; + FailOverlay.Retries = RestartCount; + FailOverlay.Show(); + } private bool onFail() { - //issue #3372 if (Beatmap.Value.Mods.Value.Any(x => x is ModEasy)) { - if (Lives != 0) { Lives--; @@ -341,33 +345,18 @@ namespace osu.Game.Screens.Play } else { - GameplayClockContainer.Stop(); - HasFailed = true; - FailOverlay.Retries = RestartCount; - FailOverlay.Show(); + Fail(); return true; - } - } - if (Beatmap.Value.Mods.Value.OfType().Any(m => !m.AllowFail)) return false; - - GameplayClockContainer.Stop(); - - HasFailed = true; - - - // There is a chance that we could be in a paused state as the ruleset's internal clock (see FrameStabilityContainer) // could process an extra frame after the GameplayClock is stopped. // In such cases we want the fail state to precede a user triggered pause. if (PauseOverlay.State == Visibility.Visible) PauseOverlay.Hide(); - - FailOverlay.Retries = RestartCount; - FailOverlay.Show(); + Fail(); return true; } From 9cfe17cbf1cec2eacdbca515844de530c6bc4ef5 Mon Sep 17 00:00:00 2001 From: RORIdev Date: Wed, 3 Apr 2019 17:28:48 -0300 Subject: [PATCH 0023/2815] Makes AppVeyour happy. --- osu.Game/Screens/Play/Player.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b71bb87bdb..800adcd928 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -325,7 +325,7 @@ namespace osu.Game.Screens.Play protected FailOverlay FailOverlay { get; private set; } - private void Fail() + private void fail() { GameplayClockContainer.Stop(); HasFailed = true; @@ -345,7 +345,7 @@ namespace osu.Game.Screens.Play } else { - Fail(); + fail(); return true; } } @@ -356,7 +356,7 @@ namespace osu.Game.Screens.Play // In such cases we want the fail state to precede a user triggered pause. if (PauseOverlay.State == Visibility.Visible) PauseOverlay.Hide(); - Fail(); + fail(); return true; } From f6e1cb07a1b9d3418105e2c8399caba3bd9fe1ee Mon Sep 17 00:00:00 2001 From: RORIdev Date: Wed, 3 Apr 2019 17:58:17 -0300 Subject: [PATCH 0024/2815] Changed the logic to ModEasy. --- osu.Game/Rulesets/Mods/ModEasy.cs | 20 +++++++++++++++++++- osu.Game/Screens/Play/Player.cs | 29 ++++++----------------------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index 56ec0bec06..ec18496966 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -5,11 +5,13 @@ using System; using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mods { - public abstract class ModEasy : Mod, IApplicableToDifficulty + public abstract class ModEasy : Mod, IApplicableToDifficulty, IApplicableToScoreProcessor { + public static int Lives = 2; public override string Name => "Easy"; public override string Acronym => "EZ"; public override IconUsage Icon => OsuIcon.ModEasy; @@ -26,5 +28,21 @@ namespace osu.Game.Rulesets.Mods difficulty.DrainRate *= ratio; difficulty.OverallDifficulty *= ratio; } + + public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) + { + scoreProcessor.Health.ValueChanged += ValueChanged =>{ + if (scoreProcessor.Health.Value == 0) + { + if (Lives != 0) + { + Lives--; + scoreProcessor.Health.Value = 100; + } + } + + } ; + + } } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 800adcd928..864cbc8878 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -325,38 +325,21 @@ namespace osu.Game.Screens.Play protected FailOverlay FailOverlay { get; private set; } - private void fail() - { - GameplayClockContainer.Stop(); - HasFailed = true; - FailOverlay.Retries = RestartCount; - FailOverlay.Show(); - } + private bool onFail() { - //issue #3372 - if (Beatmap.Value.Mods.Value.Any(x => x is ModEasy)) - { - if (Lives != 0) - { - Lives--; - ScoreProcessor.Health.Value = 100; - return false; - } - else - { - fail(); - return true; - } - } + if (Beatmap.Value.Mods.Value.OfType().Any(m => !m.AllowFail)) return false; + GameplayClockContainer.Stop(); + HasFailed = true; // There is a chance that we could be in a paused state as the ruleset's internal clock (see FrameStabilityContainer) // could process an extra frame after the GameplayClock is stopped. // In such cases we want the fail state to precede a user triggered pause. if (PauseOverlay.State == Visibility.Visible) PauseOverlay.Hide(); - fail(); + FailOverlay.Retries = RestartCount; + FailOverlay.Show(); return true; } From 621f8fd78dbde2604122d67548ed6edf635e35fc Mon Sep 17 00:00:00 2001 From: RORIdev Date: Wed, 3 Apr 2019 18:05:47 -0300 Subject: [PATCH 0025/2815] Trimmed whitespaces. --- osu.Game/Rulesets/Mods/ModEasy.cs | 4 +--- osu.Game/Screens/Play/Player.cs | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index ec18496966..9099235aed 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -40,9 +40,7 @@ namespace osu.Game.Rulesets.Mods scoreProcessor.Health.Value = 100; } } - - } ; - + }; } } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 864cbc8878..68f8fd38d3 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -325,10 +325,8 @@ namespace osu.Game.Screens.Play protected FailOverlay FailOverlay { get; private set; } - private bool onFail() { - if (Beatmap.Value.Mods.Value.OfType().Any(m => !m.AllowFail)) return false; GameplayClockContainer.Stop(); From ebaaaef4d6ac68b2a5d3007a8d3c1c88373e3275 Mon Sep 17 00:00:00 2001 From: RORIdev Date: Wed, 3 Apr 2019 18:17:21 -0300 Subject: [PATCH 0026/2815] Fixed Inconsistent Naming --- osu.Game/Rulesets/Mods/ModEasy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index 9099235aed..c0f75b7c83 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mods public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { - scoreProcessor.Health.ValueChanged += ValueChanged =>{ + scoreProcessor.Health.ValueChanged += valueChanged =>{ if (scoreProcessor.Health.Value == 0) { if (Lives != 0) From 5aa284781eef3baab0c28e54f26c8a1f9ba0f993 Mon Sep 17 00:00:00 2001 From: RORIdev Date: Wed, 3 Apr 2019 19:20:20 -0300 Subject: [PATCH 0027/2815] Reverted back Player.cs changes. --- osu.Game/Rulesets/Mods/ModEasy.cs | 5 +++-- osu.Game/Screens/Play/Player.cs | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index c0f75b7c83..16f54d0743 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mods { public abstract class ModEasy : Mod, IApplicableToDifficulty, IApplicableToScoreProcessor { - public static int Lives = 2; + public int Lives = 2; public override string Name => "Easy"; public override string Acronym => "EZ"; public override IconUsage Icon => OsuIcon.ModEasy; @@ -31,7 +31,8 @@ namespace osu.Game.Rulesets.Mods public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { - scoreProcessor.Health.ValueChanged += valueChanged =>{ + scoreProcessor.Health.ValueChanged += valueChanged => + { if (scoreProcessor.Health.Value == 0) { if (Lives != 0) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 68f8fd38d3..0eebefec86 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -51,8 +51,6 @@ namespace osu.Game.Screens.Play public int RestartCount; - public int Lives = 2; - [Resolved] private ScoreManager scoreManager { get; set; } @@ -329,13 +327,17 @@ namespace osu.Game.Screens.Play { if (Beatmap.Value.Mods.Value.OfType().Any(m => !m.AllowFail)) return false; + GameplayClockContainer.Stop(); + HasFailed = true; + // There is a chance that we could be in a paused state as the ruleset's internal clock (see FrameStabilityContainer) // could process an extra frame after the GameplayClock is stopped. // In such cases we want the fail state to precede a user triggered pause. if (PauseOverlay.State == Visibility.Visible) PauseOverlay.Hide(); + FailOverlay.Retries = RestartCount; FailOverlay.Show(); return true; From 28bf3156badefe95399de0e73c1f288aa4721f8d Mon Sep 17 00:00:00 2001 From: RORIdev Date: Wed, 3 Apr 2019 19:32:29 -0300 Subject: [PATCH 0028/2815] Fixed the mod being not resetting. --- osu.Game/Rulesets/Mods/ModEasy.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index 16f54d0743..03b69b3163 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mods { public abstract class ModEasy : Mod, IApplicableToDifficulty, IApplicableToScoreProcessor { - public int Lives = 2; + public static int Lives = 2; public override string Name => "Easy"; public override string Acronym => "EZ"; public override IconUsage Icon => OsuIcon.ModEasy; @@ -40,6 +40,10 @@ namespace osu.Game.Rulesets.Mods Lives--; scoreProcessor.Health.Value = 100; } + else + { + Lives = 2; + } } }; } From ff1815e714d66ab76192115907361854e3bf0898 Mon Sep 17 00:00:00 2001 From: RORIdev Date: Wed, 3 Apr 2019 20:22:48 -0300 Subject: [PATCH 0029/2815] Fixed lives being not reseting between maps. E.G. quitting a map with only 1 revive and getting 2 lives on another map. --- 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 03b69b3163..ae1e6145d1 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mods { public abstract class ModEasy : Mod, IApplicableToDifficulty, IApplicableToScoreProcessor { - public static int Lives = 2; + public static int Lives; public override string Name => "Easy"; public override string Acronym => "EZ"; public override IconUsage Icon => OsuIcon.ModEasy; @@ -31,6 +31,7 @@ namespace osu.Game.Rulesets.Mods public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { + Lives = 2; scoreProcessor.Health.ValueChanged += valueChanged => { if (scoreProcessor.Health.Value == 0) From 27fe6f610a384badc77c53a5021dda84a200e7c2 Mon Sep 17 00:00:00 2001 From: RORIdev Date: Wed, 3 Apr 2019 22:55:09 -0300 Subject: [PATCH 0030/2815] Removed deprecated code. Thanks peppy. --- osu.Game/Rulesets/Mods/ModEasy.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index ae1e6145d1..85cac872b8 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -41,10 +41,6 @@ namespace osu.Game.Rulesets.Mods Lives--; scoreProcessor.Health.Value = 100; } - else - { - Lives = 2; - } } }; } From 5a3e5036ed81c3a67292fb0eac1bccf8eca82a5f Mon Sep 17 00:00:00 2001 From: RORIdev Date: Thu, 4 Apr 2019 13:21:53 -0300 Subject: [PATCH 0031/2815] All suggestions were applied. --- osu.Game/Rulesets/Mods/ModEasy.cs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index 85cac872b8..bdfa4f9bb5 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -2,7 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; using osu.Framework.Graphics.Sprites; +using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Scoring; @@ -11,7 +15,7 @@ namespace osu.Game.Rulesets.Mods { public abstract class ModEasy : Mod, IApplicableToDifficulty, IApplicableToScoreProcessor { - public static int Lives; + private int Lives; public override string Name => "Easy"; public override string Acronym => "EZ"; public override IconUsage Icon => OsuIcon.ModEasy; @@ -19,7 +23,7 @@ namespace osu.Game.Rulesets.Mods public override double ScoreMultiplier => 0.5; public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModHardRock) }; - + public void ApplyToDifficulty(BeatmapDifficulty difficulty) { const float ratio = 0.5f; @@ -31,16 +35,17 @@ namespace osu.Game.Rulesets.Mods public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { + //Note : The lives has to be instaciated here in order to prevent the values from different plays to interfear + //with each other / not reseting after a restart , as this method is called once a play starts (to my knowlegde). + //This will be better implemented with a List once I know how to reliably get the game time and update it. + //If you know any information about that, please contact me because I didn't find a sollution to that. Lives = 2; scoreProcessor.Health.ValueChanged += valueChanged => { - if (scoreProcessor.Health.Value == 0) + if (scoreProcessor.Health.Value == scoreProcessor.Health.MinValue && Lives > 0) { - if (Lives != 0) - { - Lives--; - scoreProcessor.Health.Value = 100; - } + Lives--; + scoreProcessor.Health.Value = scoreProcessor.Health.MaxValue; } }; } From c19548122352bf86461b9bfe5c9abf6f4ca6f33b Mon Sep 17 00:00:00 2001 From: RORIdev Date: Thu, 4 Apr 2019 13:28:43 -0300 Subject: [PATCH 0032/2815] Trimmed Whitespace --- osu.Game/Rulesets/Mods/ModEasy.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index bdfa4f9bb5..96a2f5fd3f 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -23,7 +23,6 @@ namespace osu.Game.Rulesets.Mods public override double ScoreMultiplier => 0.5; public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModHardRock) }; - public void ApplyToDifficulty(BeatmapDifficulty difficulty) { const float ratio = 0.5f; From ca6a73cb9d5933533416df8a3750d0ae89380931 Mon Sep 17 00:00:00 2001 From: RORIdev Date: Thu, 4 Apr 2019 18:04:49 -0300 Subject: [PATCH 0033/2815] Fixed Code Inspection Fails. --- osu.Game/Rulesets/Mods/ModEasy.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index 96a2f5fd3f..4365ed256f 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -2,11 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; using osu.Framework.Graphics.Sprites; -using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Scoring; @@ -15,7 +11,7 @@ namespace osu.Game.Rulesets.Mods { public abstract class ModEasy : Mod, IApplicableToDifficulty, IApplicableToScoreProcessor { - private int Lives; + private int lives; public override string Name => "Easy"; public override string Acronym => "EZ"; public override IconUsage Icon => OsuIcon.ModEasy; @@ -23,6 +19,7 @@ namespace osu.Game.Rulesets.Mods public override double ScoreMultiplier => 0.5; public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModHardRock) }; + public void ApplyToDifficulty(BeatmapDifficulty difficulty) { const float ratio = 0.5f; @@ -38,12 +35,12 @@ namespace osu.Game.Rulesets.Mods //with each other / not reseting after a restart , as this method is called once a play starts (to my knowlegde). //This will be better implemented with a List once I know how to reliably get the game time and update it. //If you know any information about that, please contact me because I didn't find a sollution to that. - Lives = 2; + lives = 2; scoreProcessor.Health.ValueChanged += valueChanged => { - if (scoreProcessor.Health.Value == scoreProcessor.Health.MinValue && Lives > 0) + if (scoreProcessor.Health.Value == scoreProcessor.Health.MinValue && lives > 0) { - Lives--; + lives--; scoreProcessor.Health.Value = scoreProcessor.Health.MaxValue; } }; From 023a5c6e4faef1cd82bde231af6ed481f89d43c8 Mon Sep 17 00:00:00 2001 From: Tav TaOr Date: Fri, 10 May 2019 19:12:32 +0300 Subject: [PATCH 0034/2815] Add the ability to ignore the user's mouse movement. --- osu.Game.Rulesets.Osu/OsuInputManager.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index b9e083d35b..b4cc556ff2 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Osu set => ((OsuKeyBindingContainer)KeyBindingContainer).AllowUserPresses = value; } + public bool AllowUserCursorMovement { get; set; } = true; + protected override RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) => new OsuKeyBindingContainer(ruleset, variant, unique); @@ -26,6 +28,16 @@ namespace osu.Game.Rulesets.Osu { } + protected override bool Handle(UIEvent e) + { + if (!AllowUserCursorMovement && e is MouseMoveEvent) + { + return false; + } + + return base.Handle(e); + } + private class OsuKeyBindingContainer : RulesetKeyBindingContainer { public bool AllowUserPresses = true; From bda0b35d849d15e01cf6ba4cd4ddd97d225e06c2 Mon Sep 17 00:00:00 2001 From: Tav TaOr Date: Fri, 10 May 2019 19:46:13 +0300 Subject: [PATCH 0035/2815] Slight formating change. --- osu.Game.Rulesets.Osu/OsuInputManager.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index b4cc556ff2..58e275ba26 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -30,10 +30,7 @@ namespace osu.Game.Rulesets.Osu protected override bool Handle(UIEvent e) { - if (!AllowUserCursorMovement && e is MouseMoveEvent) - { - return false; - } + if (e is MouseMoveEvent && !AllowUserCursorMovement) return false; return base.Handle(e); } From 0ec7c11a90f6ec343752ea786a5adf4078c06f7c Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 10 May 2019 19:44:22 +0200 Subject: [PATCH 0036/2815] implement CatchModRelax --- osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs index 0454bc969d..47e32b1292 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs @@ -1,12 +1,48 @@ // 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.Containers; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; +using osuTK; +using System; -namespace osu.Game.Rulesets.Catch.Mods -{ - public class CatchModRelax : ModRelax - { +namespace osu.Game.Rulesets.Catch.Mods { + public class CatchModRelax : ModRelax, IApplicableToDrawableRuleset { public override string Description => @"Use the mouse to control the catcher."; + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) => + (drawableRuleset.Playfield.Parent as Container).Add(new CatchModRelaxHelper(drawableRuleset.Playfield as CatchPlayfield)); + + private class CatchModRelaxHelper : Drawable, IKeyBindingHandler, IRequireHighFrequencyMousePosition { + private CatcherArea.Catcher catcher; + + public CatchModRelaxHelper(CatchPlayfield catchPlayfield) { + catcher = catchPlayfield.CatcherArea.MovableCatcher; + RelativeSizeAxes = Axes.Both; + } + + //disable keyboard controls + public bool OnPressed(CatchAction action) => true; + public bool OnReleased(CatchAction action) => true; + + protected override bool OnMouseMove(MouseMoveEvent e) { + //lock catcher to mouse position horizontally + catcher.X = e.MousePosition.X / DrawSize.X; + + //make Yuzu face the direction he's moving + var direction = Math.Sign(e.Delta.X); + if (direction != 0) + catcher.Scale = new Vector2(Math.Abs(catcher.Scale.X) * direction, catcher.Scale.Y); + + return base.OnMouseMove(e); + } + } } } From 6739238a0dd3620805943806f99637210f9138ca Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 11 May 2019 10:03:59 +0200 Subject: [PATCH 0037/2815] formatting --- osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs index 47e32b1292..5fa22f8bdb 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs @@ -13,17 +13,21 @@ using osu.Game.Rulesets.UI; using osuTK; using System; -namespace osu.Game.Rulesets.Catch.Mods { - public class CatchModRelax : ModRelax, IApplicableToDrawableRuleset { +namespace osu.Game.Rulesets.Catch.Mods +{ + public class CatchModRelax : ModRelax, IApplicableToDrawableRuleset + { public override string Description => @"Use the mouse to control the catcher."; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) => (drawableRuleset.Playfield.Parent as Container).Add(new CatchModRelaxHelper(drawableRuleset.Playfield as CatchPlayfield)); - private class CatchModRelaxHelper : Drawable, IKeyBindingHandler, IRequireHighFrequencyMousePosition { + private class CatchModRelaxHelper : Drawable, IKeyBindingHandler, IRequireHighFrequencyMousePosition + { private CatcherArea.Catcher catcher; - public CatchModRelaxHelper(CatchPlayfield catchPlayfield) { + public CatchModRelaxHelper(CatchPlayfield catchPlayfield) + { catcher = catchPlayfield.CatcherArea.MovableCatcher; RelativeSizeAxes = Axes.Both; } @@ -32,7 +36,8 @@ namespace osu.Game.Rulesets.Catch.Mods { public bool OnPressed(CatchAction action) => true; public bool OnReleased(CatchAction action) => true; - protected override bool OnMouseMove(MouseMoveEvent e) { + protected override bool OnMouseMove(MouseMoveEvent e) + { //lock catcher to mouse position horizontally catcher.X = e.MousePosition.X / DrawSize.X; From cf192c84a8d528f23c949aa70210e1c300fa53e2 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 11 May 2019 10:26:24 +0200 Subject: [PATCH 0038/2815] make catcher field readonly --- osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs index 5fa22f8bdb..9852e753c5 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Mods private class CatchModRelaxHelper : Drawable, IKeyBindingHandler, IRequireHighFrequencyMousePosition { - private CatcherArea.Catcher catcher; + private readonly CatcherArea.Catcher catcher; public CatchModRelaxHelper(CatchPlayfield catchPlayfield) { From b87478f6e2c3f245c34a5c11e714a74d4c90e9e9 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 11 May 2019 11:25:29 +0200 Subject: [PATCH 0039/2815] replace 'as' with direct cast to avoid possible nullref --- osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs index 9852e753c5..062c0f8b26 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Catch.Mods public override string Description => @"Use the mouse to control the catcher."; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) => - (drawableRuleset.Playfield.Parent as Container).Add(new CatchModRelaxHelper(drawableRuleset.Playfield as CatchPlayfield)); + ((Container)drawableRuleset.Playfield.Parent).Add(new CatchModRelaxHelper(drawableRuleset.Playfield as CatchPlayfield)); private class CatchModRelaxHelper : Drawable, IKeyBindingHandler, IRequireHighFrequencyMousePosition { From d229fed1287c3d48878ab7e91981cf123fbd429d Mon Sep 17 00:00:00 2001 From: Tav TaOr Date: Sun, 12 May 2019 14:00:43 +0300 Subject: [PATCH 0040/2815] Implemented Autopilot --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 401bd28d7c..7c423235fa 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -2,13 +2,19 @@ // 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.Sprites; +using osu.Framework.Input.StateChanges; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModAutopilot : Mod + public class OsuModAutopilot : Mod, IApplicableFailOverride, IUpdatableByPlayfield, IApplicableToDrawableRuleset { public override string Name => "Autopilot"; public override string Acronym => "AP"; @@ -17,5 +23,41 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => @"Automatic cursor movement - just follow the rhythm."; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) }; + + public bool AllowFail => false; + + private OsuInputManager inputManager; + + private List replayFrames; + private int frameIndex = 0; + + public void Update(Playfield playfield) + { + // If we are on the last replay frame, no need to do anything + if (frameIndex == replayFrames.Count - 1) + { + return; + } + + // Check if we are closer to the next replay frame then the current one + if (Math.Abs(replayFrames[frameIndex].Time - playfield.Time.Current) >= Math.Abs(replayFrames[frameIndex + 1].Time - playfield.Time.Current)) + { + // If we are, move to the next frame, and update the mouse position + frameIndex++; + new MousePositionAbsoluteInput() { Position = playfield.ToScreenSpace(replayFrames[frameIndex].Position) }.Apply(inputManager.CurrentState, inputManager); + } + + // TODO: Implement the functionality to automatically spin spinners + } + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + // Grab the input manager to disable the user's cursor, and for future use + inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; + inputManager.AllowUserCursorMovement = false; + + // Generate the replay frames the cursor should follow + replayFrames = new OsuAutoGenerator(drawableRuleset.Beatmap).Generate().Frames.Cast().ToList(); + } } } From 2051be7453ad5c201f0d6e11ecb737a68df1866e Mon Sep 17 00:00:00 2001 From: Tav TaOr Date: Sun, 12 May 2019 15:00:59 +0300 Subject: [PATCH 0041/2815] Minor changes to pass the AppVeyor test --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 7c423235fa..a9b99f3afe 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Mods private OsuInputManager inputManager; private List replayFrames; - private int frameIndex = 0; + private int frameIndex; public void Update(Playfield playfield) { @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Mods { // If we are, move to the next frame, and update the mouse position frameIndex++; - new MousePositionAbsoluteInput() { Position = playfield.ToScreenSpace(replayFrames[frameIndex].Position) }.Apply(inputManager.CurrentState, inputManager); + new MousePositionAbsoluteInput { Position = playfield.ToScreenSpace(replayFrames[frameIndex].Position) }.Apply(inputManager.CurrentState, inputManager); } // TODO: Implement the functionality to automatically spin spinners From dfd7b1111492735a7ecd7aead43163edbee439f6 Mon Sep 17 00:00:00 2001 From: Tav TaOr Date: Sun, 12 May 2019 16:04:37 +0300 Subject: [PATCH 0042/2815] Changed the unimplemented mod test to use OsuModSpunOut instead of OsuModAutopilot since Autopilot is implemented now. --- osu.Game.Tests/Visual/UserInterface/TestCaseMods.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseMods.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseMods.cs index fd003c7ea2..7d1242083e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseMods.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseMods.cs @@ -90,7 +90,7 @@ namespace osu.Game.Tests.Visual.UserInterface var doubleTimeMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime)); - var autoPilotMod = assistMods.FirstOrDefault(m => m is OsuModAutopilot); + var spunOutMod = easierMods.FirstOrDefault(m => m is OsuModSpunOut); var easy = easierMods.FirstOrDefault(m => m is OsuModEasy); var hardRock = harderMods.FirstOrDefault(m => m is OsuModHardRock); @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Visual.UserInterface testMultiplierTextColour(noFailMod, modSelect.LowMultiplierColour); testMultiplierTextColour(hiddenMod, modSelect.HighMultiplierColour); - testUnimplementedMod(autoPilotMod); + testUnimplementedMod(spunOutMod); } [Test] From fca1b9325d4800edb79f4a24d4c4a82b500046bb Mon Sep 17 00:00:00 2001 From: Tav TaOr Date: Sun, 12 May 2019 16:18:27 +0300 Subject: [PATCH 0043/2815] Deleted the assistMods variable since it is never used --- osu.Game.Tests/Visual/UserInterface/TestCaseMods.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseMods.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseMods.cs index 7d1242083e..4fbb12d6df 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseMods.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseMods.cs @@ -83,7 +83,6 @@ namespace osu.Game.Tests.Visual.UserInterface var easierMods = instance.GetModsFor(ModType.DifficultyReduction); var harderMods = instance.GetModsFor(ModType.DifficultyIncrease); - var assistMods = instance.GetModsFor(ModType.Automation); var noFailMod = easierMods.FirstOrDefault(m => m is OsuModNoFail); var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden); From b3a95cac84e2f30bcc4acfb5eb8e4b030f9ad7d3 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 12 May 2019 20:20:11 +0200 Subject: [PATCH 0044/2815] add invisible cursor for ctb --- osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs | 14 ++++++++++++++ osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 3 +++ 2 files changed, 17 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs diff --git a/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs new file mode 100644 index 0000000000..073f2e05b2 --- /dev/null +++ b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs @@ -0,0 +1,14 @@ +using osu.Framework.Graphics; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Catch.UI { + class CatchCursorContainer : GameplayCursorContainer + { + protected override Drawable CreateCursor() => new InvisibleCursor(); + + private class InvisibleCursor : Drawable + { + + } + } +} diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index b6d8cf9cbe..7741096da2 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -9,6 +9,7 @@ using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Catch.UI @@ -19,6 +20,8 @@ namespace osu.Game.Rulesets.Catch.UI internal readonly CatcherArea CatcherArea; + protected override GameplayCursorContainer CreateCursor() => new CatchCursorContainer(); + public CatchPlayfield(BeatmapDifficulty difficulty, Func> createDrawableRepresentation) { Container explodingFruitContainer; From 9079d7a7d83962ae058e33ad75f4ba19bab02f53 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 12 May 2019 21:01:51 +0200 Subject: [PATCH 0045/2815] add license header --- osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs index 073f2e05b2..072c5d4fdf 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs @@ -1,4 +1,7 @@ -using osu.Framework.Graphics; +// 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.Rulesets.UI; namespace osu.Game.Rulesets.Catch.UI { From cadf0eb228d41b1ed941aef9d6ec11963a625639 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 12 May 2019 21:14:27 +0200 Subject: [PATCH 0046/2815] formatting --- osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs index 072c5d4fdf..642e0bb9de 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs @@ -4,7 +4,8 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.UI; -namespace osu.Game.Rulesets.Catch.UI { +namespace osu.Game.Rulesets.Catch.UI +{ class CatchCursorContainer : GameplayCursorContainer { protected override Drawable CreateCursor() => new InvisibleCursor(); From 3aa95f216284ddcc3f39c31dac000aba559903d8 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 12 May 2019 21:17:06 +0200 Subject: [PATCH 0047/2815] add missing access modifier --- osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs index 642e0bb9de..d4ac4b2f2e 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs @@ -6,7 +6,7 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Catch.UI { - class CatchCursorContainer : GameplayCursorContainer + public class CatchCursorContainer : GameplayCursorContainer { protected override Drawable CreateCursor() => new InvisibleCursor(); From 69f9d003829c98fd858f9dc0ac458dc64e7c3697 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 12 May 2019 21:23:59 +0200 Subject: [PATCH 0048/2815] more formatting --- osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs index d4ac4b2f2e..03bfad7e8c 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs @@ -10,9 +10,6 @@ namespace osu.Game.Rulesets.Catch.UI { protected override Drawable CreateCursor() => new InvisibleCursor(); - private class InvisibleCursor : Drawable - { - - } + private class InvisibleCursor : Drawable { } } } From 7c50bdd173ce5c7fbd3cd3d1eaa52725eff372e9 Mon Sep 17 00:00:00 2001 From: Tav TaOr Date: Wed, 15 May 2019 23:48:37 +0300 Subject: [PATCH 0049/2815] Removed redundant parentheses. --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index a9b99f3afe..1853b0228f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -34,10 +34,7 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { // If we are on the last replay frame, no need to do anything - if (frameIndex == replayFrames.Count - 1) - { - return; - } + if (frameIndex == replayFrames.Count - 1) return; // Check if we are closer to the next replay frame then the current one if (Math.Abs(replayFrames[frameIndex].Time - playfield.Time.Current) >= Math.Abs(replayFrames[frameIndex + 1].Time - playfield.Time.Current)) From 8bcb4485edc4e8d290f22b02693645954bb256b1 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Wed, 29 May 2019 19:00:20 +0300 Subject: [PATCH 0050/2815] implement UnderscoredLinkContainer --- .../Sections/UnderscoredLinkContainer.cs | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 osu.Game/Overlays/Profile/Sections/UnderscoredLinkContainer.cs diff --git a/osu.Game/Overlays/Profile/Sections/UnderscoredLinkContainer.cs b/osu.Game/Overlays/Profile/Sections/UnderscoredLinkContainer.cs new file mode 100644 index 0000000000..087bd03837 --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/UnderscoredLinkContainer.cs @@ -0,0 +1,79 @@ +// 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.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Sprites; +using System; +using System.Collections.Generic; + +namespace osu.Game.Overlays.Profile.Sections +{ + public class UnderscoredLinkContainer : Container + { + private const int duration = 200; + public Action ClickAction; + private readonly Container underscore; + private readonly FillFlowContainer textContent; + + public IReadOnlyList Text + { + get => textContent.Children; + set + { + textContent.Clear(); + textContent.AddRange(value); + } + } + + public UnderscoredLinkContainer() + { + AutoSizeAxes = Axes.Both; + Child = new Container + { + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + underscore = new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = 1, + Alpha = 0, + AlwaysPresent = true, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + } + }, + textContent = new FillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + }, + }, + }; + } + + protected override bool OnHover(HoverEvent e) + { + underscore.FadeIn(duration, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + underscore.FadeOut(duration, Easing.OutQuint); + base.OnHoverLost(e); + } + + protected override bool OnClick(ClickEvent e) + { + ClickAction?.Invoke(); + return base.OnClick(e); + } + } +} From 52fad723a206ea5d86bb1384405aca64af106293 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Wed, 29 May 2019 19:51:59 +0300 Subject: [PATCH 0051/2815] Implement DrawableMostPlayedBeatmap --- .../Online/TestSceneHistoricalSection.cs | 2 +- .../Historical/DrawableMostPlayedBeatmap.cs | 190 ++++++++++++++++++ .../Historical/DrawableMostPlayedRow.cs | 77 ------- .../PaginatedMostPlayedBeatmapContainer.cs | 2 +- 4 files changed, 192 insertions(+), 79 deletions(-) create mode 100644 osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs delete mode 100644 osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs b/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs index 455807649a..f309112f0d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.Online { typeof(HistoricalSection), typeof(PaginatedMostPlayedBeatmapContainer), - typeof(DrawableMostPlayedRow), + typeof(DrawableMostPlayedBeatmap), typeof(DrawableProfileRow) }; diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs new file mode 100644 index 0000000000..e8ce11555f --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs @@ -0,0 +1,190 @@ +// 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.Sprites; +using osu.Framework.Input.Events; +using osu.Framework.Localisation; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Profile.Sections.Historical +{ + public class DrawableMostPlayedBeatmap : Container + { + private readonly BeatmapInfo beatmap; + private readonly OsuSpriteText mapperText; + private readonly int playCount; + private readonly Box background; + private Color4 idleBackgroundColour; + private Color4 hoveredBackgroundColour; + private const int duration = 200; + private const int cover_width = 100; + private const int corner_radius = 10; + private readonly SpriteIcon icon; + private readonly OsuSpriteText playCountText; + private readonly UnderscoredLinkContainer mapper; + private readonly UnderscoredLinkContainer beatmapName; + + public DrawableMostPlayedBeatmap(BeatmapInfo beatmap, int playCount) + { + this.beatmap = beatmap; + this.playCount = playCount; + + RelativeSizeAxes = Axes.X; + Height = 60; + Masking = true; + CornerRadius = corner_radius; + Children = new Drawable[] + { + new UpdateableBeatmapSetCover + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Y, + Width = cover_width, + BeatmapSet = beatmap.BeatmapSet, + CoverType = BeatmapSetCoverType.List, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = cover_width - corner_radius }, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = corner_radius, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = 15, Right = 20 }, + Children = new Drawable[] + { + beatmapName = new UnderscoredLinkContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.BottomLeft, + Margin = new MarginPadding { Bottom = 2 }, + Text = new OsuSpriteText[] + { + new OsuSpriteText + { + Text = new LocalisedString(( + $"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} [{beatmap.Version}] ", + $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} [{beatmap.Version}] ")), + Font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold) + }, + new OsuSpriteText + { + Text = "by " + new LocalisedString((beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist)), + Font = OsuFont.GetFont(size: 20, weight: FontWeight.Regular) + }, + } + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.TopLeft, + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { Top = 2 }, + Children = new Drawable[] + { + mapperText = new OsuSpriteText + { + Text = "mapped by ", + Font = OsuFont.GetFont(size: 15, weight: FontWeight.Regular), + }, + mapper = new UnderscoredLinkContainer + { + Text = new OsuSpriteText[] + { + new OsuSpriteText + { + Text = beatmap.Metadata.Author.Username, + Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold), + } + } + }, + } + }, + new FillFlowContainer + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + icon = new SpriteIcon + { + Icon = FontAwesome.Solid.CaretRight, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Size = new Vector2(20), + }, + playCountText = new OsuSpriteText + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Text = playCount.ToString(), + Font = OsuFont.GetFont(size: 30, weight: FontWeight.Regular, fixedWidth: true), + }, + } + } + } + }, + } + } + } + } + }; + } + + [BackgroundDependencyLoader(permitNulls: true)] + private void load(OsuColour colors, UserProfileOverlay userProfileOverlay, BeatmapSetOverlay beatmapSetOverlay) + { + idleBackgroundColour = background.Colour = colors.GreySeafoam; + hoveredBackgroundColour = colors.GreySeafoamLight; + mapperText.Colour = mapper.Colour = colors.GreySeafoamLighter; + icon.Colour = playCountText.Colour = colors.Yellow; + + mapper.ClickAction = () => userProfileOverlay.ShowUser(beatmap.Metadata.Author.Id); + beatmapName.ClickAction = () => + { + if (beatmap.OnlineBeatmapID != null) + beatmapSetOverlay?.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value); + else if (beatmap.BeatmapSet?.OnlineBeatmapSetID != null) + beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmap.BeatmapSet.OnlineBeatmapSetID.Value); + }; + } + + protected override bool OnHover(HoverEvent e) + { + background.FadeColour(hoveredBackgroundColour, duration, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + background.FadeColour(idleBackgroundColour, duration, Easing.OutQuint); + base.OnHoverLost(e); + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs deleted file mode 100644 index 1b286f92d3..0000000000 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs +++ /dev/null @@ -1,77 +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.Game.Beatmaps; -using osu.Game.Beatmaps.Drawables; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osuTK; - -namespace osu.Game.Overlays.Profile.Sections.Historical -{ - public class DrawableMostPlayedRow : DrawableProfileRow - { - private readonly BeatmapInfo beatmap; - private readonly int playCount; - - public DrawableMostPlayedRow(BeatmapInfo beatmap, int playCount) - { - this.beatmap = beatmap; - this.playCount = playCount; - } - - protected override Drawable CreateLeftVisual() => new UpdateableBeatmapSetCover - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(80, 50), - BeatmapSet = beatmap.BeatmapSet, - CoverType = BeatmapSetCoverType.List, - }; - - [BackgroundDependencyLoader] - private void load() - { - LeftFlowContainer.Add(new BeatmapMetadataContainer(beatmap)); - LeftFlowContainer.Add(new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: 12)) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - }.With(d => - { - d.AddText("mapped by "); - d.AddUserLink(beatmap.Metadata.Author); - })); - - RightFlowContainer.Add(new FillFlowContainer - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new[] - { - new OsuSpriteText - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Text = playCount.ToString(), - Font = OsuFont.GetFont(size: 18, weight: FontWeight.SemiBold, italics: true) - }, - new OsuSpriteText - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Text = @"times played ", - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular, italics: true) - }, - } - }); - } - } -} diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs index f2eb32c53b..13fe20d063 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs @@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical foreach (var beatmap in beatmaps) { - ItemsContainer.Add(new DrawableMostPlayedRow(beatmap.GetBeatmapInfo(Rulesets), beatmap.PlayCount)); + ItemsContainer.Add(new DrawableMostPlayedBeatmap(beatmap.GetBeatmapInfo(Rulesets), beatmap.PlayCount)); } }); From 6efa61b992a44cf4c3004ca9dc284da8952c90ba Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Wed, 29 May 2019 20:24:01 +0300 Subject: [PATCH 0052/2815] Split UnderscoredLinkContainer in different classes --- .../Historical/DrawableMostPlayedBeatmap.cs | 20 ++++--------- .../Sections/UnderscoredBeatmapLink.cs | 30 +++++++++++++++++++ .../Sections/UnderscoredLinkContainer.cs | 7 +++-- .../Profile/Sections/UnderscoredUserLink.cs | 23 ++++++++++++++ 4 files changed, 62 insertions(+), 18 deletions(-) create mode 100644 osu.Game/Overlays/Profile/Sections/UnderscoredBeatmapLink.cs create mode 100644 osu.Game/Overlays/Profile/Sections/UnderscoredUserLink.cs diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs index e8ce11555f..50f419e07c 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs @@ -30,8 +30,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical private const int corner_radius = 10; private readonly SpriteIcon icon; private readonly OsuSpriteText playCountText; - private readonly UnderscoredLinkContainer mapper; - private readonly UnderscoredLinkContainer beatmapName; + private readonly UnderscoredUserLink mapper; public DrawableMostPlayedBeatmap(BeatmapInfo beatmap, int playCount) { @@ -76,7 +75,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical Padding = new MarginPadding { Left = 15, Right = 20 }, Children = new Drawable[] { - beatmapName = new UnderscoredLinkContainer + new UnderscoredBeatmapLink(beatmap) { Anchor = Anchor.CentreLeft, Origin = Anchor.BottomLeft, @@ -111,7 +110,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical Text = "mapped by ", Font = OsuFont.GetFont(size: 15, weight: FontWeight.Regular), }, - mapper = new UnderscoredLinkContainer + mapper = new UnderscoredUserLink(beatmap.Metadata.Author.Id) { Text = new OsuSpriteText[] { @@ -157,22 +156,13 @@ namespace osu.Game.Overlays.Profile.Sections.Historical }; } - [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuColour colors, UserProfileOverlay userProfileOverlay, BeatmapSetOverlay beatmapSetOverlay) + [BackgroundDependencyLoader] + private void load(OsuColour colors) { idleBackgroundColour = background.Colour = colors.GreySeafoam; hoveredBackgroundColour = colors.GreySeafoamLight; mapperText.Colour = mapper.Colour = colors.GreySeafoamLighter; icon.Colour = playCountText.Colour = colors.Yellow; - - mapper.ClickAction = () => userProfileOverlay.ShowUser(beatmap.Metadata.Author.Id); - beatmapName.ClickAction = () => - { - if (beatmap.OnlineBeatmapID != null) - beatmapSetOverlay?.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value); - else if (beatmap.BeatmapSet?.OnlineBeatmapSetID != null) - beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmap.BeatmapSet.OnlineBeatmapSetID.Value); - }; } protected override bool OnHover(HoverEvent e) diff --git a/osu.Game/Overlays/Profile/Sections/UnderscoredBeatmapLink.cs b/osu.Game/Overlays/Profile/Sections/UnderscoredBeatmapLink.cs new file mode 100644 index 0000000000..370d6d84ef --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/UnderscoredBeatmapLink.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 osu.Framework.Allocation; +using osu.Game.Beatmaps; + +namespace osu.Game.Overlays.Profile.Sections +{ + public class UnderscoredBeatmapLink : UnderscoredLinkContainer + { + private readonly BeatmapInfo beatmap; + + public UnderscoredBeatmapLink(BeatmapInfo beatmap) + { + this.beatmap = beatmap; + } + + [BackgroundDependencyLoader(true)] + private void load(BeatmapSetOverlay beatmapSetOverlay) + { + ClickAction = () => + { + if (beatmap.OnlineBeatmapID != null) + beatmapSetOverlay?.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value); + else if (beatmap.BeatmapSet?.OnlineBeatmapSetID != null) + beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmap.BeatmapSet.OnlineBeatmapSetID.Value); + }; + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/UnderscoredLinkContainer.cs b/osu.Game/Overlays/Profile/Sections/UnderscoredLinkContainer.cs index 087bd03837..8daf0bd24d 100644 --- a/osu.Game/Overlays/Profile/Sections/UnderscoredLinkContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/UnderscoredLinkContainer.cs @@ -11,13 +11,14 @@ using System.Collections.Generic; namespace osu.Game.Overlays.Profile.Sections { - public class UnderscoredLinkContainer : Container + public abstract class UnderscoredLinkContainer : Container { private const int duration = 200; - public Action ClickAction; private readonly Container underscore; private readonly FillFlowContainer textContent; + protected Action ClickAction; + public IReadOnlyList Text { get => textContent.Children; @@ -28,7 +29,7 @@ namespace osu.Game.Overlays.Profile.Sections } } - public UnderscoredLinkContainer() + protected UnderscoredLinkContainer() { AutoSizeAxes = Axes.Both; Child = new Container diff --git a/osu.Game/Overlays/Profile/Sections/UnderscoredUserLink.cs b/osu.Game/Overlays/Profile/Sections/UnderscoredUserLink.cs new file mode 100644 index 0000000000..f50bc7f7ba --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/UnderscoredUserLink.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 osu.Framework.Allocation; + +namespace osu.Game.Overlays.Profile.Sections +{ + public class UnderscoredUserLink : UnderscoredLinkContainer + { + private readonly long userId; + + public UnderscoredUserLink(long userId) + { + this.userId = userId; + } + + [BackgroundDependencyLoader(true)] + private void load(UserProfileOverlay userProfileOverlay) + { + ClickAction = () => userProfileOverlay?.ShowUser(userId); + } + } +} From 1baf922f2c3dace2bfa09f12e169cf8c078cb00b Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Wed, 29 May 2019 20:36:14 +0300 Subject: [PATCH 0053/2815] CI fixes --- .../Sections/Historical/DrawableMostPlayedBeatmap.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs index 50f419e07c..34b6884fe0 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs @@ -19,9 +19,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { public class DrawableMostPlayedBeatmap : Container { - private readonly BeatmapInfo beatmap; private readonly OsuSpriteText mapperText; - private readonly int playCount; private readonly Box background; private Color4 idleBackgroundColour; private Color4 hoveredBackgroundColour; @@ -34,9 +32,6 @@ namespace osu.Game.Overlays.Profile.Sections.Historical public DrawableMostPlayedBeatmap(BeatmapInfo beatmap, int playCount) { - this.beatmap = beatmap; - this.playCount = playCount; - RelativeSizeAxes = Axes.X; Height = 60; Masking = true; @@ -80,7 +75,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical Anchor = Anchor.CentreLeft, Origin = Anchor.BottomLeft, Margin = new MarginPadding { Bottom = 2 }, - Text = new OsuSpriteText[] + Text = new[] { new OsuSpriteText { @@ -112,7 +107,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical }, mapper = new UnderscoredUserLink(beatmap.Metadata.Author.Id) { - Text = new OsuSpriteText[] + Text = new[] { new OsuSpriteText { From b32ffab58054309c25c2a86895bfce998af3b987 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Fri, 31 May 2019 02:19:09 +0300 Subject: [PATCH 0054/2815] Make the DrawableMostPlayedBeatmap inherit OsuHoverContainer --- .../Historical/DrawableMostPlayedBeatmap.cs | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs index 34b6884fe0..98872d6141 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs @@ -6,32 +6,33 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osuTK; -using osuTK.Graphics; +using System.Collections.Generic; namespace osu.Game.Overlays.Profile.Sections.Historical { - public class DrawableMostPlayedBeatmap : Container + public class DrawableMostPlayedBeatmap : OsuHoverContainer { private readonly OsuSpriteText mapperText; private readonly Box background; - private Color4 idleBackgroundColour; - private Color4 hoveredBackgroundColour; - private const int duration = 200; private const int cover_width = 100; private const int corner_radius = 10; private readonly SpriteIcon icon; private readonly OsuSpriteText playCountText; private readonly UnderscoredUserLink mapper; + protected override IEnumerable EffectTargets => new[] { background }; + public DrawableMostPlayedBeatmap(BeatmapInfo beatmap, int playCount) { + Enabled.Value = true; //manually enabled, because we have no action + RelativeSizeAxes = Axes.X; Height = 60; Masking = true; @@ -154,22 +155,10 @@ namespace osu.Game.Overlays.Profile.Sections.Historical [BackgroundDependencyLoader] private void load(OsuColour colors) { - idleBackgroundColour = background.Colour = colors.GreySeafoam; - hoveredBackgroundColour = colors.GreySeafoamLight; + IdleColour = colors.GreySeafoam; + HoverColour = colors.GreySeafoamLight; mapperText.Colour = mapper.Colour = colors.GreySeafoamLighter; icon.Colour = playCountText.Colour = colors.Yellow; } - - protected override bool OnHover(HoverEvent e) - { - background.FadeColour(hoveredBackgroundColour, duration, Easing.OutQuint); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - background.FadeColour(idleBackgroundColour, duration, Easing.OutQuint); - base.OnHoverLost(e); - } } } From 651706b10e4ba85bea3faba3ccf70c2736cbf5aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2019 16:17:20 +0900 Subject: [PATCH 0055/2815] Account for user/system offsets when deciding on an initial seek time Closes #3043 Remove "AllowLeadIn" flag --- osu.Game/Screens/Play/GameplayClockContainer.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index c151e598f7..f6a23575e9 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -26,6 +26,10 @@ namespace osu.Game.Screens.Play private readonly WorkingBeatmap beatmap; private readonly IReadOnlyList mods; + private readonly bool allowLeadIn; + + private readonly double gameplayStartTime; + /// /// The original source (usually a 's track). /// @@ -60,6 +64,8 @@ namespace osu.Game.Screens.Play private readonly FramedOffsetClock platformOffsetClock; + private double totalOffset => userOffsetClock.Offset + platformOffsetClock.Offset; + public GameplayClockContainer(WorkingBeatmap beatmap, IReadOnlyList mods, double gameplayStartTime) { this.beatmap = beatmap; @@ -93,6 +99,9 @@ namespace osu.Game.Screens.Play userAudioOffset = config.GetBindable(OsuSetting.AudioOffset); userAudioOffset.BindValueChanged(offset => userOffsetClock.Offset = offset.NewValue, true); + adjustableClock.Seek(Math.Min(0, gameplayStartTime - totalOffset - (allowLeadIn ? beatmap.BeatmapInfo.AudioLeadIn : 0))); + adjustableClock.ProcessFrame(); + UserPlaybackRate.ValueChanged += _ => updateRate(); Seek(Math.Min(-beatmap.BeatmapInfo.AudioLeadIn, gameplayStartTime)); From d1d1c4ee7a5df46e20330b28b293dc700c9da7e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2019 16:13:58 +0900 Subject: [PATCH 0056/2815] Add lead-in tests --- .../Visual/Gameplay/TestCaseLeadIn.cs | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestCaseLeadIn.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseLeadIn.cs new file mode 100644 index 0000000000..0186ba1da1 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseLeadIn.cs @@ -0,0 +1,88 @@ +// 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; +using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestCaseLeadIn : RateAdjustedBeatmapTestCase + { + private Ruleset ruleset; + + private LeadInPlayer player; + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + Add(new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Depth = int.MaxValue + }); + + ruleset = rulesets.AvailableRulesets.First().CreateInstance(); + } + + [Test] + public void TestShortLeadIn() + { + AddStep("create player", () => loadPlayerWithBeatmap(new TestBeatmap(ruleset.RulesetInfo) { BeatmapInfo = { AudioLeadIn = 1000 } })); + AddUntilStep("correct lead-in", () => player.FirstFrameClockTime == 0); + } + + [Test] + public void TestLongLeadIn() + { + AddStep("create player", () => loadPlayerWithBeatmap(new TestBeatmap(ruleset.RulesetInfo) { BeatmapInfo = { AudioLeadIn = 10000 } })); + AddUntilStep("correct lead-in", () => player.FirstFrameClockTime == player.GameplayStartTime - 10000); + } + + private void loadPlayerWithBeatmap(IBeatmap beatmap) + { + Beatmap.Value = new TestWorkingBeatmap(beatmap, Clock); + + LoadScreen(player = new LeadInPlayer + { + AllowPause = false, + AllowResults = false, + }); + } + + private class LeadInPlayer : Player + { + public double? FirstFrameClockTime; + + public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; + + public double GameplayStartTime => DrawableRuleset.GameplayStartTime; + + public double GameplayClockTime => GameplayClockContainer.GameplayClock.CurrentTime; + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + if (!FirstFrameClockTime.HasValue) + { + FirstFrameClockTime = GameplayClockContainer.GameplayClock.CurrentTime; + AddInternal(new OsuSpriteText + { + Text = $"GameplayStartTime: {DrawableRuleset.GameplayStartTime} " + + $"LeadInTime: {Beatmap.Value.BeatmapInfo.AudioLeadIn} " + + $"FirstFrameClockTime: {FirstFrameClockTime}" + }); + } + } + } + } +} From e03a664970e39158396b478dfef1f6aa7e0574b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2019 16:18:15 +0900 Subject: [PATCH 0057/2815] Fix lead-in logic to match stable Also adds storyboard event priority support. --- osu.Game/Screens/Play/GameplayClock.cs | 1 - osu.Game/Screens/Play/GameplayClockContainer.cs | 8 +++++++- osu.Game/Storyboards/Storyboard.cs | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index b1948d02d5..6c9dacfc39 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -34,7 +34,6 @@ namespace osu.Game.Screens.Play public void ProcessFrame() { // we do not want to process the underlying clock. - // this is handled by PauseContainer. } public double ElapsedFrameTime => underlyingClock.ElapsedFrameTime; diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index f6a23575e9..7abdc8ef66 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -99,7 +99,13 @@ namespace osu.Game.Screens.Play userAudioOffset = config.GetBindable(OsuSetting.AudioOffset); userAudioOffset.BindValueChanged(offset => userOffsetClock.Offset = offset.NewValue, true); - adjustableClock.Seek(Math.Min(0, gameplayStartTime - totalOffset - (allowLeadIn ? beatmap.BeatmapInfo.AudioLeadIn : 0))); + double startTime = -beatmap.BeatmapInfo.AudioLeadIn; + + startTime = Math.Min(startTime, beatmap.Storyboard.FirstEventTime); + startTime = Math.Min(startTime, gameplayStartTime); + startTime = Math.Min(startTime, 0); + + Seek(startTime); adjustableClock.ProcessFrame(); UserPlaybackRate.ValueChanged += _ => updateRate(); diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 3d988c5fe3..401a7ce25a 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -17,6 +17,8 @@ namespace osu.Game.Storyboards public bool HasDrawable => Layers.Any(l => l.Elements.Any(e => e.IsDrawable)); + public double FirstEventTime => Layers.Min(l => l.Elements.FirstOrDefault()?.StartTime ?? 0); + public Storyboard() { layers.Add("Background", new StoryboardLayer("Background", 3)); From a2fbcb2bd39e756503d6e98a9cf25fb57dba021e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 31 May 2019 15:58:46 +0900 Subject: [PATCH 0058/2815] Fix rebase regressions --- osu.Game.Tests/Visual/Gameplay/TestCaseLeadIn.cs | 14 ++++++++------ osu.Game/Screens/Play/GameplayClockContainer.cs | 6 ------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseLeadIn.cs index 0186ba1da1..33f1e4062f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseLeadIn.cs @@ -15,7 +15,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Gameplay { - public class TestCaseLeadIn : RateAdjustedBeatmapTestCase + public class TestCaseLeadIn : RateAdjustedBeatmapTestScene { private Ruleset ruleset; @@ -52,15 +52,16 @@ namespace osu.Game.Tests.Visual.Gameplay { Beatmap.Value = new TestWorkingBeatmap(beatmap, Clock); - LoadScreen(player = new LeadInPlayer - { - AllowPause = false, - AllowResults = false, - }); + LoadScreen(player = new LeadInPlayer()); } private class LeadInPlayer : Player { + public LeadInPlayer() + : base(false, false) + { + } + public double? FirstFrameClockTime; public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; @@ -72,6 +73,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); + if (!FirstFrameClockTime.HasValue) { FirstFrameClockTime = GameplayClockContainer.GameplayClock.CurrentTime; diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 7abdc8ef66..d718424b29 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -26,10 +26,6 @@ namespace osu.Game.Screens.Play private readonly WorkingBeatmap beatmap; private readonly IReadOnlyList mods; - private readonly bool allowLeadIn; - - private readonly double gameplayStartTime; - /// /// The original source (usually a 's track). /// @@ -91,8 +87,6 @@ namespace osu.Game.Screens.Play GameplayClock.IsPaused.BindTo(IsPaused); } - private double totalOffset => userOffsetClock.Offset + platformOffsetClock.Offset; - [BackgroundDependencyLoader] private void load(OsuConfigManager config) { From 2e7922c3f9a98a985dbc3f9e2cb453a534b3f9e9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 15 Jun 2019 23:53:28 +0900 Subject: [PATCH 0059/2815] Fix 0-length sliders not getting correct lengths --- .../Objects/Legacy/Catch/ConvertHitObjectParser.cs | 3 ++- .../Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 9 +++++++-- .../Objects/Legacy/Mania/ConvertHitObjectParser.cs | 3 ++- .../Objects/Legacy/Osu/ConvertHitObjectParser.cs | 6 +++--- .../Objects/Legacy/Taiko/ConvertHitObjectParser.cs | 3 ++- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index 48f637dfe8..c968fe469a 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -37,7 +37,8 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> nodeSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount, + List> nodeSamples) { newCombo |= forceNewCombo; comboOffset += extraComboOffset; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index c14f3b6a42..36ae15efcc 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Objects.Legacy else if (type.HasFlag(ConvertHitObjectType.Slider)) { PathType pathType = PathType.Catmull; - double length = 0; + double? length = null; string[] pointSplit = split[5].Split('|'); @@ -130,7 +130,11 @@ namespace osu.Game.Rulesets.Objects.Legacy repeatCount = Math.Max(0, repeatCount - 1); if (split.Length > 7) + { length = Math.Max(0, Parsing.ParseDouble(split[7])); + if (length == 0) + length = null; + } if (split.Length > 10) readCustomSampleBanks(split[10], bankInfo); @@ -291,7 +295,8 @@ namespace osu.Game.Rulesets.Objects.Legacy /// The slider repeat count. /// The samples to be played when the slider nodes are hit. This includes the head and tail of the slider. /// The hit object. - protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> nodeSamples); + protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount, + List> nodeSamples); /// /// Creates a legacy Spinner-type hit object. diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs index 8a3e232e60..5acc085ba1 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs @@ -26,7 +26,8 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> nodeSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount, + List> nodeSamples) { return new ConvertSlider { diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs index b98de32bd0..d46e1fa86a 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.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 osuTK; using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; @@ -38,7 +37,8 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> nodeSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount, + List> nodeSamples) { newCombo |= forceNewCombo; comboOffset += extraComboOffset; @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu Position = position, NewCombo = FirstObject || newCombo, ComboOffset = comboOffset, - Path = new SliderPath(pathType, controlPoints, Math.Max(0, length)), + Path = new SliderPath(pathType, controlPoints, length), NodeSamples = nodeSamples, RepeatCount = repeatCount }; diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs index bab21b31ad..39fb9e3b3a 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs @@ -23,7 +23,8 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko return new ConvertHit(); } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> nodeSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount, + List> nodeSamples) { return new ConvertSlider { From 515534cb34c4433d09224988e54848105e343f99 Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Sun, 16 Jun 2019 00:02:26 +0700 Subject: [PATCH 0060/2815] Adding custom tooltip to DifficultyIcon --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 94 ++++++++++++++++++- 1 file changed, 92 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 0a0ad28fdf..22a3fd0c8a 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.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; @@ -6,17 +6,20 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; using osuTK; using osuTK.Graphics; namespace osu.Game.Beatmaps.Drawables { - public class DifficultyIcon : DifficultyColouredContainer + public class DifficultyIcon : DifficultyColouredContainer, IHasCustomTooltip { private readonly RulesetInfo ruleset; @@ -27,10 +30,97 @@ namespace osu.Game.Beatmaps.Drawables throw new ArgumentNullException(nameof(beatmap)); this.ruleset = ruleset ?? beatmap.Ruleset; + TooltipText = $"{beatmap.Version}${beatmap.StarDifficulty.ToString("0.##")}"; Size = new Vector2(20); } + public string TooltipText { get; set; } + + public ITooltip GetCustomTooltip() => new DifficultyIconTooltip(); + + public class DifficultyIconTooltip : VisibilityContainer, ITooltip + { + private readonly OsuSpriteText difficultyName, starRating; + private readonly Box background; + + public string TooltipText { get; set; } + + public DifficultyIconTooltip() + { + AutoSizeAxes = Axes.Both; + Masking = true; + CornerRadius = 5; + + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Padding = new MarginPadding(10), + Children = new Drawable[] + { + difficultyName = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 16, weight: FontWeight.Bold), + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + starRating = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular), + }, + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Left = 4 }, + Icon = FontAwesome.Solid.Star, + Size = new Vector2(12), + Colour = Color4.White, + }, + } + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.GreyCarmineDark; + } + + public void Refresh() + { + var info = TooltipText.Split('$'); + difficultyName.Text = info[0]; + starRating.Text = info[1]; + } + + public void Move(Vector2 pos) => this.Position = pos; + + protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); + + protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); + } + [BackgroundDependencyLoader] private void load() { From 1f2f26a5038be22a0b92fecb728ce2d862d191b8 Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Sun, 16 Jun 2019 00:40:44 +0700 Subject: [PATCH 0061/2815] Add color to star rating and star sprite icon --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 22a3fd0c8a..350e450877 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -37,7 +37,7 @@ namespace osu.Game.Beatmaps.Drawables public string TooltipText { get; set; } - public ITooltip GetCustomTooltip() => new DifficultyIconTooltip(); + public ITooltip GetCustomTooltip() => new DifficultyIconTooltip(AccentColour); public class DifficultyIconTooltip : VisibilityContainer, ITooltip { @@ -46,7 +46,7 @@ namespace osu.Game.Beatmaps.Drawables public string TooltipText { get; set; } - public DifficultyIconTooltip() + public DifficultyIconTooltip(Color4 accentColour) { AutoSizeAxes = Axes.Both; Masking = true; @@ -84,6 +84,7 @@ namespace osu.Game.Beatmaps.Drawables Anchor = Anchor.Centre, Origin = Anchor.Centre, Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular), + Colour = accentColour }, new SpriteIcon { @@ -92,7 +93,7 @@ namespace osu.Game.Beatmaps.Drawables Margin = new MarginPadding { Left = 4 }, Icon = FontAwesome.Solid.Star, Size = new Vector2(12), - Colour = Color4.White, + Colour = accentColour, }, } } From aa53f1432998eda35c61f5a81c00802e42c66371 Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Mon, 17 Jun 2019 22:44:28 +0700 Subject: [PATCH 0062/2815] Make sure tooltip appear in specific cases --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 6 +++--- osu.Game/Overlays/Direct/DirectPanel.cs | 2 +- osu.Game/Screens/Multi/Components/ModeTypeInfo.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 350e450877..5f3c712c14 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -23,14 +23,14 @@ namespace osu.Game.Beatmaps.Drawables { private readonly RulesetInfo ruleset; - public DifficultyIcon(BeatmapInfo beatmap, RulesetInfo ruleset = null) + public DifficultyIcon(BeatmapInfo beatmap, RulesetInfo ruleset = null, Boolean shouldShowTooltip = false) : base(beatmap) { if (beatmap == null) throw new ArgumentNullException(nameof(beatmap)); this.ruleset = ruleset ?? beatmap.Ruleset; - TooltipText = $"{beatmap.Version}${beatmap.StarDifficulty.ToString("0.##")}"; + TooltipText = shouldShowTooltip ? $"{beatmap.Version}${beatmap.StarDifficulty.ToString("0.##")}" : String.Empty; Size = new Vector2(20); } @@ -115,7 +115,7 @@ namespace osu.Game.Beatmaps.Drawables starRating.Text = info[1]; } - public void Move(Vector2 pos) => this.Position = pos; + public void Move(Vector2 pos) => Position = pos; protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index f413dc3771..b2c9df6442 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -140,7 +140,7 @@ namespace osu.Game.Overlays.Direct var icons = new List(); foreach (var b in SetInfo.Beatmaps.OrderBy(beatmap => beatmap.StarDifficulty)) - icons.Add(new DifficultyIcon(b)); + icons.Add(new DifficultyIcon(b, null, true)); return icons; } diff --git a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs index 6080458aec..434c5c443b 100644 --- a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs +++ b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs @@ -56,7 +56,7 @@ namespace osu.Game.Screens.Multi.Components if (item?.Beatmap != null) { drawableRuleset.FadeIn(transition_duration); - drawableRuleset.Child = new DifficultyIcon(item.Beatmap, item.Ruleset) { Size = new Vector2(height) }; + drawableRuleset.Child = new DifficultyIcon(item.Beatmap, item.Ruleset, true) { Size = new Vector2(height) }; } else drawableRuleset.FadeOut(transition_duration); From d0a452cdf9f7ff6e92073259e136110e822b67aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jun 2019 01:10:03 +0900 Subject: [PATCH 0063/2815] Show tooltips for beatmap set panels --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 51ca9902d2..68a0a8b5ee 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -190,7 +190,7 @@ namespace osu.Game.Screens.Select.Carousel private readonly BindableBool filtered = new BindableBool(); public FilterableDifficultyIcon(CarouselBeatmap item) - : base(item.Beatmap) + : base(item.Beatmap, shouldShowTooltip: true) { filtered.BindTo(item.Filtered); filtered.ValueChanged += isFiltered => Schedule(() => this.FadeTo(isFiltered.NewValue ? 0.1f : 1, 100)); From 63d0324f96282d048f273552b7c1b5c95d019b52 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jun 2019 01:10:14 +0900 Subject: [PATCH 0064/2815] Fix inspection --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 5f3c712c14..f5c8d0d029 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -30,7 +30,7 @@ namespace osu.Game.Beatmaps.Drawables throw new ArgumentNullException(nameof(beatmap)); this.ruleset = ruleset ?? beatmap.Ruleset; - TooltipText = shouldShowTooltip ? $"{beatmap.Version}${beatmap.StarDifficulty.ToString("0.##")}" : String.Empty; + TooltipText = shouldShowTooltip ? $"{beatmap.Version}${beatmap.StarDifficulty:0.##}" : String.Empty; Size = new Vector2(20); } From 0c95dff3d690bd73dccf74f7ccdcdf45d1b275ea Mon Sep 17 00:00:00 2001 From: Roman Kapustin Date: Mon, 1 Jul 2019 18:41:08 +0300 Subject: [PATCH 0065/2815] Make FlowContainer insertion cleaner --- .../Changelog/ChangelogSingleBuild.cs | 24 +++++++------------ osu.Game/Overlays/Music/PlaylistList.cs | 5 +--- .../Profile/Header/MedalHeaderContainer.cs | 4 +--- .../Sections/Ranks/DrawableProfileScore.cs | 3 +-- osu.Game/Overlays/Settings/SettingsItem.cs | 3 +-- 5 files changed, 12 insertions(+), 27 deletions(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs index 36ae5a756c..fdd3d6d555 100644 --- a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs @@ -88,24 +88,16 @@ namespace osu.Game.Overlays.Changelog }); } - NavigationIconButton left, right; - - fill.AddRange(new[] + fill.Insert(-1, new NavigationIconButton(Build.Versions?.Previous) { - left = new NavigationIconButton(Build.Versions?.Previous) - { - Icon = FontAwesome.Solid.ChevronLeft, - SelectBuild = b => SelectBuild(b) - }, - right = new NavigationIconButton(Build.Versions?.Next) - { - Icon = FontAwesome.Solid.ChevronRight, - SelectBuild = b => SelectBuild(b) - }, + Icon = FontAwesome.Solid.ChevronLeft, + SelectBuild = b => SelectBuild(b) + }); + fill.Insert(1, new NavigationIconButton(Build.Versions?.Next) + { + Icon = FontAwesome.Solid.ChevronRight, + SelectBuild = b => SelectBuild(b) }); - - fill.SetLayoutPosition(left, -1); - fill.SetLayoutPosition(right, 1); return fill; } diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index 07040f166d..539601c359 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -85,10 +85,7 @@ namespace osu.Game.Overlays.Music private void addBeatmapSet(BeatmapSetInfo obj) => Schedule(() => { - var newItem = new PlaylistItem(obj) { OnSelect = set => Selected?.Invoke(set) }; - - items.Add(newItem); - items.SetLayoutPosition(newItem, items.Count - 1); + items.Insert(items.Count - 1, new PlaylistItem(obj) { OnSelect = set => Selected?.Invoke(set) }); }); private void removeBeatmapSet(BeatmapSetInfo obj) => Schedule(() => diff --git a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs index 67229a80c0..45bc60f794 100644 --- a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs @@ -78,10 +78,8 @@ namespace osu.Game.Overlays.Profile.Header int displayIndex = index; LoadComponentAsync(new DrawableBadge(badges[index]), asyncBadge => { - badgeFlowContainer.Add(asyncBadge); - // load in stable order regardless of async load order. - badgeFlowContainer.SetLayoutPosition(asyncBadge, displayIndex); + badgeFlowContainer.Insert(displayIndex, asyncBadge); }); } } diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index 0a90c9b135..b77357edd8 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -49,8 +49,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Font = OsuFont.GetFont(size: 11, weight: FontWeight.Regular, italics: true) }; - RightFlowContainer.Add(text); - RightFlowContainer.SetLayoutPosition(text, 1); + RightFlowContainer.Insert(1, text); LeftFlowContainer.Add(new BeatmapMetadataContainer(Score.Beatmap)); LeftFlowContainer.Add(new DrawableDate(Score.Date)); diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index ae840c8c00..d48c0b6b66 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -46,8 +46,7 @@ namespace osu.Game.Overlays.Settings if (text == null) { // construct lazily for cases where the label is not needed (may be provided by the Control). - Add(text = new OsuSpriteText()); - FlowContent.SetLayoutPosition(text, -1); + FlowContent.Insert(-1, text = new OsuSpriteText()); } text.Text = value; From 7bdf73795669a69582126c13211acea559f1f261 Mon Sep 17 00:00:00 2001 From: Roman Kapustin Date: Mon, 1 Jul 2019 18:41:40 +0300 Subject: [PATCH 0066/2815] Make notification insertion cleaner --- osu.Game/Overlays/Notifications/NotificationSection.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Notifications/NotificationSection.cs b/osu.Game/Overlays/Notifications/NotificationSection.cs index f9278bbbd2..17a2d4cf9f 100644 --- a/osu.Game/Overlays/Notifications/NotificationSection.cs +++ b/osu.Game/Overlays/Notifications/NotificationSection.cs @@ -26,8 +26,7 @@ namespace osu.Game.Overlays.Notifications public void Add(Notification notification, float position) { - notifications.Add(notification); - notifications.SetLayoutPosition(notification, position); + notifications.Insert((int)position, notification); } public IEnumerable AcceptTypes; From 9037fb59de95a3cdcd6dd4de68b0461244b33a35 Mon Sep 17 00:00:00 2001 From: Roman Kapustin Date: Mon, 1 Jul 2019 18:42:18 +0300 Subject: [PATCH 0067/2815] Make BeatmapOptionsButton insertion cleaner --- osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs index 669264cef0..ede526f9da 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs @@ -110,8 +110,7 @@ namespace osu.Game.Screens.Select.Options HotKey = hotkey }; - buttonsContainer.Add(button); - buttonsContainer.SetLayoutPosition(button, depth); + buttonsContainer.Insert((int)depth, button); } } } From 5496b8bc58fae51b4dc242bb5eb0051a7d2039a1 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Mon, 1 Jul 2019 20:11:50 +0200 Subject: [PATCH 0068/2815] Rewrote traceable mod to inherit from hidden --- osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs | 3 + osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 2 + osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 90 ++++++++----------- .../Objects/Drawables/DrawableHitCircle.cs | 23 ++--- osu.Game/Overlays/Mods/ModSection.cs | 4 +- 5 files changed, 48 insertions(+), 74 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs index 8072dc09c1..9b1f43b209 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.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 osu.Framework.Bindables; @@ -28,6 +29,8 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; + public override Type[] IncompatibleMods => new[] { typeof(OsuModTraceable) }; + private Bindable increaseFirstObjectVisibility = new Bindable(); public void ReadFromConfig(OsuConfigManager config) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index ddf708d0f1..5887cb2865 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Description => @"Play with no approach circles and fading circles/sliders."; public override double ScoreMultiplier => 1.06; + public override Type[] IncompatibleMods => new[] { typeof(OsuModTraceable) }; + private const double fade_in_duration_multiplier = 0.4; private const double fade_out_duration_multiplier = 0.3; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index a6d8d35125..858b7e15c8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -2,83 +2,63 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Linq; using System.Collections.Generic; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Osu.Mods { - internal class OsuModTraceable : Mod, IApplicableToDrawableHitObjects + internal class OsuModTraceable : OsuModHidden, IReadFromConfig, IApplicableToDrawableHitObjects { public override string Name => "Traceable"; - public override string ShortenedName => "TC"; - public override FontAwesome Icon => FontAwesome.fa_snapchat_ghost; + public override string Acronym => "TC"; + public override IconUsage Icon => FontAwesome.Brands.SnapchatGhost; public override ModType Type => ModType.Fun; public override string Description => "Put your faith in the approach circles..."; public override double ScoreMultiplier => 1; + public override Type[] IncompatibleMods => new[] { typeof(OsuModHidden), typeof(OsuModGrow) }; - public void ApplyToDrawableHitObjects(IEnumerable drawables) + public override void ApplyToDrawableHitObjects(IEnumerable drawables) { - foreach (var drawable in drawables) - drawable.ApplyCustomUpdateState += ApplyTraceableState; + foreach (var drawable in drawables.Skip(IncreaseFirstObjectVisibility.Value ? 1 : 0)) + { + switch (drawable) + { + case DrawableHitCircle _: + drawable.ApplyCustomUpdateState += ApplyTraceableState; + break; + case DrawableSlider slider: + slider.ApplyCustomUpdateState += ApplyHiddenState; + slider.HeadCircle.ApplyCustomUpdateState += ApplyTraceableState; + break; + default: + drawable.ApplyCustomUpdateState += ApplyHiddenState; + break; + } + } } - /* Similar to ApplyHiddenState, only different if drawable is DrawableHitCircle. - * If we'd use ApplyHiddenState instead but only on non-DrawableHitCircle's, then - * the nested object HeadCircle of DrawableSlider would still use ApplyHiddenState, - * thus treating the DrawableHitCircle with the hidden mod instead of the traceable mod. - */ protected void ApplyTraceableState(DrawableHitObject drawable, ArmedState state) { - if (!(drawable is DrawableOsuHitObject d)) + if (!(drawable is DrawableHitCircle circle)) return; - var h = d.HitObject; + var h = circle.HitObject; - var fadeOutStartTime = h.StartTime - h.TimePreempt + h.TimeFadeIn; - - // new duration from completed fade in to end (before fading out) - var longFadeDuration = ((h as IHasEndTime)?.EndTime ?? h.StartTime) - fadeOutStartTime; - - switch (drawable) + // we only want to see the approach circle + using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) { - case DrawableHitCircle circle: - // we only want to see the approach circle - using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) - circle.HideButApproachCircle(); - - // approach circle fades out quickly at StartTime - using (drawable.BeginAbsoluteSequence(h.StartTime, true)) - circle.ApproachCircle.FadeOut(50); - - break; - case DrawableSlider slider: - using (slider.BeginAbsoluteSequence(fadeOutStartTime, true)) - slider.Body.FadeOut(longFadeDuration, Easing.Out); - - break; - case DrawableSliderTick sliderTick: - // slider ticks fade out over up to one second - var tickFadeOutDuration = Math.Min(sliderTick.HitObject.TimePreempt - DrawableSliderTick.ANIM_DURATION, 1000); - - using (sliderTick.BeginAbsoluteSequence(sliderTick.HitObject.StartTime - tickFadeOutDuration, true)) - sliderTick.FadeOut(tickFadeOutDuration); - - break; - case DrawableSpinner spinner: - // hide elements we don't care about. - spinner.Disc.Hide(); - spinner.Ticks.Hide(); - spinner.Background.Hide(); - - using (spinner.BeginAbsoluteSequence(fadeOutStartTime + longFadeDuration, true)) - spinner.FadeOut(h.TimePreempt); - - break; + circle.circle.Hide(); // CirclePiece + circle.circle.AlwaysPresent = true; + circle.ring.Hide(); + circle.flash.Hide(); + circle.explode.Hide(); + circle.number.Hide(); + circle.glow.Hide(); + circle.ApproachCircle.Show(); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 67c8371340..31c86474cd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -17,12 +17,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public class DrawableHitCircle : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach { public ApproachCircle ApproachCircle; - private readonly CirclePiece circle; - private readonly RingPiece ring; - private readonly FlashPiece flash; - private readonly ExplodePiece explode; - private readonly NumberPiece number; - private readonly GlowPiece glow; + public readonly CirclePiece circle; + public readonly RingPiece ring; + public readonly FlashPiece flash; + public readonly ExplodePiece explode; + public readonly NumberPiece number; + public readonly GlowPiece glow; private readonly IBindable positionBindable = new Bindable(); private readonly IBindable stackHeightBindable = new Bindable(); @@ -113,17 +113,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - public void HideButApproachCircle() - { - circle.Hide(); - circle.AlwaysPresent = true; - ring.Hide(); - flash.Hide(); - explode.Hide(); - number.Hide(); - glow.Hide(); - } - protected override void CheckForResult(bool userTriggered, double timeOffset) { if (!userTriggered) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index dedd397fa5..0eca1bcbec 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -112,7 +112,7 @@ namespace osu.Game.Overlays.Mods if (selected == null) continue; foreach (var type in modTypes) - if (type.IsInstanceOfType(selected)) + if (type.IsInstanceOfType(selected) && !selected.GetType().IsSubclassOf(type)) { if (immediate) button.Deselect(); @@ -130,7 +130,7 @@ namespace osu.Game.Overlays.Mods { foreach (var button in buttons) { - int i = Array.FindIndex(button.Mods, m => modTypes.Any(t => t.IsInstanceOfType(m))); + int i = Array.FindIndex(button.Mods, m => modTypes.Any(t => t.IsInstanceOfType(m) && !m.GetType().IsSubclassOf(t))); if (i >= 0) button.SelectAt(i); From d753f446e4fb995ea8ad403c851e22ff9bf46d13 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Mon, 1 Jul 2019 20:20:25 +0200 Subject: [PATCH 0069/2815] Updated license header --- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index 858b7e15c8..fe1b20c0cc 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/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; using System.Linq; From 4145173ac905e0b3eef36e77ae542695dc68712c Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Tue, 2 Jul 2019 04:04:07 +0200 Subject: [PATCH 0070/2815] Combined hidden with traceable as multi mod --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 1 - osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 22 +++++---- .../Objects/Drawables/DrawableHitCircle.cs | 48 +++++++++---------- osu.Game.Rulesets.Osu/OsuRuleset.cs | 4 +- .../TestSceneModSelectOverlay.cs | 5 +- osu.Game/Overlays/Mods/ModSection.cs | 6 ++- 6 files changed, 44 insertions(+), 42 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 5887cb2865..2723be9df8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -17,7 +17,6 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Description => @"Play with no approach circles and fading circles/sliders."; public override double ScoreMultiplier => 1.06; - public override Type[] IncompatibleMods => new[] { typeof(OsuModTraceable) }; private const double fade_in_duration_multiplier = 0.4; private const double fade_out_duration_multiplier = 0.3; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index fe1b20c0cc..64a331213f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -11,15 +11,15 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Mods { - internal class OsuModTraceable : OsuModHidden, IReadFromConfig, IApplicableToDrawableHitObjects + internal class OsuModTraceable : OsuModHidden { public override string Name => "Traceable"; public override string Acronym => "TC"; public override IconUsage Icon => FontAwesome.Brands.SnapchatGhost; - public override ModType Type => ModType.Fun; + public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Put your faith in the approach circles..."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModHidden), typeof(OsuModGrow) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModGrow) }; public override void ApplyToDrawableHitObjects(IEnumerable drawables) { @@ -30,10 +30,12 @@ namespace osu.Game.Rulesets.Osu.Mods case DrawableHitCircle _: drawable.ApplyCustomUpdateState += ApplyTraceableState; break; + case DrawableSlider slider: slider.ApplyCustomUpdateState += ApplyHiddenState; slider.HeadCircle.ApplyCustomUpdateState += ApplyTraceableState; break; + default: drawable.ApplyCustomUpdateState += ApplyHiddenState; break; @@ -51,13 +53,13 @@ namespace osu.Game.Rulesets.Osu.Mods // we only want to see the approach circle using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) { - circle.circle.Hide(); // CirclePiece - circle.circle.AlwaysPresent = true; - circle.ring.Hide(); - circle.flash.Hide(); - circle.explode.Hide(); - circle.number.Hide(); - circle.glow.Hide(); + circle.Circle.Hide(); // CirclePiece + circle.Circle.AlwaysPresent = true; + circle.Ring.Hide(); + circle.Flash.Hide(); + circle.Explode.Hide(); + circle.Number.Hide(); + circle.Glow.Hide(); circle.ApproachCircle.Show(); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 31c86474cd..e8a2f94c14 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -17,18 +17,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public class DrawableHitCircle : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach { public ApproachCircle ApproachCircle; - public readonly CirclePiece circle; - public readonly RingPiece ring; - public readonly FlashPiece flash; - public readonly ExplodePiece explode; - public readonly NumberPiece number; - public readonly GlowPiece glow; + public readonly CirclePiece Circle; + public readonly RingPiece Ring; + public readonly FlashPiece Flash; + public readonly ExplodePiece Explode; + public readonly NumberPiece Number; + public readonly GlowPiece Glow; private readonly IBindable positionBindable = new Bindable(); private readonly IBindable stackHeightBindable = new Bindable(); private readonly IBindable scaleBindable = new Bindable(); - public OsuAction? HitAction => circle.HitAction; + public OsuAction? HitAction => Circle.HitAction; private readonly Container explodeContainer; @@ -55,8 +55,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Anchor = Anchor.Centre, Children = new Drawable[] { - glow = new GlowPiece(), - circle = new CirclePiece + Glow = new GlowPiece(), + Circle = new CirclePiece { Hit = () => { @@ -67,13 +67,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return true; }, }, - number = new NumberPiece + Number = new NumberPiece { Text = (HitObject.IndexInCurrentCombo + 1).ToString(), }, - ring = new RingPiece(), - flash = new FlashPiece(), - explode = new ExplodePiece(), + Ring = new RingPiece(), + Flash = new FlashPiece(), + Explode = new ExplodePiece(), ApproachCircle = new ApproachCircle { Alpha = 0, @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }; //may not be so correct - Size = circle.DrawSize; + Size = Circle.DrawSize; } [BackgroundDependencyLoader] @@ -106,9 +106,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables set { base.AccentColour = value; - explode.Colour = AccentColour; - glow.Colour = AccentColour; - circle.Colour = AccentColour; + Explode.Colour = AccentColour; + Glow.Colour = AccentColour; + Circle.Colour = AccentColour; ApproachCircle.Colour = AccentColour; } } @@ -145,7 +145,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void UpdateCurrentState(ArmedState state) { - glow.FadeOut(400); + Glow.FadeOut(400); switch (state) { @@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Expire(true); - circle.HitAction = null; + Circle.HitAction = null; // override lifetime end as FadeIn may have been changed externally, causing out expiration to be too early. LifetimeEnd = HitObject.StartTime + HitObject.HitWindows.HalfWindowFor(HitResult.Miss); @@ -170,18 +170,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ApproachCircle.FadeOut(50); const double flash_in = 40; - flash.FadeTo(0.8f, flash_in) + Flash.FadeTo(0.8f, flash_in) .Then() .FadeOut(100); - explode.FadeIn(flash_in); + Explode.FadeIn(flash_in); using (BeginDelayedSequence(flash_in, true)) { //after the flash, we can hide some elements that were behind it - ring.FadeOut(); - circle.FadeOut(); - number.FadeOut(); + Ring.FadeOut(); + Circle.FadeOut(); + Number.FadeOut(); this.FadeOut(800); explodeContainer.ScaleTo(1.5f, 400, Easing.OutQuad); diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 169d54d64a..9c48169a24 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Osu new OsuModHardRock(), new MultiMod(new OsuModSuddenDeath(), new OsuModPerfect()), new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()), - new OsuModHidden(), + new MultiMod(new OsuModHidden(), new OsuModTraceable()), new MultiMod(new OsuModFlashlight(), new OsuModBlinds()), }; @@ -136,8 +136,6 @@ namespace osu.Game.Rulesets.Osu new OsuModWiggle(), new OsuModGrow(), new MultiMod(new ModWindUp(), new ModWindDown()), - - new OsuModTraceable(), }; default: diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 80408ab43b..9074b64eb8 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -85,7 +85,7 @@ namespace osu.Game.Tests.Visual.UserInterface var assistMods = instance.GetModsFor(ModType.Automation); var noFailMod = easierMods.FirstOrDefault(m => m is OsuModNoFail); - var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden); + var hiddenMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModHidden)); var doubleTimeMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime)); @@ -96,10 +96,11 @@ namespace osu.Game.Tests.Visual.UserInterface testSingleMod(noFailMod); testMultiMod(doubleTimeMod); + testMultiMod(hiddenMod); testIncompatibleMods(easy, hardRock); testDeselectAll(easierMods.Where(m => !(m is MultiMod))); testMultiplierTextColour(noFailMod, modSelect.LowMultiplierColour); - testMultiplierTextColour(hiddenMod, modSelect.HighMultiplierColour); + testMultiplierTextColour(hardRock, modSelect.HighMultiplierColour); testUnimplementedMod(autoPilotMod); } diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 0eca1bcbec..5c2ab8e18c 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -112,13 +112,15 @@ namespace osu.Game.Overlays.Mods if (selected == null) continue; foreach (var type in modTypes) - if (type.IsInstanceOfType(selected) && !selected.GetType().IsSubclassOf(type)) + { + if (type.IsInstanceOfType(selected)) { if (immediate) button.Deselect(); else Scheduler.AddDelayed(button.Deselect, delay += 50); } + } } } @@ -130,7 +132,7 @@ namespace osu.Game.Overlays.Mods { foreach (var button in buttons) { - int i = Array.FindIndex(button.Mods, m => modTypes.Any(t => t.IsInstanceOfType(m) && !m.GetType().IsSubclassOf(t))); + int i = Array.FindIndex(button.Mods, m => modTypes.Any(t => t.IsInstanceOfType(m))); if (i >= 0) button.SelectAt(i); From 664257fbbe31f7b1617fc55527e59a088dc5c259 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Wed, 3 Jul 2019 18:42:02 +0200 Subject: [PATCH 0071/2815] Sliders no longer modified by HD but have transparent body now --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 1 - osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 60 ++++++++++--------- osu.Game/Overlays/Mods/ModSection.cs | 2 - 3 files changed, 31 insertions(+), 32 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 2723be9df8..ddf708d0f1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -17,7 +17,6 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Description => @"Play with no approach circles and fading circles/sliders."; public override double ScoreMultiplier => 1.06; - private const double fade_in_duration_multiplier = 0.4; private const double fade_out_duration_multiplier = 0.3; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index 64a331213f..8ccff6b258 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Mods { @@ -24,43 +25,44 @@ namespace osu.Game.Rulesets.Osu.Mods public override void ApplyToDrawableHitObjects(IEnumerable drawables) { foreach (var drawable in drawables.Skip(IncreaseFirstObjectVisibility.Value ? 1 : 0)) - { - switch (drawable) - { - case DrawableHitCircle _: - drawable.ApplyCustomUpdateState += ApplyTraceableState; - break; - - case DrawableSlider slider: - slider.ApplyCustomUpdateState += ApplyHiddenState; - slider.HeadCircle.ApplyCustomUpdateState += ApplyTraceableState; - break; - - default: - drawable.ApplyCustomUpdateState += ApplyHiddenState; - break; - } - } + drawable.ApplyCustomUpdateState += ApplyTraceableState; } protected void ApplyTraceableState(DrawableHitObject drawable, ArmedState state) { - if (!(drawable is DrawableHitCircle circle)) + if (!(drawable is DrawableOsuHitObject d)) return; - var h = circle.HitObject; + var h = d.HitObject; - // we only want to see the approach circle - using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) + switch (drawable) { - circle.Circle.Hide(); // CirclePiece - circle.Circle.AlwaysPresent = true; - circle.Ring.Hide(); - circle.Flash.Hide(); - circle.Explode.Hide(); - circle.Number.Hide(); - circle.Glow.Hide(); - circle.ApproachCircle.Show(); + case DrawableHitCircle circle: + // we only want to see the approach circle + using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) + { + circle.Circle.Hide(); // CirclePiece + circle.Circle.AlwaysPresent = true; + circle.Ring.Hide(); + circle.Flash.Hide(); + circle.Explode.Hide(); + circle.Number.Hide(); + circle.Glow.Hide(); + circle.ApproachCircle.Show(); + } + + break; + + case DrawableSlider slider: + ApplyTraceableState(slider.HeadCircle, state); + slider.Body.AccentColour = Color4.Transparent; + + break; + + default: + ApplyHiddenState(drawable, state); + + break; } } } diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 5c2ab8e18c..dedd397fa5 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -112,7 +112,6 @@ namespace osu.Game.Overlays.Mods if (selected == null) continue; foreach (var type in modTypes) - { if (type.IsInstanceOfType(selected)) { if (immediate) @@ -120,7 +119,6 @@ namespace osu.Game.Overlays.Mods else Scheduler.AddDelayed(button.Deselect, delay += 50); } - } } } From 5b4640d3ead5a89f31cb30b95c2234e79beb03c4 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Wed, 3 Jul 2019 21:40:14 +0200 Subject: [PATCH 0072/2815] Traceable no longer inherits from OsuModHidden and is no longer multi mod --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 1 + osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 24 +++++++++++++------ .../Mods/OsuModeObjectScaleTween.cs | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 3 ++- .../TestSceneModSelectOverlay.cs | 5 ++-- 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index ddf708d0f1..74f9398f18 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -17,6 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Description => @"Play with no approach circles and fading circles/sliders."; public override double ScoreMultiplier => 1.06; + public override Type[] IncompatibleMods => new[] { typeof(OsuModTraceable) }; private const double fade_in_duration_multiplier = 0.4; private const double fade_out_duration_multiplier = 0.3; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index c5b5a064a6..1d68793c50 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -3,8 +3,10 @@ using System; using System.Linq; +using osu.Framework.Bindables; using System.Collections.Generic; using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -12,19 +14,25 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Mods { - internal class OsuModTraceable : OsuModHidden + internal class OsuModTraceable : Mod, IReadFromConfig, IApplicableToDrawableHitObjects { public override string Name => "Traceable"; public override string Acronym => "TC"; public override IconUsage Icon => FontAwesome.Brands.SnapchatGhost; - public override ModType Type => ModType.DifficultyIncrease; + public override ModType Type => ModType.Fun; public override string Description => "Put your faith in the approach circles..."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModeObjectScaleTween) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModHidden), typeof(OsuModeObjectScaleTween) }; + private Bindable increaseFirstObjectVisibility = new Bindable(); - public override void ApplyToDrawableHitObjects(IEnumerable drawables) + public void ReadFromConfig(OsuConfigManager config) { - foreach (var drawable in drawables.Skip(IncreaseFirstObjectVisibility.Value ? 1 : 0)) + increaseFirstObjectVisibility = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility); + } + + public void ApplyToDrawableHitObjects(IEnumerable drawables) + { + foreach (var drawable in drawables.Skip(increaseFirstObjectVisibility.Value ? 1 : 0)) drawable.ApplyCustomUpdateState += ApplyTraceableState; } @@ -59,8 +67,10 @@ namespace osu.Game.Rulesets.Osu.Mods break; - default: - ApplyHiddenState(drawable, state); + case DrawableSpinner spinner: + spinner.Disc.Hide(); + //spinner.Ticks.Hide(); // do they contribute to the theme? debatable + spinner.Background.Hide(); break; } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs index de277100b5..db61c15846 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModeObjectScaleTween) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModTraceable) }; protected virtual float StartScale => 1; diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 949b473ca6..c0e2809b38 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Osu new OsuModHardRock(), new MultiMod(new OsuModSuddenDeath(), new OsuModPerfect()), new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()), - new MultiMod(new OsuModHidden(), new OsuModTraceable()), + new OsuModHidden(), new MultiMod(new OsuModFlashlight(), new OsuModBlinds()), }; @@ -136,6 +136,7 @@ namespace osu.Game.Rulesets.Osu new OsuModWiggle(), new MultiMod(new OsuModGrow(), new OsuModDeflate()), new MultiMod(new ModWindUp(), new ModWindDown()), + new OsuModTraceable(), }; default: diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 9074b64eb8..80408ab43b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -85,7 +85,7 @@ namespace osu.Game.Tests.Visual.UserInterface var assistMods = instance.GetModsFor(ModType.Automation); var noFailMod = easierMods.FirstOrDefault(m => m is OsuModNoFail); - var hiddenMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModHidden)); + var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden); var doubleTimeMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime)); @@ -96,11 +96,10 @@ namespace osu.Game.Tests.Visual.UserInterface testSingleMod(noFailMod); testMultiMod(doubleTimeMod); - testMultiMod(hiddenMod); testIncompatibleMods(easy, hardRock); testDeselectAll(easierMods.Where(m => !(m is MultiMod))); testMultiplierTextColour(noFailMod, modSelect.LowMultiplierColour); - testMultiplierTextColour(hardRock, modSelect.HighMultiplierColour); + testMultiplierTextColour(hiddenMod, modSelect.HighMultiplierColour); testUnimplementedMod(autoPilotMod); } From a20d5baa57c3b84557f2a584cef3796091d400ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jul 2019 16:21:01 +0900 Subject: [PATCH 0073/2815] Fix skip button not being clickable after fade out --- osu.Game/Screens/Play/SkipOverlay.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 38dd179f25..d0912db63c 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -201,14 +201,15 @@ namespace osu.Game.Screens.Play protected override bool OnMouseDown(MouseDownEvent e) { + Show(); scheduledHide?.Cancel(); - return base.OnMouseDown(e); + return true; } protected override bool OnMouseUp(MouseUpEvent e) { Show(); - return base.OnMouseUp(e); + return true; } public override void Hide() => State = Visibility.Hidden; From db24ac28ec21ebac80e7525a51b787b5b920274c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jul 2019 16:53:08 +0900 Subject: [PATCH 0074/2815] Add tests --- .../Visual/Gameplay/TestSceneSkipOverlay.cs | 63 +++++++++++++++++-- osu.Game/Screens/Play/SkipOverlay.cs | 2 + 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index 0519660477..8d2f71d9b4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -1,19 +1,72 @@ // 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 NUnit.Framework; using osu.Game.Screens.Play; +using osuTK; +using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] - public class TestSceneSkipOverlay : OsuTestScene + public class TestSceneSkipOverlay : ManualInputManagerTestScene { - protected override void LoadComplete() - { - base.LoadComplete(); + private SkipOverlay skip; + private int requestCount; - Add(new SkipOverlay(Clock.CurrentTime + 5000)); + [SetUp] + public void SetUp() => Schedule(() => + { + requestCount = 0; + Child = skip = new SkipOverlay(Clock.CurrentTime + 5000) + { + RequestSeek = _ => requestCount++ + }; + }); + + [Test] + public void TestFadeOnIdle() + { + AddStep("move mouse", () => InputManager.MoveMouseTo(Vector2.Zero)); + AddUntilStep("wait for fade", () => skip.Children.First().Alpha == 0); + AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); + AddUntilStep("visible again", () => skip.Children.First().Alpha > 0); + AddUntilStep("wait for fade", () => skip.Children.First().Alpha == 0); } + + [Test] + public void TestClickableAfterFade() + { + AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); + AddUntilStep("wait for fade", () => skip.Children.First().Alpha == 0); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + checkRequestCount(1); + } + + [Test] + public void TestClickOnlyActuatesOnce() + { + AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + checkRequestCount(1); + } + + [Test] + public void TestDoesntFadeOnMouseDown() + { + AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); + AddStep("button down", () => InputManager.PressButton(MouseButton.Left)); + AddUntilStep("wait for overlay disapper", () => !skip.IsAlive); + AddAssert("ensure button didn't disappear", () => skip.Children.First().Alpha > 0); + AddStep("button up", () => InputManager.ReleaseButton(MouseButton.Left)); + checkRequestCount(0); + } + + private void checkRequestCount(int expected) => + AddAssert($"request count is {expected}", () => requestCount == expected); } } diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index d0912db63c..d6c2b59d98 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -161,6 +161,8 @@ namespace osu.Game.Screens.Play private Visibility state; private ScheduledDelegate scheduledHide; + public override bool IsPresent => true; + public Visibility State { get => state; From 73bc71f8b2b891a33d6e87fb33e0929d90c53758 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jul 2019 17:32:58 +0900 Subject: [PATCH 0075/2815] Use local clock to add consistency to skip times --- .../Visual/Gameplay/TestSceneSkipOverlay.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index 8d2f71d9b4..a0e2e10c00 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -3,6 +3,9 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Timing; using osu.Game.Screens.Play; using osuTK; using osuTK.Input; @@ -19,9 +22,20 @@ namespace osu.Game.Tests.Visual.Gameplay public void SetUp() => Schedule(() => { requestCount = 0; - Child = skip = new SkipOverlay(Clock.CurrentTime + 5000) + Child = new Container { - RequestSeek = _ => requestCount++ + RelativeSizeAxes = Axes.Both, + Clock = new FramedOffsetClock(Clock) + { + Offset = -Clock.CurrentTime, + }, + Children = new Drawable[] + { + skip = new SkipOverlay(5000) + { + RequestSeek = _ => requestCount++ + } + }, }; }); From 86e9d97b6a3006f89fc4b9b67adda55e31b51386 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jul 2019 18:38:29 +0900 Subject: [PATCH 0076/2815] Adjust variables and test length to avoid random failures --- .../Visual/Gameplay/TestSceneSkipOverlay.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index a0e2e10c00..94ae243bb9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay }, Children = new Drawable[] { - skip = new SkipOverlay(5000) + skip = new SkipOverlay(6000) { RequestSeek = _ => requestCount++ } @@ -42,11 +42,12 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestFadeOnIdle() { - AddStep("move mouse", () => InputManager.MoveMouseTo(Vector2.Zero)); - AddUntilStep("wait for fade", () => skip.Children.First().Alpha == 0); + AddUntilStep("fully visible", () => skip.Children.First().Alpha == 1); + AddUntilStep("wait for fade", () => skip.Children.First().Alpha < 1); + AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); - AddUntilStep("visible again", () => skip.Children.First().Alpha > 0); - AddUntilStep("wait for fade", () => skip.Children.First().Alpha == 0); + AddUntilStep("fully visible", () => skip.Children.First().Alpha == 1); + AddUntilStep("wait for fade", () => skip.Children.First().Alpha < 1); } [Test] From 2dc356a24e794eb9c3e31ff216c3b688660dc730 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jul 2019 18:47:43 +0900 Subject: [PATCH 0077/2815] Remove using --- osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index 94ae243bb9..2a08d2ad19 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Screens.Play; -using osuTK; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay From 1b368601695164d2789097156e02788e678add66 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jul 2019 19:26:12 +0900 Subject: [PATCH 0078/2815] Fix test cross-talk --- osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index 2a08d2ad19..b152c21454 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Screens.Play; +using osuTK; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay @@ -41,6 +42,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestFadeOnIdle() { + AddStep("move mouse", () => InputManager.MoveMouseTo(Vector2.Zero)); AddUntilStep("fully visible", () => skip.Children.First().Alpha == 1); AddUntilStep("wait for fade", () => skip.Children.First().Alpha < 1); From 3d12c709a53bf0d8343797c327daf8296644f15d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Jul 2019 15:40:47 +0930 Subject: [PATCH 0079/2815] Add test case --- .../OsuDifficultyCalculatorTest.cs | 1 + .../Testing/Beatmaps/zero-length-sliders.osu | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/zero-length-sliders.osu diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index e55dc1f902..693faee3b7 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Osu.Tests protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; [TestCase(6.931145117263422, "diffcalc-test")] + [TestCase(1.0736587013228804d, "zero-length-sliders")] public void Test(double expected, string name) => base.Test(expected, name); diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/zero-length-sliders.osu b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/zero-length-sliders.osu new file mode 100644 index 0000000000..18736043b5 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/zero-length-sliders.osu @@ -0,0 +1,28 @@ +osu file format v14 + +[Difficulty] +HPDrainRate:3 +CircleSize:3 +OverallDifficulty:3 +ApproachRate:4.5 +SliderMultiplier:0.799999999999999 +SliderTickRate:1 + +[TimingPoints] +800,260.869565217391,3,2,10,60,1,0 + +[HitObjects] +// Linear +78,193,2365,2,0,L|330:193,1,0 +78,193,3669,2,0,L|330:193,1,0 +78,193,4973,2,0,L|330:193,1,0 + +// Perfect-curve +151,206,6278,2,0,P|293:75|345:204,1,0 +151,206,8104,2,0,P|293:75|345:204,1,0 +151,206,9930,2,0,P|293:75|345:204,1,0 + +// Bezier +76,191,11756,2,0,B|176:59|358:340|438:190,1,0 +76,191,13582,2,0,B|176:59|358:340|438:190,1,0 +76,191,15408,2,0,B|176:59|358:340|438:190,1,0 From 6288cc6e5c32fcc8790a9a31bf1b22f64292b11a Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 5 Jul 2019 16:03:22 +0200 Subject: [PATCH 0080/2815] Refactor OnScreenDisplay to allow displaying of custom messages on OSD --- osu.Game/Overlays/OSD/OsdToast.cs | 40 ++++++ osu.Game/Overlays/OnScreenDisplay.cs | 203 ++------------------------- osu.Game/osu.Game.csproj | 3 + 3 files changed, 55 insertions(+), 191 deletions(-) create mode 100644 osu.Game/Overlays/OSD/OsdToast.cs diff --git a/osu.Game/Overlays/OSD/OsdToast.cs b/osu.Game/Overlays/OSD/OsdToast.cs new file mode 100644 index 0000000000..f700577ba1 --- /dev/null +++ b/osu.Game/Overlays/OSD/OsdToast.cs @@ -0,0 +1,40 @@ +// 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.Containers; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osuTK.Graphics; + +namespace osu.Game.Overlays.OSD +{ + public class OsdToast : Container + { + private readonly Container content; + protected override Container Content => content; + + public OsdToast() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Width = 240; + RelativeSizeAxes = Axes.Y; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.7f + }, + content = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + } + }; + } + } +} diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs index 88a1edddc5..0577257080 100644 --- a/osu.Game/Overlays/OnScreenDisplay.cs +++ b/osu.Game/Overlays/OnScreenDisplay.cs @@ -8,17 +8,11 @@ using osu.Framework.Configuration; using osu.Framework.Configuration.Tracking; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; using osuTK; -using osuTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Transforms; using osu.Framework.Threading; using osu.Game.Configuration; -using osu.Game.Graphics.Sprites; +using osu.Game.Overlays.OSD; namespace osu.Game.Overlays { @@ -26,16 +20,9 @@ namespace osu.Game.Overlays { private readonly Container box; - private readonly SpriteText textLine1; - private readonly SpriteText textLine2; - private readonly SpriteText textLine3; - private const float height = 110; - private const float height_notext = 98; private const float height_contracted = height * 0.9f; - private readonly FillFlowContainer optionLights; - public OnScreenDisplay() { RelativeSizeAxes = Axes.Both; @@ -52,64 +39,6 @@ namespace osu.Game.Overlays Height = height_contracted, Alpha = 0, CornerRadius = 20, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.7f, - }, - new Container // purely to add a minimum width - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 240, - RelativeSizeAxes = Axes.Y, - }, - textLine1 = new OsuSpriteText - { - Padding = new MarginPadding(10), - Font = OsuFont.GetFont(size: 14, weight: FontWeight.Black), - Spacing = new Vector2(1, 0), - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }, - textLine2 = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 24, weight: FontWeight.Light), - Padding = new MarginPadding { Left = 10, Right = 10 }, - Anchor = Anchor.Centre, - Origin = Anchor.BottomCentre, - }, - new FillFlowContainer - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - optionLights = new FillFlowContainer - { - Padding = new MarginPadding { Top = 20, Bottom = 5 }, - Spacing = new Vector2(5, 0), - Direction = FillDirection.Horizontal, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Both - }, - textLine3 = new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Margin = new MarginPadding { Bottom = 15 }, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), - Alpha = 0.3f, - }, - } - } - } }, }; } @@ -142,7 +71,7 @@ namespace osu.Game.Overlays return; configManager.LoadInto(trackedSettings); - trackedSettings.SettingChanged += display; + trackedSettings.SettingChanged += displayTrackedSettingChange; trackedConfigManagers.Add((source, configManager), trackedSettings); } @@ -162,56 +91,23 @@ namespace osu.Game.Overlays return; existing.Unload(); - existing.SettingChanged -= display; + existing.SettingChanged -= displayTrackedSettingChange; trackedConfigManagers.Remove((source, configManager)); } - private void display(SettingDescription description) + /// + /// Displays the given as parameter on the OSD + /// + /// + public void Display(OsdToast toast) { - Schedule(() => - { - textLine1.Text = description.Name.ToUpperInvariant(); - textLine2.Text = description.Value; - textLine3.Text = description.Shortcut.ToUpperInvariant(); - - if (string.IsNullOrEmpty(textLine3.Text)) - textLine3.Text = "NO KEY BOUND"; - - DisplayTemporarily(box); - - int optionCount = 0; - int selectedOption = -1; - - switch (description.RawValue) - { - case bool val: - optionCount = 1; - if (val) selectedOption = 0; - break; - - case Enum _: - var values = Enum.GetValues(description.RawValue.GetType()); - optionCount = values.Length; - selectedOption = Convert.ToInt32(description.RawValue); - break; - } - - textLine2.Origin = optionCount > 0 ? Anchor.BottomCentre : Anchor.Centre; - textLine2.Y = optionCount > 0 ? 0 : 5; - - if (optionLights.Children.Count != optionCount) - { - optionLights.Clear(); - for (int i = 0; i < optionCount; i++) - optionLights.Add(new OptionLight()); - } - - for (int i = 0; i < optionCount; i++) - optionLights.Children[i].Glowing = i == selectedOption; - }); + box.Child = toast; + DisplayTemporarily(box); } + private void displayTrackedSettingChange(SettingDescription description) => Schedule(() => Display(new OsdTrackedSettingToast(description))); + private TransformSequence fadeIn; private ScheduledDelegate fadeOut; @@ -236,80 +132,5 @@ namespace osu.Game.Overlays b => b.ResizeHeightTo(height_contracted, 1500, Easing.InQuint)); }, 500); } - - private class OptionLight : Container - { - private Color4 glowingColour, idleColour; - - private const float transition_speed = 300; - - private const float glow_strength = 0.4f; - - private readonly Box fill; - - public OptionLight() - { - Children = new[] - { - fill = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 1, - }, - }; - } - - private bool glowing; - - public bool Glowing - { - set - { - glowing = value; - if (!IsLoaded) return; - - updateGlow(); - } - } - - private void updateGlow() - { - if (glowing) - { - fill.FadeColour(glowingColour, transition_speed, Easing.OutQuint); - FadeEdgeEffectTo(glow_strength, transition_speed, Easing.OutQuint); - } - else - { - FadeEdgeEffectTo(0, transition_speed, Easing.OutQuint); - fill.FadeColour(idleColour, transition_speed, Easing.OutQuint); - } - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - fill.Colour = idleColour = Color4.White.Opacity(0.4f); - glowingColour = Color4.White; - - Size = new Vector2(25, 5); - - Masking = true; - CornerRadius = 3; - - EdgeEffect = new EdgeEffectParameters - { - Colour = colours.BlueDark.Opacity(glow_strength), - Type = EdgeEffectType.Glow, - Radius = 8, - }; - } - - protected override void LoadComplete() - { - updateGlow(); - FinishTransforms(true); - } - } } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e872cd1387..950a940fec 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -21,4 +21,7 @@ + + + From 4f0429d046da8d5ca21d43d26c40a1773febdd4b Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 5 Jul 2019 16:03:46 +0200 Subject: [PATCH 0081/2815] Add OsdTrackedSettingToast --- .../Overlays/OSD/OsdTrackedSettingToast.cs | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 osu.Game/Overlays/OSD/OsdTrackedSettingToast.cs diff --git a/osu.Game/Overlays/OSD/OsdTrackedSettingToast.cs b/osu.Game/Overlays/OSD/OsdTrackedSettingToast.cs new file mode 100644 index 0000000000..411a33b000 --- /dev/null +++ b/osu.Game/Overlays/OSD/OsdTrackedSettingToast.cs @@ -0,0 +1,180 @@ +// 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.Configuration.Tracking; +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.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; +using System; + +namespace osu.Game.Overlays.OSD +{ + public class OsdTrackedSettingToast : OsdToast + { + public OsdTrackedSettingToast(SettingDescription description) + { + SpriteText textLine2; + FillFlowContainer optionLights; + + Children = new Drawable[] + { + new OsuSpriteText + { + Padding = new MarginPadding(10), + Font = OsuFont.GetFont(size: 14, weight: FontWeight.Black), + Spacing = new Vector2(1, 0), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = description.Name.ToUpperInvariant() + }, + textLine2 = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 24, weight: FontWeight.Light), + Padding = new MarginPadding { Left = 10, Right = 10 }, + Anchor = Anchor.Centre, + Origin = Anchor.BottomCentre, + Text = description.Value + }, + new FillFlowContainer + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + optionLights = new FillFlowContainer + { + Padding = new MarginPadding { Top = 20, Bottom = 5 }, + Spacing = new Vector2(5, 0), + Direction = FillDirection.Horizontal, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding { Bottom = 15 }, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Alpha = 0.3f, + Text = string.IsNullOrEmpty(description.Shortcut) ? "NO KEY BOUND" : description.Shortcut.ToUpperInvariant() + }, + } + } + }; + + int optionCount = 0; + int selectedOption = -1; + + switch (description.RawValue) + { + case bool val: + optionCount = 1; + if (val) selectedOption = 0; + break; + + case Enum _: + var values = Enum.GetValues(description.RawValue.GetType()); + optionCount = values.Length; + selectedOption = Convert.ToInt32(description.RawValue); + break; + } + + textLine2.Origin = optionCount > 0 ? Anchor.BottomCentre : Anchor.Centre; + textLine2.Y = optionCount > 0 ? 0 : 5; + + for (int i = 0; i < optionCount; i++) + { + optionLights.Add(new OptionLight + { + Glowing = i == selectedOption + }); + } + } + + private class OptionLight : Container + { + private Color4 glowingColour, idleColour; + + private const float transition_speed = 300; + + private const float glow_strength = 0.4f; + + private readonly Box fill; + + public OptionLight() + { + Children = new[] + { + fill = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 1, + }, + }; + } + + private bool glowing; + + public bool Glowing + { + set + { + glowing = value; + if (!IsLoaded) return; + + updateGlow(); + } + } + + private void updateGlow() + { + if (glowing) + { + fill.FadeColour(glowingColour, transition_speed, Easing.OutQuint); + FadeEdgeEffectTo(glow_strength, transition_speed, Easing.OutQuint); + } + else + { + FadeEdgeEffectTo(0, transition_speed, Easing.OutQuint); + fill.FadeColour(idleColour, transition_speed, Easing.OutQuint); + } + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + fill.Colour = idleColour = Color4.White.Opacity(0.4f); + glowingColour = Color4.White; + + Size = new Vector2(25, 5); + + Masking = true; + CornerRadius = 3; + + EdgeEffect = new EdgeEffectParameters + { + Colour = colours.BlueDark.Opacity(glow_strength), + Type = EdgeEffectType.Glow, + Radius = 8, + }; + } + + protected override void LoadComplete() + { + updateGlow(); + FinishTransforms(true); + } + } + } +} From bc60b1dc13d2b069518d3b4b4d1698c43aa48ab9 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 5 Jul 2019 16:04:47 +0200 Subject: [PATCH 0082/2815] Add test for empty OsdToast --- osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs index d900526c07..b28f794a58 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs @@ -22,6 +22,7 @@ namespace osu.Game.Tests.Visual.UserInterface osd.BeginTracking(this, config); Add(osd); + AddStep("Display empty osd toast", () => osd.Display(new Overlays.OSD.OsdToast())); AddRepeatStep("Change toggle (no bind)", () => config.ToggleSetting(TestConfigSetting.ToggleSettingNoKeybind), 2); AddRepeatStep("Change toggle (with bind)", () => config.ToggleSetting(TestConfigSetting.ToggleSettingWithKeybind), 2); AddRepeatStep("Change enum (no bind)", () => config.IncrementEnumSetting(TestConfigSetting.EnumSettingNoKeybind), 3); From df75f9d3124f169e2d7950fd317af11c5b481b1a Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 5 Jul 2019 16:08:02 +0200 Subject: [PATCH 0083/2815] Revert changes made by VS to csproj --- osu.Game/osu.Game.csproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 950a940fec..e872cd1387 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -21,7 +21,4 @@ - - - From 70372dd03d1e1735de241d431b629bdb684ae9c1 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 5 Jul 2019 16:14:04 +0200 Subject: [PATCH 0084/2815] Add global actions for game-wide music jukebox manipulation --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 14d356f889..6d85a69804 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -38,6 +38,10 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.MouseWheelDown, GlobalAction.DecreaseVolume), new KeyBinding(InputKey.F4, GlobalAction.ToggleMute), + new KeyBinding(InputKey.F5, GlobalAction.MusicPrev), + new KeyBinding(InputKey.F6, GlobalAction.MusicNext), + new KeyBinding(InputKey.X, GlobalAction.MusicPlay), + new KeyBinding(InputKey.Escape, GlobalAction.Back), new KeyBinding(InputKey.MouseButton1, GlobalAction.Back), @@ -88,6 +92,16 @@ namespace osu.Game.Input.Bindings [Description("Toggle mute")] ToggleMute, + // Game-wide beatmap jukebox keybindings + [Description("Jukebox next track")] + MusicNext, + + [Description("Jukebox previous track")] + MusicPrev, + + [Description("Jukebox play / pause current track")] + MusicPlay, + // In-Game Keybindings [Description("Skip cutscene")] SkipCutscene, From b5bd863dd080d25f0c01f9cdd30c0d6cde4a9c5d Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 5 Jul 2019 16:21:50 +0200 Subject: [PATCH 0085/2815] Add methods to MusicController to play or pause, select next or previous track --- osu.Game/Overlays/MusicController.cs | 31 +++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 29ae5983be..91fa2f5a95 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -529,6 +529,35 @@ namespace osu.Game.Overlays /// /// Play the next random or playlist track. /// - public void NextTrack() => next(); + /// Returns whether the track could be changed or not + public bool NextTrack() + { + if (beatmap.Disabled) return false; + + next(); + return true; + } + + /// + /// Play the previous random or playlist track. + /// + public bool PreviousTrack() + { + if (beatmap.Disabled) return false; + + prev(); + return true; + } + + /// + /// Play or pause the current beatmap track. + /// Returns whether the current track could be played / paused or not + public bool PlayTrack() + { + if (beatmap.Disabled) return false; + + play(); + return true; + } } } From c9e44e5e34aa481ce0210898616829c1770a67c0 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 5 Jul 2019 16:25:09 +0200 Subject: [PATCH 0086/2815] Add OsdIconToast + test --- .../UserInterface/TestSceneOnScreenDisplay.cs | 2 + osu.Game/Overlays/OSD/OsdIconToast.cs | 42 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 osu.Game/Overlays/OSD/OsdIconToast.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs index b28f794a58..047c89ae54 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Configuration.Tracking; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays; namespace osu.Game.Tests.Visual.UserInterface @@ -23,6 +24,7 @@ namespace osu.Game.Tests.Visual.UserInterface Add(osd); AddStep("Display empty osd toast", () => osd.Display(new Overlays.OSD.OsdToast())); + AddStep("Display osd toast with icon and message", () => osd.Display(new Overlays.OSD.OsdIconToast("Hey there !", FontAwesome.Solid.HandSpock))); AddRepeatStep("Change toggle (no bind)", () => config.ToggleSetting(TestConfigSetting.ToggleSettingNoKeybind), 2); AddRepeatStep("Change toggle (with bind)", () => config.ToggleSetting(TestConfigSetting.ToggleSettingWithKeybind), 2); AddRepeatStep("Change enum (no bind)", () => config.IncrementEnumSetting(TestConfigSetting.EnumSettingNoKeybind), 3); diff --git a/osu.Game/Overlays/OSD/OsdIconToast.cs b/osu.Game/Overlays/OSD/OsdIconToast.cs new file mode 100644 index 0000000000..c00949d48b --- /dev/null +++ b/osu.Game/Overlays/OSD/OsdIconToast.cs @@ -0,0 +1,42 @@ +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Overlays.OSD +{ + public class OsdIconToast : OsdToast + { + public OsdIconToast(string message, IconUsage icon) + { + Children = new Drawable[] + { + new FillFlowContainer() + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Vertical, + Spacing = new osuTK.Vector2(10), + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.BottomCentre, + Font = OsuFont.GetFont(size: 24, weight: FontWeight.Light), + Text = message + }, + new SpriteIcon + { + Icon = icon, + Size = new osuTK.Vector2(45), + Anchor = Anchor.Centre, + Origin = Anchor.BottomCentre, + } + } + } + }; + } + } +} From 5cad2b21927199dd4baf026c5d27baf85c29c43b Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 5 Jul 2019 16:28:37 +0200 Subject: [PATCH 0087/2815] Add missing function to MusicController --- osu.Game/Overlays/MusicController.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 91fa2f5a95..f6d67e9981 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -559,5 +559,10 @@ namespace osu.Game.Overlays play(); return true; } + + /// + /// Returns whether the current beatmap track is playing. + /// + public bool IsPlaying => beatmap.Value.Track.IsRunning; } } From 89acd9da3e0c9e5aa55d1bdc2647c5349ec1a556 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 5 Jul 2019 16:33:13 +0200 Subject: [PATCH 0088/2815] Add game-wide jukebox keybindings handling to OsuGame --- osu.Game/OsuGame.cs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 0a472d4dc1..32539055a7 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -67,6 +67,8 @@ namespace osu.Game private BeatmapSetOverlay beatmapSetOverlay; + private OnScreenDisplay osd; + [Cached] private readonly ScreenshotManager screenshotManager = new ScreenshotManager(); @@ -456,7 +458,7 @@ namespace osu.Game }); loadComponentSingleFile(volume = new VolumeOverlay(), leftFloatingOverlayContent.Add); - loadComponentSingleFile(new OnScreenDisplay(), Add, true); + loadComponentSingleFile(osd = new OnScreenDisplay(), Add, true); loadComponentSingleFile(notifications = new NotificationOverlay { @@ -719,6 +721,24 @@ namespace osu.Game case GlobalAction.ToggleGameplayMouseButtons: LocalConfig.Set(OsuSetting.MouseDisableButtons, !LocalConfig.Get(OsuSetting.MouseDisableButtons)); return true; + + case GlobalAction.MusicPlay: + if (!musicController.IsLoaded) return true; + if (musicController.PlayTrack()) + osd.Display(new Overlays.OSD.OsdIconToast(musicController.IsPlaying ? "Play track" : "Pause track", musicController.IsPlaying ? FontAwesome.Solid.PlayCircle : FontAwesome.Solid.PauseCircle)); + return true; + + case GlobalAction.MusicNext: + if (!musicController.IsLoaded) return true; + if (musicController.NextTrack()) + osd.Display(new Overlays.OSD.OsdIconToast("Next track", FontAwesome.Solid.FastForward)); + return true; + + case GlobalAction.MusicPrev: + if (!musicController.IsLoaded) return true; + if (musicController.PreviousTrack()) + osd.Display(new Overlays.OSD.OsdIconToast("Previous track", FontAwesome.Solid.FastForward)); + return true; } return false; From 5f8bd6eca7a7e0f43b890ef1726da86d41cb0c82 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 5 Jul 2019 16:37:37 +0200 Subject: [PATCH 0089/2815] Fix CI issues + minor issues --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 8 ++++---- osu.Game/OsuGame.cs | 5 ++++- osu.Game/Overlays/MusicController.cs | 1 + osu.Game/Overlays/OSD/OsdIconToast.cs | 7 +++++-- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 6d85a69804..e756694285 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -38,16 +38,16 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.MouseWheelDown, GlobalAction.DecreaseVolume), new KeyBinding(InputKey.F4, GlobalAction.ToggleMute), - new KeyBinding(InputKey.F5, GlobalAction.MusicPrev), - new KeyBinding(InputKey.F6, GlobalAction.MusicNext), - new KeyBinding(InputKey.X, GlobalAction.MusicPlay), - new KeyBinding(InputKey.Escape, GlobalAction.Back), new KeyBinding(InputKey.MouseButton1, GlobalAction.Back), new KeyBinding(InputKey.Space, GlobalAction.Select), new KeyBinding(InputKey.Enter, GlobalAction.Select), new KeyBinding(InputKey.KeypadEnter, GlobalAction.Select), + + new KeyBinding(InputKey.F5, GlobalAction.MusicPrev), + new KeyBinding(InputKey.F6, GlobalAction.MusicNext), + new KeyBinding(InputKey.X, GlobalAction.MusicPlay), }; public IEnumerable InGameKeyBindings => new[] diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 32539055a7..bae301a8a6 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -724,20 +724,23 @@ namespace osu.Game case GlobalAction.MusicPlay: if (!musicController.IsLoaded) return true; + if (musicController.PlayTrack()) osd.Display(new Overlays.OSD.OsdIconToast(musicController.IsPlaying ? "Play track" : "Pause track", musicController.IsPlaying ? FontAwesome.Solid.PlayCircle : FontAwesome.Solid.PauseCircle)); return true; case GlobalAction.MusicNext: if (!musicController.IsLoaded) return true; + if (musicController.NextTrack()) osd.Display(new Overlays.OSD.OsdIconToast("Next track", FontAwesome.Solid.FastForward)); return true; case GlobalAction.MusicPrev: if (!musicController.IsLoaded) return true; + if (musicController.PreviousTrack()) - osd.Display(new Overlays.OSD.OsdIconToast("Previous track", FontAwesome.Solid.FastForward)); + osd.Display(new Overlays.OSD.OsdIconToast("Previous track", FontAwesome.Solid.FastBackward)); return true; } diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index f6d67e9981..618187069d 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -551,6 +551,7 @@ namespace osu.Game.Overlays /// /// Play or pause the current beatmap track. + /// /// Returns whether the current track could be played / paused or not public bool PlayTrack() { diff --git a/osu.Game/Overlays/OSD/OsdIconToast.cs b/osu.Game/Overlays/OSD/OsdIconToast.cs index c00949d48b..0e2bcd377f 100644 --- a/osu.Game/Overlays/OSD/OsdIconToast.cs +++ b/osu.Game/Overlays/OSD/OsdIconToast.cs @@ -1,4 +1,7 @@ -using osu.Framework.Graphics; +// 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.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; @@ -12,7 +15,7 @@ namespace osu.Game.Overlays.OSD { Children = new Drawable[] { - new FillFlowContainer() + new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, From 71e40b46843e69d9bba072cbf6eb7aeede9a3d46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 6 Jul 2019 12:32:16 +0900 Subject: [PATCH 0090/2815] Force SQLite to multithreading mode --- osu.Game/Database/OsuDbContext.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 538ec41b3d..ea3318598f 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -41,6 +41,9 @@ namespace osu.Game.Database { // required to initialise native SQLite libraries on some platforms. SQLitePCL.Batteries_V2.Init(); + + // https://github.com/aspnet/EntityFrameworkCore/issues/9994#issuecomment-508588678 + SQLitePCL.raw.sqlite3_config(2 /*SQLITE_CONFIG_MULTITHREAD*/); } /// From 58183ad3d5b01a9bf49bfa11c43186ad19644424 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 6 Jul 2019 19:05:12 +0900 Subject: [PATCH 0091/2815] Change intro test to test full intro screen --- .../Visual/Menus/TestSceneIntroSequence.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroSequence.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroSequence.cs index b59fb18428..9cb335c3a3 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroSequence.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroSequence.cs @@ -4,10 +4,12 @@ using System; using System.Collections.Generic; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Timing; +using osu.Game.Screens; using osu.Game.Screens.Menu; using osuTK.Graphics; @@ -19,12 +21,16 @@ namespace osu.Game.Tests.Visual.Menus public override IReadOnlyList RequiredTypes => new[] { typeof(OsuLogo), + typeof(Intro), + typeof(StartupScreen), + typeof(OsuScreen) }; + [Cached] + private OsuLogo logo; + public TestSceneIntroSequence() { - OsuLogo logo; - var rateAdjustClock = new StopwatchClock(true); var framedClock = new FramedClock(rateAdjustClock); framedClock.ProcessFrame(); @@ -40,14 +46,17 @@ namespace osu.Game.Tests.Visual.Menus RelativeSizeAxes = Axes.Both, Colour = Color4.Black, }, + new OsuScreenStack(new Intro()) + { + RelativeSizeAxes = Axes.Both, + }, logo = new OsuLogo { Anchor = Anchor.Centre, - } + }, } }); - AddStep(@"Restart", logo.PlayIntro); AddSliderStep("Playback speed", 0.0, 2.0, 1, v => rateAdjustClock.Rate = v); } } From 6bee26cfc9764bf0db15d168d3a3fa6d4691d507 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 6 Jul 2019 19:05:37 +0900 Subject: [PATCH 0092/2815] Rename to match --- .../Menus/{TestSceneIntroSequence.cs => TestSceneIntro.cs} | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) rename osu.Game.Tests/Visual/Menus/{TestSceneIntroSequence.cs => TestSceneIntro.cs} (93%) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroSequence.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntro.cs similarity index 93% rename from osu.Game.Tests/Visual/Menus/TestSceneIntroSequence.cs rename to osu.Game.Tests/Visual/Menus/TestSceneIntro.cs index 9cb335c3a3..15b5c2338e 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroSequence.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntro.cs @@ -16,12 +16,11 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Menus { [TestFixture] - public class TestSceneIntroSequence : OsuTestScene + public class TestSceneIntro : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(OsuLogo), - typeof(Intro), typeof(StartupScreen), typeof(OsuScreen) }; @@ -29,7 +28,7 @@ namespace osu.Game.Tests.Visual.Menus [Cached] private OsuLogo logo; - public TestSceneIntroSequence() + public TestSceneIntro() { var rateAdjustClock = new StopwatchClock(true); var framedClock = new FramedClock(rateAdjustClock); From ea911b2fd222d8ab9673927abfdd00fd73c0b78a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 6 Jul 2019 19:05:42 +0900 Subject: [PATCH 0093/2815] Ensure intro restarts track --- osu.Game/Screens/Menu/Intro.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/Intro.cs b/osu.Game/Screens/Menu/Intro.cs index f6fbcf6498..3fb16eaf90 100644 --- a/osu.Game/Screens/Menu/Intro.cs +++ b/osu.Game/Screens/Menu/Intro.cs @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Menu // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Manu. if (menuMusic.Value) { - track.Start(); + track.Restart(); track = null; } From 1a117d1511d48523a7103693e4340a8bd858141d Mon Sep 17 00:00:00 2001 From: Desconocidosmh Date: Sun, 7 Jul 2019 17:05:53 +0200 Subject: [PATCH 0094/2815] Closes #5277 --- osu.Game/Screens/Edit/Editor.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 89da9ae063..69fbf7d387 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -168,6 +168,8 @@ namespace osu.Game.Screens.Edit menuBar.Mode.ValueChanged += onModeChanged; + host.Exiting += onQuittingGame; + bottomBackground.Colour = colours.Gray2; } @@ -235,16 +237,27 @@ namespace osu.Game.Screens.Edit Beatmap.Value.Track?.Stop(); } + private bool isExitingGame = false; + public override bool OnExiting(IScreen next) { Background.FadeColour(Color4.White, 500); if (Beatmap.Value.Track != null) { - Beatmap.Value.Track.Tempo.Value = 1; - Beatmap.Value.Track.Start(); + if (isExitingGame) + { + Beatmap.Value.Track.Stop(); + } + else + { + Beatmap.Value.Track.Tempo.Value = 1; + Beatmap.Value.Track.Start(); + } } + host.Exiting -= onQuittingGame; + return base.OnExiting(next); } @@ -281,5 +294,7 @@ namespace osu.Game.Screens.Edit else clock.SeekForward(!clock.IsRunning, amount); } + + private bool onQuittingGame() => isExitingGame = true; } } From 84cadc688a4f12df27485e7ec4c1b570acf065bf Mon Sep 17 00:00:00 2001 From: Desconocidosmh Date: Sun, 7 Jul 2019 17:11:54 +0200 Subject: [PATCH 0095/2815] Change method name --- osu.Game/Screens/Edit/Editor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 69fbf7d387..0a9f7672d3 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -168,7 +168,7 @@ namespace osu.Game.Screens.Edit menuBar.Mode.ValueChanged += onModeChanged; - host.Exiting += onQuittingGame; + host.Exiting += onExitingGame; bottomBackground.Colour = colours.Gray2; } @@ -256,7 +256,7 @@ namespace osu.Game.Screens.Edit } } - host.Exiting -= onQuittingGame; + host.Exiting -= onExitingGame; return base.OnExiting(next); } @@ -295,6 +295,6 @@ namespace osu.Game.Screens.Edit clock.SeekForward(!clock.IsRunning, amount); } - private bool onQuittingGame() => isExitingGame = true; + private bool onExitingGame() => isExitingGame = true; } } From 188c80374e2401032d9522a0a1f596882228be1c Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 7 Jul 2019 18:14:23 +0300 Subject: [PATCH 0096/2815] Add sorting by BPM --- osu.Game/Beatmaps/BeatmapManager.cs | 6 +++++- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 7ef50da7d3..c2d8e06d53 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -271,7 +271,11 @@ namespace osu.Game.Beatmaps OnlineBeatmapSetID = beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID, Beatmaps = new List(), Metadata = beatmap.Metadata, - DateAdded = DateTimeOffset.UtcNow + DateAdded = DateTimeOffset.UtcNow, + OnlineInfo = new BeatmapSetOnlineInfo + { + BPM = beatmap.ControlPointInfo.BPMMode, + } }; } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index f1951e27ab..c51638277d 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -48,6 +48,9 @@ namespace osu.Game.Screens.Select.Carousel case SortMode.DateAdded: return otherSet.BeatmapSet.DateAdded.CompareTo(BeatmapSet.DateAdded); + case SortMode.BPM: + return BeatmapSet.OnlineInfo.BPM.CompareTo(otherSet.BeatmapSet.OnlineInfo.BPM); + case SortMode.Difficulty: return BeatmapSet.MaxStarDifficulty.CompareTo(otherSet.BeatmapSet.MaxStarDifficulty); } From 65c8249c94212dacfff06683b0156e2a2eea0fb7 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 7 Jul 2019 18:25:52 +0300 Subject: [PATCH 0097/2815] Add beatmap extension for calculating length --- osu.Game/Beatmaps/Beatmap.cs | 12 ++++++++++++ osu.Game/Screens/Select/BeatmapInfoWedge.cs | 5 +---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 4ebeee40bf..7fca13a1e2 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -8,6 +8,7 @@ using System.Linq; using osu.Game.Beatmaps.ControlPoints; using Newtonsoft.Json; using osu.Game.IO.Serialization.Converters; +using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Beatmaps { @@ -61,4 +62,15 @@ namespace osu.Game.Beatmaps { public new Beatmap Clone() => (Beatmap)base.Clone(); } + + public static class BeatmapExtensions + { + public static double CalculateLength(this IBeatmap b) + { + HitObject lastObject = b.HitObjects.LastOrDefault(); + var endTime = (lastObject as IHasEndTime)?.EndTime ?? lastObject?.StartTime ?? 0; + + return endTime - b.HitObjects.FirstOrDefault()?.StartTime ?? 0; + } + } } diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index fa9ffd0706..e0521307fc 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -289,14 +289,11 @@ namespace osu.Game.Screens.Select if (b?.HitObjects?.Any() == true) { - HitObject lastObject = b.HitObjects.LastOrDefault(); - double endTime = (lastObject as IHasEndTime)?.EndTime ?? lastObject?.StartTime ?? 0; - labels.Add(new InfoLabel(new BeatmapStatistic { Name = "Length", Icon = FontAwesome.Regular.Clock, - Content = TimeSpan.FromMilliseconds(endTime - b.HitObjects.First().StartTime).ToString(@"m\:ss"), + Content = TimeSpan.FromMilliseconds(b.CalculateLength()).ToString(@"m\:ss"), })); labels.Add(new InfoLabel(new BeatmapStatistic From b4ef64fa61a56cc02f09294ab8442fe5739da944 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 7 Jul 2019 18:26:56 +0300 Subject: [PATCH 0098/2815] Add sorting by Length --- osu.Game/Beatmaps/BeatmapManager.cs | 5 +++++ osu.Game/Beatmaps/BeatmapSetInfo.cs | 2 ++ osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 7 +++++++ osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 3 +++ 4 files changed, 17 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index c2d8e06d53..b4c211ad53 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -307,6 +307,11 @@ namespace osu.Game.Beatmaps // TODO: this should be done in a better place once we actually need to dynamically update it. beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating ?? 0; + beatmap.BeatmapInfo.OnlineInfo = new BeatmapOnlineInfo + { + Length = beatmap.CalculateLength(), + }; + beatmapInfos.Add(beatmap.BeatmapInfo); } } diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 524ed0ed56..cdf8ddb5a1 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -37,6 +37,8 @@ namespace osu.Game.Beatmaps public double MaxStarDifficulty => Beatmaps?.Max(b => b.StarDifficulty) ?? 0; + public double MaxLength => Beatmaps?.Max(b => b.OnlineInfo.Length) ?? 0; + [NotMapped] public bool DeletePending { get; set; } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 712ab7b571..fa24a64d51 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -47,6 +47,13 @@ namespace osu.Game.Screens.Select.Carousel if (ruleset != 0) return ruleset; return Beatmap.StarDifficulty.CompareTo(otherBeatmap.Beatmap.StarDifficulty); + + case SortMode.Length: + // Length comparing must be in seconds + if (TimeSpan.FromMilliseconds(Beatmap.OnlineInfo.Length).Seconds != TimeSpan.FromMilliseconds(otherBeatmap.Beatmap.OnlineInfo.Length).Seconds) + return Beatmap.OnlineInfo.Length.CompareTo(otherBeatmap.Beatmap.OnlineInfo.Length); + + goto case SortMode.Difficulty; } } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index c51638277d..7934cf388f 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -51,6 +51,9 @@ namespace osu.Game.Screens.Select.Carousel case SortMode.BPM: return BeatmapSet.OnlineInfo.BPM.CompareTo(otherSet.BeatmapSet.OnlineInfo.BPM); + case SortMode.Length: + return BeatmapSet.MaxLength.CompareTo(otherSet.BeatmapSet.MaxLength); + case SortMode.Difficulty: return BeatmapSet.MaxStarDifficulty.CompareTo(otherSet.BeatmapSet.MaxStarDifficulty); } From 31e1d204d4d1d63debe92159443e09db9ddb1cd1 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 7 Jul 2019 18:27:12 +0300 Subject: [PATCH 0099/2815] Add test for sorting by BPM and Length --- .../SongSelect/TestScenePlaySongSelect.cs | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 962e0fb362..04c75d70e3 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -133,6 +133,9 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep(@"Sort by Artist", delegate { songSelect.FilterControl.Sort = SortMode.Artist; }); AddStep(@"Sort by Title", delegate { songSelect.FilterControl.Sort = SortMode.Title; }); AddStep(@"Sort by Author", delegate { songSelect.FilterControl.Sort = SortMode.Author; }); + AddStep(@"Sort by DateAdded", delegate { songSelect.FilterControl.Sort = SortMode.DateAdded; }); + AddStep(@"Sort by BPM", delegate { songSelect.FilterControl.Sort = SortMode.BPM; }); + AddStep(@"Sort by Length", delegate { songSelect.FilterControl.Sort = SortMode.Length; }); AddStep(@"Sort by Difficulty", delegate { songSelect.FilterControl.Sort = SortMode.Difficulty; }); } @@ -264,20 +267,26 @@ namespace osu.Game.Tests.Visual.SongSelect for (int i = 0; i < 6; i++) { int beatmapId = setId * 10 + i; + int length = RNG.Next(30000, 200000); beatmaps.Add(new BeatmapInfo { Ruleset = getRuleset(), OnlineBeatmapID = beatmapId, Path = "normal.osu", - Version = $"{beatmapId}", + Version = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss})", BaseDifficulty = new BeatmapDifficulty { OverallDifficulty = 3.5f, + }, + OnlineInfo = new BeatmapOnlineInfo + { + Length = length, } }); } + double bpm = RNG.NextSingle(80, 200); return new BeatmapSetInfo { OnlineBeatmapSetID = setId, @@ -286,10 +295,15 @@ namespace osu.Game.Tests.Visual.SongSelect { // Create random metadata, then we can check if sorting works based on these Artist = "Some Artist " + RNG.Next(0, 9), - Title = $"Some Song (set id {setId})", + Title = $"Some Song (set id {setId}, bpm {bpm:0.#})", AuthorString = "Some Guy " + RNG.Next(0, 9), }, - Beatmaps = beatmaps + Beatmaps = beatmaps, + DateAdded = DateTimeOffset.UtcNow, + OnlineInfo = new BeatmapSetOnlineInfo + { + BPM = bpm, + } }; } } From 28cc797fb61ee60fa82193982e84f2dc86c2d48f Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 7 Jul 2019 18:29:25 +0300 Subject: [PATCH 0100/2815] Add migrations --- ...7134040_AddBPMAndLengthSorting.Designer.cs | 500 ++++++++++++++++++ .../20190707134040_AddBPMAndLengthSorting.cs | 17 + 2 files changed, 517 insertions(+) create mode 100644 osu.Game/Migrations/20190707134040_AddBPMAndLengthSorting.Designer.cs create mode 100644 osu.Game/Migrations/20190707134040_AddBPMAndLengthSorting.cs diff --git a/osu.Game/Migrations/20190707134040_AddBPMAndLengthSorting.Designer.cs b/osu.Game/Migrations/20190707134040_AddBPMAndLengthSorting.Designer.cs new file mode 100644 index 0000000000..7038fcba6e --- /dev/null +++ b/osu.Game/Migrations/20190707134040_AddBPMAndLengthSorting.Designer.cs @@ -0,0 +1,500 @@ +// +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("20190707134040_AddBPMAndLengthSorting")] + partial class AddBPMAndLengthSorting + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); + + 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("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + 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.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/20190707134040_AddBPMAndLengthSorting.cs b/osu.Game/Migrations/20190707134040_AddBPMAndLengthSorting.cs new file mode 100644 index 0000000000..98a94b1c4e --- /dev/null +++ b/osu.Game/Migrations/20190707134040_AddBPMAndLengthSorting.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddBPMAndLengthSorting : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} From 6ee10640e31f5343fc3eeba33913ab43a1b96591 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 7 Jul 2019 19:26:41 +0300 Subject: [PATCH 0101/2815] Remove unnecessary migration + Fix CI issues --- ...7134040_AddBPMAndLengthSorting.Designer.cs | 500 ------------------ .../20190707134040_AddBPMAndLengthSorting.cs | 17 - osu.Game/Screens/Select/BeatmapInfoWedge.cs | 2 - 3 files changed, 519 deletions(-) delete mode 100644 osu.Game/Migrations/20190707134040_AddBPMAndLengthSorting.Designer.cs delete mode 100644 osu.Game/Migrations/20190707134040_AddBPMAndLengthSorting.cs diff --git a/osu.Game/Migrations/20190707134040_AddBPMAndLengthSorting.Designer.cs b/osu.Game/Migrations/20190707134040_AddBPMAndLengthSorting.Designer.cs deleted file mode 100644 index 7038fcba6e..0000000000 --- a/osu.Game/Migrations/20190707134040_AddBPMAndLengthSorting.Designer.cs +++ /dev/null @@ -1,500 +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("20190707134040_AddBPMAndLengthSorting")] - partial class AddBPMAndLengthSorting - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); - - 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("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - 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.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/20190707134040_AddBPMAndLengthSorting.cs b/osu.Game/Migrations/20190707134040_AddBPMAndLengthSorting.cs deleted file mode 100644 index 98a94b1c4e..0000000000 --- a/osu.Game/Migrations/20190707134040_AddBPMAndLengthSorting.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddBPMAndLengthSorting : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - - } - } -} diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index e0521307fc..26a83f930c 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -18,8 +18,6 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Effects; From 3ea9629daf6cdbd22658ec4ef5f457d8f86f4d3d Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 7 Jul 2019 20:11:44 +0300 Subject: [PATCH 0102/2815] Move BPM out of OnlineInfo --- osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs | 4 ++-- osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs | 2 +- osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs | 5 +---- osu.Game.Tournament/Components/SongBar.cs | 2 +- osu.Game/Beatmaps/BeatmapManager.cs | 5 +---- osu.Game/Beatmaps/BeatmapSetInfo.cs | 5 +++++ osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs | 5 ----- osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs | 2 +- osu.Game/Overlays/BeatmapSet/BasicStats.cs | 2 +- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 2 +- 10 files changed, 14 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index a9c44c9020..75e8c88f9d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -96,11 +96,11 @@ namespace osu.Game.Tests.Visual.Online FavouriteCount = 456, Submitted = DateTime.Now, Ranked = DateTime.Now, - BPM = 111, HasVideo = true, HasStoryboard = true, Covers = new BeatmapSetOnlineCovers(), }, + BPM = 111, Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }, Beatmaps = new List { @@ -169,11 +169,11 @@ namespace osu.Game.Tests.Visual.Online FavouriteCount = 456, Submitted = DateTime.Now, Ranked = DateTime.Now, - BPM = 111, HasVideo = true, HasStoryboard = true, Covers = new BeatmapSetOnlineCovers(), }, + BPM = 111, Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }, Beatmaps = new List { diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs index 53dbaeddda..5f80223541 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs @@ -47,11 +47,11 @@ namespace osu.Game.Tests.Visual.Online Preview = @"https://b.ppy.sh/preview/12345.mp3", PlayCount = 123, FavouriteCount = 456, - BPM = 111, HasVideo = true, HasStoryboard = true, Covers = new BeatmapSetOnlineCovers(), }, + BPM = 111, Beatmaps = new List { new BeatmapInfo diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 04c75d70e3..5813e9e6d5 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -300,10 +300,7 @@ namespace osu.Game.Tests.Visual.SongSelect }, Beatmaps = beatmaps, DateAdded = DateTimeOffset.UtcNow, - OnlineInfo = new BeatmapSetOnlineInfo - { - BPM = bpm, - } + BPM = bpm, }; } } diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index c07882ddd0..06541bc264 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -158,7 +158,7 @@ namespace osu.Game.Tournament.Components return; } - var bpm = beatmap.BeatmapSet.OnlineInfo.BPM; + var bpm = beatmap.BeatmapSet.BPM; var length = beatmap.OnlineInfo.Length; string hardRockExtra = ""; string srExtra = ""; diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index b4c211ad53..f94c461c9a 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -272,10 +272,7 @@ namespace osu.Game.Beatmaps Beatmaps = new List(), Metadata = beatmap.Metadata, DateAdded = DateTimeOffset.UtcNow, - OnlineInfo = new BeatmapSetOnlineInfo - { - BPM = beatmap.ControlPointInfo.BPMMode, - } + BPM = beatmap.ControlPointInfo.BPMMode, }; } diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index cdf8ddb5a1..2b4ef961ce 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -35,6 +35,11 @@ namespace osu.Game.Beatmaps [NotMapped] public BeatmapSetMetrics Metrics { get; set; } + /// + /// The beats per minute of this beatmap set's song. + /// + public double BPM { get; set; } + public double MaxStarDifficulty => Beatmaps?.Max(b => b.StarDifficulty) ?? 0; public double MaxLength => Beatmaps?.Max(b => b.OnlineInfo.Length) ?? 0; diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs index ea3f0b61b9..903d07bea0 100644 --- a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs @@ -51,11 +51,6 @@ namespace osu.Game.Beatmaps /// public string Preview { get; set; } - /// - /// The beats per minute of this beatmap set's song. - /// - public double BPM { get; set; } - /// /// The amount of plays this beatmap set has. /// diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs index 200a705500..50128bde7a 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs @@ -77,13 +77,13 @@ namespace osu.Game.Online.API.Requests.Responses Metadata = this, Status = Status, Metrics = ratings == null ? null : new BeatmapSetMetrics { Ratings = ratings }, + BPM = bpm, OnlineInfo = new BeatmapSetOnlineInfo { Covers = covers, Preview = preview, PlayCount = playCount, FavouriteCount = favouriteCount, - BPM = bpm, Status = Status, HasVideo = hasVideo, HasStoryboard = hasStoryboard, diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs index 6a583baf38..651ef30bdc 100644 --- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs +++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs @@ -50,7 +50,7 @@ namespace osu.Game.Overlays.BeatmapSet private void updateDisplay() { - bpm.Value = BeatmapSet?.OnlineInfo?.BPM.ToString(@"0.##") ?? "-"; + bpm.Value = BeatmapSet?.BPM.ToString(@"0.##") ?? "-"; if (beatmap == null) { diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 7934cf388f..6457f78746 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -49,7 +49,7 @@ namespace osu.Game.Screens.Select.Carousel return otherSet.BeatmapSet.DateAdded.CompareTo(BeatmapSet.DateAdded); case SortMode.BPM: - return BeatmapSet.OnlineInfo.BPM.CompareTo(otherSet.BeatmapSet.OnlineInfo.BPM); + return BeatmapSet.BPM.CompareTo(otherSet.BeatmapSet.BPM); case SortMode.Length: return BeatmapSet.MaxLength.CompareTo(otherSet.BeatmapSet.MaxLength); From 729f0901f7bca3595c2834822a440f2f92433910 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 7 Jul 2019 20:25:36 +0300 Subject: [PATCH 0103/2815] Move Length out of OnlineInfo --- osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs | 4 ++-- osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs | 5 +---- osu.Game.Tournament/Components/SongBar.cs | 2 +- osu.Game/Beatmaps/BeatmapInfo.cs | 5 +++++ osu.Game/Beatmaps/BeatmapManager.cs | 6 +----- osu.Game/Beatmaps/BeatmapOnlineInfo.cs | 5 ----- osu.Game/Beatmaps/BeatmapSetInfo.cs | 2 +- osu.Game/Online/API/Requests/Responses/APIBeatmap.cs | 2 +- osu.Game/Overlays/BeatmapSet/BasicStats.cs | 2 +- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 4 ++-- 10 files changed, 15 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index 75e8c88f9d..02b26acb7c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -108,6 +108,7 @@ namespace osu.Game.Tests.Visual.Online { StarDifficulty = 9.99, Version = @"TEST", + Length = 456000, Ruleset = maniaRuleset, BaseDifficulty = new BeatmapDifficulty { @@ -118,7 +119,6 @@ namespace osu.Game.Tests.Visual.Online }, OnlineInfo = new BeatmapOnlineInfo { - Length = 456000, CircleCount = 111, SliderCount = 12, PlayCount = 222, @@ -181,6 +181,7 @@ namespace osu.Game.Tests.Visual.Online { StarDifficulty = 5.67, Version = @"ANOTHER TEST", + Length = 123000, Ruleset = taikoRuleset, BaseDifficulty = new BeatmapDifficulty { @@ -191,7 +192,6 @@ namespace osu.Game.Tests.Visual.Online }, OnlineInfo = new BeatmapOnlineInfo { - Length = 123000, CircleCount = 123, SliderCount = 45, PlayCount = 567, diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 5813e9e6d5..0b0f65f572 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -275,14 +275,11 @@ namespace osu.Game.Tests.Visual.SongSelect OnlineBeatmapID = beatmapId, Path = "normal.osu", Version = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss})", + Length = length, BaseDifficulty = new BeatmapDifficulty { OverallDifficulty = 3.5f, }, - OnlineInfo = new BeatmapOnlineInfo - { - Length = length, - } }); } diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index 06541bc264..938fbba303 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -159,7 +159,7 @@ namespace osu.Game.Tournament.Components } var bpm = beatmap.BeatmapSet.BPM; - var length = beatmap.OnlineInfo.Length; + var length = beatmap.Length; string hardRockExtra = ""; string srExtra = ""; diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 3c082bb71e..1610e37620 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -51,6 +51,11 @@ namespace osu.Game.Beatmaps [NotMapped] public BeatmapOnlineInfo OnlineInfo { get; set; } + /// + /// The length in milliseconds of this beatmap's song. + /// + public double Length { get; set; } + public string Path { get; set; } [JsonProperty("file_sha2")] diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index f94c461c9a..810db1589e 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -303,11 +303,7 @@ namespace osu.Game.Beatmaps beatmap.BeatmapInfo.Ruleset = ruleset; // TODO: this should be done in a better place once we actually need to dynamically update it. beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating ?? 0; - - beatmap.BeatmapInfo.OnlineInfo = new BeatmapOnlineInfo - { - Length = beatmap.CalculateLength(), - }; + beatmap.BeatmapInfo.Length = beatmap.CalculateLength(); beatmapInfos.Add(beatmap.BeatmapInfo); } diff --git a/osu.Game/Beatmaps/BeatmapOnlineInfo.cs b/osu.Game/Beatmaps/BeatmapOnlineInfo.cs index faae74db88..bfeacd9bfc 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineInfo.cs +++ b/osu.Game/Beatmaps/BeatmapOnlineInfo.cs @@ -8,11 +8,6 @@ namespace osu.Game.Beatmaps /// public class BeatmapOnlineInfo { - /// - /// The length in milliseconds of this beatmap's song. - /// - public double Length { get; set; } - /// /// The amount of circles in this beatmap. /// diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 2b4ef961ce..62366cab4e 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -42,7 +42,7 @@ namespace osu.Game.Beatmaps public double MaxStarDifficulty => Beatmaps?.Max(b => b.StarDifficulty) ?? 0; - public double MaxLength => Beatmaps?.Max(b => b.OnlineInfo.Length) ?? 0; + public double MaxLength => Beatmaps?.Max(b => b.Length) ?? 0; [NotMapped] public bool DeletePending { get; set; } diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs index bcbe060f82..ff4d240bf0 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs @@ -71,6 +71,7 @@ namespace osu.Game.Online.API.Requests.Responses StarDifficulty = starDifficulty, OnlineBeatmapID = OnlineBeatmapID, Version = version, + Length = length, Status = Status, BeatmapSet = set, Metrics = metrics, @@ -85,7 +86,6 @@ namespace osu.Game.Online.API.Requests.Responses { PlayCount = playCount, PassCount = passCount, - Length = length, CircleCount = circleCount, SliderCount = sliderCount, }, diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs index 651ef30bdc..f97212f180 100644 --- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs +++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs @@ -60,7 +60,7 @@ namespace osu.Game.Overlays.BeatmapSet } else { - length.Value = TimeSpan.FromSeconds(beatmap.OnlineInfo.Length).ToString(@"m\:ss"); + length.Value = TimeSpan.FromSeconds(beatmap.Length).ToString(@"m\:ss"); circleCount.Value = beatmap.OnlineInfo.CircleCount.ToString(); sliderCount.Value = beatmap.OnlineInfo.SliderCount.ToString(); } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index fa24a64d51..490012da61 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -50,8 +50,8 @@ namespace osu.Game.Screens.Select.Carousel case SortMode.Length: // Length comparing must be in seconds - if (TimeSpan.FromMilliseconds(Beatmap.OnlineInfo.Length).Seconds != TimeSpan.FromMilliseconds(otherBeatmap.Beatmap.OnlineInfo.Length).Seconds) - return Beatmap.OnlineInfo.Length.CompareTo(otherBeatmap.Beatmap.OnlineInfo.Length); + if (TimeSpan.FromMilliseconds(Beatmap.Length).Seconds != TimeSpan.FromMilliseconds(otherBeatmap.Beatmap.Length).Seconds) + return Beatmap.Length.CompareTo(otherBeatmap.Beatmap.Length); goto case SortMode.Difficulty; } From d8745746129c26f25d6ac40ac67a5d1c2b52effc Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 7 Jul 2019 20:25:59 +0300 Subject: [PATCH 0104/2815] Add migration --- ...7172237_AddBPMAndLengthColumns.Designer.cs | 504 ++++++++++++++++++ .../20190707172237_AddBPMAndLengthColumns.cs | 33 ++ .../Migrations/OsuDbContextModelSnapshot.cs | 4 + 3 files changed, 541 insertions(+) create mode 100644 osu.Game/Migrations/20190707172237_AddBPMAndLengthColumns.Designer.cs create mode 100644 osu.Game/Migrations/20190707172237_AddBPMAndLengthColumns.cs diff --git a/osu.Game/Migrations/20190707172237_AddBPMAndLengthColumns.Designer.cs b/osu.Game/Migrations/20190707172237_AddBPMAndLengthColumns.Designer.cs new file mode 100644 index 0000000000..c1e9f5071a --- /dev/null +++ b/osu.Game/Migrations/20190707172237_AddBPMAndLengthColumns.Designer.cs @@ -0,0 +1,504 @@ +// +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("20190707172237_AddBPMAndLengthColumns")] + partial class AddBPMAndLengthColumns + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); + + 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("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.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("BPM"); + + 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/20190707172237_AddBPMAndLengthColumns.cs b/osu.Game/Migrations/20190707172237_AddBPMAndLengthColumns.cs new file mode 100644 index 0000000000..b14722daf6 --- /dev/null +++ b/osu.Game/Migrations/20190707172237_AddBPMAndLengthColumns.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddBPMAndLengthColumns : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "BPM", + table: "BeatmapSetInfo", + nullable: false, + defaultValue: 0.0); + + migrationBuilder.AddColumn( + name: "Length", + table: "BeatmapInfo", + nullable: false, + defaultValue: 0.0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "BPM", + table: "BeatmapSetInfo"); + + migrationBuilder.DropColumn( + name: "Length", + table: "BeatmapInfo"); + } + } +} diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index 11b032a941..9e65dc7f0f 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -61,6 +61,8 @@ namespace osu.Game.Migrations b.Property("Hidden"); + b.Property("Length"); + b.Property("LetterboxInBreaks"); b.Property("MD5Hash"); @@ -166,6 +168,8 @@ namespace osu.Game.Migrations b.Property("ID") .ValueGeneratedOnAdd(); + b.Property("BPM"); + b.Property("DateAdded"); b.Property("DeletePending"); From 2747d7692b1db44559f305c3e4873e78048a6f48 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 8 Jul 2019 14:55:05 +0900 Subject: [PATCH 0105/2815] Create ReplayPlayerLoader for local mod caching --- osu.Game/OsuGame.cs | 3 +- osu.Game/Screens/Play/ReplayPlayerLoader.cs | 32 +++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Screens/Play/ReplayPlayerLoader.cs diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 0a472d4dc1..ab7efc6e38 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -284,9 +284,8 @@ namespace osu.Game { Ruleset.Value = databasedScoreInfo.Ruleset; Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); - Mods.Value = databasedScoreInfo.Mods; - menuScreen.Push(new PlayerLoader(() => new ReplayPlayer(databasedScore))); + menuScreen.Push(new ReplayPlayerLoader(() => new ReplayPlayer(databasedScore), databasedScoreInfo.Mods)); }, $"watch {databasedScoreInfo}", bypassScreenAllowChecks: true); } diff --git a/osu.Game/Screens/Play/ReplayPlayerLoader.cs b/osu.Game/Screens/Play/ReplayPlayerLoader.cs new file mode 100644 index 0000000000..1c24709e41 --- /dev/null +++ b/osu.Game/Screens/Play/ReplayPlayerLoader.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 System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Screens.Play +{ + public class ReplayPlayerLoader : PlayerLoader + { + private readonly IReadOnlyList mods; + + public ReplayPlayerLoader(Func player, IReadOnlyList mods) + : base(player) + { + this.mods = mods; + } + + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + dependencies = new DependencyContainer(parent); + dependencies.Cache(new Bindable>(mods)); + + return base.CreateChildDependencies(dependencies); + } + } +} From 90d5484818b9e0372694a1d99a3801d340699bd3 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 8 Jul 2019 09:10:41 +0300 Subject: [PATCH 0106/2815] Return BPM back to OnlineInfo Revert commit of "Move BPM out of OnlineInfo" --- osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs | 4 ++-- osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs | 2 +- osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs | 5 ++++- osu.Game/Beatmaps/BeatmapManager.cs | 5 ++++- osu.Game/Beatmaps/BeatmapSetInfo.cs | 5 ----- osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs | 5 +++++ osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs | 2 +- osu.Game/Overlays/BeatmapSet/BasicStats.cs | 2 +- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 2 +- 9 files changed, 19 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index 02b26acb7c..daee419b52 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -96,11 +96,11 @@ namespace osu.Game.Tests.Visual.Online FavouriteCount = 456, Submitted = DateTime.Now, Ranked = DateTime.Now, + BPM = 111, HasVideo = true, HasStoryboard = true, Covers = new BeatmapSetOnlineCovers(), }, - BPM = 111, Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }, Beatmaps = new List { @@ -169,11 +169,11 @@ namespace osu.Game.Tests.Visual.Online FavouriteCount = 456, Submitted = DateTime.Now, Ranked = DateTime.Now, + BPM = 111, HasVideo = true, HasStoryboard = true, Covers = new BeatmapSetOnlineCovers(), }, - BPM = 111, Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }, Beatmaps = new List { diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs index 5f80223541..53dbaeddda 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs @@ -47,11 +47,11 @@ namespace osu.Game.Tests.Visual.Online Preview = @"https://b.ppy.sh/preview/12345.mp3", PlayCount = 123, FavouriteCount = 456, + BPM = 111, HasVideo = true, HasStoryboard = true, Covers = new BeatmapSetOnlineCovers(), }, - BPM = 111, Beatmaps = new List { new BeatmapInfo diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 0b0f65f572..27a341ffb7 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -297,7 +297,10 @@ namespace osu.Game.Tests.Visual.SongSelect }, Beatmaps = beatmaps, DateAdded = DateTimeOffset.UtcNow, - BPM = bpm, + OnlineInfo = new BeatmapSetOnlineInfo + { + BPM = bpm, + } }; } } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 810db1589e..34a8ddf5c9 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -272,7 +272,10 @@ namespace osu.Game.Beatmaps Beatmaps = new List(), Metadata = beatmap.Metadata, DateAdded = DateTimeOffset.UtcNow, - BPM = beatmap.ControlPointInfo.BPMMode, + OnlineInfo = new BeatmapSetOnlineInfo + { + BPM = beatmap.ControlPointInfo.BPMMode, + } }; } diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 62366cab4e..a77dfa2148 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -35,11 +35,6 @@ namespace osu.Game.Beatmaps [NotMapped] public BeatmapSetMetrics Metrics { get; set; } - /// - /// The beats per minute of this beatmap set's song. - /// - public double BPM { get; set; } - public double MaxStarDifficulty => Beatmaps?.Max(b => b.StarDifficulty) ?? 0; public double MaxLength => Beatmaps?.Max(b => b.Length) ?? 0; diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs index 903d07bea0..ea3f0b61b9 100644 --- a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs @@ -51,6 +51,11 @@ namespace osu.Game.Beatmaps /// public string Preview { get; set; } + /// + /// The beats per minute of this beatmap set's song. + /// + public double BPM { get; set; } + /// /// The amount of plays this beatmap set has. /// diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs index 50128bde7a..200a705500 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs @@ -77,13 +77,13 @@ namespace osu.Game.Online.API.Requests.Responses Metadata = this, Status = Status, Metrics = ratings == null ? null : new BeatmapSetMetrics { Ratings = ratings }, - BPM = bpm, OnlineInfo = new BeatmapSetOnlineInfo { Covers = covers, Preview = preview, PlayCount = playCount, FavouriteCount = favouriteCount, + BPM = bpm, Status = Status, HasVideo = hasVideo, HasStoryboard = hasStoryboard, diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs index f97212f180..2926c82631 100644 --- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs +++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs @@ -50,7 +50,7 @@ namespace osu.Game.Overlays.BeatmapSet private void updateDisplay() { - bpm.Value = BeatmapSet?.BPM.ToString(@"0.##") ?? "-"; + bpm.Value = BeatmapSet?.OnlineInfo?.BPM.ToString(@"0.##") ?? "-"; if (beatmap == null) { diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 6457f78746..7934cf388f 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -49,7 +49,7 @@ namespace osu.Game.Screens.Select.Carousel return otherSet.BeatmapSet.DateAdded.CompareTo(BeatmapSet.DateAdded); case SortMode.BPM: - return BeatmapSet.BPM.CompareTo(otherSet.BeatmapSet.BPM); + return BeatmapSet.OnlineInfo.BPM.CompareTo(otherSet.BeatmapSet.OnlineInfo.BPM); case SortMode.Length: return BeatmapSet.MaxLength.CompareTo(otherSet.BeatmapSet.MaxLength); From 79ddb8d5d36f76131d5c75da5f6650d91ffa22c9 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 8 Jul 2019 09:23:01 +0300 Subject: [PATCH 0107/2815] Change to a more convenient xmldoc --- osu.Game/Beatmaps/BeatmapInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 1610e37620..609f75461c 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -52,7 +52,7 @@ namespace osu.Game.Beatmaps public BeatmapOnlineInfo OnlineInfo { get; set; } /// - /// The length in milliseconds of this beatmap's song. + /// The playable length of this beatmap. /// public double Length { get; set; } From 2d0c924bdf579bd935f1eaf9fa19ce50aba39d5f Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 8 Jul 2019 09:35:12 +0300 Subject: [PATCH 0108/2815] Add xmldoc for MaxStarDifficulty and MaxLength --- osu.Game/Beatmaps/BeatmapSetInfo.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index a77dfa2148..3e6385bdd5 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -35,8 +35,14 @@ namespace osu.Game.Beatmaps [NotMapped] public BeatmapSetMetrics Metrics { get; set; } + /// + /// The maximum star difficulty of all beatmaps in this set. + /// public double MaxStarDifficulty => Beatmaps?.Max(b => b.StarDifficulty) ?? 0; + /// + /// The maximum playable length of all beatmaps in this set. + /// public double MaxLength => Beatmaps?.Max(b => b.Length) ?? 0; [NotMapped] From 5853a877c257a14316cf03e5a39dd2a70e912e90 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 8 Jul 2019 15:40:10 +0900 Subject: [PATCH 0109/2815] create base dependencies before caching, create player in playerloader --- osu.Game/OsuGame.cs | 2 +- osu.Game/Screens/Play/ReplayPlayerLoader.cs | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ab7efc6e38..1c426d928f 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -285,7 +285,7 @@ namespace osu.Game Ruleset.Value = databasedScoreInfo.Ruleset; Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); - menuScreen.Push(new ReplayPlayerLoader(() => new ReplayPlayer(databasedScore), databasedScoreInfo.Mods)); + menuScreen.Push(new ReplayPlayerLoader(databasedScore, databasedScoreInfo.Mods)); }, $"watch {databasedScoreInfo}", bypassScreenAllowChecks: true); } diff --git a/osu.Game/Screens/Play/ReplayPlayerLoader.cs b/osu.Game/Screens/Play/ReplayPlayerLoader.cs index 1c24709e41..41f02b5cb1 100644 --- a/osu.Game/Screens/Play/ReplayPlayerLoader.cs +++ b/osu.Game/Screens/Play/ReplayPlayerLoader.cs @@ -1,32 +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 System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Rulesets.Mods; +using osu.Game.Scoring; namespace osu.Game.Screens.Play { public class ReplayPlayerLoader : PlayerLoader { - private readonly IReadOnlyList mods; + private readonly Bindable> mods; - public ReplayPlayerLoader(Func player, IReadOnlyList mods) - : base(player) + public ReplayPlayerLoader(Score score, IReadOnlyList mods) + : base(() => new ReplayPlayer(score)) { - this.mods = mods; + this.mods = new Bindable>(mods); } - private DependencyContainer dependencies; - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - dependencies = new DependencyContainer(parent); - dependencies.Cache(new Bindable>(mods)); + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.Cache(mods); - return base.CreateChildDependencies(dependencies); + // Overwrite the global mods here for use in the mod hud. + Mods.Value = mods.Value; + + return dependencies; } } } From 6a86f62d17b05a899059337ed9f82d25a92f2d5d Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 8 Jul 2019 16:13:03 +0900 Subject: [PATCH 0110/2815] Get mods from score info --- osu.Game/OsuGame.cs | 2 +- osu.Game/Screens/Play/ReplayPlayerLoader.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 1c426d928f..2260c5bb57 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -285,7 +285,7 @@ namespace osu.Game Ruleset.Value = databasedScoreInfo.Ruleset; Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); - menuScreen.Push(new ReplayPlayerLoader(databasedScore, databasedScoreInfo.Mods)); + menuScreen.Push(new ReplayPlayerLoader(databasedScore)); }, $"watch {databasedScoreInfo}", bypassScreenAllowChecks: true); } diff --git a/osu.Game/Screens/Play/ReplayPlayerLoader.cs b/osu.Game/Screens/Play/ReplayPlayerLoader.cs index 41f02b5cb1..da30ed80b6 100644 --- a/osu.Game/Screens/Play/ReplayPlayerLoader.cs +++ b/osu.Game/Screens/Play/ReplayPlayerLoader.cs @@ -13,10 +13,10 @@ namespace osu.Game.Screens.Play { private readonly Bindable> mods; - public ReplayPlayerLoader(Score score, IReadOnlyList mods) + public ReplayPlayerLoader(Score score) : base(() => new ReplayPlayer(score)) { - this.mods = new Bindable>(mods); + mods = new Bindable>(score.ScoreInfo.Mods); } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) From ef22ab9340d152cb9713b4aded8957321f6b5412 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 8 Jul 2019 16:32:11 +0900 Subject: [PATCH 0111/2815] remove bindable --- osu.Game/Screens/Play/ReplayPlayerLoader.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/ReplayPlayerLoader.cs b/osu.Game/Screens/Play/ReplayPlayerLoader.cs index da30ed80b6..b877e7e2ca 100644 --- a/osu.Game/Screens/Play/ReplayPlayerLoader.cs +++ b/osu.Game/Screens/Play/ReplayPlayerLoader.cs @@ -11,21 +11,20 @@ namespace osu.Game.Screens.Play { public class ReplayPlayerLoader : PlayerLoader { - private readonly Bindable> mods; + private readonly IReadOnlyList mods; public ReplayPlayerLoader(Score score) : base(() => new ReplayPlayer(score)) { - mods = new Bindable>(score.ScoreInfo.Mods); + mods = score.ScoreInfo.Mods; } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Cache(mods); // Overwrite the global mods here for use in the mod hud. - Mods.Value = mods.Value; + Mods.Value = mods; return dependencies; } From 72362d92d47e388af4bb9457ed0b963bc1bd77ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Jul 2019 16:34:11 +0900 Subject: [PATCH 0112/2815] Fix a few inspections from EAP r# --- osu.Game.Tests/Visual/Online/TestSceneChannelTabControl.cs | 2 +- osu.Game.Tests/Visual/UserInterface/TestSceneBackButton.cs | 7 ++++--- .../UserInterface/TestSceneScreenBreadcrumbControl.cs | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelTabControl.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelTabControl.cs index 364c986723..16e47c5df9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelTabControl.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelTabControl.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Online }); channelTabControl.OnRequestLeave += channel => channelTabControl.RemoveChannel(channel); - channelTabControl.Current.ValueChanged += channel => currentText.Text = "Currently selected channel: " + channel.NewValue.ToString(); + channelTabControl.Current.ValueChanged += channel => currentText.Text = "Currently selected channel: " + channel.NewValue; AddStep("Add random private channel", addRandomPrivateChannel); AddAssert("There is only one channels", () => channelTabControl.Items.Count() == 2); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBackButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBackButton.cs index 867b3130c9..38a9af05d8 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBackButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBackButton.cs @@ -14,8 +14,6 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestSceneBackButton : OsuTestScene { - private readonly BackButton button; - public override IReadOnlyList RequiredTypes => new[] { typeof(TwoLayerButton) @@ -23,6 +21,8 @@ namespace osu.Game.Tests.Visual.UserInterface public TestSceneBackButton() { + BackButton button; + Child = new Container { Anchor = Anchor.Centre, @@ -40,11 +40,12 @@ namespace osu.Game.Tests.Visual.UserInterface { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Action = () => button.Hide(), } } }; + button.Action = () => button.Hide(); + AddStep("show button", () => button.Show()); AddStep("hide button", () => button.Hide()); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneScreenBreadcrumbControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenBreadcrumbControl.cs index 9c83fdf96c..0cb8683d72 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneScreenBreadcrumbControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenBreadcrumbControl.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.UserInterface }, }; - breadcrumbs.Current.ValueChanged += screen => titleText.Text = $"Changed to {screen.NewValue.ToString()}"; + breadcrumbs.Current.ValueChanged += screen => titleText.Text = $"Changed to {screen.NewValue}"; breadcrumbs.Current.TriggerChange(); waitForCurrent(); From 129899f41950da4d0f11557000e5af1a617777d7 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 8 Jul 2019 10:43:35 +0300 Subject: [PATCH 0113/2815] Add a BPM property in BeatmapInfo --- .../Visual/SongSelect/TestScenePlaySongSelect.cs | 12 +++++------- osu.Game.Tournament/Components/SongBar.cs | 2 +- osu.Game/Beatmaps/BeatmapInfo.cs | 5 +++++ osu.Game/Beatmaps/BeatmapManager.cs | 7 ++----- osu.Game/Beatmaps/BeatmapSetInfo.cs | 5 +++++ .../Screens/Select/Carousel/CarouselBeatmapSet.cs | 2 +- 6 files changed, 19 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 27a341ffb7..f3255814f2 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -267,15 +267,18 @@ namespace osu.Game.Tests.Visual.SongSelect for (int i = 0; i < 6; i++) { int beatmapId = setId * 10 + i; + int length = RNG.Next(30000, 200000); + double bpm = RNG.NextSingle(80, 200); beatmaps.Add(new BeatmapInfo { Ruleset = getRuleset(), OnlineBeatmapID = beatmapId, Path = "normal.osu", - Version = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss})", + Version = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", Length = length, + BPM = bpm, BaseDifficulty = new BeatmapDifficulty { OverallDifficulty = 3.5f, @@ -283,7 +286,6 @@ namespace osu.Game.Tests.Visual.SongSelect }); } - double bpm = RNG.NextSingle(80, 200); return new BeatmapSetInfo { OnlineBeatmapSetID = setId, @@ -292,15 +294,11 @@ namespace osu.Game.Tests.Visual.SongSelect { // Create random metadata, then we can check if sorting works based on these Artist = "Some Artist " + RNG.Next(0, 9), - Title = $"Some Song (set id {setId}, bpm {bpm:0.#})", + Title = $"Some Song (set id {setId}, max bpm {beatmaps.Max(b => b.BPM):0.#})", AuthorString = "Some Guy " + RNG.Next(0, 9), }, Beatmaps = beatmaps, DateAdded = DateTimeOffset.UtcNow, - OnlineInfo = new BeatmapSetOnlineInfo - { - BPM = bpm, - } }; } } diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index 938fbba303..ec021a8d1f 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -158,7 +158,7 @@ namespace osu.Game.Tournament.Components return; } - var bpm = beatmap.BeatmapSet.BPM; + var bpm = beatmap.BeatmapSet.OnlineInfo.BPM; var length = beatmap.Length; string hardRockExtra = ""; string srExtra = ""; diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 609f75461c..fa1282647e 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -56,6 +56,11 @@ namespace osu.Game.Beatmaps /// public double Length { get; set; } + /// + /// The most common BPM of this beatmap. + /// + public double BPM { get; set; } + public string Path { get; set; } [JsonProperty("file_sha2")] diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 34a8ddf5c9..56816607ee 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -271,11 +271,7 @@ namespace osu.Game.Beatmaps OnlineBeatmapSetID = beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID, Beatmaps = new List(), Metadata = beatmap.Metadata, - DateAdded = DateTimeOffset.UtcNow, - OnlineInfo = new BeatmapSetOnlineInfo - { - BPM = beatmap.ControlPointInfo.BPMMode, - } + DateAdded = DateTimeOffset.UtcNow }; } @@ -307,6 +303,7 @@ namespace osu.Game.Beatmaps // TODO: this should be done in a better place once we actually need to dynamically update it. beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating ?? 0; beatmap.BeatmapInfo.Length = beatmap.CalculateLength(); + beatmap.BeatmapInfo.BPM = beatmap.ControlPointInfo.BPMMode; beatmapInfos.Add(beatmap.BeatmapInfo); } diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 3e6385bdd5..4075263e12 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -45,6 +45,11 @@ namespace osu.Game.Beatmaps /// public double MaxLength => Beatmaps?.Max(b => b.Length) ?? 0; + /// + /// The maximum BPM of all beatmaps in this set. + /// + public double MaxBPM => Beatmaps?.Max(b => b.BPM) ?? 0; + [NotMapped] public bool DeletePending { get; set; } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 7934cf388f..5a3996bb49 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -49,7 +49,7 @@ namespace osu.Game.Screens.Select.Carousel return otherSet.BeatmapSet.DateAdded.CompareTo(BeatmapSet.DateAdded); case SortMode.BPM: - return BeatmapSet.OnlineInfo.BPM.CompareTo(otherSet.BeatmapSet.OnlineInfo.BPM); + return BeatmapSet.MaxBPM.CompareTo(otherSet.BeatmapSet.MaxBPM); case SortMode.Length: return BeatmapSet.MaxLength.CompareTo(otherSet.BeatmapSet.MaxLength); From 574d9a51b393a1baae947eab18d08ff1fa74da85 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 8 Jul 2019 10:44:23 +0300 Subject: [PATCH 0114/2815] Update migrations --- ...cs => 20190708070844_AddBPMAndLengthColumns.Designer.cs} | 6 +++--- ...hColumns.cs => 20190708070844_AddBPMAndLengthColumns.cs} | 4 ++-- osu.Game/Migrations/OsuDbContextModelSnapshot.cs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) rename osu.Game/Migrations/{20190707172237_AddBPMAndLengthColumns.Designer.cs => 20190708070844_AddBPMAndLengthColumns.Designer.cs} (99%) rename osu.Game/Migrations/{20190707172237_AddBPMAndLengthColumns.cs => 20190708070844_AddBPMAndLengthColumns.cs} (91%) diff --git a/osu.Game/Migrations/20190707172237_AddBPMAndLengthColumns.Designer.cs b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs similarity index 99% rename from osu.Game/Migrations/20190707172237_AddBPMAndLengthColumns.Designer.cs rename to osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs index c1e9f5071a..c5fcc16f84 100644 --- a/osu.Game/Migrations/20190707172237_AddBPMAndLengthColumns.Designer.cs +++ b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs @@ -9,7 +9,7 @@ using osu.Game.Database; namespace osu.Game.Migrations { [DbContext(typeof(OsuDbContext))] - [Migration("20190707172237_AddBPMAndLengthColumns")] + [Migration("20190708070844_AddBPMAndLengthColumns")] partial class AddBPMAndLengthColumns { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -47,6 +47,8 @@ namespace osu.Game.Migrations b.Property("AudioLeadIn"); + b.Property("BPM"); + b.Property("BaseDifficultyID"); b.Property("BeatDivisor"); @@ -170,8 +172,6 @@ namespace osu.Game.Migrations b.Property("ID") .ValueGeneratedOnAdd(); - b.Property("BPM"); - b.Property("DateAdded"); b.Property("DeletePending"); diff --git a/osu.Game/Migrations/20190707172237_AddBPMAndLengthColumns.cs b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs similarity index 91% rename from osu.Game/Migrations/20190707172237_AddBPMAndLengthColumns.cs rename to osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs index b14722daf6..f5963ebf5e 100644 --- a/osu.Game/Migrations/20190707172237_AddBPMAndLengthColumns.cs +++ b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs @@ -8,7 +8,7 @@ namespace osu.Game.Migrations { migrationBuilder.AddColumn( name: "BPM", - table: "BeatmapSetInfo", + table: "BeatmapInfo", nullable: false, defaultValue: 0.0); @@ -23,7 +23,7 @@ namespace osu.Game.Migrations { migrationBuilder.DropColumn( name: "BPM", - table: "BeatmapSetInfo"); + table: "BeatmapInfo"); migrationBuilder.DropColumn( name: "Length", diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index 9e65dc7f0f..761dca2801 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -45,6 +45,8 @@ namespace osu.Game.Migrations b.Property("AudioLeadIn"); + b.Property("BPM"); + b.Property("BaseDifficultyID"); b.Property("BeatDivisor"); @@ -168,8 +170,6 @@ namespace osu.Game.Migrations b.Property("ID") .ValueGeneratedOnAdd(); - b.Property("BPM"); - b.Property("DateAdded"); b.Property("DeletePending"); From e78e326f34d84ce8cc3abbcc581adf35f8194bad Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 8 Jul 2019 17:02:42 +0900 Subject: [PATCH 0115/2815] remove unused using --- osu.Game/Screens/Play/ReplayPlayerLoader.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/ReplayPlayerLoader.cs b/osu.Game/Screens/Play/ReplayPlayerLoader.cs index b877e7e2ca..62edb661b9 100644 --- a/osu.Game/Screens/Play/ReplayPlayerLoader.cs +++ b/osu.Game/Screens/Play/ReplayPlayerLoader.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; From 16c993579bf943cb8ca71e89654e4ffd1115302c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Jul 2019 17:10:12 +0900 Subject: [PATCH 0116/2815] Fix track transfer not running when beatmap is retrieved from cache --- osu.Game/Beatmaps/BeatmapManager.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 7ef50da7d3..5031176790 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -180,19 +180,18 @@ namespace osu.Game.Beatmaps lock (workingCache) { - var cached = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == beatmapInfo.ID); + var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == beatmapInfo.ID); - if (cached != null) - return cached; + if (working == null) + { + if (beatmapInfo.Metadata == null) + beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata; - if (beatmapInfo.Metadata == null) - beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata; - - WorkingBeatmap working = new BeatmapManagerWorkingBeatmap(Files.Store, new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store)), beatmapInfo, audioManager); + workingCache.Add(working = new BeatmapManagerWorkingBeatmap(Files.Store, + new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store)), beatmapInfo, audioManager)); + } previous?.TransferTo(working); - workingCache.Add(working); - return working; } } From a0efd50f629476bda2f1bfa86e7d801cd2662a48 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 8 Jul 2019 11:25:25 +0300 Subject: [PATCH 0117/2815] Extend APILegacyScores request --- osu.Game/Online/API/Requests/GetScoresRequest.cs | 8 ++++++++ .../Online/API/Requests/Responses/APILegacyScores.cs | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index 6b0e680eb5..50844fa256 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -42,6 +42,14 @@ namespace osu.Game.Online.API.Requests score.Beatmap = beatmap; score.Ruleset = ruleset; } + + var userScore = r.UserScore; + + if (userScore != null) + { + userScore.Score.Beatmap = beatmap; + userScore.Score.Ruleset = ruleset; + } } protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}/scores{createQueryParameters()}"; diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs index c629caaa6f..318fcb00de 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs @@ -10,5 +10,17 @@ namespace osu.Game.Online.API.Requests.Responses { [JsonProperty(@"scores")] public List Scores; + + [JsonProperty(@"userScore")] + public APILegacyUserTopScoreInfo UserScore; + } + + public class APILegacyUserTopScoreInfo + { + [JsonProperty(@"position")] + public int Position; + + [JsonProperty(@"score")] + public APILegacyScoreInfo Score; } } From fbd300e6643a582b9fab5299265633e907e16d6b Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 8 Jul 2019 17:37:20 +0900 Subject: [PATCH 0118/2815] Move ruleset into ReplayPlayerLoader as well --- osu.Game/OsuGame.cs | 1 - osu.Game/Screens/Play/ReplayPlayerLoader.cs | 9 ++++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 2260c5bb57..361ff62155 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -282,7 +282,6 @@ namespace osu.Game performFromMainMenu(() => { - Ruleset.Value = databasedScoreInfo.Ruleset; Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); menuScreen.Push(new ReplayPlayerLoader(databasedScore)); diff --git a/osu.Game/Screens/Play/ReplayPlayerLoader.cs b/osu.Game/Screens/Play/ReplayPlayerLoader.cs index 62edb661b9..329e799f7c 100644 --- a/osu.Game/Screens/Play/ReplayPlayerLoader.cs +++ b/osu.Game/Screens/Play/ReplayPlayerLoader.cs @@ -1,21 +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.Collections.Generic; using osu.Framework.Allocation; -using osu.Game.Rulesets.Mods; using osu.Game.Scoring; namespace osu.Game.Screens.Play { public class ReplayPlayerLoader : PlayerLoader { - private readonly IReadOnlyList mods; + private readonly ScoreInfo scoreInfo; public ReplayPlayerLoader(Score score) : base(() => new ReplayPlayer(score)) { - mods = score.ScoreInfo.Mods; + scoreInfo = score.ScoreInfo; } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -23,7 +21,8 @@ namespace osu.Game.Screens.Play var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); // Overwrite the global mods here for use in the mod hud. - Mods.Value = mods; + Mods.Value = scoreInfo.Mods; + Ruleset.Value = scoreInfo.Ruleset; return dependencies; } From 67a6abb96c7e08c0afa5e4f5eb49c0e6bbeb369c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 8 Jul 2019 11:49:33 +0300 Subject: [PATCH 0119/2815] Add user top score on selected beatmap --- .../BeatmapSet/Scores/DrawableTopScore.cs | 22 +++------- .../BeatmapSet/Scores/ScoresContainer.cs | 41 +++++++++++++++---- .../BeatmapSet/Scores/TopScoreUserSection.cs | 4 +- 3 files changed, 41 insertions(+), 26 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index 8e806c6747..bdae730f7e 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -23,10 +23,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private Color4 backgroundHoveredColour; private readonly Box background; - private readonly TopScoreUserSection userSection; - private readonly TopScoreStatisticsSection statisticsSection; - public DrawableTopScore() + public DrawableTopScore(ScoreInfo score, int position = 1) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -61,16 +59,18 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { new Drawable[] { - userSection = new TopScoreUserSection + new TopScoreUserSection(position) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, + Score = score, }, null, - statisticsSection = new TopScoreStatisticsSection + new TopScoreStatisticsSection { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, + Score = score, } }, }, @@ -91,18 +91,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores background.Colour = backgroundIdleColour; } - /// - /// Sets the score to be displayed. - /// - public ScoreInfo Score - { - set - { - userSection.Score = value; - statisticsSection.Score = value; - } - } - protected override bool OnHover(HoverEvent e) { background.FadeColour(backgroundHoveredColour, fade_duration, Easing.OutQuint); diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 3e6c938802..30685fb826 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -14,6 +14,7 @@ using osuTK; using System.Collections.Generic; using System.Linq; using osu.Game.Scoring; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -25,7 +26,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly Box background; private readonly ScoreTable scoreTable; - private readonly DrawableTopScore topScore; + private readonly FillFlowContainer topScoresContainer; private readonly LoadingAnimation loadingAnimation; [Resolved] @@ -54,7 +55,13 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Margin = new MarginPadding { Vertical = spacing }, Children = new Drawable[] { - topScore = new DrawableTopScore(), + topScoresContainer = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + }, scoreTable = new ScoreTable { Anchor = Anchor.TopCentre, @@ -97,6 +104,20 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } } + private APILegacyUserTopScoreInfo userScore; + + public APILegacyUserTopScoreInfo UserScore + { + get => userScore; + set + { + getScoresRequest?.Cancel(); + userScore = value; + + updateDisplay(); + } + } + private BeatmapInfo beatmap; public BeatmapInfo Beatmap @@ -114,7 +135,12 @@ namespace osu.Game.Overlays.BeatmapSet.Scores loading = true; getScoresRequest = new GetScoresRequest(beatmap, beatmap.Ruleset); - getScoresRequest.Success += r => Schedule(() => Scores = r.Scores); + getScoresRequest.Success += r => Schedule(() => + { + scores = r.Scores; + userScore = r.UserScore; + updateDisplay(); + }); api.Queue(getScoresRequest); } } @@ -122,17 +148,18 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private void updateDisplay() { loading = false; + topScoresContainer.Clear(); scoreTable.Scores = scores?.Count > 1 ? scores : new List(); scoreTable.FadeTo(scores?.Count > 1 ? 1 : 0); if (scores?.Any() == true) { - topScore.Score = scores.FirstOrDefault(); - topScore.Show(); + topScoresContainer.Add(new DrawableTopScore(scores.FirstOrDefault())); + + if (userScore != null && userScore.Position != 1) + topScoresContainer.Add(new DrawableTopScore(userScore.Score, userScore.Position)); } - else - topScore.Hide(); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs index 1d9c4e7fc8..1314573cb4 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly SpriteText date; private readonly UpdateableFlag flag; - public TopScoreUserSection() + public TopScoreUserSection(int position) { AutoSizeAxes = Axes.Both; @@ -43,7 +43,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = "#1", + Text = position.ToString(), Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold, italics: true) }, rank = new UpdateableRank(ScoreRank.D) From 5f3f59629ec04a69132f3419d8e2667795500ffb Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 8 Jul 2019 11:55:07 +0300 Subject: [PATCH 0120/2815] Use the length field instead of recalculating --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 26a83f930c..2551ffe2fc 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -291,7 +291,7 @@ namespace osu.Game.Screens.Select { Name = "Length", Icon = FontAwesome.Regular.Clock, - Content = TimeSpan.FromMilliseconds(b.CalculateLength()).ToString(@"m\:ss"), + Content = TimeSpan.FromMilliseconds(b.BeatmapInfo.Length).ToString(@"m\:ss"), })); labels.Add(new InfoLabel(new BeatmapStatistic From b62e69d170ebd0ed24c704a2a1ebf6397af32a82 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 8 Jul 2019 11:56:48 +0300 Subject: [PATCH 0121/2815] Calculate length inside BeatmapManager --- osu.Game/Beatmaps/BeatmapManager.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 56816607ee..6fe70b6ec6 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -23,6 +23,7 @@ using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Beatmaps { @@ -302,7 +303,7 @@ namespace osu.Game.Beatmaps beatmap.BeatmapInfo.Ruleset = ruleset; // TODO: this should be done in a better place once we actually need to dynamically update it. beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating ?? 0; - beatmap.BeatmapInfo.Length = beatmap.CalculateLength(); + beatmap.BeatmapInfo.Length = calculateLength(beatmap); beatmap.BeatmapInfo.BPM = beatmap.ControlPointInfo.BPMMode; beatmapInfos.Add(beatmap.BeatmapInfo); @@ -312,6 +313,14 @@ namespace osu.Game.Beatmaps return beatmapInfos; } + private double calculateLength(IBeatmap b) + { + var lastObject = b.HitObjects.LastOrDefault(); + var endTime = (lastObject as IHasEndTime)?.EndTime ?? lastObject?.StartTime ?? 0; + + return endTime - b.HitObjects.FirstOrDefault()?.StartTime ?? 0; + } + /// /// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation. /// From 11ef65e3e2d72cd750f4dc1626c3373f0df92864 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 8 Jul 2019 11:57:02 +0300 Subject: [PATCH 0122/2815] Remove unnecessary extension --- osu.Game/Beatmaps/Beatmap.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 7fca13a1e2..4ebeee40bf 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -8,7 +8,6 @@ using System.Linq; using osu.Game.Beatmaps.ControlPoints; using Newtonsoft.Json; using osu.Game.IO.Serialization.Converters; -using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Beatmaps { @@ -62,15 +61,4 @@ namespace osu.Game.Beatmaps { public new Beatmap Clone() => (Beatmap)base.Clone(); } - - public static class BeatmapExtensions - { - public static double CalculateLength(this IBeatmap b) - { - HitObject lastObject = b.HitObjects.LastOrDefault(); - var endTime = (lastObject as IHasEndTime)?.EndTime ?? lastObject?.StartTime ?? 0; - - return endTime - b.HitObjects.FirstOrDefault()?.StartTime ?? 0; - } - } } From d489a77fe18c3a3f06b1cccb733fdb8a410bcb39 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 8 Jul 2019 17:57:29 +0900 Subject: [PATCH 0123/2815] remove new container and comment --- osu.Game/Screens/Play/ReplayPlayerLoader.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/ReplayPlayerLoader.cs b/osu.Game/Screens/Play/ReplayPlayerLoader.cs index 329e799f7c..8681ae6887 100644 --- a/osu.Game/Screens/Play/ReplayPlayerLoader.cs +++ b/osu.Game/Screens/Play/ReplayPlayerLoader.cs @@ -18,9 +18,8 @@ namespace osu.Game.Screens.Play protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + var dependencies = base.CreateChildDependencies(parent); - // Overwrite the global mods here for use in the mod hud. Mods.Value = scoreInfo.Mods; Ruleset.Value = scoreInfo.Ruleset; From 59cfd39670c62df39ba8ce95f71e23c86395e7f1 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 8 Jul 2019 12:02:10 +0300 Subject: [PATCH 0124/2815] Add testcase --- .../Visual/Online/TestSceneScoresContainer.cs | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 06414af865..e4e6acec06 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.MathUtils; using osu.Game.Graphics; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; @@ -165,6 +166,29 @@ namespace osu.Game.Tests.Visual.Online }, }; + var myBestScore = new APILegacyUserTopScoreInfo + { + Score = new APILegacyScoreInfo + { + User = new User + { + Id = 7151382, + Username = @"Mayuri Hana", + Country = new Country + { + FullName = @"Thailand", + FlagName = @"TH", + }, + }, + Rank = ScoreRank.D, + PP = 160, + MaxCombo = 1234, + TotalScore = 123456, + Accuracy = 0.6543, + }, + Position = 1337, + }; + foreach (var s in scores) { s.Statistics.Add(HitResult.Great, RNG.Next(2000)); @@ -173,9 +197,18 @@ namespace osu.Game.Tests.Visual.Online s.Statistics.Add(HitResult.Miss, RNG.Next(2000)); } - AddStep("Load all scores", () => scoresContainer.Scores = scores); - AddStep("Load null scores", () => scoresContainer.Scores = null); + AddStep("Load all scores", () => + { + scoresContainer.Scores = scores; + scoresContainer.UserScore = myBestScore; + }); + AddStep("Load null scores", () => + { + scoresContainer.Scores = null; + scoresContainer.UserScore = null; + }); AddStep("Load only one score", () => scoresContainer.Scores = new[] { scores.First() }); + AddStep("Add my best score", () => scoresContainer.UserScore = myBestScore); } [BackgroundDependencyLoader] From 1e5639acee4858571dfe4c832354a04fef38432d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 8 Jul 2019 12:10:08 +0300 Subject: [PATCH 0125/2815] Add forgotten symbol --- osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs index 1314573cb4..385d8ff38c 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs @@ -43,7 +43,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = position.ToString(), + Text = $"#{position.ToString()}", Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold, italics: true) }, rank = new UpdateableRank(ScoreRank.D) From 39f04e497d5734ec57393b7a974cb255540f4ae6 Mon Sep 17 00:00:00 2001 From: Desconocidosmh Date: Mon, 8 Jul 2019 11:24:06 +0200 Subject: [PATCH 0126/2815] Add UserRequestedPause --- osu.Game/Overlays/MusicController.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 29ae5983be..3db71d39ee 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -66,6 +66,8 @@ namespace osu.Game.Overlays /// public Func GetToolbarHeight; + public bool UserRequestedPause { get; private set; } + public MusicController() { Width = 400; @@ -287,6 +289,8 @@ namespace osu.Game.Overlays return; } + UserRequestedPause = track.IsRunning; + if (track.IsRunning) track.Stop(); else From 0cf4bf2352048fd6feca7495a69b6d9e09b992f9 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 8 Jul 2019 18:46:12 +0900 Subject: [PATCH 0127/2815] Manually set clock for storyboard if loading before being given a parent --- osu.Game/Screens/Play/Player.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 0da9c77f25..ea614e7658 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -362,7 +362,12 @@ namespace osu.Game.Screens.Play storyboard.Masking = true; if (asyncLoad) - LoadComponentAsync(storyboard, StoryboardContainer.Add); + LoadComponentAsync(storyboard, c => + { + // Since the storyboard was loaded before it can be added to the draw hierarchy, manually set the clock for it here. + c.Clock = GameplayClockContainer.GameplayClock; + StoryboardContainer.Add(c); + }); else StoryboardContainer.Add(storyboard); } From 581ffb7fb09d50fc0b64bb422841f830c61d1ac7 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Mon, 8 Jul 2019 13:52:15 +0200 Subject: [PATCH 0128/2815] Adjusted slider border colour --- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index 1d68793c50..4918ea60b2 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -64,14 +64,13 @@ namespace osu.Game.Rulesets.Osu.Mods case DrawableSlider slider: ApplyTraceableState(slider.HeadCircle, state); slider.Body.AccentColour = Color4.Transparent; - + slider.Body.BorderColour = slider.HeadCircle.AccentColour; break; case DrawableSpinner spinner: spinner.Disc.Hide(); //spinner.Ticks.Hide(); // do they contribute to the theme? debatable spinner.Background.Hide(); - break; } } From 54f5e6aedf4ffcce98f62a830020f1252eb42d93 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Jul 2019 22:37:39 +0900 Subject: [PATCH 0129/2815] Add assertion and comment about lease logic --- osu.Game/Screens/Play/ReplayPlayerLoader.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Play/ReplayPlayerLoader.cs b/osu.Game/Screens/Play/ReplayPlayerLoader.cs index 8681ae6887..86179ef067 100644 --- a/osu.Game/Screens/Play/ReplayPlayerLoader.cs +++ b/osu.Game/Screens/Play/ReplayPlayerLoader.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.Allocation; using osu.Game.Scoring; @@ -13,6 +14,9 @@ namespace osu.Game.Screens.Play public ReplayPlayerLoader(Score score) : base(() => new ReplayPlayer(score)) { + if (score.Replay == null) + throw new ArgumentNullException(nameof(score.Replay), $"{nameof(score)} must have a non-null {nameof(score.Replay)}."); + scoreInfo = score.ScoreInfo; } @@ -20,6 +24,7 @@ namespace osu.Game.Screens.Play { var dependencies = base.CreateChildDependencies(parent); + // these will be reverted thanks to PlayerLoader's lease. Mods.Value = scoreInfo.Mods; Ruleset.Value = scoreInfo.Ruleset; From 338371c3fcec0e9c45ac7ec440e732e083eae972 Mon Sep 17 00:00:00 2001 From: Desconocidosmh Date: Tue, 9 Jul 2019 00:08:18 +0200 Subject: [PATCH 0130/2815] Fix music playing while exiting from editor --- osu.Game/OsuGame.cs | 6 +++--- osu.Game/Screens/Edit/Editor.cs | 6 ------ osu.Game/Screens/Menu/MainMenu.cs | 4 +++- osu.Game/Screens/OsuScreen.cs | 11 ++++++++++- osu.Game/Screens/Select/SongSelect.cs | 2 ++ 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 0a472d4dc1..325d2cbd85 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -306,7 +306,7 @@ namespace osu.Game private void currentTrackCompleted() { if (!Beatmap.Value.Track.Looping && !Beatmap.Disabled) - musicController.NextTrack(); + MusicController.NextTrack(); } #endregion @@ -484,7 +484,7 @@ namespace osu.Game Origin = Anchor.TopRight, }, rightFloatingOverlayContent.Add, true); - loadComponentSingleFile(musicController = new MusicController + loadComponentSingleFile(MusicController = new MusicController { GetToolbarHeight = () => ToolbarOffset, Anchor = Anchor.TopRight, @@ -752,7 +752,7 @@ namespace osu.Game private ScalingContainer screenContainer; - private MusicController musicController; + public MusicController MusicController { get; private set; } protected override bool OnExiting() { diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 89da9ae063..676e060433 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -239,12 +239,6 @@ namespace osu.Game.Screens.Edit { Background.FadeColour(Color4.White, 500); - if (Beatmap.Value.Track != null) - { - Beatmap.Value.Track.Tempo.Value = 1; - Beatmap.Value.Track.Start(); - } - return base.OnExiting(next); } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index c64bea840f..dcce49179d 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -120,7 +120,7 @@ namespace osu.Game.Screens.Menu var track = Beatmap.Value.Track; var metadata = Beatmap.Value.Metadata; - if (last is Intro && track != null) + if (last is Intro && track != null && !Game.MusicController.UserRequestedPause) { if (!track.IsRunning) { @@ -189,6 +189,8 @@ namespace osu.Game.Screens.Menu //we may have consumed our preloaded instance, so let's make another. preloadSongSelect(); + + ResumeIfNoUserPauseRequested(); } public override bool OnExiting(IScreen next) diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 328631ff9c..0682710133 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -50,7 +50,7 @@ namespace osu.Game.Screens public virtual bool CursorVisible => true; - protected new OsuGameBase Game => base.Game as OsuGameBase; + protected new OsuGame Game => base.Game as OsuGame; /// /// The to set the user's activity automatically to when this screen is entered @@ -179,6 +179,15 @@ namespace osu.Game.Screens api.Activity.Value = activity; } + protected void ResumeIfNoUserPauseRequested() + { + if (Beatmap.Value.Track != null && !Game.MusicController.UserRequestedPause) + { + Beatmap.Value.Track.Tempo.Value = 1; + Beatmap.Value.Track.Start(); + } + } + /// /// Fired when this screen was entered or resumed and the logo state is required to be adjusted. /// diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index bf5857f725..17b2ae376f 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -426,6 +426,8 @@ namespace osu.Game.Screens.Select { base.OnEntering(last); + ResumeIfNoUserPauseRequested(); + this.FadeInFromZero(250); FilterControl.Activate(); } From 8f7476e9ccf2f8dd3744315cb56a852d5ff4197a Mon Sep 17 00:00:00 2001 From: Oskar Solecki <31374466+Desconocidosmh@users.noreply.github.com> Date: Tue, 9 Jul 2019 00:26:57 +0200 Subject: [PATCH 0131/2815] Remove unused stuff --- osu.Game/Screens/Edit/Editor.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index cffe756548..676e060433 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -168,8 +168,6 @@ namespace osu.Game.Screens.Edit menuBar.Mode.ValueChanged += onModeChanged; - host.Exiting += onExitingGame; - bottomBackground.Colour = colours.Gray2; } @@ -237,8 +235,6 @@ namespace osu.Game.Screens.Edit Beatmap.Value.Track?.Stop(); } - private bool isExitingGame = false; - public override bool OnExiting(IScreen next) { Background.FadeColour(Color4.White, 500); @@ -279,7 +275,5 @@ namespace osu.Game.Screens.Edit else clock.SeekForward(!clock.IsRunning, amount); } - - private bool onExitingGame() => isExitingGame = true; } } From 5d81445454bd03c1018bce7c660e65fc931af915 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 9 Jul 2019 08:05:34 +0300 Subject: [PATCH 0132/2815] Move api request outside the scores container --- .../Visual/Online/TestSceneScoresContainer.cs | 243 ++++++++++-------- .../BeatmapSet/Scores/ScoresContainer.cs | 82 ++---- osu.Game/Overlays/BeatmapSetOverlay.cs | 30 ++- 3 files changed, 184 insertions(+), 171 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index e4e6acec06..730bf0d4e2 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -50,120 +50,123 @@ namespace osu.Game.Tests.Visual.Online } }; - var scores = new List + var allScores = new APILegacyScores { - new ScoreInfo + Scores = new List { - User = new User + new APILegacyScoreInfo { - Id = 6602580, - Username = @"waaiiru", - Country = new Country + User = new User { - FullName = @"Spain", - FlagName = @"ES", + Id = 6602580, + Username = @"waaiiru", + Country = new Country + { + FullName = @"Spain", + FlagName = @"ES", + }, }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModHidden(), - new OsuModFlashlight(), - new OsuModHardRock(), - }, - Rank = ScoreRank.XH, - PP = 200, - MaxCombo = 1234, - TotalScore = 1234567890, - Accuracy = 1, - }, - new ScoreInfo - { - User = new User - { - Id = 4608074, - Username = @"Skycries", - Country = new Country + Mods = new Mod[] { - FullName = @"Brazil", - FlagName = @"BR", + new OsuModDoubleTime(), + new OsuModHidden(), + new OsuModFlashlight(), + new OsuModHardRock(), }, + Rank = ScoreRank.XH, + PP = 200, + MaxCombo = 1234, + TotalScore = 1234567890, + Accuracy = 1, }, - Mods = new Mod[] + new APILegacyScoreInfo { - new OsuModDoubleTime(), - new OsuModHidden(), - new OsuModFlashlight(), - }, - Rank = ScoreRank.S, - PP = 190, - MaxCombo = 1234, - TotalScore = 1234789, - Accuracy = 0.9997, - }, - new ScoreInfo - { - User = new User - { - Id = 1014222, - Username = @"eLy", - Country = new Country + User = new User { - FullName = @"Japan", - FlagName = @"JP", + Id = 4608074, + Username = @"Skycries", + Country = new Country + { + FullName = @"Brazil", + FlagName = @"BR", + }, }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModHidden(), - }, - Rank = ScoreRank.B, - PP = 180, - MaxCombo = 1234, - TotalScore = 12345678, - Accuracy = 0.9854, - }, - new ScoreInfo - { - User = new User - { - Id = 1541390, - Username = @"Toukai", - Country = new Country + Mods = new Mod[] { - FullName = @"Canada", - FlagName = @"CA", + new OsuModDoubleTime(), + new OsuModHidden(), + new OsuModFlashlight(), }, + Rank = ScoreRank.S, + PP = 190, + MaxCombo = 1234, + TotalScore = 1234789, + Accuracy = 0.9997, }, - Mods = new Mod[] + new APILegacyScoreInfo { - new OsuModDoubleTime(), - }, - Rank = ScoreRank.C, - PP = 170, - MaxCombo = 1234, - TotalScore = 1234567, - Accuracy = 0.8765, - }, - new ScoreInfo - { - User = new User - { - Id = 7151382, - Username = @"Mayuri Hana", - Country = new Country + User = new User { - FullName = @"Thailand", - FlagName = @"TH", + Id = 1014222, + Username = @"eLy", + Country = new Country + { + FullName = @"Japan", + FlagName = @"JP", + }, }, + Mods = new Mod[] + { + new OsuModDoubleTime(), + new OsuModHidden(), + }, + Rank = ScoreRank.B, + PP = 180, + MaxCombo = 1234, + TotalScore = 12345678, + Accuracy = 0.9854, }, - Rank = ScoreRank.D, - PP = 160, - MaxCombo = 1234, - TotalScore = 123456, - Accuracy = 0.6543, - }, + new APILegacyScoreInfo + { + User = new User + { + Id = 1541390, + Username = @"Toukai", + Country = new Country + { + FullName = @"Canada", + FlagName = @"CA", + }, + }, + Mods = new Mod[] + { + new OsuModDoubleTime(), + }, + Rank = ScoreRank.C, + PP = 170, + MaxCombo = 1234, + TotalScore = 1234567, + Accuracy = 0.8765, + }, + new APILegacyScoreInfo + { + User = new User + { + Id = 7151382, + Username = @"Mayuri Hana", + Country = new Country + { + FullName = @"Thailand", + FlagName = @"TH", + }, + }, + Rank = ScoreRank.D, + PP = 160, + MaxCombo = 1234, + TotalScore = 123456, + Accuracy = 0.6543, + }, + } }; var myBestScore = new APILegacyUserTopScoreInfo @@ -189,7 +192,39 @@ namespace osu.Game.Tests.Visual.Online Position = 1337, }; - foreach (var s in scores) + var oneScore = new APILegacyScores + { + Scores = new List + { + new APILegacyScoreInfo + { + User = new User + { + Id = 6602580, + Username = @"waaiiru", + Country = new Country + { + FullName = @"Spain", + FlagName = @"ES", + }, + }, + Mods = new Mod[] + { + new OsuModDoubleTime(), + new OsuModHidden(), + new OsuModFlashlight(), + new OsuModHardRock(), + }, + Rank = ScoreRank.XH, + PP = 200, + MaxCombo = 1234, + TotalScore = 1234567890, + Accuracy = 1, + } + } + }; + + foreach (var s in allScores.Scores) { s.Statistics.Add(HitResult.Great, RNG.Next(2000)); s.Statistics.Add(HitResult.Good, RNG.Next(2000)); @@ -199,16 +234,16 @@ namespace osu.Game.Tests.Visual.Online AddStep("Load all scores", () => { - scoresContainer.Scores = scores; - scoresContainer.UserScore = myBestScore; + allScores.UserScore = null; + scoresContainer.Scores = allScores; }); - AddStep("Load null scores", () => + AddStep("Load null scores", () => scoresContainer.Scores = null); + AddStep("Load only one score", () => scoresContainer.Scores = oneScore); + AddStep("Load scores with my best", () => { - scoresContainer.Scores = null; - scoresContainer.UserScore = null; + allScores.UserScore = myBestScore; + scoresContainer.Scores = allScores; }); - AddStep("Load only one score", () => scoresContainer.Scores = new[] { scores.First() }); - AddStep("Add my best score", () => scoresContainer.UserScore = myBestScore); } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 30685fb826..7e0f3d0b1c 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -5,15 +5,11 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests; using osuTK; using System.Collections.Generic; using System.Linq; -using osu.Game.Scoring; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.BeatmapSet.Scores @@ -29,9 +25,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly FillFlowContainer topScoresContainer; private readonly LoadingAnimation loadingAnimation; - [Resolved] - private IAPIProvider api { get; set; } - public ScoresContainer() { RelativeSizeAxes = Axes.X; @@ -72,7 +65,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores loadingAnimation = new LoadingAnimation { Alpha = 0, - Margin = new MarginPadding(20) + Margin = new MarginPadding(20), }, }; } @@ -84,87 +77,46 @@ namespace osu.Game.Overlays.BeatmapSet.Scores updateDisplay(); } - private bool loading + public bool Loading { - set => loadingAnimation.FadeTo(value ? 1 : 0, fade_duration); + set + { + loadingAnimation.FadeTo(value ? 1 : 0, fade_duration); + + if (value) + Scores = null; + } } - private GetScoresRequest getScoresRequest; - private IReadOnlyList scores; + private APILegacyScores scores; - public IReadOnlyList Scores + public APILegacyScores Scores { get => scores; set { - getScoresRequest?.Cancel(); scores = value; updateDisplay(); } } - private APILegacyUserTopScoreInfo userScore; - - public APILegacyUserTopScoreInfo UserScore - { - get => userScore; - set - { - getScoresRequest?.Cancel(); - userScore = value; - - updateDisplay(); - } - } - - private BeatmapInfo beatmap; - - public BeatmapInfo Beatmap - { - get => beatmap; - set - { - beatmap = value; - - Scores = null; - - if (beatmap?.OnlineBeatmapID.HasValue != true) - return; - - loading = true; - - getScoresRequest = new GetScoresRequest(beatmap, beatmap.Ruleset); - getScoresRequest.Success += r => Schedule(() => - { - scores = r.Scores; - userScore = r.UserScore; - updateDisplay(); - }); - api.Queue(getScoresRequest); - } - } - private void updateDisplay() { - loading = false; topScoresContainer.Clear(); - scoreTable.Scores = scores?.Count > 1 ? scores : new List(); - scoreTable.FadeTo(scores?.Count > 1 ? 1 : 0); + scoreTable.Scores = scores?.Scores.Count > 1 ? scores.Scores : new List(); + scoreTable.FadeTo(scores?.Scores.Count > 1 ? 1 : 0); - if (scores?.Any() == true) + if (scores?.Scores.Any() == true) { - topScoresContainer.Add(new DrawableTopScore(scores.FirstOrDefault())); + topScoresContainer.Add(new DrawableTopScore(scores.Scores.FirstOrDefault())); + + var userScore = scores.UserScore; if (userScore != null && userScore.Position != 1) topScoresContainer.Add(new DrawableTopScore(userScore.Score, userScore.Position)); } } - - protected override void Dispose(bool isDisposing) - { - getScoresRequest?.Cancel(); - } } } diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 19f6a3f692..df132f9d47 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -11,6 +11,7 @@ using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet.Scores; @@ -30,9 +31,14 @@ namespace osu.Game.Overlays protected readonly Header Header; private RulesetStore rulesets; + private ScoresContainer scores; + private GetScoresRequest getScoresRequest; private readonly Bindable beatmapSet = new Bindable(); + [Resolved] + private IAPIProvider api { get; set; } + // receive input outside our bounds so we can trigger a close event on ourselves. public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; @@ -40,7 +46,6 @@ namespace osu.Game.Overlays { OsuScrollContainer scroll; Info info; - ScoresContainer scores; Children = new Drawable[] { @@ -74,12 +79,33 @@ namespace osu.Game.Overlays Header.Picker.Beatmap.ValueChanged += b => { info.Beatmap = b.NewValue; - scores.Beatmap = b.NewValue; + getScores(b.NewValue); scroll.ScrollToStart(); }; } + private void getScores(BeatmapInfo b) + { + getScoresRequest?.Cancel(); + + if (b?.OnlineBeatmapID.HasValue != true) + { + scores.Scores = null; + return; + } + + scores.Loading = true; + + getScoresRequest = new GetScoresRequest(b, b.Ruleset); + getScoresRequest.Success += r => Schedule(() => + { + scores.Scores = r; + scores.Loading = false; + }); + api.Queue(getScoresRequest); + } + [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { From 8d46d4a28e6fc0a6ba3c72df86faac97c4439dfd Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 9 Jul 2019 08:09:31 +0300 Subject: [PATCH 0133/2815] Fix grade layout --- .../BeatmapSet/Scores/TopScoreUserSection.cs | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs index 385d8ff38c..056fe71a39 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs @@ -39,19 +39,30 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Spacing = new Vector2(10, 0), Children = new Drawable[] { - rankText = new OsuSpriteText + new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = $"#{position.ToString()}", - Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold, italics: true) - }, - rank = new UpdateableRank(ScoreRank.D) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(40), - FillMode = FillMode.Fit, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 3), + Children = new Drawable[] + { + rankText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = $"#{position.ToString()}", + Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold, italics: true) + }, + rank = new UpdateableRank(ScoreRank.D) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(40), + FillMode = FillMode.Fit, + }, + } }, avatar = new UpdateableAvatar(hideImmediately: true) { From eb4ef8f6ac8a2c32cb53f4f1cc006e687e7252dc Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 9 Jul 2019 08:25:10 +0300 Subject: [PATCH 0134/2815] CI fixes --- osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs | 1 - osu.Game/Overlays/BeatmapSetOverlay.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 730bf0d4e2..827a300a5e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index df132f9d47..44475dc53c 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -31,7 +31,7 @@ namespace osu.Game.Overlays protected readonly Header Header; private RulesetStore rulesets; - private ScoresContainer scores; + private readonly ScoresContainer scores; private GetScoresRequest getScoresRequest; private readonly Bindable beatmapSet = new Bindable(); From 89cb8a0cacb4ac50a1b83cbf32923d40ba5d13d7 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 9 Jul 2019 16:23:59 +0900 Subject: [PATCH 0135/2815] Move storyboard initialization to new StoryboardContainer --- .../TestSceneBackgroundScreenBeatmap.cs | 26 ++++---- .../Containers/StoryboardContainer.cs | 59 +++++++++++++++++++ .../Graphics/Containers/UserDimContainer.cs | 52 ++++++++-------- osu.Game/Screens/Play/Player.cs | 42 ++----------- 4 files changed, 103 insertions(+), 76 deletions(-) create mode 100644 osu.Game/Graphics/Containers/StoryboardContainer.cs diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs index 8b941e4633..0d62bf9bdc 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs @@ -28,6 +28,7 @@ using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Play; using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Select; +using osu.Game.Storyboards; using osu.Game.Tests.Resources; using osu.Game.Users; using osuTK; @@ -333,9 +334,9 @@ namespace osu.Game.Tests.Visual.Background { protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value); - protected override UserDimContainer CreateStoryboardContainer() + protected override StoryboardContainer CreateStoryboardContainer(Storyboard storyboard) { - return new TestUserDimContainer(true) + return new TestStoryboardContainer { RelativeSizeAxes = Axes.Both, Alpha = 1, @@ -343,7 +344,7 @@ namespace osu.Game.Tests.Visual.Background }; } - public UserDimContainer CurrentStoryboardContainer => StoryboardContainer; + public TestStoryboardContainer CurrentStoryboardContainer => (TestStoryboardContainer)StoryboardContainer; // Whether or not the player should be allowed to load. public bool BlockLoad; @@ -357,9 +358,9 @@ namespace osu.Game.Tests.Visual.Background { } - public bool IsStoryboardVisible() => ((TestUserDimContainer)CurrentStoryboardContainer).CurrentAlpha == 1; + public bool IsStoryboardVisible() => CurrentStoryboardContainer.CurrentAlpha == 1; - public bool IsStoryboardInvisible() => ((TestUserDimContainer)CurrentStoryboardContainer).CurrentAlpha <= 1; + public bool IsStoryboardInvisible() => CurrentStoryboardContainer.CurrentAlpha <= 1; [BackgroundDependencyLoader] private void load(OsuConfigManager config, CancellationToken token) @@ -408,15 +409,20 @@ namespace osu.Game.Tests.Visual.Background } } + private class TestStoryboardContainer : StoryboardContainer + { + public float CurrentAlpha => DimContainer.Alpha; + + public TestStoryboardContainer() + : base(new Storyboard()) + { + } + } + private class TestUserDimContainer : UserDimContainer { public Color4 CurrentColour => DimContainer.Colour; public float CurrentAlpha => DimContainer.Alpha; - - public TestUserDimContainer(bool isStoryboard = false) - : base(isStoryboard) - { - } } } } diff --git a/osu.Game/Graphics/Containers/StoryboardContainer.cs b/osu.Game/Graphics/Containers/StoryboardContainer.cs new file mode 100644 index 0000000000..c425d38d48 --- /dev/null +++ b/osu.Game/Graphics/Containers/StoryboardContainer.cs @@ -0,0 +1,59 @@ +// 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.Game.Storyboards; +using osu.Game.Storyboards.Drawables; + +namespace osu.Game.Graphics.Containers +{ + /// + /// A container that handles loading, as well as applies user-specified visual settings to it. + /// + public class StoryboardContainer : UserDimContainer + { + private readonly Storyboard storyboard; + private DrawableStoryboard drawableStoryboard; + + public StoryboardContainer(Storyboard storyboard) + { + this.storyboard = storyboard; + } + + [BackgroundDependencyLoader] + private void load() + { + initializeStoryboard(false); + } + + protected override void LoadComplete() + { + ShowStoryboard.ValueChanged += _ => initializeStoryboard(true); + base.LoadComplete(); + } + + protected override void ApplyFade() + { + // Storyboards cannot be blurred, so we should just hide the storyboard if it gets toggled. + DimContainer.FadeTo(!ShowStoryboard.Value || UserDimLevel.Value == 1 ? 0 : 1, BACKGROUND_FADE_DURATION, Easing.OutQuint); + } + + private void initializeStoryboard(bool async) + { + if (drawableStoryboard != null) + return; + + if (!ShowStoryboard.Value) + return; + + drawableStoryboard = storyboard.CreateDrawable(); + drawableStoryboard.Masking = true; + + if (async) + LoadComponentAsync(drawableStoryboard, Add); + else + Add(drawableStoryboard); + } + } +} diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index fe9eb7baf4..b14051f432 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -16,11 +16,10 @@ namespace osu.Game.Graphics.Containers { /// /// A container that applies user-configured visual settings to its contents. - /// This container specifies behavior that applies to both Storyboards and Backgrounds. /// public class UserDimContainer : Container { - private const float background_fade_duration = 800; + protected const float BACKGROUND_FADE_DURATION = 800; /// /// Whether or not user-configured dim levels should be applied to the container. @@ -40,17 +39,15 @@ namespace osu.Game.Graphics.Containers /// public readonly Bindable BlurAmount = new Bindable(); - private Bindable userDimLevel { get; set; } + protected Bindable UserDimLevel { get; private set; } - private Bindable userBlurLevel { get; set; } - - private Bindable showStoryboard { get; set; } + protected Bindable ShowStoryboard { get; private set; } protected Container DimContainer { get; } protected override Container Content => DimContainer; - private readonly bool isStoryboard; + private Bindable userBlurLevel { get; set; } /// /// As an optimisation, we add the two blur portions to be applied rather than actually applying two separate blurs. @@ -62,15 +59,12 @@ namespace osu.Game.Graphics.Containers /// /// Creates a new . /// - /// Whether or not this instance contains a storyboard. /// - /// While both backgrounds and storyboards allow user dim levels to be applied, storyboards can be toggled via + /// While both backgrounds and storyboards allow user dim levels to be applied, storyboards can be toggled via /// and can cause backgrounds to become hidden via . Storyboards are also currently unable to be blurred. /// - /// - public UserDimContainer(bool isStoryboard = false) + public UserDimContainer() { - this.isStoryboard = isStoryboard; AddInternal(DimContainer = new Container { RelativeSizeAxes = Axes.Both }); } @@ -97,16 +91,16 @@ namespace osu.Game.Graphics.Containers [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - userDimLevel = config.GetBindable(OsuSetting.DimLevel); + UserDimLevel = config.GetBindable(OsuSetting.DimLevel); userBlurLevel = config.GetBindable(OsuSetting.BlurLevel); - showStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); + ShowStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); EnableUserDim.ValueChanged += _ => updateVisuals(); - userDimLevel.ValueChanged += _ => updateVisuals(); - userBlurLevel.ValueChanged += _ => updateVisuals(); - showStoryboard.ValueChanged += _ => updateVisuals(); + UserDimLevel.ValueChanged += _ => updateVisuals(); + ShowStoryboard.ValueChanged += _ => updateVisuals(); StoryboardReplacesBackground.ValueChanged += _ => updateVisuals(); BlurAmount.ValueChanged += _ => updateVisuals(); + userBlurLevel.ValueChanged += _ => updateVisuals(); } protected override void LoadComplete() @@ -115,21 +109,21 @@ namespace osu.Game.Graphics.Containers updateVisuals(); } + /// + /// Apply non-dim related settings to the background, such as hiding and blurring. + /// + protected virtual void ApplyFade() + { + // The background needs to be hidden in the case of it being replaced by the storyboard + DimContainer.FadeTo(ShowStoryboard.Value && StoryboardReplacesBackground.Value ? 0 : 1, BACKGROUND_FADE_DURATION, Easing.OutQuint); + Background?.BlurTo(blurTarget, BACKGROUND_FADE_DURATION, Easing.OutQuint); + } + private void updateVisuals() { - if (isStoryboard) - { - DimContainer.FadeTo(!showStoryboard.Value || userDimLevel.Value == 1 ? 0 : 1, background_fade_duration, Easing.OutQuint); - } - else - { - // The background needs to be hidden in the case of it being replaced by the storyboard - DimContainer.FadeTo(showStoryboard.Value && StoryboardReplacesBackground.Value ? 0 : 1, background_fade_duration, Easing.OutQuint); + ApplyFade(); - Background?.BlurTo(blurTarget, background_fade_duration, Easing.OutQuint); - } - - DimContainer.FadeColour(EnableUserDim.Value ? OsuColour.Gray(1 - (float)userDimLevel.Value) : Color4.White, background_fade_duration, Easing.OutQuint); + DimContainer.FadeColour(EnableUserDim.Value ? OsuColour.Gray(1 - (float)UserDimLevel.Value) : Color4.White, BACKGROUND_FADE_DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ea614e7658..621df0b562 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -26,7 +26,7 @@ using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Skinning; -using osu.Game.Storyboards.Drawables; +using osu.Game.Storyboards; using osu.Game.Users; namespace osu.Game.Screens.Play @@ -109,7 +109,7 @@ namespace osu.Game.Screens.Play sampleRestart = audio.Samples.Get(@"Gameplay/restart"); mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); - showStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); + config.GetBindable(OsuSetting.ShowStoryboard); ScoreProcessor = DrawableRuleset.CreateScoreProcessor(); ScoreProcessor.Mods.BindTo(Mods); @@ -121,7 +121,7 @@ namespace osu.Game.Screens.Play GameplayClockContainer.Children = new[] { - StoryboardContainer = CreateStoryboardContainer(), + StoryboardContainer = CreateStoryboardContainer(Beatmap.Value.Storyboard), new ScalingContainer(ScalingMode.Gameplay) { Child = new LocalSkinOverrideContainer(working.Skin) @@ -199,9 +199,6 @@ namespace osu.Game.Screens.Play // bind clock into components that require it DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); - // load storyboard as part of player's load if we can - initializeStoryboard(false); - // Bind ScoreProcessor to ourselves ScoreProcessor.AllJudged += onCompletion; ScoreProcessor.Failed += onFail; @@ -336,42 +333,15 @@ namespace osu.Game.Screens.Play #region Storyboard - private DrawableStoryboard storyboard; - protected UserDimContainer StoryboardContainer { get; private set; } + protected StoryboardContainer StoryboardContainer { get; private set; } - protected virtual UserDimContainer CreateStoryboardContainer() => new UserDimContainer(true) + protected virtual StoryboardContainer CreateStoryboardContainer(Storyboard storyboard) => new StoryboardContainer(storyboard) { RelativeSizeAxes = Axes.Both, Alpha = 1, EnableUserDim = { Value = true } }; - private Bindable showStoryboard; - - private void initializeStoryboard(bool asyncLoad) - { - if (StoryboardContainer == null || storyboard != null) - return; - - if (!showStoryboard.Value) - return; - - var beatmap = Beatmap.Value; - - storyboard = beatmap.Storyboard.CreateDrawable(); - storyboard.Masking = true; - - if (asyncLoad) - LoadComponentAsync(storyboard, c => - { - // Since the storyboard was loaded before it can be added to the draw hierarchy, manually set the clock for it here. - c.Clock = GameplayClockContainer.GameplayClock; - StoryboardContainer.Add(c); - }); - else - StoryboardContainer.Add(storyboard); - } - #endregion #region Fail Logic @@ -491,8 +461,6 @@ namespace osu.Game.Screens.Play .Delay(250) .FadeIn(250); - showStoryboard.ValueChanged += _ => initializeStoryboard(true); - Background.EnableUserDim.Value = true; Background.BlurAmount.Value = 0; From 1b5fadf93f468ea0c6894cba50e80f03797d48f0 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 9 Jul 2019 16:38:12 +0900 Subject: [PATCH 0136/2815] move comment to more relevant location --- osu.Game/Graphics/Containers/UserDimContainer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index b14051f432..89c1821425 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -59,10 +59,6 @@ namespace osu.Game.Graphics.Containers /// /// Creates a new . /// - /// - /// While both backgrounds and storyboards allow user dim levels to be applied, storyboards can be toggled via - /// and can cause backgrounds to become hidden via . Storyboards are also currently unable to be blurred. - /// public UserDimContainer() { AddInternal(DimContainer = new Container { RelativeSizeAxes = Axes.Both }); @@ -112,6 +108,10 @@ namespace osu.Game.Graphics.Containers /// /// Apply non-dim related settings to the background, such as hiding and blurring. /// + /// + /// While both backgrounds and storyboards allow user dim levels to be applied, storyboards can be toggled via + /// and can cause backgrounds to become hidden via . Storyboards are also currently unable to be blurred. + /// protected virtual void ApplyFade() { // The background needs to be hidden in the case of it being replaced by the storyboard From 5bb21ecae0aa027c80ef2108ac3a1991495ed6f3 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 9 Jul 2019 16:50:37 +0900 Subject: [PATCH 0137/2815] remove storyboard region --- .../Containers/StoryboardContainer.cs | 2 +- .../Graphics/Containers/UserDimContainer.cs | 2 +- osu.Game/Screens/Play/Player.cs | 22 ++++++++----------- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/osu.Game/Graphics/Containers/StoryboardContainer.cs b/osu.Game/Graphics/Containers/StoryboardContainer.cs index c425d38d48..472e22e212 100644 --- a/osu.Game/Graphics/Containers/StoryboardContainer.cs +++ b/osu.Game/Graphics/Containers/StoryboardContainer.cs @@ -35,7 +35,7 @@ namespace osu.Game.Graphics.Containers protected override void ApplyFade() { - // Storyboards cannot be blurred, so we should just hide the storyboard if it gets toggled. + // Storyboards cannot be blurred, so just hide the storyboard if it gets toggled. DimContainer.FadeTo(!ShowStoryboard.Value || UserDimLevel.Value == 1 ? 0 : 1, BACKGROUND_FADE_DURATION, Easing.OutQuint); } diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 89c1821425..ad6f73eff5 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -106,7 +106,7 @@ namespace osu.Game.Graphics.Containers } /// - /// Apply non-dim related settings to the background, such as hiding and blurring. + /// Apply non-dim related settings to the content, such as hiding and blurring. /// /// /// While both backgrounds and storyboards allow user dim levels to be applied, storyboards can be toggled via diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 621df0b562..d9e050a681 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -76,6 +76,15 @@ namespace osu.Game.Screens.Play protected GameplayClockContainer GameplayClockContainer { get; private set; } + protected StoryboardContainer StoryboardContainer { get; private set; } + + protected virtual StoryboardContainer CreateStoryboardContainer(Storyboard storyboard) => new StoryboardContainer(storyboard) + { + RelativeSizeAxes = Axes.Both, + Alpha = 1, + EnableUserDim = { Value = true } + }; + [Cached] [Cached(Type = typeof(IBindable>))] protected new readonly Bindable> Mods = new Bindable>(Array.Empty()); @@ -331,19 +340,6 @@ namespace osu.Game.Screens.Play protected virtual Results CreateResults(ScoreInfo score) => new SoloResults(score); - #region Storyboard - - protected StoryboardContainer StoryboardContainer { get; private set; } - - protected virtual StoryboardContainer CreateStoryboardContainer(Storyboard storyboard) => new StoryboardContainer(storyboard) - { - RelativeSizeAxes = Axes.Both, - Alpha = 1, - EnableUserDim = { Value = true } - }; - - #endregion - #region Fail Logic protected FailOverlay FailOverlay { get; private set; } From 8d6af1625abda2a84879c508419e116c57448e75 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 9 Jul 2019 11:40:51 +0300 Subject: [PATCH 0138/2815] Visibility improvements --- .../Visual/Online/TestSceneScoresContainer.cs | 1 + .../BeatmapSet/Scores/ScoresContainer.cs | 142 ++++++++++++++---- osu.Game/Overlays/BeatmapSetOverlay.cs | 6 +- 3 files changed, 118 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 827a300a5e..c414b6b940 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -243,6 +243,7 @@ namespace osu.Game.Tests.Visual.Online allScores.UserScore = myBestScore; scoresContainer.Scores = allScores; }); + AddStep("Trigger loading", () => scoresContainer.Loading = !scoresContainer.Loading); } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 7e0f3d0b1c..eacbe6200a 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -17,13 +17,14 @@ namespace osu.Game.Overlays.BeatmapSet.Scores public class ScoresContainer : CompositeDrawable { private const int spacing = 15; - private const int fade_duration = 200; + private const int padding = 20; private readonly Box background; private readonly ScoreTable scoreTable; private readonly FillFlowContainer topScoresContainer; - private readonly LoadingAnimation loadingAnimation; + private readonly ContentContainer resizableContainer; + private readonly LoadingContainer loadingContainer; public ScoresContainer() { @@ -38,53 +39,85 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }, new FillFlowContainer { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Width = 0.95f, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, spacing), - Margin = new MarginPadding { Vertical = spacing }, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, Children = new Drawable[] { - topScoresContainer = new FillFlowContainer + loadingContainer = new LoadingContainer { RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), + Masking = true, }, - scoreTable = new ScoreTable + resizableContainer = new ContentContainer { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - } + RelativeSizeAxes = Axes.X, + Masking = true, + Children = new Drawable[] + { + new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.95f, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, spacing), + Padding = new MarginPadding { Vertical = padding }, + Children = new Drawable[] + { + topScoresContainer = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + }, + scoreTable = new ScoreTable + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + } + } + }, + } + }, } - }, - loadingAnimation = new LoadingAnimation - { - Alpha = 0, - Margin = new MarginPadding(20), - }, + } }; + + Loading = true; } [BackgroundDependencyLoader] private void load(OsuColour colours) { background.Colour = colours.Gray2; - updateDisplay(); } + private bool loading; + public bool Loading { + get => loading; set { - loadingAnimation.FadeTo(value ? 1 : 0, fade_duration); + if (loading == value) + return; + + loading = value; if (value) - Scores = null; + { + loadingContainer.Show(); + resizableContainer.Hide(); + } + else + { + loadingContainer.Hide(); + resizableContainer.Show(); + } } } @@ -117,6 +150,63 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (userScore != null && userScore.Position != 1) topScoresContainer.Add(new DrawableTopScore(userScore.Score, userScore.Position)); } + + Loading = false; + } + + private class ContentContainer : VisibilityContainer + { + private const int duration = 300; + + private float maxHeight; + + protected override void PopIn() => this.ResizeHeightTo(maxHeight, duration, Easing.OutQuint); + + protected override void PopOut() => this.ResizeHeightTo(0, duration, Easing.OutQuint); + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + if (State.Value == Visibility.Hidden) + return; + + float height = 0; + + foreach (var c in Children) + { + height += c.Height; + } + + maxHeight = height; + + this.ResizeHeightTo(maxHeight, duration, Easing.OutQuint); + } + } + + private class LoadingContainer : VisibilityContainer + { + private const int duration = 300; + private const int height = 50; + + private readonly LoadingAnimation loadingAnimation; + + public LoadingContainer() + { + Child = loadingAnimation = new LoadingAnimation(); + } + + protected override void PopIn() + { + this.ResizeHeightTo(height, duration, Easing.OutQuint); + loadingAnimation.Show(); + } + + protected override void PopOut() + { + this.ResizeHeightTo(0, duration, Easing.OutQuint); + loadingAnimation.Hide(); + } } } } diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 44475dc53c..1c408ead54 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -98,11 +98,7 @@ namespace osu.Game.Overlays scores.Loading = true; getScoresRequest = new GetScoresRequest(b, b.Ruleset); - getScoresRequest.Success += r => Schedule(() => - { - scores.Scores = r; - scores.Loading = false; - }); + getScoresRequest.Success += r => Schedule(() => scores.Scores = r); api.Queue(getScoresRequest); } From 0580c322639e62dc155ed969deb7c7f1a97fda94 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jul 2019 17:59:40 +0900 Subject: [PATCH 0139/2815] Abstract intro screen logic to base class --- osu.Desktop/OsuGameDesktop.cs | 2 +- osu.Game.Tests/Visual/Menus/TestSceneIntro.cs | 7 +- osu.Game/OsuGame.cs | 7 +- .../Menu/{Intro.cs => IntroCircles.cs} | 94 ++------------- osu.Game/Screens/Menu/IntroScreen.cs | 114 ++++++++++++++++++ osu.Game/Screens/Menu/MainMenu.cs | 2 +- 6 files changed, 136 insertions(+), 90 deletions(-) rename osu.Game/Screens/Menu/{Intro.cs => IntroCircles.cs} (52%) create mode 100644 osu.Game/Screens/Menu/IntroScreen.cs diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 975b7f9f5a..18f0cd1f80 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -71,7 +71,7 @@ namespace osu.Desktop switch (newScreen) { - case Intro _: + case IntroScreen _: case MainMenu _: versionManager?.Show(); break; diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntro.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntro.cs index 15b5c2338e..8b993f618f 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntro.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntro.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Timing; using osu.Game.Screens; using osu.Game.Screens.Menu; +using osuTK; using osuTK.Graphics; namespace osu.Game.Tests.Visual.Menus @@ -45,13 +46,15 @@ namespace osu.Game.Tests.Visual.Menus RelativeSizeAxes = Axes.Both, Colour = Color4.Black, }, - new OsuScreenStack(new Intro()) + new OsuScreenStack(new IntroCircles()) { RelativeSizeAxes = Axes.Both, }, logo = new OsuLogo { - Anchor = Anchor.Centre, + Alpha = 0, + RelativePositionAxes = Axes.Both, + Position = new Vector2(0.5f), }, } }); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 361ff62155..1ca4527786 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -86,7 +86,8 @@ namespace osu.Game private BackButton backButton; private MainMenu menuScreen; - private Intro introScreen; + + private IntroScreen introScreen; private Bindable configRuleset; @@ -760,7 +761,7 @@ namespace osu.Game if (introScreen == null) return true; - if (!introScreen.DidLoadMenu || !(screenStack.CurrentScreen is Intro)) + if (!introScreen.DidLoadMenu || !(screenStack.CurrentScreen is IntroScreen)) { Scheduler.Add(introScreen.MakeCurrent); return true; @@ -795,7 +796,7 @@ namespace osu.Game { switch (newScreen) { - case Intro intro: + case IntroScreen intro: introScreen = intro; break; diff --git a/osu.Game/Screens/Menu/Intro.cs b/osu.Game/Screens/Menu/IntroCircles.cs similarity index 52% rename from osu.Game/Screens/Menu/Intro.cs rename to osu.Game/Screens/Menu/IntroCircles.cs index 3fb16eaf90..4fa1a81123 100644 --- a/osu.Game/Screens/Menu/Intro.cs +++ b/osu.Game/Screens/Menu/IntroCircles.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; using osu.Framework.Bindables; @@ -12,41 +11,24 @@ using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.IO.Archives; -using osu.Game.Screens.Backgrounds; -using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.Menu { - public class Intro : StartupScreen + public class IntroCircles : IntroScreen { private const string menu_music_beatmap_hash = "3c8b1fcc9434dbb29e2fb613d3b9eada9d7bb6c125ceb32396c3b53437280c83"; - /// - /// Whether we have loaded the menu previously. - /// - public bool DidLoadMenu; - - private MainMenu mainMenu; private SampleChannel welcome; - private SampleChannel seeya; - protected override BackgroundScreen CreateBackground() => new BackgroundScreenBlack(); - - private readonly BindableDouble exitingVolumeFade = new BindableDouble(1); - - [Resolved] - private AudioManager audio { get; set; } - - private Bindable menuVoice; private Bindable menuMusic; + private Track track; + private WorkingBeatmap introBeatmap; [BackgroundDependencyLoader] - private void load(OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game) + private void load(OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game, ISampleStore samples) { - menuVoice = config.GetBindable(OsuSetting.MenuVoice); menuMusic = config.GetBindable(OsuSetting.MenuMusic); BeatmapSetInfo setInfo = null; @@ -75,15 +57,13 @@ namespace osu.Game.Screens.Menu introBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); track = introBeatmap.Track; - welcome = audio.Samples.Get(@"welcome"); - seeya = audio.Samples.Get(@"seeya"); + if (config.Get(OsuSetting.MenuVoice)) + welcome = samples.Get(@"welcome"); } private const double delay_step_one = 2300; private const double delay_step_two = 600; - public const int EXIT_DELAY = 3000; - protected override void LogoArriving(OsuLogo logo, bool resuming) { base.LogoArriving(logo, resuming); @@ -93,8 +73,7 @@ namespace osu.Game.Screens.Menu Beatmap.Value = introBeatmap; introBeatmap = null; - if (menuVoice.Value) - welcome.Play(); + welcome?.Play(); Scheduler.AddDelayed(delegate { @@ -105,74 +84,23 @@ namespace osu.Game.Screens.Menu track = null; } - LoadComponentAsync(mainMenu = new MainMenu()); + PrepareMenuLoad(); - Scheduler.AddDelayed(delegate - { - DidLoadMenu = true; - this.Push(mainMenu); - }, delay_step_one); + Scheduler.AddDelayed(LoadMenu, delay_step_one); }, delay_step_two); - } - logo.Colour = Color4.White; - logo.Ripple = false; - - const int quick_appear = 350; - - int initialMovementTime = logo.Alpha > 0.2f ? quick_appear : 0; - - logo.MoveTo(new Vector2(0.5f), initialMovementTime, Easing.OutQuint); - - if (!resuming) - { logo.ScaleTo(1); logo.FadeIn(); logo.PlayIntro(); } - else - { - logo.Triangles = false; - - logo - .ScaleTo(1, initialMovementTime, Easing.OutQuint) - .FadeIn(quick_appear, Easing.OutQuint) - .Then() - .RotateTo(20, EXIT_DELAY * 1.5f) - .FadeOut(EXIT_DELAY); - } } public override void OnSuspending(IScreen next) { + track = null; + this.FadeOut(300); base.OnSuspending(next); } - - public override bool OnExiting(IScreen next) - { - //cancel exiting if we haven't loaded the menu yet. - return !DidLoadMenu; - } - - public override void OnResuming(IScreen last) - { - this.FadeIn(300); - - double fadeOutTime = EXIT_DELAY; - //we also handle the exit transition. - if (menuVoice.Value) - seeya.Play(); - else - fadeOutTime = 500; - - audio.AddAdjustment(AdjustableProperty.Volume, exitingVolumeFade); - this.TransformBindableTo(exitingVolumeFade, 0, fadeOutTime).OnComplete(_ => this.Exit()); - - //don't want to fade out completely else we will stop running updates. - Game.FadeTo(0.01f, fadeOutTime); - - base.OnResuming(last); - } } } diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs new file mode 100644 index 0000000000..27f3c9a45b --- /dev/null +++ b/osu.Game/Screens/Menu/IntroScreen.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.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Screens; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Screens.Backgrounds; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Menu +{ + public abstract class IntroScreen : StartupScreen + { + private readonly BindableDouble exitingVolumeFade = new BindableDouble(1); + + public const int EXIT_DELAY = 3000; + + [Resolved] + private AudioManager audio { get; set; } + + private SampleChannel seeya; + + private Bindable menuVoice; + + protected override BackgroundScreen CreateBackground() => new BackgroundScreenBlack(); + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game) + { + menuVoice = config.GetBindable(OsuSetting.MenuVoice); + seeya = audio.Samples.Get(@"seeya"); + } + + /// + /// Whether we have loaded the menu previously. + /// + public bool DidLoadMenu { get; private set; } + + public override bool OnExiting(IScreen next) + { + //cancel exiting if we haven't loaded the menu yet. + return !DidLoadMenu; + } + + public override void OnResuming(IScreen last) + { + this.FadeIn(300); + + double fadeOutTime = EXIT_DELAY; + //we also handle the exit transition. + if (menuVoice.Value) + seeya.Play(); + else + fadeOutTime = 500; + + audio.AddAdjustment(AdjustableProperty.Volume, exitingVolumeFade); + this.TransformBindableTo(exitingVolumeFade, 0, fadeOutTime).OnComplete(_ => this.Exit()); + + //don't want to fade out completely else we will stop running updates. + Game.FadeTo(0.01f, fadeOutTime); + + base.OnResuming(last); + } + + protected override void LogoArriving(OsuLogo logo, bool resuming) + { + base.LogoArriving(logo, resuming); + + logo.Colour = Color4.White; + logo.Triangles = false; + logo.Ripple = false; + + if (!resuming) + { + logo.MoveTo(new Vector2(0.5f)); + logo.ScaleTo(Vector2.One); + logo.Hide(); + } + else + { + const int quick_appear = 350; + int initialMovementTime = logo.Alpha > 0.2f ? quick_appear : 0; + + logo.MoveTo(new Vector2(0.5f), initialMovementTime, Easing.OutQuint); + + logo + .ScaleTo(1, initialMovementTime, Easing.OutQuint) + .FadeIn(quick_appear, Easing.OutQuint) + .Then() + .RotateTo(20, EXIT_DELAY * 1.5f) + .FadeOut(EXIT_DELAY); + } + } + + private MainMenu mainMenu; + + protected void PrepareMenuLoad() + { + LoadComponentAsync(mainMenu = new MainMenu()); + } + + protected void LoadMenu() + { + DidLoadMenu = true; + this.Push(mainMenu); + } + } +} diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index c64bea840f..f73de6f730 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -120,7 +120,7 @@ namespace osu.Game.Screens.Menu var track = Beatmap.Value.Track; var metadata = Beatmap.Value.Metadata; - if (last is Intro && track != null) + if (last is IntroScreen && track != null) { if (!track.IsRunning) { From e835cd0f6f498cd3db205c31d16f6a1ba0343924 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jul 2019 18:06:49 +0900 Subject: [PATCH 0140/2815] Improve information flow to Disclaimer --- osu.Game/Screens/Loader.cs | 10 +++++++++- osu.Game/Screens/Menu/Disclaimer.cs | 11 +++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index 8add730c4e..de00ba2e9f 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -45,7 +45,15 @@ namespace osu.Game.Screens private OsuScreen loadableScreen; private ShaderPrecompiler precompiler; - protected virtual OsuScreen CreateLoadableScreen() => showDisclaimer ? (OsuScreen)new Disclaimer() : new Intro(); + protected virtual OsuScreen CreateLoadableScreen() + { + if (showDisclaimer) + return new Disclaimer(getIntroSequence()); + + return getIntroSequence(); + } + + private IntroScreen getIntroSequence() => new IntroCircles(); protected virtual ShaderPrecompiler CreateShaderPrecompiler() => new ShaderPrecompiler(); diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 97231a1331..073ab639e3 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -21,7 +21,6 @@ namespace osu.Game.Screens.Menu { public class Disclaimer : StartupScreen { - private Intro intro; private SpriteIcon icon; private Color4 iconColour; private LinkFlowContainer textFlow; @@ -32,10 +31,13 @@ namespace osu.Game.Screens.Menu private const float icon_y = -85; private const float icon_size = 30; + private readonly OsuScreen nextScreen; + private readonly Bindable currentUser = new Bindable(); - public Disclaimer() + public Disclaimer(OsuScreen nextScreen = null) { + this.nextScreen = nextScreen; ValidForResume = false; } @@ -146,7 +148,8 @@ namespace osu.Game.Screens.Menu protected override void LoadComplete() { base.LoadComplete(); - LoadComponentAsync(intro = new Intro()); + if (nextScreen != null) + LoadComponentAsync(nextScreen); } public override void OnEntering(IScreen last) @@ -170,7 +173,7 @@ namespace osu.Game.Screens.Menu .Then(5500) .FadeOut(250) .ScaleTo(0.9f, 250, Easing.InQuint) - .Finally(d => this.Push(intro)); + .Finally(d => this.Push(nextScreen)); } } } From e8b9b1b0bfe52812948b27b6fbaf834699f27ef3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 9 Jul 2019 12:16:58 +0300 Subject: [PATCH 0141/2815] visibility logic adjustments --- .../Visual/Online/TestSceneScoresContainer.cs | 15 ++--- .../BeatmapSet/Scores/ScoresContainer.cs | 67 ++++++++----------- 2 files changed, 35 insertions(+), 47 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index c414b6b940..10207ea192 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -3,12 +3,10 @@ using System; using System.Collections.Generic; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.MathUtils; -using osu.Game.Graphics; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Rulesets.Mods; @@ -16,6 +14,7 @@ using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Users; +using osuTK.Graphics; namespace osu.Game.Tests.Visual.Online { @@ -44,7 +43,11 @@ namespace osu.Game.Tests.Visual.Online Width = 0.8f, Children = new Drawable[] { - background = new Box { RelativeSizeAxes = Axes.Both }, + background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, scoresContainer = new ScoresContainer(), } }; @@ -245,11 +248,5 @@ namespace osu.Game.Tests.Visual.Online }); AddStep("Trigger loading", () => scoresContainer.Loading = !scoresContainer.Loading); } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - background.Colour = colours.Gray2; - } } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index eacbe6200a..63e18f3da7 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly ScoreTable scoreTable; private readonly FillFlowContainer topScoresContainer; - private readonly ContentContainer resizableContainer; + private readonly ContentContainer contentContainer; private readonly LoadingContainer loadingContainer; public ScoresContainer() @@ -49,36 +49,33 @@ namespace osu.Game.Overlays.BeatmapSet.Scores RelativeSizeAxes = Axes.X, Masking = true, }, - resizableContainer = new ContentContainer + contentContainer = new ContentContainer { RelativeSizeAxes = Axes.X, Masking = true, - Children = new Drawable[] + Child = new FillFlowContainer { - new FillFlowContainer + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.95f, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, spacing), + Padding = new MarginPadding { Vertical = padding }, + Children = new Drawable[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Width = 0.95f, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, spacing), - Padding = new MarginPadding { Vertical = padding }, - Children = new Drawable[] + topScoresContainer = new FillFlowContainer { - topScoresContainer = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), - }, - scoreTable = new ScoreTable - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - } + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + }, + scoreTable = new ScoreTable + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, } }, } @@ -103,20 +100,21 @@ namespace osu.Game.Overlays.BeatmapSet.Scores get => loading; set { - if (loading == value) - return; - loading = value; if (value) { loadingContainer.Show(); - resizableContainer.Hide(); + contentContainer.Hide(); } else { loadingContainer.Hide(); - resizableContainer.Show(); + + if (scores == null || scores?.Scores.Count < 1) + contentContainer.Hide(); + else + contentContainer.Show(); } } } @@ -171,14 +169,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (State.Value == Visibility.Hidden) return; - float height = 0; - - foreach (var c in Children) - { - height += c.Height; - } - - maxHeight = height; + maxHeight = Child.DrawHeight; this.ResizeHeightTo(maxHeight, duration, Easing.OutQuint); } From e3e72a627607751a84560077586ecdb80cfd1fee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jul 2019 18:15:31 +0900 Subject: [PATCH 0142/2815] Reorganise tests and add restart step --- osu.Game.Tests/Visual/Menus/IntroTestScene.cs | 66 +++++++++++++++++++ osu.Game.Tests/Visual/Menus/TestSceneIntro.cs | 65 ------------------ .../Visual/Menus/TestSceneIntroCircles.cs | 15 +++++ 3 files changed, 81 insertions(+), 65 deletions(-) create mode 100644 osu.Game.Tests/Visual/Menus/IntroTestScene.cs delete mode 100644 osu.Game.Tests/Visual/Menus/TestSceneIntro.cs create mode 100644 osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs diff --git a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs new file mode 100644 index 0000000000..0d78a43cf6 --- /dev/null +++ b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs @@ -0,0 +1,66 @@ +// 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.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Screens; +using osu.Game.Screens; +using osu.Game.Screens.Menu; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Menus +{ + [TestFixture] + public abstract class IntroTestScene : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(OsuLogo), + typeof(StartupScreen), + typeof(IntroScreen), + typeof(OsuScreen), + typeof(IntroTestScene), + }; + + [Cached] + private OsuLogo logo; + + protected IntroTestScene() + { + Drawable introStack = null; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Depth = float.MaxValue, + Colour = Color4.Black, + }, + logo = new OsuLogo + { + Alpha = 0, + RelativePositionAxes = Axes.Both, + Depth = float.MinValue, + Position = new Vector2(0.5f), + } + }; + + AddStep("restart sequence", () => + { + introStack?.Expire(); + Add(introStack = new OsuScreenStack(CreateScreen()) + { + RelativeSizeAxes = Axes.Both, + }); + }); + } + + protected abstract IScreen CreateScreen(); + } +} diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntro.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntro.cs deleted file mode 100644 index 8b993f618f..0000000000 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntro.cs +++ /dev/null @@ -1,65 +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 System; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Timing; -using osu.Game.Screens; -using osu.Game.Screens.Menu; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Tests.Visual.Menus -{ - [TestFixture] - public class TestSceneIntro : OsuTestScene - { - public override IReadOnlyList RequiredTypes => new[] - { - typeof(OsuLogo), - typeof(StartupScreen), - typeof(OsuScreen) - }; - - [Cached] - private OsuLogo logo; - - public TestSceneIntro() - { - var rateAdjustClock = new StopwatchClock(true); - var framedClock = new FramedClock(rateAdjustClock); - framedClock.ProcessFrame(); - - Add(new Container - { - RelativeSizeAxes = Axes.Both, - Clock = framedClock, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - new OsuScreenStack(new IntroCircles()) - { - RelativeSizeAxes = Axes.Both, - }, - logo = new OsuLogo - { - Alpha = 0, - RelativePositionAxes = Axes.Both, - Position = new Vector2(0.5f), - }, - } - }); - - AddSliderStep("Playback speed", 0.0, 2.0, 1, v => rateAdjustClock.Rate = v); - } - } -} diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs new file mode 100644 index 0000000000..107734cc8d --- /dev/null +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs @@ -0,0 +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 NUnit.Framework; +using osu.Framework.Screens; +using osu.Game.Screens.Menu; + +namespace osu.Game.Tests.Visual.Menus +{ + [TestFixture] + public class TestSceneIntroCircles : IntroTestScene + { + protected override IScreen CreateScreen() => new IntroCircles(); + } +} From 276873ff8ada90e18a9fde2b8f6e2641828c2e17 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 9 Jul 2019 12:28:59 +0300 Subject: [PATCH 0143/2815] remove unused field --- osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 10207ea192..4ce689ce6b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -29,8 +29,6 @@ namespace osu.Game.Tests.Visual.Online typeof(ScoreTableRowBackground), }; - private readonly Box background; - public TestSceneScoresContainer() { ScoresContainer scoresContainer; @@ -43,7 +41,7 @@ namespace osu.Game.Tests.Visual.Online Width = 0.8f, Children = new Drawable[] { - background = new Box + new Box { RelativeSizeAxes = Axes.Both, Colour = Color4.Black, From 2546f647be43d3e44477fa962a22f6dcd768e510 Mon Sep 17 00:00:00 2001 From: Desconocidosmh Date: Tue, 9 Jul 2019 11:32:49 +0200 Subject: [PATCH 0144/2815] Completely change the way we fix the bug --- osu.Game/OsuGame.cs | 6 +++--- osu.Game/Overlays/MusicController.cs | 4 ++-- osu.Game/Screens/Menu/MainMenu.cs | 18 ++++++++---------- osu.Game/Screens/OsuScreen.cs | 11 +---------- osu.Game/Screens/Select/SongSelect.cs | 7 ++++--- 5 files changed, 18 insertions(+), 28 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 325d2cbd85..0a472d4dc1 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -306,7 +306,7 @@ namespace osu.Game private void currentTrackCompleted() { if (!Beatmap.Value.Track.Looping && !Beatmap.Disabled) - MusicController.NextTrack(); + musicController.NextTrack(); } #endregion @@ -484,7 +484,7 @@ namespace osu.Game Origin = Anchor.TopRight, }, rightFloatingOverlayContent.Add, true); - loadComponentSingleFile(MusicController = new MusicController + loadComponentSingleFile(musicController = new MusicController { GetToolbarHeight = () => ToolbarOffset, Anchor = Anchor.TopRight, @@ -752,7 +752,7 @@ namespace osu.Game private ScalingContainer screenContainer; - public MusicController MusicController { get; private set; } + private MusicController musicController; protected override bool OnExiting() { diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 3db71d39ee..ad0c0717ac 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -55,6 +55,8 @@ namespace osu.Game.Overlays private Container dragContainer; private Container playerContainer; + public bool UserRequestedPause { get; private set; } + [Resolved] private Bindable beatmap { get; set; } @@ -66,8 +68,6 @@ namespace osu.Game.Overlays /// public Func GetToolbarHeight; - public bool UserRequestedPause { get; private set; } - public MusicController() { Width = 400; diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index dcce49179d..078f9c5a15 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -42,6 +42,9 @@ namespace osu.Game.Screens.Menu [Resolved] private GameHost host { get; set; } + [Resolved] + private MusicController musicController { get; set; } + private BackgroundScreenDefault background; protected override BackgroundScreen CreateBackground() => background; @@ -120,15 +123,6 @@ namespace osu.Game.Screens.Menu var track = Beatmap.Value.Track; var metadata = Beatmap.Value.Metadata; - if (last is Intro && track != null && !Game.MusicController.UserRequestedPause) - { - if (!track.IsRunning) - { - track.Seek(metadata.PreviewTime != -1 ? metadata.PreviewTime : 0.4f * track.Length); - track.Start(); - } - } - Beatmap.ValueChanged += beatmap_ValueChanged; } @@ -190,7 +184,11 @@ namespace osu.Game.Screens.Menu //we may have consumed our preloaded instance, so let's make another. preloadSongSelect(); - ResumeIfNoUserPauseRequested(); + if (Beatmap.Value.Track != null && !musicController.UserRequestedPause) + { + Beatmap.Value.Track.Tempo.Value = 1; + Beatmap.Value.Track.Start(); + } } public override bool OnExiting(IScreen next) diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 0682710133..328631ff9c 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -50,7 +50,7 @@ namespace osu.Game.Screens public virtual bool CursorVisible => true; - protected new OsuGame Game => base.Game as OsuGame; + protected new OsuGameBase Game => base.Game as OsuGameBase; /// /// The to set the user's activity automatically to when this screen is entered @@ -179,15 +179,6 @@ namespace osu.Game.Screens api.Activity.Value = activity; } - protected void ResumeIfNoUserPauseRequested() - { - if (Beatmap.Value.Track != null && !Game.MusicController.UserRequestedPause) - { - Beatmap.Value.Track.Tempo.Value = 1; - Beatmap.Value.Track.Start(); - } - } - /// /// Fired when this screen was entered or resumed and the logo state is required to be adjusted. /// diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 17b2ae376f..dd115e04d4 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -87,6 +87,9 @@ namespace osu.Game.Screens.Select private readonly Bindable decoupledRuleset = new Bindable(); + [Resolved] + private MusicController musicController { get; set; } + [Cached] [Cached(Type = typeof(IBindable>))] private readonly Bindable> mods = new Bindable>(Array.Empty()); // Bound to the game's mods, but is not reset on exiting @@ -426,8 +429,6 @@ namespace osu.Game.Screens.Select { base.OnEntering(last); - ResumeIfNoUserPauseRequested(); - this.FadeInFromZero(250); FilterControl.Activate(); } @@ -572,7 +573,7 @@ namespace osu.Game.Screens.Select { Track track = Beatmap.Value.Track; - if (!track.IsRunning || restart) + if ((!track.IsRunning || restart) && !musicController.UserRequestedPause) { track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; track.Restart(); From 8eeba069cc06fdced468da0e8184e834914f6cc3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jul 2019 18:51:10 +0900 Subject: [PATCH 0145/2815] Ensure logo isn't left in a bad state on re-run --- osu.Game.Tests/Visual/Menus/IntroTestScene.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs index 0d78a43cf6..e8addf59ed 100644 --- a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs +++ b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs @@ -53,7 +53,9 @@ namespace osu.Game.Tests.Visual.Menus AddStep("restart sequence", () => { + logo.FinishTransforms(); introStack?.Expire(); + Add(introStack = new OsuScreenStack(CreateScreen()) { RelativeSizeAxes = Axes.Both, From 2d0e6652f90e645538f9b1a87d20d8641e96fece Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jul 2019 18:59:56 +0900 Subject: [PATCH 0146/2815] Ensure logo doesn't get stuck tracking --- osu.Game.Tests/Visual/Menus/IntroTestScene.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs index e8addf59ed..aa27ed48df 100644 --- a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs +++ b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs @@ -54,6 +54,8 @@ namespace osu.Game.Tests.Visual.Menus AddStep("restart sequence", () => { logo.FinishTransforms(); + logo.IsTracking = false; + introStack?.Expire(); Add(introStack = new OsuScreenStack(CreateScreen()) From a0d048ad8ddca7cda23f6a8ab023c0a28c87952f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jul 2019 19:31:53 +0900 Subject: [PATCH 0147/2815] Don't test the logo Breaks main menu adding --- 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 aa27ed48df..d03d341ee4 100644 --- a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs +++ b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs @@ -20,7 +20,6 @@ namespace osu.Game.Tests.Visual.Menus { public override IReadOnlyList RequiredTypes => new[] { - typeof(OsuLogo), typeof(StartupScreen), typeof(IntroScreen), typeof(OsuScreen), From 3472979d0b4d9d2d652ded1a3b9f9c56e2642e1f Mon Sep 17 00:00:00 2001 From: Oskar Solecki <31374466+Desconocidosmh@users.noreply.github.com> Date: Tue, 9 Jul 2019 12:53:40 +0200 Subject: [PATCH 0148/2815] Make sure exiting editor doesn't unpause the music --- osu.Game/Screens/Edit/Editor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 676e060433..8b3cf1ec90 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -238,7 +238,7 @@ namespace osu.Game.Screens.Edit public override bool OnExiting(IScreen next) { Background.FadeColour(Color4.White, 500); - + Beatmap.Value.Track?.Stop(); return base.OnExiting(next); } From 2472c6b4b121d788c5ef835e5156eb179d582f8e Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Tue, 9 Jul 2019 21:07:29 +0930 Subject: [PATCH 0149/2815] Fix iOS visual tests not supporting raw keyboard handler --- osu.Game.Rulesets.Catch.Tests.iOS/Application.cs | 2 +- osu.Game.Rulesets.Mania.Tests.iOS/Application.cs | 2 +- osu.Game.Rulesets.Osu.Tests.iOS/Application.cs | 2 +- osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs | 2 +- osu.Game.Tests.iOS/Application.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs b/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs index 44817c1304..beca477943 100644 --- a/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs +++ b/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Catch.Tests.iOS { public static void Main(string[] args) { - UIApplication.Main(args, null, "AppDelegate"); + UIApplication.Main(args, "GameUIApplication", "AppDelegate"); } } } diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs b/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs index d47ac4643f..0362402320 100644 --- a/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs +++ b/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mania.Tests.iOS { public static void Main(string[] args) { - UIApplication.Main(args, null, "AppDelegate"); + UIApplication.Main(args, "GameUIApplication", "AppDelegate"); } } } diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs b/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs index 7a0797a909..3718264a42 100644 --- a/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs +++ b/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Osu.Tests.iOS { public static void Main(string[] args) { - UIApplication.Main(args, null, "AppDelegate"); + UIApplication.Main(args, "GameUIApplication", "AppDelegate"); } } } diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs b/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs index 6613e9e2b4..330cb42901 100644 --- a/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.iOS { public static void Main(string[] args) { - UIApplication.Main(args, null, "AppDelegate"); + UIApplication.Main(args, "GameUIApplication", "AppDelegate"); } } } diff --git a/osu.Game.Tests.iOS/Application.cs b/osu.Game.Tests.iOS/Application.cs index a23fe4e129..d96a3e27a4 100644 --- a/osu.Game.Tests.iOS/Application.cs +++ b/osu.Game.Tests.iOS/Application.cs @@ -9,7 +9,7 @@ namespace osu.Game.Tests.iOS { public static void Main(string[] args) { - UIApplication.Main(args, null, "AppDelegate"); + UIApplication.Main(args, "GameUIApplication", "AppDelegate"); } } } From e0c1fb78181571b926f12e84e4f6a508861191ad Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 9 Jul 2019 14:47:54 +0300 Subject: [PATCH 0150/2815] Compare by milliseconds for length --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 490012da61..c38b13cfca 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -49,11 +49,7 @@ namespace osu.Game.Screens.Select.Carousel return Beatmap.StarDifficulty.CompareTo(otherBeatmap.Beatmap.StarDifficulty); case SortMode.Length: - // Length comparing must be in seconds - if (TimeSpan.FromMilliseconds(Beatmap.Length).Seconds != TimeSpan.FromMilliseconds(otherBeatmap.Beatmap.Length).Seconds) - return Beatmap.Length.CompareTo(otherBeatmap.Beatmap.Length); - - goto case SortMode.Difficulty; + return Beatmap.Length.CompareTo(otherBeatmap.Beatmap.Length); } } From 38bc652bf2b8b740fcf1008dcb67759f93fccc52 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 9 Jul 2019 17:02:51 +0300 Subject: [PATCH 0151/2815] Remove sorting by length for beatmaps --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index c38b13cfca..712ab7b571 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -47,9 +47,6 @@ namespace osu.Game.Screens.Select.Carousel if (ruleset != 0) return ruleset; return Beatmap.StarDifficulty.CompareTo(otherBeatmap.Beatmap.StarDifficulty); - - case SortMode.Length: - return Beatmap.Length.CompareTo(otherBeatmap.Beatmap.Length); } } From f3329f4d792dc97f064f5ab0f0e51384b9ae795c Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 9 Jul 2019 17:22:21 +0300 Subject: [PATCH 0152/2815] Use a more readable code for calculating length --- osu.Game/Beatmaps/BeatmapManager.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 6fe70b6ec6..4f67139706 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -315,10 +315,15 @@ namespace osu.Game.Beatmaps private double calculateLength(IBeatmap b) { - var lastObject = b.HitObjects.LastOrDefault(); - var endTime = (lastObject as IHasEndTime)?.EndTime ?? lastObject?.StartTime ?? 0; + if (!b.HitObjects.Any()) + return 0; - return endTime - b.HitObjects.FirstOrDefault()?.StartTime ?? 0; + var lastObject = b.HitObjects.Last(); + + double endTime = (lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime; + double startTime = b.HitObjects.First().StartTime; + + return endTime - startTime; } /// From 1485c273ab1f3b36f7357823db220536e79c4a6b Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 9 Jul 2019 17:31:15 +0300 Subject: [PATCH 0153/2815] Describe the xmldoc mo --- osu.Game/Beatmaps/BeatmapInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index fa1282647e..8042f6b4b9 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -52,7 +52,7 @@ namespace osu.Game.Beatmaps public BeatmapOnlineInfo OnlineInfo { get; set; } /// - /// The playable length of this beatmap. + /// The playable length in milliseconds of this beatmap. /// public double Length { get; set; } From 9907a58ec4aaf3427769b2044248d060f3a0563b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 9 Jul 2019 17:38:17 +0300 Subject: [PATCH 0154/2815] Revert animations and apply suggested changes --- .../Visual/Online/TestSceneScoresContainer.cs | 1 - .../BeatmapSet/Scores/DrawableTopScore.cs | 3 +- .../BeatmapSet/Scores/ScoresContainer.cs | 137 ++++-------------- .../BeatmapSet/Scores/TopScoreUserSection.cs | 9 +- osu.Game/Overlays/BeatmapSetOverlay.cs | 14 +- 5 files changed, 45 insertions(+), 119 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 4ce689ce6b..824280fe68 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -244,7 +244,6 @@ namespace osu.Game.Tests.Visual.Online allScores.UserScore = myBestScore; scoresContainer.Scores = allScores; }); - AddStep("Trigger loading", () => scoresContainer.Loading = !scoresContainer.Loading); } } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index bdae730f7e..d263483046 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -59,11 +59,12 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { new Drawable[] { - new TopScoreUserSection(position) + new TopScoreUserSection { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Score = score, + ScorePosition = position, }, null, new TopScoreStatisticsSection diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 63e18f3da7..94bcfdee4f 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -17,20 +17,17 @@ namespace osu.Game.Overlays.BeatmapSet.Scores public class ScoresContainer : CompositeDrawable { private const int spacing = 15; - private const int padding = 20; + private const int fade_duration = 200; private readonly Box background; private readonly ScoreTable scoreTable; - private readonly FillFlowContainer topScoresContainer; - private readonly ContentContainer contentContainer; - private readonly LoadingContainer loadingContainer; + private readonly LoadingAnimation loadingAnimation; public ScoresContainer() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - InternalChildren = new Drawable[] { background = new Box @@ -39,83 +36,53 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }, new FillFlowContainer { - Direction = FillDirection.Vertical, - AutoSizeAxes = Axes.Y, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.95f, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, spacing), + Margin = new MarginPadding { Vertical = spacing }, Children = new Drawable[] { - loadingContainer = new LoadingContainer + topScoresContainer = new FillFlowContainer { RelativeSizeAxes = Axes.X, - Masking = true, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), }, - contentContainer = new ContentContainer + scoreTable = new ScoreTable { - RelativeSizeAxes = Axes.X, - Masking = true, - Child = new FillFlowContainer - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Width = 0.95f, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, spacing), - Padding = new MarginPadding { Vertical = padding }, - Children = new Drawable[] - { - topScoresContainer = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), - }, - scoreTable = new ScoreTable - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - } - }, - } - }, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + } } - } + }, + loadingAnimation = new LoadingAnimation + { + Alpha = 0, + Margin = new MarginPadding(20), + }, }; - - Loading = true; } [BackgroundDependencyLoader] private void load(OsuColour colours) { background.Colour = colours.Gray2; + updateDisplay(); } - private bool loading; - public bool Loading { - get => loading; set { - loading = value; + loadingAnimation.FadeTo(value ? 1 : 0, fade_duration); if (value) - { - loadingContainer.Show(); - contentContainer.Hide(); - } - else - { - loadingContainer.Hide(); - - if (scores == null || scores?.Scores.Count < 1) - contentContainer.Hide(); - else - contentContainer.Show(); - } + Scores = null; } } @@ -139,7 +106,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores scoreTable.Scores = scores?.Scores.Count > 1 ? scores.Scores : new List(); scoreTable.FadeTo(scores?.Scores.Count > 1 ? 1 : 0); - if (scores?.Scores.Any() == true) + if (scores?.Scores.Any() ?? false) { topScoresContainer.Add(new DrawableTopScore(scores.Scores.FirstOrDefault())); @@ -148,56 +115,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (userScore != null && userScore.Position != 1) topScoresContainer.Add(new DrawableTopScore(userScore.Score, userScore.Position)); } - - Loading = false; - } - - private class ContentContainer : VisibilityContainer - { - private const int duration = 300; - - private float maxHeight; - - protected override void PopIn() => this.ResizeHeightTo(maxHeight, duration, Easing.OutQuint); - - protected override void PopOut() => this.ResizeHeightTo(0, duration, Easing.OutQuint); - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - if (State.Value == Visibility.Hidden) - return; - - maxHeight = Child.DrawHeight; - - this.ResizeHeightTo(maxHeight, duration, Easing.OutQuint); - } - } - - private class LoadingContainer : VisibilityContainer - { - private const int duration = 300; - private const int height = 50; - - private readonly LoadingAnimation loadingAnimation; - - public LoadingContainer() - { - Child = loadingAnimation = new LoadingAnimation(); - } - - protected override void PopIn() - { - this.ResizeHeightTo(height, duration, Easing.OutQuint); - loadingAnimation.Show(); - } - - protected override void PopOut() - { - this.ResizeHeightTo(0, duration, Easing.OutQuint); - loadingAnimation.Hide(); - } } } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs index 056fe71a39..36e60b3fd9 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs @@ -28,7 +28,12 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly SpriteText date; private readonly UpdateableFlag flag; - public TopScoreUserSection(int position) + public int ScorePosition + { + set => rankText.Text = $"#{value}"; + } + + public TopScoreUserSection() { AutoSizeAxes = Axes.Both; @@ -52,7 +57,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = $"#{position.ToString()}", + Text = $"#1", Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold, italics: true) }, rank = new UpdateableRank(ScoreRank.D) diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 1c408ead54..abd86df920 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -17,17 +17,14 @@ using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Rulesets; using osuTK; - namespace osu.Game.Overlays { public class BeatmapSetOverlay : FullscreenOverlay { private const int fade_duration = 300; - public const float X_PADDING = 40; public const float TOP_PADDING = 25; public const float RIGHT_WIDTH = 275; - protected readonly Header Header; private RulesetStore rulesets; @@ -46,7 +43,6 @@ namespace osu.Game.Overlays { OsuScrollContainer scroll; Info info; - Children = new Drawable[] { new Box @@ -98,7 +94,11 @@ namespace osu.Game.Overlays scores.Loading = true; getScoresRequest = new GetScoresRequest(b, b.Ruleset); - getScoresRequest.Success += r => Schedule(() => scores.Scores = r); + getScoresRequest.Success += r => Schedule(() => + { + scores.Scores = r; + scores.Loading = false; + }); api.Queue(getScoresRequest); } @@ -123,6 +123,7 @@ namespace osu.Game.Overlays public void FetchAndShowBeatmap(int beatmapId) { beatmapSet.Value = null; + var req = new GetBeatmapSetRequest(beatmapId, BeatmapSetLookupType.BeatmapId); req.Success += res => { @@ -130,15 +131,18 @@ namespace osu.Game.Overlays Header.Picker.Beatmap.Value = Header.BeatmapSet.Value.Beatmaps.First(b => b.OnlineBeatmapID == beatmapId); }; API.Queue(req); + Show(); } public void FetchAndShowBeatmapSet(int beatmapSetId) { beatmapSet.Value = null; + var req = new GetBeatmapSetRequest(beatmapSetId); req.Success += res => beatmapSet.Value = res.ToBeatmapSet(rulesets); API.Queue(req); + Show(); } From e73f22eff8844b3ee429764801c8b4164a194eb6 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 9 Jul 2019 17:53:34 +0300 Subject: [PATCH 0155/2815] Convert length retrieved from online to milliseconds --- osu.Game.Tournament/Components/SongBar.cs | 2 +- osu.Game/Beatmaps/BeatmapSetInfo.cs | 2 +- osu.Game/Online/API/Requests/Responses/APIBeatmap.cs | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index ec021a8d1f..7005c068ae 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -180,7 +180,7 @@ namespace osu.Game.Tournament.Components panelContents.Children = new Drawable[] { - new DiffPiece(("Length", TimeSpan.FromSeconds(length).ToString(@"mm\:ss"))) + new DiffPiece(("Length", TimeSpan.FromMilliseconds(length).ToString(@"mm\:ss"))) { Anchor = Anchor.CentreLeft, Origin = Anchor.BottomLeft, diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 4075263e12..03bc7c7312 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -41,7 +41,7 @@ namespace osu.Game.Beatmaps public double MaxStarDifficulty => Beatmaps?.Max(b => b.StarDifficulty) ?? 0; /// - /// The maximum playable length of all beatmaps in this set. + /// The maximum playable length in milliseconds of all beatmaps in this set. /// public double MaxLength => Beatmaps?.Max(b => b.Length) ?? 0; diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs index ff4d240bf0..f50e281dd0 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.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 Newtonsoft.Json; using osu.Game.Beatmaps; using osu.Game.Rulesets; @@ -71,7 +72,7 @@ namespace osu.Game.Online.API.Requests.Responses StarDifficulty = starDifficulty, OnlineBeatmapID = OnlineBeatmapID, Version = version, - Length = length, + Length = TimeSpan.FromSeconds(length).Milliseconds, Status = Status, BeatmapSet = set, Metrics = metrics, From a0389c338ba2d89e6c07579b397e101d6c4e1c1d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 9 Jul 2019 17:56:08 +0300 Subject: [PATCH 0156/2815] CI fixes --- osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs | 1 - osu.Game/Overlays/BeatmapSetOverlay.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs index 36e60b3fd9..6d43ec6177 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs @@ -57,7 +57,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = $"#1", Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold, italics: true) }, rank = new UpdateableRank(ScoreRank.D) diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index abd86df920..3a17b6eec1 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -17,11 +17,11 @@ using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Rulesets; using osuTK; + namespace osu.Game.Overlays { public class BeatmapSetOverlay : FullscreenOverlay { - private const int fade_duration = 300; public const float X_PADDING = 40; public const float TOP_PADDING = 25; public const float RIGHT_WIDTH = 275; From b9be4080d315007715870c8f2381448961691f03 Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 9 Jul 2019 07:59:38 -0700 Subject: [PATCH 0157/2815] Update beatmap leaderboard to placeholder when signing out --- osu.Game/Online/Leaderboards/Leaderboard.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index dea2ff1a21..35f7ba1c1b 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -203,8 +203,13 @@ namespace osu.Game.Online.Leaderboards public void APIStateChanged(IAPIProvider api, APIState state) { - if (state == APIState.Online) - UpdateScores(); + switch (state) + { + case APIState.Online: + case APIState.Offline: + UpdateScores(); + break; + } } protected void UpdateScores() From 41afe89c0b1ee2f194c48f45c119fcab5dde72f3 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 10 Jul 2019 00:46:34 +0900 Subject: [PATCH 0158/2815] delete no longer needed bindable --- osu.Game/Screens/Play/Player.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index d9e050a681..663113f747 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -118,7 +118,6 @@ namespace osu.Game.Screens.Play sampleRestart = audio.Samples.Get(@"Gameplay/restart"); mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); - config.GetBindable(OsuSetting.ShowStoryboard); ScoreProcessor = DrawableRuleset.CreateScoreProcessor(); ScoreProcessor.Mods.BindTo(Mods); From 80ddfc3b1e33f3c93c5174fef01fd4819bbccfcb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Jul 2019 10:27:51 +0900 Subject: [PATCH 0159/2815] Disable frame accurate replay playback I want to prioritise better playback performance over accuracy for now. Also, in my testing this is still 100% accurate due to the addition of the FrameStabilityContainer, which is pretty cool. --- osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index 3830fa5cbe..4c011388fa 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Replays /// When set, we will ensure frames executed by nested drawables are frame-accurate to replay data. /// Disabling this can make replay playback smoother (useful for autoplay, currently). /// - public bool FrameAccuratePlayback = true; + public bool FrameAccuratePlayback = false; protected bool HasFrames => Frames.Count > 0; From 2a3601e43b26c70f93f7d4532e0313db92d72c03 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Jul 2019 11:42:30 +0900 Subject: [PATCH 0160/2815] Fix test class filename case --- ...eplayinputHandlerTest.cs => FramedReplayInputHandlerTest.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Tests/NonVisual/{FramedReplayinputHandlerTest.cs => FramedReplayInputHandlerTest.cs} (99%) diff --git a/osu.Game.Tests/NonVisual/FramedReplayinputHandlerTest.cs b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs similarity index 99% rename from osu.Game.Tests/NonVisual/FramedReplayinputHandlerTest.cs rename to osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs index 73387fa5ab..aa5bb02cdd 100644 --- a/osu.Game.Tests/NonVisual/FramedReplayinputHandlerTest.cs +++ b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.Replays; namespace osu.Game.Tests.NonVisual { [TestFixture] - public class FramedReplayinputHandlerTest + public class FramedReplayInputHandlerTest { private Replay replay; private TestInputHandler handler; From bd53a96507ee406e28e38272fe904cb7607597eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Jul 2019 11:47:50 +0900 Subject: [PATCH 0161/2815] Ensure tests cannot run forever --- .../NonVisual/FramedReplayInputHandlerTest.cs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs index aa5bb02cdd..2f1c4831a4 100644 --- a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs +++ b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.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 NUnit.Framework; using osu.Game.Replays; @@ -160,10 +161,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestRewindInsideImportantSection() { - // fast forward to important section - while (handler.SetFrameFromTime(3000) != null) - { - } + fastForwardToPoint(3000); setTime(4000, 4000); confirmCurrentFrame(4); @@ -205,10 +203,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestRewindOutOfImportantSection() { - // fast forward to important section - while (handler.SetFrameFromTime(3500) != null) - { - } + fastForwardToPoint(3500); confirmCurrentFrame(3); confirmNextFrame(4); @@ -227,6 +222,15 @@ namespace osu.Game.Tests.NonVisual confirmNextFrame(2); } + private void fastForwardToPoint(double destination) + { + for (int i = 0; i < 1000; i++) + if (handler.SetFrameFromTime(destination) == null) + return; + + throw new TimeoutException("Seek was never fulfilled"); + } + private void setTime(double set, double? expect) { Assert.AreEqual(expect, handler.SetFrameFromTime(set)); From 5e2adf59beaff47a0dd0724067852df1dfd2dc8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Jul 2019 11:53:34 +0900 Subject: [PATCH 0162/2815] Enforce frame accuracy for tests --- osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs index 2f1c4831a4..18cbd4e7c5 100644 --- a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs +++ b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs @@ -278,6 +278,7 @@ namespace osu.Game.Tests.NonVisual public TestInputHandler(Replay replay) : base(replay) { + FrameAccuratePlayback = true; } protected override double AllowedImportantTimeSpan => 1000; From 7929104b8a8297c7333cc127b97d16370d453a71 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 10 Jul 2019 12:24:05 +0900 Subject: [PATCH 0163/2815] move default into StoryboardContainer, fix load bug, remove comment --- .../Background/TestSceneBackgroundScreenBeatmap.cs | 10 +--------- osu.Game/Graphics/Containers/StoryboardContainer.cs | 5 +++-- osu.Game/Screens/Play/Player.cs | 7 +------ 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs index 0d62bf9bdc..f0e50f8498 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs @@ -334,15 +334,7 @@ namespace osu.Game.Tests.Visual.Background { protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value); - protected override StoryboardContainer CreateStoryboardContainer(Storyboard storyboard) - { - return new TestStoryboardContainer - { - RelativeSizeAxes = Axes.Both, - Alpha = 1, - EnableUserDim = { Value = true } - }; - } + protected override StoryboardContainer CreateStoryboardContainer(Storyboard storyboard) => new TestStoryboardContainer { RelativeSizeAxes = Axes.Both }; public TestStoryboardContainer CurrentStoryboardContainer => (TestStoryboardContainer)StoryboardContainer; diff --git a/osu.Game/Graphics/Containers/StoryboardContainer.cs b/osu.Game/Graphics/Containers/StoryboardContainer.cs index 472e22e212..899cbe1f0d 100644 --- a/osu.Game/Graphics/Containers/StoryboardContainer.cs +++ b/osu.Game/Graphics/Containers/StoryboardContainer.cs @@ -19,6 +19,8 @@ namespace osu.Game.Graphics.Containers public StoryboardContainer(Storyboard storyboard) { this.storyboard = storyboard; + EnableUserDim.Default = true; + EnableUserDim.Value = true; } [BackgroundDependencyLoader] @@ -29,13 +31,12 @@ namespace osu.Game.Graphics.Containers protected override void LoadComplete() { - ShowStoryboard.ValueChanged += _ => initializeStoryboard(true); + ShowStoryboard.BindValueChanged(_ => initializeStoryboard(true), true); base.LoadComplete(); } protected override void ApplyFade() { - // Storyboards cannot be blurred, so just hide the storyboard if it gets toggled. DimContainer.FadeTo(!ShowStoryboard.Value || UserDimLevel.Value == 1 ? 0 : 1, BACKGROUND_FADE_DURATION, Easing.OutQuint); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 663113f747..f44cb069a9 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -78,12 +78,7 @@ namespace osu.Game.Screens.Play protected StoryboardContainer StoryboardContainer { get; private set; } - protected virtual StoryboardContainer CreateStoryboardContainer(Storyboard storyboard) => new StoryboardContainer(storyboard) - { - RelativeSizeAxes = Axes.Both, - Alpha = 1, - EnableUserDim = { Value = true } - }; + protected virtual StoryboardContainer CreateStoryboardContainer(Storyboard storyboard) => new StoryboardContainer(storyboard) { RelativeSizeAxes = Axes.Both }; [Cached] [Cached(Type = typeof(IBindable>))] From 221ee58f550549a6f0e65c68a0baf4e68771cad6 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 10 Jul 2019 12:36:58 +0900 Subject: [PATCH 0164/2815] make storyboard text more visible --- .../Visual/Background/TestSceneBackgroundScreenBeatmap.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs index f0e50f8498..d9f4631ea8 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs @@ -10,6 +10,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Input.States; using osu.Framework.Platform; @@ -244,12 +245,13 @@ namespace osu.Game.Tests.Visual.Background player.ReplacesBackground.Value = false; player.CurrentStoryboardContainer.Add(new OsuSpriteText { - Size = new Vector2(250, 50), + Size = new Vector2(500, 50), Alpha = 1, - Colour = Color4.Tomato, + Colour = Color4.White, Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = "THIS IS A STORYBOARD", + Font = new FontUsage(size: 50) }); }); From 8b8e67fd726d5b1a2ae2380a3afd62ecbeb38ddc Mon Sep 17 00:00:00 2001 From: Desconocidosmh Date: Wed, 10 Jul 2019 10:41:52 +0200 Subject: [PATCH 0165/2815] Add accidentally deleted code --- osu.Game/Screens/Menu/MainMenu.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 078f9c5a15..b67801f9ba 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -123,6 +123,15 @@ namespace osu.Game.Screens.Menu var track = Beatmap.Value.Track; var metadata = Beatmap.Value.Metadata; + if (last is Intro && track != null) + { + if (!track.IsRunning) + { + track.Seek(metadata.PreviewTime != -1 ? metadata.PreviewTime : 0.4f * track.Length); + track.Start(); + } + } + Beatmap.ValueChanged += beatmap_ValueChanged; } From 100d15e651f3c8d27de36a689d6660c7a4e89083 Mon Sep 17 00:00:00 2001 From: Desconocidosmh Date: Wed, 10 Jul 2019 10:43:02 +0200 Subject: [PATCH 0166/2815] Move reseting tempo to Editor --- osu.Game/Screens/Edit/Editor.cs | 8 +++++++- osu.Game/Screens/Menu/MainMenu.cs | 3 --- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 8b3cf1ec90..310ce27d45 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -238,7 +238,13 @@ namespace osu.Game.Screens.Edit public override bool OnExiting(IScreen next) { Background.FadeColour(Color4.White, 500); - Beatmap.Value.Track?.Stop(); + + if (Beatmap.Value.Track != null) + { + Beatmap.Value.Track.Tempo.Value = 1; + Beatmap.Value.Track.Stop(); + } + return base.OnExiting(next); } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index b67801f9ba..6e1c471c1a 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -194,10 +194,7 @@ namespace osu.Game.Screens.Menu preloadSongSelect(); if (Beatmap.Value.Track != null && !musicController.UserRequestedPause) - { - Beatmap.Value.Track.Tempo.Value = 1; Beatmap.Value.Track.Start(); - } } public override bool OnExiting(IScreen next) From fae3348a69127b10fcadff6437c1823ab8c9ade4 Mon Sep 17 00:00:00 2001 From: Desconocidosmh Date: Wed, 10 Jul 2019 13:20:23 +0200 Subject: [PATCH 0167/2815] Add caching MusicController in tests --- .../Visual/Background/TestSceneBackgroundScreenBeatmap.cs | 3 ++- osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs index 8b941e4633..a4bb5a38f0 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs @@ -20,6 +20,7 @@ using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Scoring; @@ -71,7 +72,7 @@ namespace osu.Game.Tests.Visual.Background Dependencies.Cache(rulesets = new RulesetStore(factory)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, audio, host, Beatmap.Default)); Dependencies.Cache(new OsuConfigManager(LocalStorage)); - + Dependencies.Cache(new MusicController()); manager.Import(TestResources.GetTestBeatmapForImport()).Wait(); Beatmap.SetDefault(); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 962e0fb362..446a632b31 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -16,6 +16,7 @@ using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; @@ -96,6 +97,7 @@ namespace osu.Game.Tests.Visual.SongSelect Dependencies.Cache(rulesets = new RulesetStore(factory)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, audio, host, defaultBeatmap = Beatmap.Default)); + Dependencies.Cache(new MusicController()); Beatmap.SetDefault(); } From c3315e805f92e2cb2e38d3ad42bdca7fa8283416 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 10 Jul 2019 16:49:32 +0300 Subject: [PATCH 0168/2815] Use milliseconds for BasicStats' beatmap length --- osu.Game/Overlays/BeatmapSet/BasicStats.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs index 2926c82631..5b10c4e0bb 100644 --- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs +++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs @@ -60,7 +60,7 @@ namespace osu.Game.Overlays.BeatmapSet } else { - length.Value = TimeSpan.FromSeconds(beatmap.Length).ToString(@"m\:ss"); + length.Value = TimeSpan.FromMilliseconds(beatmap.Length).ToString(@"m\:ss"); circleCount.Value = beatmap.OnlineInfo.CircleCount.ToString(); sliderCount.Value = beatmap.OnlineInfo.SliderCount.ToString(); } From 9986178bf41b051c2c288549ed8087f630e2331b Mon Sep 17 00:00:00 2001 From: Desconocidosmh Date: Wed, 10 Jul 2019 13:20:23 +0200 Subject: [PATCH 0169/2815] Revert "Add caching MusicController in tests" This reverts commit fae3348a69127b10fcadff6437c1823ab8c9ade4. --- .../Visual/Background/TestSceneBackgroundScreenBeatmap.cs | 3 +-- osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs index a4bb5a38f0..8b941e4633 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs @@ -20,7 +20,6 @@ using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Scoring; @@ -72,7 +71,7 @@ namespace osu.Game.Tests.Visual.Background Dependencies.Cache(rulesets = new RulesetStore(factory)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, audio, host, Beatmap.Default)); Dependencies.Cache(new OsuConfigManager(LocalStorage)); - Dependencies.Cache(new MusicController()); + manager.Import(TestResources.GetTestBeatmapForImport()).Wait(); Beatmap.SetDefault(); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 446a632b31..962e0fb362 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -16,7 +16,6 @@ using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Database; -using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; @@ -97,7 +96,6 @@ namespace osu.Game.Tests.Visual.SongSelect Dependencies.Cache(rulesets = new RulesetStore(factory)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, audio, host, defaultBeatmap = Beatmap.Default)); - Dependencies.Cache(new MusicController()); Beatmap.SetDefault(); } From b225b2eb3958cf7c3f1d4fd245a1703885d3d228 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Jul 2019 00:18:19 +0900 Subject: [PATCH 0170/2815] Rename to IsUserPaused --- osu.Game/Overlays/MusicController.cs | 14 +++++++++----- osu.Game/Screens/Menu/MainMenu.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index ad0c0717ac..abbcec5094 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -55,7 +55,7 @@ namespace osu.Game.Overlays private Container dragContainer; private Container playerContainer; - public bool UserRequestedPause { get; private set; } + public bool IsUserPaused { get; private set; } [Resolved] private Bindable beatmap { get; set; } @@ -159,7 +159,7 @@ namespace osu.Game.Overlays Origin = Anchor.Centre, Scale = new Vector2(1.4f), IconScale = new Vector2(1.4f), - Action = play, + Action = togglePause, Icon = FontAwesome.Regular.PlayCircle, }, nextButton = new MusicIconButton @@ -278,7 +278,7 @@ namespace osu.Game.Overlays } } - private void play() + private void togglePause() { var track = current?.Track; @@ -289,12 +289,16 @@ namespace osu.Game.Overlays return; } - UserRequestedPause = track.IsRunning; - if (track.IsRunning) + { + IsUserPaused = true; track.Stop(); + } else + { track.Start(); + IsUserPaused = false; + } } private void prev() diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 6e1c471c1a..93c413452d 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -193,7 +193,7 @@ namespace osu.Game.Screens.Menu //we may have consumed our preloaded instance, so let's make another. preloadSongSelect(); - if (Beatmap.Value.Track != null && !musicController.UserRequestedPause) + if (Beatmap.Value.Track != null && !musicController.IsUserPaused) Beatmap.Value.Track.Start(); } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index dd115e04d4..085232b27f 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -573,7 +573,7 @@ namespace osu.Game.Screens.Select { Track track = Beatmap.Value.Track; - if ((!track.IsRunning || restart) && !musicController.UserRequestedPause) + if ((!track.IsRunning || restart) && !musicController.IsUserPaused) { track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; track.Restart(); From 6819c528db75c4d88f75ab35c837f1f3e81e13bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Jul 2019 00:20:01 +0900 Subject: [PATCH 0171/2815] Use canBeNull instead of needlessly caching MusicController for tests --- osu.Game/Screens/Menu/MainMenu.cs | 6 +++--- osu.Game/Screens/Select/SongSelect.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 93c413452d..5999cbdfb5 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -42,8 +42,8 @@ namespace osu.Game.Screens.Menu [Resolved] private GameHost host { get; set; } - [Resolved] - private MusicController musicController { get; set; } + [Resolved(canBeNull: true)] + private MusicController music { get; set; } private BackgroundScreenDefault background; @@ -193,7 +193,7 @@ namespace osu.Game.Screens.Menu //we may have consumed our preloaded instance, so let's make another. preloadSongSelect(); - if (Beatmap.Value.Track != null && !musicController.IsUserPaused) + if (Beatmap.Value.Track != null && music?.IsUserPaused != true) Beatmap.Value.Track.Start(); } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 085232b27f..0eeffda5eb 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -87,8 +87,8 @@ namespace osu.Game.Screens.Select private readonly Bindable decoupledRuleset = new Bindable(); - [Resolved] - private MusicController musicController { get; set; } + [Resolved(canBeNull: true)] + private MusicController music { get; set; } [Cached] [Cached(Type = typeof(IBindable>))] @@ -573,7 +573,7 @@ namespace osu.Game.Screens.Select { Track track = Beatmap.Value.Track; - if ((!track.IsRunning || restart) && !musicController.IsUserPaused) + if ((!track.IsRunning || restart) && music?.IsUserPaused != true) { track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; track.Restart(); From ad873b542a09334a86956be8b34249bce849b2f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Jul 2019 00:22:40 +0900 Subject: [PATCH 0172/2815] Simplify editor logic --- osu.Game/Screens/Edit/Editor.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 310ce27d45..8cc227d9be 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -224,30 +224,32 @@ namespace osu.Game.Screens.Edit public override void OnResuming(IScreen last) { - Beatmap.Value.Track?.Stop(); base.OnResuming(last); + Beatmap.Value.Track?.Stop(); } public override void OnEntering(IScreen last) { base.OnEntering(last); + Background.FadeColour(Color4.DarkGray, 500); - Beatmap.Value.Track?.Stop(); + resetTrack(); } public override bool OnExiting(IScreen next) { Background.FadeColour(Color4.White, 500); - - if (Beatmap.Value.Track != null) - { - Beatmap.Value.Track.Tempo.Value = 1; - Beatmap.Value.Track.Stop(); - } + resetTrack(); return base.OnExiting(next); } + private void resetTrack() + { + Beatmap.Value.Track?.ResetSpeedAdjustments(); + Beatmap.Value.Track?.Stop(); + } + private void exportBeatmap() => host.OpenFileExternally(Beatmap.Value.Save()); private void onModeChanged(ValueChangedEvent e) From f21e700b7af378f7196732cf1a725137433de8fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Jul 2019 00:42:14 +0900 Subject: [PATCH 0173/2815] Code style cleanup --- osu.Game/Overlays/BeatmapSetOverlay.cs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 3a17b6eec1..154e6f20f6 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -28,7 +28,9 @@ namespace osu.Game.Overlays protected readonly Header Header; private RulesetStore rulesets; - private readonly ScoresContainer scores; + + private readonly ScoresContainer scoreContainer; + private GetScoresRequest getScoresRequest; private readonly Bindable beatmapSet = new Bindable(); @@ -63,7 +65,7 @@ namespace osu.Game.Overlays { Header = new Header(), info = new Info(), - scores = new ScoresContainer(), + scoreContainer = new ScoresContainer(), }, }, }, @@ -81,23 +83,24 @@ namespace osu.Game.Overlays }; } - private void getScores(BeatmapInfo b) + private void getScores(BeatmapInfo beatmap) { getScoresRequest?.Cancel(); + getScoresRequest = null; - if (b?.OnlineBeatmapID.HasValue != true) + if (beatmap?.OnlineBeatmapID.HasValue != true) { - scores.Scores = null; + scoreContainer.Scores = null; return; } - scores.Loading = true; + scoreContainer.Loading = true; - getScoresRequest = new GetScoresRequest(b, b.Ruleset); - getScoresRequest.Success += r => Schedule(() => + getScoresRequest = new GetScoresRequest(beatmap, beatmap.Ruleset); + getScoresRequest.Success += scores => Schedule(() => { - scores.Scores = r; - scores.Loading = false; + scoreContainer.Scores = scores; + scoreContainer.Loading = false; }); api.Queue(getScoresRequest); } From 953d32366c8f067fe11f41825d64268e18889ed4 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 10 Jul 2019 19:40:29 +0300 Subject: [PATCH 0174/2815] Move request inside the ScoresContainer again --- .../Visual/Online/TestSceneScoresContainer.cs | 12 +++- .../BeatmapSet/Scores/ScoresContainer.cs | 64 ++++++++++++++++--- osu.Game/Overlays/BeatmapSetOverlay.cs | 34 +--------- 3 files changed, 68 insertions(+), 42 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 824280fe68..b26de1984a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Online public TestSceneScoresContainer() { - ScoresContainer scoresContainer; + TestScoresContainer scoresContainer; Child = new Container { @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Online RelativeSizeAxes = Axes.Both, Colour = Color4.Black, }, - scoresContainer = new ScoresContainer(), + scoresContainer = new TestScoresContainer(), } }; @@ -245,5 +245,13 @@ namespace osu.Game.Tests.Visual.Online scoresContainer.Scores = allScores; }); } + + private class TestScoresContainer : ScoresContainer + { + public new APILegacyScores Scores + { + set => base.Scores = value; + } + } } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 94bcfdee4f..bcb9383d0b 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -11,6 +11,9 @@ using osuTK; using System.Collections.Generic; using System.Linq; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Beatmaps; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -24,6 +27,40 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly FillFlowContainer topScoresContainer; private readonly LoadingAnimation loadingAnimation; + [Resolved] + private IAPIProvider api { get; set; } + + private GetScoresRequest getScoresRequest; + + private APILegacyScores scores; + + protected APILegacyScores Scores + { + get => scores; + set + { + scores = value; + + updateDisplay(); + } + } + + private BeatmapInfo beatmap; + + public BeatmapInfo Beatmap + { + get => beatmap; + set + { + if (beatmap == value) + return; + + beatmap = value; + + getScores(beatmap); + } + } + public ScoresContainer() { RelativeSizeAxes = Axes.X; @@ -75,7 +112,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores updateDisplay(); } - public bool Loading + private bool loading { set { @@ -86,17 +123,26 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } } - private APILegacyScores scores; - - public APILegacyScores Scores + private void getScores(BeatmapInfo beatmap) { - get => scores; - set - { - scores = value; + getScoresRequest?.Cancel(); + getScoresRequest = null; - updateDisplay(); + if (beatmap?.OnlineBeatmapID.HasValue != true) + { + Scores = null; + return; } + + loading = true; + + getScoresRequest = new GetScoresRequest(beatmap, beatmap.Ruleset); + getScoresRequest.Success += scores => Schedule(() => + { + Scores = scores; + loading = false; + }); + api.Queue(getScoresRequest); } private void updateDisplay() diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 154e6f20f6..c20e6368d8 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -11,7 +11,6 @@ using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet.Scores; @@ -29,15 +28,8 @@ namespace osu.Game.Overlays private RulesetStore rulesets; - private readonly ScoresContainer scoreContainer; - - private GetScoresRequest getScoresRequest; - private readonly Bindable beatmapSet = new Bindable(); - [Resolved] - private IAPIProvider api { get; set; } - // receive input outside our bounds so we can trigger a close event on ourselves. public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; @@ -45,6 +37,8 @@ namespace osu.Game.Overlays { OsuScrollContainer scroll; Info info; + ScoresContainer scoreContainer; + Children = new Drawable[] { new Box @@ -77,34 +71,12 @@ namespace osu.Game.Overlays Header.Picker.Beatmap.ValueChanged += b => { info.Beatmap = b.NewValue; - getScores(b.NewValue); + scoreContainer.Beatmap = b.NewValue; scroll.ScrollToStart(); }; } - private void getScores(BeatmapInfo beatmap) - { - getScoresRequest?.Cancel(); - getScoresRequest = null; - - if (beatmap?.OnlineBeatmapID.HasValue != true) - { - scoreContainer.Scores = null; - return; - } - - scoreContainer.Loading = true; - - getScoresRequest = new GetScoresRequest(beatmap, beatmap.Ruleset); - getScoresRequest.Success += scores => Schedule(() => - { - scoreContainer.Scores = scores; - scoreContainer.Loading = false; - }); - api.Queue(getScoresRequest); - } - [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { From b32b078e48d38a88cfd222c085cb5e9d648e98e8 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 10 Jul 2019 21:55:43 +0200 Subject: [PATCH 0175/2815] Set default keybindings for jukebox to stable's ones. --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index e756694285..cdd821c173 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -45,9 +45,9 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.Enter, GlobalAction.Select), new KeyBinding(InputKey.KeypadEnter, GlobalAction.Select), - new KeyBinding(InputKey.F5, GlobalAction.MusicPrev), - new KeyBinding(InputKey.F6, GlobalAction.MusicNext), - new KeyBinding(InputKey.X, GlobalAction.MusicPlay), + new KeyBinding(InputKey.F1, GlobalAction.MusicPrev), + new KeyBinding(InputKey.F5, GlobalAction.MusicNext), + new KeyBinding(InputKey.F3, GlobalAction.MusicPlay), }; public IEnumerable InGameKeyBindings => new[] From b2f23a10c87437db39a20b98a1fd2ad6afcf738f Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 10 Jul 2019 23:12:18 +0300 Subject: [PATCH 0176/2815] Use the correct property to retrieve the milliseconds --- osu.Game/Online/API/Requests/Responses/APIBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs index f50e281dd0..f4d67a56aa 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs @@ -72,7 +72,7 @@ namespace osu.Game.Online.API.Requests.Responses StarDifficulty = starDifficulty, OnlineBeatmapID = OnlineBeatmapID, Version = version, - Length = TimeSpan.FromSeconds(length).Milliseconds, + Length = TimeSpan.FromSeconds(length).TotalMilliseconds, Status = Status, BeatmapSet = set, Metrics = metrics, From a49bde7ed3369a734f27957b3a08e4456cbc8f90 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Jul 2019 10:38:32 +0900 Subject: [PATCH 0177/2815] Move protected below public --- .../BeatmapSet/Scores/ScoresContainer.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index bcb9383d0b..dfe6c45750 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -32,19 +32,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private GetScoresRequest getScoresRequest; - private APILegacyScores scores; - - protected APILegacyScores Scores - { - get => scores; - set - { - scores = value; - - updateDisplay(); - } - } - private BeatmapInfo beatmap; public BeatmapInfo Beatmap @@ -61,6 +48,19 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } } + private APILegacyScores scores; + + protected APILegacyScores Scores + { + get => scores; + set + { + scores = value; + + updateDisplay(); + } + } + public ScoresContainer() { RelativeSizeAxes = Axes.X; From cc9ee472d6090584587c56098c92bee6f5e34d30 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Jul 2019 11:07:30 +0900 Subject: [PATCH 0178/2815] Move score nulling out of loading property --- .../Overlays/BeatmapSet/Scores/ScoresContainer.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index dfe6c45750..5f200d7343 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -114,13 +114,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private bool loading { - set - { - loadingAnimation.FadeTo(value ? 1 : 0, fade_duration); - - if (value) - Scores = null; - } + set => loadingAnimation.FadeTo(value ? 1 : 0, fade_duration); } private void getScores(BeatmapInfo beatmap) @@ -128,11 +122,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores getScoresRequest?.Cancel(); getScoresRequest = null; + Scores = null; + if (beatmap?.OnlineBeatmapID.HasValue != true) - { - Scores = null; return; - } loading = true; From 8f9b8ed5a19d121977a08c16b308dc734ef2d3a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Jul 2019 11:17:33 +0900 Subject: [PATCH 0179/2815] Simplify information propagation logic --- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 2 +- .../BeatmapSet/Scores/ScoresContainer.cs | 60 +++++++++---------- 2 files changed, 29 insertions(+), 33 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 15816be327..347522fb48 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Content = null; backgroundFlow.Clear(); - if (value == null || !value.Any()) + if (value?.Any() != true) return; for (int i = 0; i < value.Count; i++) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 5f200d7343..22d7ea9c97 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osuTK; -using System.Collections.Generic; using System.Linq; using osu.Game.Online.API.Requests.Responses; using osu.Game.Beatmaps; @@ -48,16 +47,34 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } } - private APILegacyScores scores; - protected APILegacyScores Scores { - get => scores; set { - scores = value; + Schedule(() => + { + loading = false; - updateDisplay(); + topScoresContainer.Clear(); + + if (value?.Scores.Any() != true) + { + scoreTable.Scores = null; + scoreTable.Hide(); + return; + } + + scoreTable.Scores = value.Scores; + scoreTable.Show(); + + var topScore = value.Scores.First(); + var userScore = value.UserScore; + + topScoresContainer.Add(new DrawableTopScore(topScore)); + + if (userScore != null && userScore.Score.OnlineScoreID != topScore.OnlineScoreID) + topScoresContainer.Add(new DrawableTopScore(userScore.Score, userScore.Position)); + }); } } @@ -109,7 +126,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private void load(OsuColour colours) { background.Colour = colours.Gray2; - updateDisplay(); } private bool loading @@ -125,35 +141,15 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Scores = null; if (beatmap?.OnlineBeatmapID.HasValue != true) + { + loading = false; return; - - loading = true; + } getScoresRequest = new GetScoresRequest(beatmap, beatmap.Ruleset); - getScoresRequest.Success += scores => Schedule(() => - { - Scores = scores; - loading = false; - }); + getScoresRequest.Success += scores => Scores = scores; api.Queue(getScoresRequest); - } - - private void updateDisplay() - { - topScoresContainer.Clear(); - - scoreTable.Scores = scores?.Scores.Count > 1 ? scores.Scores : new List(); - scoreTable.FadeTo(scores?.Scores.Count > 1 ? 1 : 0); - - if (scores?.Scores.Any() ?? false) - { - topScoresContainer.Add(new DrawableTopScore(scores.Scores.FirstOrDefault())); - - var userScore = scores.UserScore; - - if (userScore != null && userScore.Position != 1) - topScoresContainer.Add(new DrawableTopScore(userScore.Score, userScore.Position)); - } + loading = true; } } } From 85f2212ebcb8d3612e41304f49d4caf0289374ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Jul 2019 11:32:42 +0900 Subject: [PATCH 0180/2815] Reduce spacing and font for rank position --- .../BeatmapSet/Scores/TopScoreUserSection.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs index 6d43ec6177..a15d3c5fd1 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs @@ -28,11 +28,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly SpriteText date; private readonly UpdateableFlag flag; - public int ScorePosition - { - set => rankText.Text = $"#{value}"; - } - public TopScoreUserSection() { AutoSizeAxes = Axes.Both; @@ -50,14 +45,13 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Origin = Anchor.Centre, AutoSizeAxes = Axes.Both, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 3), Children = new Drawable[] { rankText = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold, italics: true) + Font = OsuFont.GetFont(size: 24, weight: FontWeight.Bold, italics: true) }, rank = new UpdateableRank(ScoreRank.D) { @@ -124,6 +118,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores rankText.Colour = colours.Yellow; } + public int ScorePosition + { + set => rankText.Text = $"#{value}"; + } + /// /// Sets the score to be displayed. /// From 321266e96fc1de7735441dbb70ead7c856bff191 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 11 Jul 2019 13:17:28 +0900 Subject: [PATCH 0181/2815] Make UserDimContainer abstract --- .../TestSceneBackgroundScreenBeatmap.cs | 20 +++--- .../Containers/DimmableBackgroundContainer.cs | 68 +++++++++++++++++++ ...iner.cs => DimmableStoryboardContainer.cs} | 6 +- .../Graphics/Containers/UserDimContainer.cs | 67 +++--------------- .../Backgrounds/BackgroundScreenBeatmap.cs | 4 +- osu.Game/Screens/Play/Player.cs | 8 +-- 6 files changed, 95 insertions(+), 78 deletions(-) create mode 100644 osu.Game/Graphics/Containers/DimmableBackgroundContainer.cs rename osu.Game/Graphics/Containers/{StoryboardContainer.cs => DimmableStoryboardContainer.cs} (89%) diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs index d9f4631ea8..09aaee4adc 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs @@ -243,7 +243,7 @@ namespace osu.Game.Tests.Visual.Background { player.StoryboardEnabled.Value = false; player.ReplacesBackground.Value = false; - player.CurrentStoryboardContainer.Add(new OsuSpriteText + player.CurrentDimmableStoryboardContainer.Add(new OsuSpriteText { Size = new Vector2(500, 50), Alpha = 1, @@ -336,9 +336,9 @@ namespace osu.Game.Tests.Visual.Background { protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value); - protected override StoryboardContainer CreateStoryboardContainer(Storyboard storyboard) => new TestStoryboardContainer { RelativeSizeAxes = Axes.Both }; + protected override DimmableStoryboardContainer CreateStoryboardContainer(Storyboard storyboard) => new TestDimmableStoryboardContainer { RelativeSizeAxes = Axes.Both }; - public TestStoryboardContainer CurrentStoryboardContainer => (TestStoryboardContainer)StoryboardContainer; + public TestDimmableStoryboardContainer CurrentDimmableStoryboardContainer => (TestDimmableStoryboardContainer)DimmableStoryboardContainer; // Whether or not the player should be allowed to load. public bool BlockLoad; @@ -352,9 +352,9 @@ namespace osu.Game.Tests.Visual.Background { } - public bool IsStoryboardVisible() => CurrentStoryboardContainer.CurrentAlpha == 1; + public bool IsStoryboardVisible() => CurrentDimmableStoryboardContainer.CurrentAlpha == 1; - public bool IsStoryboardInvisible() => CurrentStoryboardContainer.CurrentAlpha <= 1; + public bool IsStoryboardInvisible() => CurrentDimmableStoryboardContainer.CurrentAlpha <= 1; [BackgroundDependencyLoader] private void load(OsuConfigManager config, CancellationToken token) @@ -387,7 +387,7 @@ namespace osu.Game.Tests.Visual.Background private class FadeAccessibleBackground : BackgroundScreenBeatmap { - protected override UserDimContainer CreateFadeContainer() => fadeContainer = new TestUserDimContainer { RelativeSizeAxes = Axes.Both }; + protected override DimmableBackgroundContainer CreateFadeContainer() => fadeContainer = new TestDimmableBackgroundContainer { RelativeSizeAxes = Axes.Both }; public Color4 CurrentColour => fadeContainer.CurrentColour; @@ -395,7 +395,7 @@ namespace osu.Game.Tests.Visual.Background public Vector2 CurrentBlur => Background.BlurSigma; - private TestUserDimContainer fadeContainer; + private TestDimmableBackgroundContainer fadeContainer; public FadeAccessibleBackground(WorkingBeatmap beatmap) : base(beatmap) @@ -403,17 +403,17 @@ namespace osu.Game.Tests.Visual.Background } } - private class TestStoryboardContainer : StoryboardContainer + private class TestDimmableStoryboardContainer : DimmableStoryboardContainer { public float CurrentAlpha => DimContainer.Alpha; - public TestStoryboardContainer() + public TestDimmableStoryboardContainer() : base(new Storyboard()) { } } - private class TestUserDimContainer : UserDimContainer + private class TestDimmableBackgroundContainer : DimmableBackgroundContainer { public Color4 CurrentColour => DimContainer.Colour; public float CurrentAlpha => DimContainer.Alpha; diff --git a/osu.Game/Graphics/Containers/DimmableBackgroundContainer.cs b/osu.Game/Graphics/Containers/DimmableBackgroundContainer.cs new file mode 100644 index 0000000000..2d010943bd --- /dev/null +++ b/osu.Game/Graphics/Containers/DimmableBackgroundContainer.cs @@ -0,0 +1,68 @@ +// 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.Game.Configuration; +using osu.Game.Graphics.Backgrounds; +using osuTK; + +namespace osu.Game.Graphics.Containers +{ + public class DimmableBackgroundContainer : UserDimContainer + { + /// + /// The amount of blur to be applied to the background in addition to user-specified blur. + /// + /// + /// Used in contexts where there can potentially be both user and screen-specified blurring occuring at the same time, such as in + /// + public readonly Bindable BlurAmount = new Bindable(); + + private Bindable userBlurLevel { get; set; } + + private Background background; + + public Background Background + { + get => background; + set + { + base.Add(background = value); + background.BlurTo(blurTarget, 0, Easing.OutQuint); + } + } + + public override void Add(Drawable drawable) + { + if (drawable is Background) + throw new InvalidOperationException($"Use {nameof(Background)} to set a background."); + + base.Add(drawable); + } + + /// + /// As an optimisation, we add the two blur portions to be applied rather than actually applying two separate blurs. + /// + private Vector2 blurTarget => EnableUserDim.Value + ? new Vector2(BlurAmount.Value + (float)userBlurLevel.Value * 25) + : new Vector2(BlurAmount.Value); + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + userBlurLevel = config.GetBindable(OsuSetting.BlurLevel); + BlurAmount.ValueChanged += _ => UpdateVisuals(); + userBlurLevel.ValueChanged += _ => UpdateVisuals(); + } + + protected override void ApplyFade() + { + // The background needs to be hidden in the case of it being replaced by the storyboard + DimContainer.FadeTo(ShowStoryboard.Value && StoryboardReplacesBackground.Value ? 0 : 1, BACKGROUND_FADE_DURATION, Easing.OutQuint); + Background?.BlurTo(blurTarget, BACKGROUND_FADE_DURATION, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Graphics/Containers/StoryboardContainer.cs b/osu.Game/Graphics/Containers/DimmableStoryboardContainer.cs similarity index 89% rename from osu.Game/Graphics/Containers/StoryboardContainer.cs rename to osu.Game/Graphics/Containers/DimmableStoryboardContainer.cs index 899cbe1f0d..a8a7b67e01 100644 --- a/osu.Game/Graphics/Containers/StoryboardContainer.cs +++ b/osu.Game/Graphics/Containers/DimmableStoryboardContainer.cs @@ -11,16 +11,14 @@ namespace osu.Game.Graphics.Containers /// /// A container that handles loading, as well as applies user-specified visual settings to it. /// - public class StoryboardContainer : UserDimContainer + public class DimmableStoryboardContainer : UserDimContainer { private readonly Storyboard storyboard; private DrawableStoryboard drawableStoryboard; - public StoryboardContainer(Storyboard storyboard) + public DimmableStoryboardContainer(Storyboard storyboard) { this.storyboard = storyboard; - EnableUserDim.Default = true; - EnableUserDim.Value = true; } [BackgroundDependencyLoader] diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index ad6f73eff5..e6a040fcd8 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -1,15 +1,11 @@ // 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.Game.Configuration; -using osu.Game.Graphics.Backgrounds; -using osu.Game.Screens.Play; -using osuTK; using osuTK.Graphics; namespace osu.Game.Graphics.Containers @@ -17,7 +13,7 @@ namespace osu.Game.Graphics.Containers /// /// A container that applies user-configured visual settings to its contents. /// - public class UserDimContainer : Container + public abstract class UserDimContainer : Container { protected const float BACKGROUND_FADE_DURATION = 800; @@ -31,14 +27,6 @@ namespace osu.Game.Graphics.Containers /// public readonly Bindable StoryboardReplacesBackground = new Bindable(); - /// - /// The amount of blur to be applied to the background in addition to user-specified blur. - /// - /// - /// Used in contexts where there can potentially be both user and screen-specified blurring occuring at the same time, such as in - /// - public readonly Bindable BlurAmount = new Bindable(); - protected Bindable UserDimLevel { get; private set; } protected Bindable ShowStoryboard { get; private set; } @@ -47,62 +35,30 @@ namespace osu.Game.Graphics.Containers protected override Container Content => DimContainer; - private Bindable userBlurLevel { get; set; } - - /// - /// As an optimisation, we add the two blur portions to be applied rather than actually applying two separate blurs. - /// - private Vector2 blurTarget => EnableUserDim.Value - ? new Vector2(BlurAmount.Value + (float)userBlurLevel.Value * 25) - : new Vector2(BlurAmount.Value); - /// /// Creates a new . /// - public UserDimContainer() + protected UserDimContainer() { AddInternal(DimContainer = new Container { RelativeSizeAxes = Axes.Both }); } - private Background background; - - public Background Background - { - get => background; - set - { - base.Add(background = value); - background.BlurTo(blurTarget, 0, Easing.OutQuint); - } - } - - public override void Add(Drawable drawable) - { - if (drawable is Background) - throw new InvalidOperationException($"Use {nameof(Background)} to set a background."); - - base.Add(drawable); - } - [BackgroundDependencyLoader] private void load(OsuConfigManager config) { UserDimLevel = config.GetBindable(OsuSetting.DimLevel); - userBlurLevel = config.GetBindable(OsuSetting.BlurLevel); ShowStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); - EnableUserDim.ValueChanged += _ => updateVisuals(); - UserDimLevel.ValueChanged += _ => updateVisuals(); - ShowStoryboard.ValueChanged += _ => updateVisuals(); - StoryboardReplacesBackground.ValueChanged += _ => updateVisuals(); - BlurAmount.ValueChanged += _ => updateVisuals(); - userBlurLevel.ValueChanged += _ => updateVisuals(); + EnableUserDim.ValueChanged += _ => UpdateVisuals(); + UserDimLevel.ValueChanged += _ => UpdateVisuals(); + ShowStoryboard.ValueChanged += _ => UpdateVisuals(); + StoryboardReplacesBackground.ValueChanged += _ => UpdateVisuals(); } protected override void LoadComplete() { base.LoadComplete(); - updateVisuals(); + UpdateVisuals(); } /// @@ -112,14 +68,9 @@ namespace osu.Game.Graphics.Containers /// While both backgrounds and storyboards allow user dim levels to be applied, storyboards can be toggled via /// and can cause backgrounds to become hidden via . Storyboards are also currently unable to be blurred. /// - protected virtual void ApplyFade() - { - // The background needs to be hidden in the case of it being replaced by the storyboard - DimContainer.FadeTo(ShowStoryboard.Value && StoryboardReplacesBackground.Value ? 0 : 1, BACKGROUND_FADE_DURATION, Easing.OutQuint); - Background?.BlurTo(blurTarget, BACKGROUND_FADE_DURATION, Easing.OutQuint); - } + protected abstract void ApplyFade(); - private void updateVisuals() + protected void UpdateVisuals() { ApplyFade(); diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index b6c2d016d2..1bb613755b 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -30,9 +30,9 @@ namespace osu.Game.Screens.Backgrounds /// public readonly Bindable BlurAmount = new Bindable(); - private readonly UserDimContainer fadeContainer; + private readonly DimmableBackgroundContainer fadeContainer; - protected virtual UserDimContainer CreateFadeContainer() => new UserDimContainer { RelativeSizeAxes = Axes.Both }; + protected virtual DimmableBackgroundContainer CreateFadeContainer() => new DimmableBackgroundContainer() { RelativeSizeAxes = Axes.Both }; public BackgroundScreenBeatmap(WorkingBeatmap beatmap = null) { diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index f44cb069a9..55e759d215 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -76,9 +76,9 @@ namespace osu.Game.Screens.Play protected GameplayClockContainer GameplayClockContainer { get; private set; } - protected StoryboardContainer StoryboardContainer { get; private set; } + protected DimmableStoryboardContainer DimmableStoryboardContainer { get; private set; } - protected virtual StoryboardContainer CreateStoryboardContainer(Storyboard storyboard) => new StoryboardContainer(storyboard) { RelativeSizeAxes = Axes.Both }; + protected virtual DimmableStoryboardContainer CreateStoryboardContainer(Storyboard storyboard) => new DimmableStoryboardContainer(storyboard) { RelativeSizeAxes = Axes.Both }; [Cached] [Cached(Type = typeof(IBindable>))] @@ -124,7 +124,7 @@ namespace osu.Game.Screens.Play GameplayClockContainer.Children = new[] { - StoryboardContainer = CreateStoryboardContainer(Beatmap.Value.Storyboard), + DimmableStoryboardContainer = CreateStoryboardContainer(Beatmap.Value.Storyboard), new ScalingContainer(ScalingMode.Gameplay) { Child = new LocalSkinOverrideContainer(working.Skin) @@ -455,7 +455,7 @@ namespace osu.Game.Screens.Play Background.BlurAmount.Value = 0; Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); - StoryboardContainer.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); + DimmableStoryboardContainer.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); storyboardReplacesBackground.Value = Beatmap.Value.Storyboard.ReplacesBackground && Beatmap.Value.Storyboard.HasDrawable; From ac170a695749c8b1e9b9662d193de78348a93e7c Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 11 Jul 2019 14:00:25 +0900 Subject: [PATCH 0182/2815] add comment and cleanup --- osu.Game/Graphics/Containers/DimmableBackgroundContainer.cs | 1 + osu.Game/Graphics/Containers/DimmableStoryboardContainer.cs | 4 ++++ osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/DimmableBackgroundContainer.cs b/osu.Game/Graphics/Containers/DimmableBackgroundContainer.cs index 2d010943bd..f415792993 100644 --- a/osu.Game/Graphics/Containers/DimmableBackgroundContainer.cs +++ b/osu.Game/Graphics/Containers/DimmableBackgroundContainer.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Graphics.Backgrounds; +using osu.Game.Screens.Play; using osuTK; namespace osu.Game.Graphics.Containers diff --git a/osu.Game/Graphics/Containers/DimmableStoryboardContainer.cs b/osu.Game/Graphics/Containers/DimmableStoryboardContainer.cs index a8a7b67e01..16379baeab 100644 --- a/osu.Game/Graphics/Containers/DimmableStoryboardContainer.cs +++ b/osu.Game/Graphics/Containers/DimmableStoryboardContainer.cs @@ -19,6 +19,10 @@ namespace osu.Game.Graphics.Containers public DimmableStoryboardContainer(Storyboard storyboard) { this.storyboard = storyboard; + + // Storyboards current do not get used in scenarios without user dim, so default to enabled here. + EnableUserDim.Default = true; + EnableUserDim.Value = true; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 1bb613755b..97f77f789a 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Backgrounds private readonly DimmableBackgroundContainer fadeContainer; - protected virtual DimmableBackgroundContainer CreateFadeContainer() => new DimmableBackgroundContainer() { RelativeSizeAxes = Axes.Both }; + protected virtual DimmableBackgroundContainer CreateFadeContainer() => new DimmableBackgroundContainer { RelativeSizeAxes = Axes.Both }; public BackgroundScreenBeatmap(WorkingBeatmap beatmap = null) { From 932243cfd4c7908d548d15df939d4eb2df6e8eb1 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 11 Jul 2019 14:14:00 +0900 Subject: [PATCH 0183/2815] public before private --- .../Graphics/Containers/DimmableBackgroundContainer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/Containers/DimmableBackgroundContainer.cs b/osu.Game/Graphics/Containers/DimmableBackgroundContainer.cs index f415792993..7339548069 100644 --- a/osu.Game/Graphics/Containers/DimmableBackgroundContainer.cs +++ b/osu.Game/Graphics/Containers/DimmableBackgroundContainer.cs @@ -22,10 +22,6 @@ namespace osu.Game.Graphics.Containers /// public readonly Bindable BlurAmount = new Bindable(); - private Bindable userBlurLevel { get; set; } - - private Background background; - public Background Background { get => background; @@ -36,6 +32,10 @@ namespace osu.Game.Graphics.Containers } } + private Bindable userBlurLevel { get; set; } + + private Background background; + public override void Add(Drawable drawable) { if (drawable is Background) From 3bc789fca892d8120e9916c6b285d56f64490086 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Jul 2019 15:09:06 +0900 Subject: [PATCH 0184/2815] Remove osd prefix and prefer upper-case OSD --- .../Visual/UserInterface/TestSceneOnScreenDisplay.cs | 3 ++- osu.Game/Overlays/OSD/{OsdToast.cs => Toast.cs} | 6 +++--- .../{OsdTrackedSettingToast.cs => TrackedSettingToast.cs} | 6 +++--- osu.Game/Overlays/OnScreenDisplay.cs | 6 +++--- 4 files changed, 11 insertions(+), 10 deletions(-) rename osu.Game/Overlays/OSD/{OsdToast.cs => Toast.cs} (94%) rename osu.Game/Overlays/OSD/{OsdTrackedSettingToast.cs => TrackedSettingToast.cs} (98%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs index b28f794a58..59a35591bd 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs @@ -7,6 +7,7 @@ using osu.Framework.Configuration; using osu.Framework.Configuration.Tracking; using osu.Framework.Graphics; using osu.Game.Overlays; +using osu.Game.Overlays.OSD; namespace osu.Game.Tests.Visual.UserInterface { @@ -22,7 +23,7 @@ namespace osu.Game.Tests.Visual.UserInterface osd.BeginTracking(this, config); Add(osd); - AddStep("Display empty osd toast", () => osd.Display(new Overlays.OSD.OsdToast())); + AddStep("Display empty osd toast", () => osd.Display(new Toast())); AddRepeatStep("Change toggle (no bind)", () => config.ToggleSetting(TestConfigSetting.ToggleSettingNoKeybind), 2); AddRepeatStep("Change toggle (with bind)", () => config.ToggleSetting(TestConfigSetting.ToggleSettingWithKeybind), 2); AddRepeatStep("Change enum (no bind)", () => config.IncrementEnumSetting(TestConfigSetting.EnumSettingNoKeybind), 3); diff --git a/osu.Game/Overlays/OSD/OsdToast.cs b/osu.Game/Overlays/OSD/Toast.cs similarity index 94% rename from osu.Game/Overlays/OSD/OsdToast.cs rename to osu.Game/Overlays/OSD/Toast.cs index f700577ba1..6634959bca 100644 --- a/osu.Game/Overlays/OSD/OsdToast.cs +++ b/osu.Game/Overlays/OSD/Toast.cs @@ -1,19 +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.Framework.Graphics.Containers; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osuTK.Graphics; namespace osu.Game.Overlays.OSD { - public class OsdToast : Container + public class Toast : Container { private readonly Container content; protected override Container Content => content; - public OsdToast() + public Toast() { Anchor = Anchor.Centre; Origin = Anchor.Centre; diff --git a/osu.Game/Overlays/OSD/OsdTrackedSettingToast.cs b/osu.Game/Overlays/OSD/TrackedSettingToast.cs similarity index 98% rename from osu.Game/Overlays/OSD/OsdTrackedSettingToast.cs rename to osu.Game/Overlays/OSD/TrackedSettingToast.cs index 411a33b000..8def8476f0 100644 --- a/osu.Game/Overlays/OSD/OsdTrackedSettingToast.cs +++ b/osu.Game/Overlays/OSD/TrackedSettingToast.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.Allocation; using osu.Framework.Configuration.Tracking; using osu.Framework.Extensions.Color4Extensions; @@ -13,13 +14,12 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; -using System; namespace osu.Game.Overlays.OSD { - public class OsdTrackedSettingToast : OsdToast + public class TrackedSettingToast : Toast { - public OsdTrackedSettingToast(SettingDescription description) + public TrackedSettingToast(SettingDescription description) { SpriteText textLine2; FillFlowContainer optionLights; diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs index 0577257080..a30ce5c56f 100644 --- a/osu.Game/Overlays/OnScreenDisplay.cs +++ b/osu.Game/Overlays/OnScreenDisplay.cs @@ -97,16 +97,16 @@ namespace osu.Game.Overlays } /// - /// Displays the given as parameter on the OSD + /// Displays the given as parameter on the OSD /// /// - public void Display(OsdToast toast) + public void Display(Toast toast) { box.Child = toast; DisplayTemporarily(box); } - private void displayTrackedSettingChange(SettingDescription description) => Schedule(() => Display(new OsdTrackedSettingToast(description))); + private void displayTrackedSettingChange(SettingDescription description) => Schedule(() => Display(new TrackedSettingToast(description))); private TransformSequence fadeIn; private ScheduledDelegate fadeOut; From dd13e2508ae854c483b60afcfa8aadd994393bc5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Jul 2019 15:14:57 +0900 Subject: [PATCH 0185/2815] Add note about toast height --- osu.Game/Overlays/OSD/Toast.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/OSD/Toast.cs b/osu.Game/Overlays/OSD/Toast.cs index 6634959bca..3ca010c5f4 100644 --- a/osu.Game/Overlays/OSD/Toast.cs +++ b/osu.Game/Overlays/OSD/Toast.cs @@ -18,6 +18,8 @@ namespace osu.Game.Overlays.OSD Anchor = Anchor.Centre; Origin = Anchor.Centre; Width = 240; + + // A toast's height is decided (and transformed) by the containing OnScreenDisplay. RelativeSizeAxes = Axes.Y; InternalChildren = new Drawable[] From 2c62891c4836ceb5b24957331dd6aa615ca8f85e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Jul 2019 15:15:34 +0900 Subject: [PATCH 0186/2815] Make Toast base class abstract --- .../Visual/UserInterface/TestSceneOnScreenDisplay.cs | 6 +++++- osu.Game/Overlays/OSD/Toast.cs | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs index 59a35591bd..ca14822205 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs @@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.UserInterface osd.BeginTracking(this, config); Add(osd); - AddStep("Display empty osd toast", () => osd.Display(new Toast())); + AddStep("Display empty osd toast", () => osd.Display(new EmptyToast())); AddRepeatStep("Change toggle (no bind)", () => config.ToggleSetting(TestConfigSetting.ToggleSettingNoKeybind), 2); AddRepeatStep("Change toggle (with bind)", () => config.ToggleSetting(TestConfigSetting.ToggleSettingWithKeybind), 2); AddRepeatStep("Change enum (no bind)", () => config.IncrementEnumSetting(TestConfigSetting.EnumSettingNoKeybind), 3); @@ -88,6 +88,10 @@ namespace osu.Game.Tests.Visual.UserInterface Setting4 } + private class EmptyToast : Toast + { + } + private class TestOnScreenDisplay : OnScreenDisplay { protected override void DisplayTemporarily(Drawable toDisplay) => toDisplay.FadeIn().ResizeHeightTo(110); diff --git a/osu.Game/Overlays/OSD/Toast.cs b/osu.Game/Overlays/OSD/Toast.cs index 3ca010c5f4..c6e3227cd1 100644 --- a/osu.Game/Overlays/OSD/Toast.cs +++ b/osu.Game/Overlays/OSD/Toast.cs @@ -8,12 +8,12 @@ using osuTK.Graphics; namespace osu.Game.Overlays.OSD { - public class Toast : Container + public abstract class Toast : Container { private readonly Container content; protected override Container Content => content; - public Toast() + protected Toast() { Anchor = Anchor.Centre; Origin = Anchor.Centre; From b6e15fb79186fc94379f2993694b8474af35c076 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Jul 2019 22:21:37 +0900 Subject: [PATCH 0187/2815] Update framework --- osu.Android.props | 2 +- osu.Game/Input/Bindings/GlobalActionContainer.cs | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 5ee0573c58..98f9bf1a42 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -63,6 +63,6 @@ - + diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 14d356f889..669fd62e45 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -39,7 +39,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.F4, GlobalAction.ToggleMute), new KeyBinding(InputKey.Escape, GlobalAction.Back), - new KeyBinding(InputKey.MouseButton1, GlobalAction.Back), + new KeyBinding(InputKey.ExtraMouseButton1, GlobalAction.Back), new KeyBinding(InputKey.Space, GlobalAction.Select), new KeyBinding(InputKey.Enter, GlobalAction.Select), diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e872cd1387..436ba90a88 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -15,7 +15,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index a319094cb1..c24349bcb5 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From cd7c03c13a0519c069b531b4c7c127539b85871f Mon Sep 17 00:00:00 2001 From: StanR Date: Thu, 11 Jul 2019 16:44:48 +0300 Subject: [PATCH 0188/2815] Add genre and language sections to beatmapset overlay --- .../Online/TestSceneBeatmapSetOverlay.cs | 2 ++ osu.Game/Beatmaps/BeatmapSetOnlineGenre.cs | 20 ++++++++++++++++++ osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs | 10 +++++++++ osu.Game/Beatmaps/BeatmapSetOnlineLanguage.cs | 21 +++++++++++++++++++ .../API/Requests/Responses/APIBeatmapSet.cs | 8 +++++++ osu.Game/Overlays/BeatmapSet/Info.cs | 21 ++++++++++++++++++- 6 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Beatmaps/BeatmapSetOnlineGenre.cs create mode 100644 osu.Game/Beatmaps/BeatmapSetOnlineLanguage.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index daee419b52..d87d9910c3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -173,6 +173,8 @@ namespace osu.Game.Tests.Visual.Online HasVideo = true, HasStoryboard = true, Covers = new BeatmapSetOnlineCovers(), + Language = BeatmapSetOnlineLanguage.English, + Genre = BeatmapSetOnlineGenre.Rock, }, Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }, Beatmaps = new List diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineGenre.cs b/osu.Game/Beatmaps/BeatmapSetOnlineGenre.cs new file mode 100644 index 0000000000..cea9d94987 --- /dev/null +++ b/osu.Game/Beatmaps/BeatmapSetOnlineGenre.cs @@ -0,0 +1,20 @@ +// 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.Beatmaps +{ + public enum BeatmapSetOnlineGenre + { + Any = 0, + Unspecified = 1, + VideoGame = 2, + Anime = 3, + Rock = 4, + Pop = 5, + Other = 6, + Novelty = 7, + // genre_id 8 doesnt exist + HipHop = 9, + Electronic = 10 + } +} diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs index ea3f0b61b9..2d2f06a57c 100644 --- a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs @@ -70,6 +70,16 @@ namespace osu.Game.Beatmaps /// The availability of this beatmap set. /// public BeatmapSetOnlineAvailability Availability { get; set; } + + /// + /// Beatmap set's song genre. + /// + public BeatmapSetOnlineGenre Genre { get; set; } + + /// + /// Beatmap set's song language. + /// + public BeatmapSetOnlineLanguage Language { get; set; } } public class BeatmapSetOnlineCovers diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineLanguage.cs b/osu.Game/Beatmaps/BeatmapSetOnlineLanguage.cs new file mode 100644 index 0000000000..0fb0dec904 --- /dev/null +++ b/osu.Game/Beatmaps/BeatmapSetOnlineLanguage.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. + +namespace osu.Game.Beatmaps +{ + public enum BeatmapSetOnlineLanguage + { + Any = 0, + Other = 1, + English = 2, + Japanese = 3, + Chinese = 4, + Instrumental = 5, + Korean = 6, + French = 7, + German = 8, + Swedish = 9, + Spanish = 10, + Italian = 11 + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs index 200a705500..9ca2cb0b81 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs @@ -66,6 +66,12 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"availability")] private BeatmapSetOnlineAvailability availability { get; set; } + [JsonProperty(@"genre_id")] + private BeatmapSetOnlineGenre genre { get; set; } + + [JsonProperty(@"language_id")] + private BeatmapSetOnlineLanguage language { get; set; } + [JsonProperty(@"beatmaps")] private IEnumerable beatmaps { get; set; } @@ -91,6 +97,8 @@ namespace osu.Game.Online.API.Requests.Responses Ranked = ranked, LastUpdated = lastUpdated, Availability = availability, + Genre = genre, + Language = language }, Beatmaps = beatmaps?.Select(b => b.ToBeatmap(rulesets)).ToList(), }; diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 44827f0a0c..27ca58fbd3 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.BeatmapSet public Info() { - MetadataSection source, tags; + MetadataSection source, tags, genre, language; RelativeSizeAxes = Axes.X; Height = 220; Masking = true; @@ -88,6 +88,19 @@ namespace osu.Game.Overlays.BeatmapSet Children = new[] { source = new MetadataSection("Source"), + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Width = 0.5f, + FillMode = FillMode.Fit, + Children = new Drawable[] + { + genre = new MetadataSection("Genre"), + language = new MetadataSection("Language"), + } + }, tags = new MetadataSection("Tags"), }, }, @@ -119,6 +132,12 @@ namespace osu.Game.Overlays.BeatmapSet { source.Text = b.NewValue?.Metadata.Source ?? string.Empty; tags.Text = b.NewValue?.Metadata.Tags ?? string.Empty; + + var genreId = b.NewValue?.OnlineInfo.Genre ?? BeatmapSetOnlineGenre.Unspecified; + genre.Text = genreId.ToString(); + + var languageId = b.NewValue?.OnlineInfo.Language ?? BeatmapSetOnlineLanguage.Other; + language.Text = languageId.ToString(); }; } From 1e04fcc6b54d8dab5d24478794ad27478cf38b80 Mon Sep 17 00:00:00 2001 From: StanR Date: Thu, 11 Jul 2019 17:47:09 +0300 Subject: [PATCH 0189/2815] Apply fixes --- osu.Game/Beatmaps/BeatmapSetOnlineGenre.cs | 20 ------- osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs | 56 ++++++++++++++++++- osu.Game/Beatmaps/BeatmapSetOnlineLanguage.cs | 21 ------- osu.Game/Overlays/BeatmapSet/Info.cs | 9 +-- 4 files changed, 57 insertions(+), 49 deletions(-) delete mode 100644 osu.Game/Beatmaps/BeatmapSetOnlineGenre.cs delete mode 100644 osu.Game/Beatmaps/BeatmapSetOnlineLanguage.cs diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineGenre.cs b/osu.Game/Beatmaps/BeatmapSetOnlineGenre.cs deleted file mode 100644 index cea9d94987..0000000000 --- a/osu.Game/Beatmaps/BeatmapSetOnlineGenre.cs +++ /dev/null @@ -1,20 +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.Beatmaps -{ - public enum BeatmapSetOnlineGenre - { - Any = 0, - Unspecified = 1, - VideoGame = 2, - Anime = 3, - Rock = 4, - Pop = 5, - Other = 6, - Novelty = 7, - // genre_id 8 doesnt exist - HipHop = 9, - Electronic = 10 - } -} diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs index 2d2f06a57c..4cb50b59fb 100644 --- a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.ComponentModel; using Newtonsoft.Json; namespace osu.Game.Beatmaps @@ -72,16 +73,67 @@ namespace osu.Game.Beatmaps public BeatmapSetOnlineAvailability Availability { get; set; } /// - /// Beatmap set's song genre. + /// The song genre of this beatmap set. /// public BeatmapSetOnlineGenre Genre { get; set; } /// - /// Beatmap set's song language. + /// The song language of this beatmap set. /// public BeatmapSetOnlineLanguage Language { get; set; } } + public enum BeatmapSetOnlineGenre + { + [Description("Any")] + Any = 0, + + [Description("Unspecified")] + Unspecified = 1, + + [Description("Video Game")] + VideoGame = 2, + + [Description("Anime")] + Anime = 3, + + [Description("Rock")] + Rock = 4, + + [Description("Pop")] + Pop = 5, + + [Description("Other")] + Other = 6, + + [Description("Novelty")] + Novelty = 7, + + // genre_id 8 doesn't exist + + [Description("Hip-Hop")] + HipHop = 9, + + [Description("Electronic")] + Electronic = 10 + } + + public enum BeatmapSetOnlineLanguage + { + Any, + Other, + English, + Japanese, + Chinese, + Instrumental, + Korean, + French, + German, + Swedish, + Spanish, + Italian + } + public class BeatmapSetOnlineCovers { public string CoverLowRes { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineLanguage.cs b/osu.Game/Beatmaps/BeatmapSetOnlineLanguage.cs deleted file mode 100644 index 0fb0dec904..0000000000 --- a/osu.Game/Beatmaps/BeatmapSetOnlineLanguage.cs +++ /dev/null @@ -1,21 +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.Beatmaps -{ - public enum BeatmapSetOnlineLanguage - { - Any = 0, - Other = 1, - English = 2, - Japanese = 3, - Chinese = 4, - Instrumental = 5, - Korean = 6, - French = 7, - German = 8, - Swedish = 9, - Spanish = 10, - Italian = 11 - } -} diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 27ca58fbd3..0e4e9db948 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -132,12 +133,8 @@ namespace osu.Game.Overlays.BeatmapSet { source.Text = b.NewValue?.Metadata.Source ?? string.Empty; tags.Text = b.NewValue?.Metadata.Tags ?? string.Empty; - - var genreId = b.NewValue?.OnlineInfo.Genre ?? BeatmapSetOnlineGenre.Unspecified; - genre.Text = genreId.ToString(); - - var languageId = b.NewValue?.OnlineInfo.Language ?? BeatmapSetOnlineLanguage.Other; - language.Text = languageId.ToString(); + genre.Text = (b.NewValue?.OnlineInfo.Genre ?? BeatmapSetOnlineGenre.Unspecified).GetDescription(); + language.Text = (b.NewValue?.OnlineInfo.Language ?? BeatmapSetOnlineLanguage.Other).ToString(); }; } From 0d9f978857a869f433bd6733d3640a0fb387cdcd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2019 11:38:15 +0900 Subject: [PATCH 0190/2815] Don't expose DimContainer --- .../TestSceneBackgroundScreenBeatmap.cs | 6 ++--- .../Containers/DimmableBackgroundContainer.cs | 10 ++++---- .../Containers/DimmableStoryboardContainer.cs | 6 +---- .../Graphics/Containers/UserDimContainer.cs | 24 +++++++++---------- 4 files changed, 21 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs index 09aaee4adc..6a4145b24f 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs @@ -405,7 +405,7 @@ namespace osu.Game.Tests.Visual.Background private class TestDimmableStoryboardContainer : DimmableStoryboardContainer { - public float CurrentAlpha => DimContainer.Alpha; + public float CurrentAlpha => Content.Alpha; public TestDimmableStoryboardContainer() : base(new Storyboard()) @@ -415,8 +415,8 @@ namespace osu.Game.Tests.Visual.Background private class TestDimmableBackgroundContainer : DimmableBackgroundContainer { - public Color4 CurrentColour => DimContainer.Colour; - public float CurrentAlpha => DimContainer.Alpha; + public Color4 CurrentColour => Content.Colour; + public float CurrentAlpha => Content.Alpha; } } } diff --git a/osu.Game/Graphics/Containers/DimmableBackgroundContainer.cs b/osu.Game/Graphics/Containers/DimmableBackgroundContainer.cs index 7339548069..32b333528f 100644 --- a/osu.Game/Graphics/Containers/DimmableBackgroundContainer.cs +++ b/osu.Game/Graphics/Containers/DimmableBackgroundContainer.cs @@ -55,14 +55,16 @@ namespace osu.Game.Graphics.Containers private void load(OsuConfigManager config) { userBlurLevel = config.GetBindable(OsuSetting.BlurLevel); - BlurAmount.ValueChanged += _ => UpdateVisuals(); userBlurLevel.ValueChanged += _ => UpdateVisuals(); + BlurAmount.ValueChanged += _ => UpdateVisuals(); } - protected override void ApplyFade() + 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() { - // The background needs to be hidden in the case of it being replaced by the storyboard - DimContainer.FadeTo(ShowStoryboard.Value && StoryboardReplacesBackground.Value ? 0 : 1, BACKGROUND_FADE_DURATION, Easing.OutQuint); + base.UpdateVisuals(); + Background?.BlurTo(blurTarget, BACKGROUND_FADE_DURATION, Easing.OutQuint); } } diff --git a/osu.Game/Graphics/Containers/DimmableStoryboardContainer.cs b/osu.Game/Graphics/Containers/DimmableStoryboardContainer.cs index 16379baeab..0d87e64447 100644 --- a/osu.Game/Graphics/Containers/DimmableStoryboardContainer.cs +++ b/osu.Game/Graphics/Containers/DimmableStoryboardContainer.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Graphics; using osu.Game.Storyboards; using osu.Game.Storyboards.Drawables; @@ -37,10 +36,7 @@ namespace osu.Game.Graphics.Containers base.LoadComplete(); } - protected override void ApplyFade() - { - DimContainer.FadeTo(!ShowStoryboard.Value || UserDimLevel.Value == 1 ? 0 : 1, BACKGROUND_FADE_DURATION, Easing.OutQuint); - } + protected override bool ShowDimContent => ShowStoryboard.Value && UserDimLevel.Value < 1; private void initializeStoryboard(bool async) { diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index e6a040fcd8..93af0be923 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -31,16 +31,16 @@ namespace osu.Game.Graphics.Containers protected Bindable ShowStoryboard { get; private set; } - protected Container DimContainer { get; } + protected override Container Content => dimContent; - protected override Container Content => DimContainer; + private Container dimContent { get; } /// /// Creates a new . /// protected UserDimContainer() { - AddInternal(DimContainer = new Container { RelativeSizeAxes = Axes.Both }); + AddInternal(dimContent = new Container { RelativeSizeAxes = Axes.Both }); } [BackgroundDependencyLoader] @@ -62,19 +62,17 @@ namespace osu.Game.Graphics.Containers } /// - /// Apply non-dim related settings to the content, such as hiding and blurring. + /// Whether the content of this container should currently be visible. /// - /// - /// While both backgrounds and storyboards allow user dim levels to be applied, storyboards can be toggled via - /// and can cause backgrounds to become hidden via . Storyboards are also currently unable to be blurred. - /// - protected abstract void ApplyFade(); + protected virtual bool ShowDimContent => true; - protected void UpdateVisuals() + /// + /// Should be invoked when any dependent dim level or user setting is changed and bring the visual state up-to-date. + /// + protected virtual void UpdateVisuals() { - ApplyFade(); - - DimContainer.FadeColour(EnableUserDim.Value ? OsuColour.Gray(1 - (float)UserDimLevel.Value) : Color4.White, BACKGROUND_FADE_DURATION, Easing.OutQuint); + dimContent.FadeTo(ShowDimContent ? 1 : 0, BACKGROUND_FADE_DURATION, Easing.OutQuint); + dimContent.FadeColour(EnableUserDim.Value ? OsuColour.Gray(1 - (float)UserDimLevel.Value) : Color4.White, BACKGROUND_FADE_DURATION, Easing.OutQuint); } } } From b5ca7faca4dad93ea1bb0c31a3426f2e74433d67 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2019 11:40:32 +0900 Subject: [PATCH 0191/2815] Move default value for EnableUserDim to base class --- osu.Game/Graphics/Containers/DimmableStoryboardContainer.cs | 4 ---- osu.Game/Graphics/Containers/UserDimContainer.cs | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Containers/DimmableStoryboardContainer.cs b/osu.Game/Graphics/Containers/DimmableStoryboardContainer.cs index 0d87e64447..d0ef7a2c25 100644 --- a/osu.Game/Graphics/Containers/DimmableStoryboardContainer.cs +++ b/osu.Game/Graphics/Containers/DimmableStoryboardContainer.cs @@ -18,10 +18,6 @@ namespace osu.Game.Graphics.Containers public DimmableStoryboardContainer(Storyboard storyboard) { this.storyboard = storyboard; - - // Storyboards current do not get used in scenarios without user dim, so default to enabled here. - EnableUserDim.Default = true; - EnableUserDim.Value = true; } [BackgroundDependencyLoader] diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 93af0be923..f5958a092b 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -20,7 +20,7 @@ namespace osu.Game.Graphics.Containers /// /// Whether or not user-configured dim levels should be applied to the container. /// - public readonly Bindable EnableUserDim = new Bindable(); + public readonly Bindable EnableUserDim = new Bindable(true); /// /// Whether or not the storyboard loaded should completely hide the background behind it. From 46f7bb885bbd445c851c4259663d3260d2ada4e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2019 11:50:06 +0900 Subject: [PATCH 0192/2815] Move classes to local namespaces Also renames test scene to more appropriate name. --- ...eatmap.cs => TestSceneUserDimContainer.cs} | 26 +++---- .../Containers/DimmableBackgroundContainer.cs | 71 ----------------- .../Backgrounds/BackgroundScreenBeatmap.cs | 76 +++++++++++++++++-- .../Play/DimmableStoryboard.cs} | 7 +- osu.Game/Screens/Play/Player.cs | 8 +- 5 files changed, 90 insertions(+), 98 deletions(-) rename osu.Game.Tests/Visual/Background/{TestSceneBackgroundScreenBeatmap.cs => TestSceneUserDimContainer.cs} (92%) delete mode 100644 osu.Game/Graphics/Containers/DimmableBackgroundContainer.cs rename osu.Game/{Graphics/Containers/DimmableStoryboardContainer.cs => Screens/Play/DimmableStoryboard.cs} (89%) diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs similarity index 92% rename from osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs rename to osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index 6a4145b24f..5578fee42d 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -38,7 +38,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Background { [TestFixture] - public class TestSceneBackgroundScreenBeatmap : ManualInputManagerTestScene + public class TestSceneUserDimContainer : ManualInputManagerTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -243,7 +243,7 @@ namespace osu.Game.Tests.Visual.Background { player.StoryboardEnabled.Value = false; player.ReplacesBackground.Value = false; - player.CurrentDimmableStoryboardContainer.Add(new OsuSpriteText + player.DimmableStoryboard.Add(new OsuSpriteText { Size = new Vector2(500, 50), Alpha = 1, @@ -336,9 +336,9 @@ namespace osu.Game.Tests.Visual.Background { protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value); - protected override DimmableStoryboardContainer CreateStoryboardContainer(Storyboard storyboard) => new TestDimmableStoryboardContainer { RelativeSizeAxes = Axes.Both }; + protected override DimmableStoryboard CreateStoryboardContainer(Storyboard storyboard) => new TestDimmableStoryboard { RelativeSizeAxes = Axes.Both }; - public TestDimmableStoryboardContainer CurrentDimmableStoryboardContainer => (TestDimmableStoryboardContainer)DimmableStoryboardContainer; + public new TestDimmableStoryboard DimmableStoryboard => (TestDimmableStoryboard)base.DimmableStoryboard; // Whether or not the player should be allowed to load. public bool BlockLoad; @@ -352,9 +352,9 @@ namespace osu.Game.Tests.Visual.Background { } - public bool IsStoryboardVisible() => CurrentDimmableStoryboardContainer.CurrentAlpha == 1; + public bool IsStoryboardVisible() => DimmableStoryboard.CurrentAlpha == 1; - public bool IsStoryboardInvisible() => CurrentDimmableStoryboardContainer.CurrentAlpha <= 1; + public bool IsStoryboardInvisible() => DimmableStoryboard.CurrentAlpha <= 1; [BackgroundDependencyLoader] private void load(OsuConfigManager config, CancellationToken token) @@ -387,15 +387,15 @@ namespace osu.Game.Tests.Visual.Background private class FadeAccessibleBackground : BackgroundScreenBeatmap { - protected override DimmableBackgroundContainer CreateFadeContainer() => fadeContainer = new TestDimmableBackgroundContainer { RelativeSizeAxes = Axes.Both }; + protected override DimmableBackground CreateFadeContainer() => dimmable = new TestDimmableBackground { RelativeSizeAxes = Axes.Both }; - public Color4 CurrentColour => fadeContainer.CurrentColour; + public Color4 CurrentColour => dimmable.CurrentColour; - public float CurrentAlpha => fadeContainer.CurrentAlpha; + public float CurrentAlpha => dimmable.CurrentAlpha; public Vector2 CurrentBlur => Background.BlurSigma; - private TestDimmableBackgroundContainer fadeContainer; + private TestDimmableBackground dimmable; public FadeAccessibleBackground(WorkingBeatmap beatmap) : base(beatmap) @@ -403,17 +403,17 @@ namespace osu.Game.Tests.Visual.Background } } - private class TestDimmableStoryboardContainer : DimmableStoryboardContainer + private class TestDimmableStoryboard : DimmableStoryboard { public float CurrentAlpha => Content.Alpha; - public TestDimmableStoryboardContainer() + public TestDimmableStoryboard() : base(new Storyboard()) { } } - private class TestDimmableBackgroundContainer : DimmableBackgroundContainer + private class TestDimmableBackground : BackgroundScreenBeatmap.DimmableBackground { public Color4 CurrentColour => Content.Colour; public float CurrentAlpha => Content.Alpha; diff --git a/osu.Game/Graphics/Containers/DimmableBackgroundContainer.cs b/osu.Game/Graphics/Containers/DimmableBackgroundContainer.cs deleted file mode 100644 index 32b333528f..0000000000 --- a/osu.Game/Graphics/Containers/DimmableBackgroundContainer.cs +++ /dev/null @@ -1,71 +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 System; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Game.Configuration; -using osu.Game.Graphics.Backgrounds; -using osu.Game.Screens.Play; -using osuTK; - -namespace osu.Game.Graphics.Containers -{ - public class DimmableBackgroundContainer : UserDimContainer - { - /// - /// The amount of blur to be applied to the background in addition to user-specified blur. - /// - /// - /// Used in contexts where there can potentially be both user and screen-specified blurring occuring at the same time, such as in - /// - public readonly Bindable BlurAmount = new Bindable(); - - public Background Background - { - get => background; - set - { - base.Add(background = value); - background.BlurTo(blurTarget, 0, Easing.OutQuint); - } - } - - private Bindable userBlurLevel { get; set; } - - private Background background; - - public override void Add(Drawable drawable) - { - if (drawable is Background) - throw new InvalidOperationException($"Use {nameof(Background)} to set a background."); - - base.Add(drawable); - } - - /// - /// As an optimisation, we add the two blur portions to be applied rather than actually applying two separate blurs. - /// - private Vector2 blurTarget => EnableUserDim.Value - ? new Vector2(BlurAmount.Value + (float)userBlurLevel.Value * 25) - : new Vector2(BlurAmount.Value); - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - userBlurLevel = config.GetBindable(OsuSetting.BlurLevel); - userBlurLevel.ValueChanged += _ => UpdateVisuals(); - BlurAmount.ValueChanged += _ => UpdateVisuals(); - } - - 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() - { - base.UpdateVisuals(); - - Background?.BlurTo(blurTarget, BACKGROUND_FADE_DURATION, Easing.OutQuint); - } - } -} diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 97f77f789a..8d6caf4c71 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -1,14 +1,18 @@ // 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.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; +using osu.Game.Screens.Play; +using osuTK; namespace osu.Game.Screens.Backgrounds { @@ -30,16 +34,17 @@ namespace osu.Game.Screens.Backgrounds /// public readonly Bindable BlurAmount = new Bindable(); - private readonly DimmableBackgroundContainer fadeContainer; + private readonly DimmableBackground dimmable; - protected virtual DimmableBackgroundContainer CreateFadeContainer() => new DimmableBackgroundContainer { RelativeSizeAxes = Axes.Both }; + protected virtual DimmableBackground CreateFadeContainer() => new DimmableBackground { RelativeSizeAxes = Axes.Both }; public BackgroundScreenBeatmap(WorkingBeatmap beatmap = null) { Beatmap = beatmap; - InternalChild = fadeContainer = CreateFadeContainer(); - fadeContainer.EnableUserDim.BindTo(EnableUserDim); - fadeContainer.BlurAmount.BindTo(BlurAmount); + + InternalChild = dimmable = CreateFadeContainer(); + dimmable.EnableUserDim.BindTo(EnableUserDim); + dimmable.BlurAmount.BindTo(BlurAmount); } [BackgroundDependencyLoader] @@ -86,8 +91,8 @@ namespace osu.Game.Screens.Backgrounds } b.Depth = newDepth; - fadeContainer.Background = Background = b; - StoryboardReplacesBackground.BindTo(fadeContainer.StoryboardReplacesBackground); + dimmable.Background = Background = b; + StoryboardReplacesBackground.BindTo(dimmable.StoryboardReplacesBackground); } public override bool Equals(BackgroundScreen other) @@ -112,5 +117,62 @@ namespace osu.Game.Screens.Backgrounds Sprite.Texture = Beatmap?.Background ?? textures.Get(@"Backgrounds/bg1"); } } + + public class DimmableBackground : UserDimContainer + { + /// + /// The amount of blur to be applied to the background in addition to user-specified blur. + /// + /// + /// Used in contexts where there can potentially be both user and screen-specified blurring occuring at the same time, such as in + /// + public readonly Bindable BlurAmount = new Bindable(); + + public Background Background + { + get => background; + set + { + base.Add(background = value); + background.BlurTo(blurTarget, 0, Easing.OutQuint); + } + } + + private Bindable userBlurLevel { get; set; } + + private Background background; + + public override void Add(Drawable drawable) + { + if (drawable is Background) + throw new InvalidOperationException($"Use {nameof(Background)} to set a background."); + + base.Add(drawable); + } + + /// + /// As an optimisation, we add the two blur portions to be applied rather than actually applying two separate blurs. + /// + private Vector2 blurTarget => EnableUserDim.Value + ? new Vector2(BlurAmount.Value + (float)userBlurLevel.Value * 25) + : new Vector2(BlurAmount.Value); + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + userBlurLevel = config.GetBindable(OsuSetting.BlurLevel); + userBlurLevel.ValueChanged += _ => UpdateVisuals(); + BlurAmount.ValueChanged += _ => UpdateVisuals(); + } + + 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() + { + base.UpdateVisuals(); + + Background?.BlurTo(blurTarget, BACKGROUND_FADE_DURATION, Easing.OutQuint); + } + } } } diff --git a/osu.Game/Graphics/Containers/DimmableStoryboardContainer.cs b/osu.Game/Screens/Play/DimmableStoryboard.cs similarity index 89% rename from osu.Game/Graphics/Containers/DimmableStoryboardContainer.cs rename to osu.Game/Screens/Play/DimmableStoryboard.cs index d0ef7a2c25..45dff039b6 100644 --- a/osu.Game/Graphics/Containers/DimmableStoryboardContainer.cs +++ b/osu.Game/Screens/Play/DimmableStoryboard.cs @@ -2,20 +2,21 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Game.Graphics.Containers; using osu.Game.Storyboards; using osu.Game.Storyboards.Drawables; -namespace osu.Game.Graphics.Containers +namespace osu.Game.Screens.Play { /// /// A container that handles loading, as well as applies user-specified visual settings to it. /// - public class DimmableStoryboardContainer : UserDimContainer + public class DimmableStoryboard : UserDimContainer { private readonly Storyboard storyboard; private DrawableStoryboard drawableStoryboard; - public DimmableStoryboardContainer(Storyboard storyboard) + public DimmableStoryboard(Storyboard storyboard) { this.storyboard = storyboard; } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 55e759d215..0396d85d75 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -76,9 +76,9 @@ namespace osu.Game.Screens.Play protected GameplayClockContainer GameplayClockContainer { get; private set; } - protected DimmableStoryboardContainer DimmableStoryboardContainer { get; private set; } + protected DimmableStoryboard DimmableStoryboard { get; private set; } - protected virtual DimmableStoryboardContainer CreateStoryboardContainer(Storyboard storyboard) => new DimmableStoryboardContainer(storyboard) { RelativeSizeAxes = Axes.Both }; + protected virtual DimmableStoryboard CreateStoryboardContainer(Storyboard storyboard) => new DimmableStoryboard(storyboard) { RelativeSizeAxes = Axes.Both }; [Cached] [Cached(Type = typeof(IBindable>))] @@ -124,7 +124,7 @@ namespace osu.Game.Screens.Play GameplayClockContainer.Children = new[] { - DimmableStoryboardContainer = CreateStoryboardContainer(Beatmap.Value.Storyboard), + DimmableStoryboard = CreateStoryboardContainer(Beatmap.Value.Storyboard), new ScalingContainer(ScalingMode.Gameplay) { Child = new LocalSkinOverrideContainer(working.Skin) @@ -455,7 +455,7 @@ namespace osu.Game.Screens.Play Background.BlurAmount.Value = 0; Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); - DimmableStoryboardContainer.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); + DimmableStoryboard.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); storyboardReplacesBackground.Value = Beatmap.Value.Storyboard.ReplacesBackground && Beatmap.Value.Storyboard.HasDrawable; From 8b67f88d161283548df4ff732d7f2e33fefa528c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2019 12:04:45 +0900 Subject: [PATCH 0193/2815] Don't expose dimmable container creation in player --- .../Background/TestSceneUserDimContainer.cs | 23 ++++--------------- .../Graphics/Containers/UserDimContainer.cs | 9 +++++++- osu.Game/Screens/Play/Player.cs | 5 +--- 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index 5578fee42d..f0893abadc 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -29,7 +29,6 @@ using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Play; using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Select; -using osu.Game.Storyboards; using osu.Game.Tests.Resources; using osu.Game.Users; using osuTK; @@ -139,14 +138,14 @@ namespace osu.Game.Tests.Visual.Background player.StoryboardEnabled.Value = true; }); waitForDim(); - AddAssert("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible()); + AddAssert("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible); AddStep("Storyboard Disabled", () => { player.ReplacesBackground.Value = false; player.StoryboardEnabled.Value = false; }); waitForDim(); - AddAssert("Background is visible, storyboard is invisible", () => songSelect.IsBackgroundVisible() && player.IsStoryboardInvisible()); + AddAssert("Background is visible, storyboard is invisible", () => songSelect.IsBackgroundVisible() && !player.IsStoryboardVisible); } /// @@ -336,9 +335,7 @@ namespace osu.Game.Tests.Visual.Background { protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value); - protected override DimmableStoryboard CreateStoryboardContainer(Storyboard storyboard) => new TestDimmableStoryboard { RelativeSizeAxes = Axes.Both }; - - public new TestDimmableStoryboard DimmableStoryboard => (TestDimmableStoryboard)base.DimmableStoryboard; + public new DimmableStoryboard DimmableStoryboard => base.DimmableStoryboard; // Whether or not the player should be allowed to load. public bool BlockLoad; @@ -352,9 +349,7 @@ namespace osu.Game.Tests.Visual.Background { } - public bool IsStoryboardVisible() => DimmableStoryboard.CurrentAlpha == 1; - - public bool IsStoryboardInvisible() => DimmableStoryboard.CurrentAlpha <= 1; + public bool IsStoryboardVisible => DimmableStoryboard.ContentDisplayed; [BackgroundDependencyLoader] private void load(OsuConfigManager config, CancellationToken token) @@ -403,16 +398,6 @@ namespace osu.Game.Tests.Visual.Background } } - private class TestDimmableStoryboard : DimmableStoryboard - { - public float CurrentAlpha => Content.Alpha; - - public TestDimmableStoryboard() - : base(new Storyboard()) - { - } - } - private class TestDimmableBackground : BackgroundScreenBeatmap.DimmableBackground { public Color4 CurrentColour => Content.Colour; diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index f5958a092b..03de5f651f 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -27,6 +27,11 @@ namespace osu.Game.Graphics.Containers /// public readonly Bindable StoryboardReplacesBackground = new Bindable(); + /// + /// Whether the content of this container is currently being displayed. + /// + public bool ContentDisplayed { get; private set; } + protected Bindable UserDimLevel { get; private set; } protected Bindable ShowStoryboard { get; private set; } @@ -71,7 +76,9 @@ namespace osu.Game.Graphics.Containers /// protected virtual void UpdateVisuals() { - dimContent.FadeTo(ShowDimContent ? 1 : 0, BACKGROUND_FADE_DURATION, Easing.OutQuint); + ContentDisplayed = ShowDimContent; + + dimContent.FadeTo((ContentDisplayed) ? 1 : 0, BACKGROUND_FADE_DURATION, Easing.OutQuint); dimContent.FadeColour(EnableUserDim.Value ? OsuColour.Gray(1 - (float)UserDimLevel.Value) : Color4.White, BACKGROUND_FADE_DURATION, Easing.OutQuint); } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 0396d85d75..8dc16af575 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -26,7 +26,6 @@ using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Skinning; -using osu.Game.Storyboards; using osu.Game.Users; namespace osu.Game.Screens.Play @@ -78,8 +77,6 @@ namespace osu.Game.Screens.Play protected DimmableStoryboard DimmableStoryboard { get; private set; } - protected virtual DimmableStoryboard CreateStoryboardContainer(Storyboard storyboard) => new DimmableStoryboard(storyboard) { RelativeSizeAxes = Axes.Both }; - [Cached] [Cached(Type = typeof(IBindable>))] protected new readonly Bindable> Mods = new Bindable>(Array.Empty()); @@ -124,7 +121,7 @@ namespace osu.Game.Screens.Play GameplayClockContainer.Children = new[] { - DimmableStoryboard = CreateStoryboardContainer(Beatmap.Value.Storyboard), + DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }, new ScalingContainer(ScalingMode.Gameplay) { Child = new LocalSkinOverrideContainer(working.Skin) From 671f7f99cd139bf255a3d7e393d9e8ac211517df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2019 13:44:43 +0900 Subject: [PATCH 0194/2815] Use constant for blur amount --- .../Visual/Background/TestSceneUserDimContainer.cs | 2 +- osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index f0893abadc..dc4ceed59e 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -302,7 +302,7 @@ namespace osu.Game.Tests.Visual.Background public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).CurrentColour == Color4.White; - public bool IsUserBlurApplied() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2((float)BlurLevel.Value * 25); + public bool IsUserBlurApplied() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2((float)BlurLevel.Value * BackgroundScreenBeatmap.USER_BLUR_FACTOR); public bool IsUserBlurDisabled() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(0); diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 8d6caf4c71..7b4feb1819 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -18,6 +18,11 @@ namespace osu.Game.Screens.Backgrounds { public class BackgroundScreenBeatmap : BackgroundScreen { + /// + /// The amount of blur to apply when full user blur is requested. + /// + public const float USER_BLUR_FACTOR = 25; + protected Background Background; private WorkingBeatmap beatmap; @@ -154,7 +159,7 @@ namespace osu.Game.Screens.Backgrounds /// As an optimisation, we add the two blur portions to be applied rather than actually applying two separate blurs. /// private Vector2 blurTarget => EnableUserDim.Value - ? new Vector2(BlurAmount.Value + (float)userBlurLevel.Value * 25) + ? new Vector2(BlurAmount.Value + (float)userBlurLevel.Value * USER_BLUR_FACTOR) : new Vector2(BlurAmount.Value); [BackgroundDependencyLoader] From 44855d8bb2203af41b8c57c7f56b1f12172c208e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2019 13:45:34 +0900 Subject: [PATCH 0195/2815] Ensure any existing background is expired if set more than once --- osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 7b4feb1819..08f1881038 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -138,6 +138,8 @@ namespace osu.Game.Screens.Backgrounds get => background; set { + background?.Expire(); + base.Add(background = value); background.BlurTo(blurTarget, 0, Easing.OutQuint); } From 09d679de8d6367ec6894e470318d3cda625d5fd3 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 12 Jul 2019 08:50:53 +0200 Subject: [PATCH 0196/2815] Move text display layout to Toast. --- .../UserInterface/TestSceneOnScreenDisplay.cs | 4 ++ osu.Game/Overlays/OSD/Toast.cs | 35 +++++++++++++++- osu.Game/Overlays/OSD/TrackedSettingToast.cs | 41 +++---------------- 3 files changed, 42 insertions(+), 38 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs index ca14822205..4decfc7dd6 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs @@ -90,6 +90,10 @@ namespace osu.Game.Tests.Visual.UserInterface private class EmptyToast : Toast { + public EmptyToast() + : base("", "", "") + { + } } private class TestOnScreenDisplay : OnScreenDisplay diff --git a/osu.Game/Overlays/OSD/Toast.cs b/osu.Game/Overlays/OSD/Toast.cs index c6e3227cd1..6572830d10 100644 --- a/osu.Game/Overlays/OSD/Toast.cs +++ b/osu.Game/Overlays/OSD/Toast.cs @@ -4,6 +4,9 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK; using osuTK.Graphics; namespace osu.Game.Overlays.OSD @@ -13,7 +16,7 @@ namespace osu.Game.Overlays.OSD private readonly Container content; protected override Container Content => content; - protected Toast() + protected Toast(string description, string value, string keybinding) { Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -35,7 +38,35 @@ namespace osu.Game.Overlays.OSD Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - } + }, + new OsuSpriteText + { + Padding = new MarginPadding(10), + Name = "Description", + Font = OsuFont.GetFont(size: 14, weight: FontWeight.Black), + Spacing = new Vector2(1, 0), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = description.ToUpperInvariant() + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 24, weight: FontWeight.Light), + Padding = new MarginPadding { Left = 10, Right = 10 }, + Name = "Value", + Anchor = Anchor.Centre, + Origin = Anchor.BottomCentre, + Text = value + }, + new OsuSpriteText + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Name = "Shortcut", + Margin = new MarginPadding { Bottom = 15 }, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Text = string.IsNullOrEmpty(keybinding) ? "NO KEY BOUND" : keybinding.ToUpperInvariant() + }, }; } } diff --git a/osu.Game/Overlays/OSD/TrackedSettingToast.cs b/osu.Game/Overlays/OSD/TrackedSettingToast.cs index 8def8476f0..454ba84d70 100644 --- a/osu.Game/Overlays/OSD/TrackedSettingToast.cs +++ b/osu.Game/Overlays/OSD/TrackedSettingToast.cs @@ -9,9 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; @@ -20,55 +18,29 @@ namespace osu.Game.Overlays.OSD public class TrackedSettingToast : Toast { public TrackedSettingToast(SettingDescription description) + : base(description.Name, description.Value, description.Shortcut) { - SpriteText textLine2; FillFlowContainer optionLights; Children = new Drawable[] { - new OsuSpriteText - { - Padding = new MarginPadding(10), - Font = OsuFont.GetFont(size: 14, weight: FontWeight.Black), - Spacing = new Vector2(1, 0), - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = description.Name.ToUpperInvariant() - }, - textLine2 = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 24, weight: FontWeight.Light), - Padding = new MarginPadding { Left = 10, Right = 10 }, - Anchor = Anchor.Centre, - Origin = Anchor.BottomCentre, - Text = description.Value - }, new FillFlowContainer { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, Direction = FillDirection.Vertical, + Margin = new MarginPadding { Top = 70 }, Children = new Drawable[] { optionLights = new FillFlowContainer { - Padding = new MarginPadding { Top = 20, Bottom = 5 }, + Padding = new MarginPadding { Bottom = 5 }, Spacing = new Vector2(5, 0), Direction = FillDirection.Horizontal, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, AutoSizeAxes = Axes.Both }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Margin = new MarginPadding { Bottom = 15 }, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), - Alpha = 0.3f, - Text = string.IsNullOrEmpty(description.Shortcut) ? "NO KEY BOUND" : description.Shortcut.ToUpperInvariant() - }, } } }; @@ -90,9 +62,6 @@ namespace osu.Game.Overlays.OSD break; } - textLine2.Origin = optionCount > 0 ? Anchor.BottomCentre : Anchor.Centre; - textLine2.Y = optionCount > 0 ? 0 : 5; - for (int i = 0; i < optionCount; i++) { optionLights.Add(new OptionLight From b24a6e64bdf994fd729bf4bd1e99d4161460ad9f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2019 16:22:02 +0900 Subject: [PATCH 0197/2815] Add link to bounty history to readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2c330e403c..35dc010d93 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ Before starting, please make sure you are familiar with the [development and tes Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured; with any libraries we are using; with any processes involved with contributing, *please* bring it up. We welcome all feedback so we can make contributing to this project as pain-free as possible. -For those interested, we love to reward quality contributions via bounties, paid out via paypal or osu! supporter tags. Don't hesitate to [request a bounty](https://docs.google.com/forms/d/e/1FAIpQLSet_8iFAgPMG526pBZ2Kic6HSh7XPM3fE8xPcnWNkMzINDdYg/viewform) for your work on this project. +For those interested, we love to reward quality contributions via [bounties](https://docs.google.com/spreadsheets/d/1jNXfj_S3Pb5PErA-czDdC9DUu4IgUbe1Lt8E7CYUJuE/view?&rm=minimal#gid=523803337), paid out via paypal or osu! supporter tags. Don't hesitate to [request a bounty](https://docs.google.com/forms/d/e/1FAIpQLSet_8iFAgPMG526pBZ2Kic6HSh7XPM3fE8xPcnWNkMzINDdYg/viewform) for your work on this project. ## Licence From 376d228add3a4fa34a0d16fbd2af635fd20a994d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2019 17:49:59 +0900 Subject: [PATCH 0198/2815] Use new logo style for icons / readme --- README.md | 2 +- assets/lazer.png | Bin 39498 -> 77579 bytes osu.Desktop/lazer.ico | Bin 93005 -> 86650 bytes .../AppIcon.appiconset/Contents.json | 250 +----------------- .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 1237 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 3602 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 6457 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 2250 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 6069 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 10842 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 3602 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 9677 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 16681 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 16681 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 28381 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 8946 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 22553 -> 0 bytes .../Icon-App-83.5x83.5@2x.png | Bin 25862 -> 0 bytes .../AppIcon.appiconset/ItunesArtwork@2x.png | Bin 251219 -> 0 bytes .../AppIcon.appiconset/iOSAppStore.png | Bin 0 -> 350640 bytes .../AppIcon.appiconset/iPadApp1x.png | Bin 0 -> 10106 bytes .../AppIcon.appiconset/iPadApp2x.png | Bin 0 -> 24966 bytes .../AppIcon.appiconset/iPadNotification1x.png | Bin 0 -> 2227 bytes .../AppIcon.appiconset/iPadNotification2x.png | Bin 0 -> 4485 bytes .../AppIcon.appiconset/iPadProApp2x.png | Bin 0 -> 28089 bytes .../AppIcon.appiconset/iPadSettings1x.png | Bin 0 -> 3192 bytes .../AppIcon.appiconset/iPadSettings2x.png | Bin 0 -> 7156 bytes .../AppIcon.appiconset/iPadSpotlight1x.png | Bin 0 -> 4485 bytes .../AppIcon.appiconset/iPadSpotlight2x.png | Bin 0 -> 10768 bytes .../AppIcon.appiconset/iPhoneApp2x.png | Bin 0 -> 18204 bytes .../AppIcon.appiconset/iPhoneApp3x.png | Bin 0 -> 30946 bytes .../iPhoneNotification2x.png | Bin 0 -> 4485 bytes .../iPhoneNotification3x.png | Bin 0 -> 7458 bytes .../AppIcon.appiconset/iPhoneSettings2x.png | Bin 0 -> 7156 bytes .../AppIcon.appiconset/iPhoneSettings3x.png | Bin 0 -> 12085 bytes .../AppIcon.appiconset/iPhoneSpotlight2x.png | Bin 0 -> 10768 bytes .../AppIcon.appiconset/iPhoneSpotlight3x.png | Bin 0 -> 18204 bytes osu.iOS/iTunesArtwork | Bin 99849 -> 142214 bytes osu.iOS/iTunesArtwork@2x | Bin 251219 -> 350640 bytes osu.iOS/osu.iOS.csproj | 41 +-- 40 files changed, 24 insertions(+), 269 deletions(-) mode change 100644 => 100755 osu.Desktop/lazer.ico delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png delete mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iOSAppStore.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadApp1x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadApp2x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadNotification1x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadNotification2x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadProApp2x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSettings1x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSettings2x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSpotlight1x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSpotlight2x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneApp2x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneApp3x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneNotification2x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneNotification3x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSettings2x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSettings3x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSpotlight2x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSpotlight3x.png diff --git a/README.md b/README.md index 35dc010d93..590442f01b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- +

# osu! diff --git a/assets/lazer.png b/assets/lazer.png index 075a8e7184ad508cedd8fe9ae549d8cea696b51a..19b5fc2151e570bf321596998437072943422c26 100644 GIT binary patch literal 77579 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±JV~w(ThIDK>wg};C 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 X{r?ZpP_kcw2Ot$CjR&O)ra}J$c_$^Y diff --git a/osu.Desktop/lazer.ico b/osu.Desktop/lazer.ico old mode 100644 new mode 100755 index 0c894dca412abb8566e630dbc82397e4c79395c9..a6aa8abb9f3f10a2aac07c1168d9a2adb25ce1ea GIT binary patch literal 86650 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

@r7l@_>dLwGP1inIvgp>RYo9-;{?Vr` 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 V!WImv>?mfrbAj*puHX3W{{h`h-3$N# diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json index bbd5f4f3ba..af4b103867 100644 --- a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,249 +1 @@ -{ - "images": [ - { - "filename": "Icon-App-20x20@2x.png", - "size": "20x20", - "scale": "2x", - "idiom": "iphone" - }, - { - "filename": "Icon-App-20x20@3x.png", - "size": "20x20", - "scale": "3x", - "idiom": "iphone" - }, - { - "filename": "Icon-App-29x29@2x.png", - "size": "29x29", - "scale": "2x", - "idiom": "iphone" - }, - { - "filename": "Icon-App-29x29@3x.png", - "size": "29x29", - "scale": "3x", - "idiom": "iphone" - }, - { - "filename": "Icon-App-40x40@2x.png", - "size": "40x40", - "scale": "2x", - "idiom": "iphone" - }, - { - "filename": "Icon-App-40x40@3x.png", - "size": "40x40", - "scale": "3x", - "idiom": "iphone" - }, - { - "filename": "Icon-App-60x60@2x.png", - "size": "60x60", - "scale": "2x", - "idiom": "iphone" - }, - { - "filename": "Icon-App-60x60@3x.png", - "size": "60x60", - "scale": "3x", - "idiom": "iphone" - }, - { - "filename": "Icon-App-20x20@1x.png", - "size": "20x20", - "scale": "1x", - "idiom": "ipad" - }, - { - "filename": "Icon-App-20x20@2x.png", - "size": "20x20", - "scale": "2x", - "idiom": "ipad" - }, - { - "filename": "Icon-App-29x29@1x.png", - "size": "29x29", - "scale": "1x", - "idiom": "ipad" - }, - { - "filename": "Icon-App-29x29@2x.png", - "size": "29x29", - "scale": "2x", - "idiom": "ipad" - }, - { - "filename": "Icon-App-40x40@1x.png", - "size": "40x40", - "scale": "1x", - "idiom": "ipad" - }, - { - "filename": "Icon-App-40x40@2x.png", - "size": "40x40", - "scale": "2x", - "idiom": "ipad" - }, - { - "filename": "Icon-App-83.5x83.5@2x.png", - "size": "83.5x83.5", - "scale": "2x", - "idiom": "ipad" - }, - { - "filename": "Icon-App-76x76@1x.png", - "size": "76x76", - "scale": "1x", - "idiom": "ipad" - }, - { - "filename": "Icon-App-76x76@2x.png", - "size": "76x76", - "scale": "2x", - "idiom": "ipad" - }, - { - "filename": "ItunesArtwork@2x.png", - "size": "1024x1024", - "scale": "1x", - "idiom": "ios-marketing" - }, - { - "size": "60x60", - "scale": "2x", - "idiom": "car" - }, - { - "size": "60x60", - "scale": "3x", - "idiom": "car" - }, - { - "role": "notificationCenter", - "size": "24x24", - "subtype": "38mm", - "scale": "2x", - "idiom": "watch" - }, - { - "role": "notificationCenter", - "size": "27.5x27.5", - "subtype": "42mm", - "scale": "2x", - "idiom": "watch" - }, - { - "role": "companionSettings", - "size": "29x29", - "scale": "2x", - "idiom": "watch" - }, - { - "role": "companionSettings", - "size": "29x29", - "scale": "3x", - "idiom": "watch" - }, - { - "role": "appLauncher", - "size": "40x40", - "subtype": "38mm", - "scale": "2x", - "idiom": "watch" - }, - { - "role": "appLauncher", - "size": "44x44", - "subtype": "40mm", - "scale": "2x", - "idiom": "watch" - }, - { - "role": "appLauncher", - "size": "50x50", - "subtype": "44mm", - "scale": "2x", - "idiom": "watch" - }, - { - "role": "quickLook", - "size": "86x86", - "subtype": "38mm", - "scale": "2x", - "idiom": "watch" - }, - { - "role": "quickLook", - "size": "98x98", - "subtype": "42mm", - "scale": "2x", - "idiom": "watch" - }, - { - "role": "quickLook", - "size": "108x108", - "subtype": "44mm", - "scale": "2x", - "idiom": "watch" - }, - { - "size": "1024x1024", - "scale": "1x", - "idiom": "watch-marketing" - }, - { - "size": "16x16", - "scale": "1x", - "idiom": "mac" - }, - { - "size": "16x16", - "scale": "2x", - "idiom": "mac" - }, - { - "size": "32x32", - "scale": "1x", - "idiom": "mac" - }, - { - "size": "32x32", - "scale": "2x", - "idiom": "mac" - }, - { - "size": "128x128", - "scale": "1x", - "idiom": "mac" - }, - { - "size": "128x128", - "scale": "2x", - "idiom": "mac" - }, - { - "size": "256x256", - "scale": "1x", - "idiom": "mac" - }, - { - "size": "256x256", - "scale": "2x", - "idiom": "mac" - }, - { - "size": "512x512", - "scale": "1x", - "idiom": "mac" - }, - { - "size": "512x512", - "scale": "2x", - "idiom": "mac" - } - ], - "info": { - "version": 1, - "author": "xcode" - } -} \ No newline at end of file +{"images":[{"size":"20x20","idiom":"iphone","filename":"iPhoneNotification2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"iPhoneNotification3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"iPhoneSettings2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"iPhoneSettings3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"iPhoneSpotlight2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"iPhoneSpotlight3x.png","scale":"3x"},{"size":"60x60","idiom":"iphone","filename":"iPhoneApp2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"iPhoneApp3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"iPadNotification1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"iPadNotification2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"iPadSettings1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"iPadSettings2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"iPadSpotlight1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"iPadSpotlight2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"iPadApp1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"iPadApp2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"iPadProApp2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"iOSAppStore.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} \ No newline at end of file diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index dfcce1297b8ad9b84ca073334fb12d949c51d250..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1237 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 Y2YK{MGIEg$@&Et;07*qoM6N<$g67-*fB*mh diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index 8a21d9f81d2a2c0f6b8658761476e32ad36565f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6457 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 T58IaJ00000NkvXXu0mjfwzQG) diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4477ece73a546a35bbb5abc8fc20c1645683e607..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2250 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 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index 0f8c36a03870c9e7c0561fe0ca1e80d9fb7446a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10842 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 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png deleted file mode 100644 index d563e8cf5e44bfb5c1dd733564da90f9379defdb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3602 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 Y2YK{MGIEg$@&Et;07*qoM6N<$g67-*fB*mh diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index 0208f80e3bc60c86225146f78a3497db63738b23..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9677 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 P00000NkvXXu0mjfGd9th diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index f7ad8bb6bbbf3adf4610e6d613db4057bc46eb45..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16681 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 BAs_$% diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index f7ad8bb6bbbf3adf4610e6d613db4057bc46eb45..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16681 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 BAs_$% diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index 3d85a133aa71f1e744c51c96acfda1d19f9edfb6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28381 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 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index edd53bc9542a7dfcb242f917db19752046a120df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8946 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 q1s6C9c^_cU&A_fDRS@U>S36fzJQ* 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 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index edff3788f08b1e096269fd187f08fdf65594dab1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25862 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 BVm<%> diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png deleted file mode 100644 index c04f6f3ec4965f7b06e29a1bda5b8d5fe27ba72c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 251219 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 ihBCBv)8__?8cC&XLrk0D^;$L%D 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�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 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iOSAppStore.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iOSAppStore.png new file mode 100644 index 0000000000000000000000000000000000000000..0e8bb029bc964d313c07fea891a1fcfa1b10e139 GIT binary patch literal 350640 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 z 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

?+l7 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�Kv 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 z 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 VRQY{WNd^D_002ovPDHLkV1gW!zV844 literal 0 HcmV?d00001 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadNotification1x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadNotification1x.png new file mode 100644 index 0000000000000000000000000000000000000000..8c483a0a7a92ab5a46e6cfcfa49f8fdf5ddb173b GIT binary patch literal 2227 zcmV;k2u$~hP)Hg1+lHrgWSWcKdPn90sKGrRqvPeo9CG3uKX#J{(IASm?@+di}}l?o-=)F3E6 zwD^Ni=!>T7nL9I?X}YoAW$t|Qo$sD|?zw001?ah|SeB6#0T!CBEf+H4bBB+JJu8re zhoBb*p;u8ID_yBf0ya+zcePvJL&AGs+11_tpRKn>9TgyPA7ZoSs0)aX0r00)%XR^J z`jH<$>RKN5V(7OqK*TS4xZz{h!*f1C3ECFkK$#7nA@pGN!$;%jYv zwjAKwmYb0gKL(K8-kPtb5${A?tlI~wzMrJ6wTdBr=Y%%%EaEMQ&o}4FQ^DA)s*}Z> z!FI&AHCpoWI|RUqx?7s@$8!5^Q=anY%X@i5{QA6kNcMelpE>R6eCYFpmMsVT zrI(b06~u#xf1yS}_UGdMvD``!0~u->P=lA4?YN`hilQ z|3tHka)7T{2CGqwjZfMwx$5irQN_*|e4l)UHmiYuz74Yp1t^#>hrJ3-SOXDcC_o0^ z7T9R1gAN8V6s;5)ieI5-7aQlmJn}lUna#nz!j%5V$X|o`xX!dHWQRV27P1=rj;t2b zW$~+pTw@bIek?ZvKPDL<64`^#UNTAck#RBsB6*5DP4<%UA_FqU$I>2EH_cM;u)Q~SI+rg`Rn{L z_AC5qq~L$#SMj%U$6Cz0vP{G5Y*=%5RT^yu;}-DInZ=349rJPVM6C3K^oO)8y(fJr{l>k`ead~!ea?NsT>_Ci%bnxC;Vy6= zb6>{xYV#Ue-+LB$7`JEXmTRm^AtP)R9u{)KHsMiWGV&)32xCG~*nyU<>-!d;FP=Re z4r3qYr~6#KE>;1F`>_J_P5xC?ROxV(DIHdCO*p$HRQI@7^PwV@Pvuf+ z5K}u-6REM(K@W$srgorh0{i?O)v0c>QtHxU-hBdD(>iYJ4b2sIOVX2K8m~4gmYVA5 zh^QEb$V`rCQ-|7ZS{nuL-t>?3n=-o(6I(7vocj#GzCZEo`!3>+v;dYIfPu#&ZWzzX z2i^rZ^Mu;6+rb@?NPG+6)c5T6zxpzGe*M(x+{AON=PiJ>H#?ob-|uwRK0yDg0B4PV z0id6JRRdfL?*ISMZUrf*k6*ChPpT+|w%)~?&LZ-nb17Y3hFmq)L#-QuEuI;_O zzdYwVMi9SEyY}95Kj(ZtpL5PcPfu@12SNxudH9;=dGLK-UFP3>q#+@7`M-E|lkan2 zys@ zorE0Ap?WEYT45b7G0=ntUDA7d4Dr5w(B4hq`>|1+J^LjBdXArGR(fk|>o5blu7^*C zhH&QS2^2s53C@*y$^wvzu%Z^E89^XI1a%LXdjKQ5g5HyFA|iE6{544lg_2B(W|JOA z`VM1o;60Q+y9jS_6`mpB2fnJBvxh-IMri7xNZdkrw}jw}-*CF?b@cbXjDWM#(Kwj( z`S625gcoiAOC@Ngp(xZnR|)U>9z3FD5G8)ssDP>-z+NpwpIyP3L+`*eO$DiOU?!bb z22?N4K@bIsMM%^_a#nbx4X83o>eOsiv0bSm=uSZI>x46V2dTz3?0c?*l6sIn^;(Jm zH_ z3cR%qB%7KMl6D%2MNLb#`vEp=qC%iSp+X=o{8AMgKl}l?zZJ5j5#BvAB4&`&F0kPs zBu0ibsGTAVqmIUoEZqCu5ogS#E@u@E> z>r`o56Rxe@#&rH~NCxCGTmty!yK^v(zX*NTGtfwIp%#Up&7vb>utu93iTlm6O!%au zo@j*3wBg&!7nGsA7xZK@In2TTEG)p--hjiWjv|eGe5%Z?o1@8a=002ovPDHLkV1i)_ BI!^!q literal 0 HcmV?d00001 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadNotification2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadNotification2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a45b01b91c65e3a8d8feefe5122bef85c77a78fe GIT binary patch literal 4485 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 X5L}ax?^OsQ00000NkvXXu0mjf-pQ8F literal 0 HcmV?d00001 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadProApp2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadProApp2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d2ba8f3a7ee97837b87d619cb0f7c1f3243d7591 GIT binary patch literal 28089 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 Y0}pcCLt@!TD*ylh07*qoM6N<$f{85HlmGw# literal 0 HcmV?d00001 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSettings1x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSettings1x.png new file mode 100644 index 0000000000000000000000000000000000000000..43d577040eeca37d88ca15030a9994678c44f14a GIT binary patch literal 3192 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 X5L}ax?^OsQ00000NkvXXu0mjf-pQ8F literal 0 HcmV?d00001 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSpotlight2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSpotlight2x.png new file mode 100644 index 0000000000000000000000000000000000000000..717603dd684e9a71f55243678b954a70b89f0a13 GIT binary patch literal 10768 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

rD-NlRi2>YR3M-xKh z9c4Kh`=aD%&%Ndl%vr+n}RYro+f%j>+#f{sNr+KALrZ~C8bYLjxa@X>BCtfCeq)hRhhs? z-~Ze8kq>^v>i6|nL7#UsKJXM&7f7IQ(oXjL*|Yt8=K*~{qg%%f_dd1Bk3Ou|TxhF~ z2R;?RLiDUj3Ce<@%1B4+NMiZeWYZ-&PXE^PtU-h`2#bZxI%d(;2}@#BvKTk7g^9y+ z+U;{+_?&&}Z$B#>X-AnK$A@;mOJC9E1dwPl($bZjwyd%li%*|(_C-NEjWm9lkd#unpyJ7=zK)B?Fr-tiZ9*AMTJ!78ttRC177RC3a{L)=iQ ziNBvx0*C4bl^qiqLU&7=r|EkGzy7Q5w)gz{do+<;#bVEI`ukM=gjLIj!l=b9RwR4E%ntJ5Nj4jgyMV-8EAae!#FgIqyHd;Ln%J#0GK3n2Lw@H zPOxDKl7?(WCIgPBKjzp3S;`I}e)5!*Pkzh*=?iYABQ3gyyY0?zf6Ko34}WhPHg5Fw z%Pf+kp9L2IAED0;S)Ite8lKa6O&Eb8T<~F|dU&Q1G%5_S2m`Pnd6&2*6C;dSET#$1 zSiZvE^~>+Hx4h*oHhbD>a&4-gdu@LrK_DAM$HY!DEFoMEs5|;&JZ3CH;4UU`=SfKC zeXQz&M3W~MV_BZ5B6*|pQNW2oEqIP|o-hK3X-0@iC1^yb3=IoJrf|Z;l@L0SW+c0>FCp}T zdGqbfx7}vH@C$FV^UlI zm&P;iCWOEgL;*C6DMuuk`Cb4*QYqY>G3OE<-UoGqt2C%@^62f=7dF*@tz5a%ZWFPu zx#k*Mdio-{Je5-YqG|Yq)5JdkAfro#C4_?Lz*xlNy;nhh{LXXfH|dd5so$LXW1Fkt z5MSYz=HI-1tNrwW2kh><@3!Zjd(I9YIqYq1(Q+fS71{|zC+$s$OHpy#c|H*ccqV`F z9r6U1xH&Og(|Dc`0z-_@$52=x7!^QrA#iw~ZrBK+QQILcgdQ1Q^(j3cyO=72?ELf3 zvsJ6Ew5zVVQr7n}ecwsQhu}yxYr{B2R(+|TMTPGhwHs6~1Oy-;#22FY>;T6y<~UQD z)B89wP^SmUf$jw5sNM~mHrk_)K5CCW`k1}2<^|igZ@*7=z|eT1KsfCHxvY9*+|RqD zDCaW&eX@TL9&ieAacAei|!XHzExT4OpkF`aOI1+H0FO*{kc<+nP0NY~9**wr9^CKeYf=$UJQzCddS- zTrf2T6`6zhKwxs8C}5*m!a$|tCQE`v{z&&JBQOYu2q1k#MN0zIScHaE4{6m=t7&t@u~Y|Q<0DTDG|KIji;*;3ty(+W(J|SkPoM4=Gc;4COtJP!?WH|E$)b3s2>W__ z{iO6GI&1mxp+k01*Wts5?TAj;?a?Rg1T{nTY5 zCkz6vLia^E{00JZHF9E}97$meJU|`*_sVO8=!8RL z`X1CI4W!}D7`)SSvfQ-P&6YGY5}eeIsZmLw(lV=rM6w2pIo@P}d3;a$(?lI7e)fP& z+8Pi|dy(W5T|+ezrb%0a=gSP%fKLzBFD~X zK|*L2AB;#ijT;)O(?!3Bkt2nx#uB%#o&GV=BA!9UpM(z+0tG_F_k_@}LmY>-QpxEy zw2zB2xv)7-6>+IPngJ#Pz%u}oSHts!5g4S!SOts>9 z!U!B1L!$#{g|Lad2LvW}2$vuf1cP*xaft`Y#2&x=K-?r|D1)a!^sGFHmz0_H_SA;e zisuO@@TqN9I`>M#ByW>2BsnHjI%WF#c;cqh{=fYE|2cuHqxRn`!K$}7`_2Ca1L!4r T1Z_6R00000NkvXXu0mjfx&MOR literal 0 HcmV?d00001 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneApp3x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneApp3x.png new file mode 100644 index 0000000000000000000000000000000000000000..78ef8d12b73234c4a0d842db591fddf8646b5efa GIT binary patch literal 30946 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 Va~q+uY%u@;002ovPDHLkV1i*114sY> literal 0 HcmV?d00001 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneNotification2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneNotification2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a45b01b91c65e3a8d8feefe5122bef85c77a78fe GIT binary patch literal 4485 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 X5L}ax?^OsQ00000NkvXXu0mjf-pQ8F literal 0 HcmV?d00001 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneNotification3x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneNotification3x.png new file mode 100644 index 0000000000000000000000000000000000000000..46ddf1179de6fe5a5d0ea2907a1b2823ad0d14b5 GIT binary patch literal 7458 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

#ey>)?|3_`yyL|7a`H`_C`|$RPW{X#!{8F#Ruy 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 literal 0 HcmV?d00001 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSettings2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSettings2x.png new file mode 100644 index 0000000000000000000000000000000000000000..1ebec1390b7d9dd55bda6e79f655d2ddbf5114dd GIT binary patch literal 7156 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 literal 0 HcmV?d00001 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSpotlight2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSpotlight2x.png new file mode 100644 index 0000000000000000000000000000000000000000..717603dd684e9a71f55243678b954a70b89f0a13 GIT binary patch literal 10768 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

rD-NlRi2>YR3M-xKh z9c4Kh`=aD%&%Ndl%vr+n}RYro+f%j>+#f{sNr+KALrZ~C8bYLjxa@X>BCtfCeq)hRhhs? z-~Ze8kq>^v>i6|nL7#UsKJXM&7f7IQ(oXjL*|Yt8=K*~{qg%%f_dd1Bk3Ou|TxhF~ z2R;?RLiDUj3Ce<@%1B4+NMiZeWYZ-&PXE^PtU-h`2#bZxI%d(;2}@#BvKTk7g^9y+ z+U;{+_?&&}Z$B#>X-AnK$A@;mOJC9E1dwPl($bZjwyd%li%*|(_C-NEjWm9lkd#unpyJ7=zK)B?Fr-tiZ9*AMTJ!78ttRC177RC3a{L)=iQ ziNBvx0*C4bl^qiqLU&7=r|EkGzy7Q5w)gz{do+<;#bVEI`ukM=gjLIj!l=b9RwR4E%ntJ5Nj4jgyMV-8EAae!#FgIqyHd;Ln%J#0GK3n2Lw@H zPOxDKl7?(WCIgPBKjzp3S;`I}e)5!*Pkzh*=?iYABQ3gyyY0?zf6Ko34}WhPHg5Fw z%Pf+kp9L2IAED0;S)Ite8lKa6O&Eb8T<~F|dU&Q1G%5_S2m`Pnd6&2*6C;dSET#$1 zSiZvE^~>+Hx4h*oHhbD>a&4-gdu@LrK_DAM$HY!DEFoMEs5|;&JZ3CH;4UU`=SfKC zeXQz&M3W~MV_BZ5B6*|pQNW2oEqIP|o-hK3X-0@iC1^yb3=IoJrf|Z;l@L0SW+c0>FCp}T zdGqbfx7}vH@C$FV^UlI zm&P;iCWOEgL;*C6DMuuk`Cb4*QYqY>G3OE<-UoGqt2C%@^62f=7dF*@tz5a%ZWFPu zx#k*Mdio-{Je5-YqG|Yq)5JdkAfro#C4_?Lz*xlNy;nhh{LXXfH|dd5so$LXW1Fkt z5MSYz=HI-1tNrwW2kh><@3!Zjd(I9YIqYq1(Q+fS71{|zC+$s$OHpy#c|H*ccqV`F z9r6U1xH&Og(|Dc`0z-_@$52=x7!^QrA#iw~ZrBK+QQILcgdQ1Q^(j3cyO=72?ELf3 zvsJ6Ew5zVVQr7n}ecwsQhu}yxYr{B2R(+|TMTPGhwHs6~1Oy-;#22FY>;T6y<~UQD z)B89wP^SmUf$jw5sNM~mHrk_)K5CCW`k1}2<^|igZ@*7=z|eT1KsfCHxvY9*+|RqD zDCaW&eX@TL9&ieAacAei|!XHzExT4OpkF`aOI1+H0FO*{kc<+nP0NY~9**wr9^CKeYf=$UJQzCddS- zTrf2T6`6zhKwxs8C}5*m!a$|tCQE`v{z&&JBQOYu2q1k#MN0zIScHaE4{6m=t7&t@u~Y|Q<0DTDG|KIji;*;3ty(+W(J|SkPoM4=Gc;4COtJP!?WH|E$)b3s2>W__ z{iO6GI&1mxp+k01*Wts5?TAj;?a?Rg1T{nTY5 zCkz6vLia^E{00JZHF9E}97$meJU|`*_sVO8=!8RL z`X1CI4W!}D7`)SSvfQ-P&6YGY5}eeIsZmLw(lV=rM6w2pIo@P}d3;a$(?lI7e)fP& z+8Pi|dy(W5T|+ezrb%0a=gSP%fKLzBFD~X zK|*L2AB;#ijT;)O(?!3Bkt2nx#uB%#o&GV=BA!9UpM(z+0tG_F_k_@}LmY>-QpxEy zw2zB2xv)7-6>+IPngJ#Pz%u}oSHts!5g4S!SOts>9 z!U!B1L!$#{g|Lad2LvW}2$vuf1cP*xaft`Y#2&x=K-?r|D1)a!^sGFHmz0_H_SA;e zisuO@@TqN9I`>M#ByW>2BsnHjI%WF#c;cqh{=fYE|2cuHqxRn`!K$}7`_2Ca1L!4r T1Z_6R00000NkvXXu0mjfx&MOR literal 0 HcmV?d00001 diff --git a/osu.iOS/iTunesArtwork b/osu.iOS/iTunesArtwork index ef7441433ad414ffc56fd513e5956d279aef8551..1939459992eba68e1067c5af904fa25624cef86d 100644 GIT binary patch literal 142214 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 Z{y%t>pLdzx&T#+$002ovPDHLkV1j&!dny0` literal 99849 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�vg)9FoZZi~G>J|56a#Nu`6$e1IG|S%b(^!Thc3o|pCt0POu* z3{guoFO<7EejWk2p`7W7l#IMMU0<#Ii-?Kb#H;N$ZGNiP=vnf2)JVP~lh!|0`XrgB z{(ktAH7P0xWHAi%wqwE^Wc>+29RY?f;_^J$3^${~k(h=%^8l7eHZSL-OeFR;%n`je+3PXB%Q7g=p1J5?-v{ME=>e$;60@&ypWVh zer1RSA<*!#)K%;#Zv{Yo^dq@k)8ZPT1wW;I?IV!%i+Bz$q#DzqDfoM zuPP)D)W~n==P|AXZ!$a#8t7osP1yozw?R;NNPS^FyU*S%W9f)yw+zS$*ms%eGZyHS zchX$3X<*zjTSgou4V418e;^vfKx*ly)kljulnN>J@$y_b9P78cK zZJ(_FlIIZ(gk?d_oW3E7*{yAi%_)J6LT(FM%;laTd}?Bq%<}prnipHoU4X_LA6qL6 z2QjDPS`yNZK6{9qE2B^IR-q36Pw$~xzAW$wTySqbrs;N|2n+)P z;H-nWWD>#biGD0$jVEJbKk=)t-poF9M^gw?8Z@-1Gw~JM8`!<|6dX(li{b`0!sqe* zi>QqLbm`Qf;CFhbB{{O;X2T7YO`BLxKdsOaU==c|yJI59^#wpn-rpLo1NliYTcN6o zi%so~3xc=?#!zPvHX29HR|)p@u2{JfcVWFS zTOM>{B{szYouFBT$QfIutCPG5e_&MAl>y&N4KYS86nGCg-^|Ig2+10nwZUJDAD|9DWvMO~8ow@B1fHS1rB zb@ZOs>fYXa?3+<^I4F+0x4z)D60<{}4rZO~n)qEpRG34r0x9XE2FEARE^q7@6|3 z&@tBYg~AQK{8z$*BG*ySJOmh|X6=DTMft&{6w-8A_TTEhHFk`=dZ}dU!hf@y_KBHH z+VGCkSmJ{7hk{LQHWt)eLos{2YL-otkv)FMdxAyxy?Ws1q2M=68W|X{n}@*kzbW<1 z3OfyLuQZ~9TKAZ~O21fRjXibe5M>^Jb4n4@NX6o~X#Cl!k8X%oBbGQ&*FhVVnX4fP zFs>7Cb{MPSJUY6bO8PHeIm~z`gm*KH7gD=;NZYOOpu%bAE@85lIDn-={kGKXZ(P>I zhK3z{C0ALCx9O;M2rvkK zkIlqopa(Dpy@X%-$~pQjKTBpWvez&>ptb%*?HQz<`)qufsPkDe?Z*A;$;U^2QPGI2 zvhqaM?$U-xckyyNRViT4ATGTz$@bpuIk74!Bf=PuH-qA!#2uZRagD#DG6jR5=l^3j z4YLS_l_sYX%GC(A;TNsjoWItr$HJ*A z^+QT`gw<9_%|5xBKU1G)%;0bVz6(UP14Zl~&*i@vZ?beT)x*{oaeMdB=g64zH|(1( zVPt{xpt-WGeo8P5thN_f&~y?&{{dzsA6G2AMox=b`1U$w4L1iLV-g)F`srkWDTX{0 zKVXz3C?%8TP{7x6?R4~Xh2%t-uu#iZqDTg#b|3^+SFyLGo!8*_?x|m4LhDz}^gguc z(97aR)Gog%tB{L-%gFD}D*C(p`4TngCp-9>8SAexSLH z6+-dJ`)1;ZXiSRYnKnZ=sj=HcM~9@!chwz6Wz^E>Ht2vTMZ%PRCxFJsY>~F^(Q@QW z1OJaneYb1FC;Lh(K$_cje=rTqT9{vlwqqjz1HOWyBFRsUKQGt(Et)8ts8Gc~kh!cP zv>EKDmppGa`=`c|%-^t9NrOQ5-BM2MT1!)rW;hv$4-yi8L=ogBSVTSZhu`U2L0c69 zGN7;iC*$)cU1oD7)lg>|@uj7(Osw^nNUf~%!LQ`p|IfdFR ziXlDFDC$_IlUANMS+lV|^mGx&-LJ;F<85w{Qi|ielXV_f2up==&v1f#qn;H{8n&q$ zP5ssrJs7be*VuC(YP^;NH-M&Q@Wb=Ph8DTpDFRzN5svF2B6!D78)9L4BFWltm=Gg- z!yu$dl+!apq z&B|5&qK^MrsSO$`5P4dX<*|66^L?=fnYi5UDQ?SJq#nK~^|=nMu2E857kC$$@|gD& zPVdisn^(SQ@Cm1trhMe8Kv45->Bb80bi3K)|&5((gnX`$G%x?a|&3k>G$xjhvo&XUzNK)%?6}ii!=&5PV|R^yEg+i9*BOnYUd)R=xw+$Kp;f(ByX*j zB#1dUdN5g$WO%d+iGDy*Bnf#vdm(=FJK?WhTWEC@Jh+;j^`|YVvNk0qs6$`q{pJS= zE(X#$8&I(-0v5*&K1zyY_>6)ei~i$kc(YW=nmap7^vzYa!Meu(NrB$qJ^Fm%#>b}V z(1|7C;C!l4lhEqR<25wdiar!xG<_t&Of*sGIejH6z1@8?`-BP#_)YlRW2FY8Q8TN& z)fy2dQ~&9F6PJR2x*m*4le$?~m4W)k08XHArbdXT9UC_+dKtUbR8q1Vh|Pg$Jd!{_ zU*CuYEsUEi^FBtK8U!V3GP6efae9BPO#@2O7UuU;@l6RpV({HS|5A_q?)ac)tqIDzgL34 zr30mF=A-392V<_oq?ups^3!0n@SO}t0q%wu>E9wm2kH>gM12e|yj^xXW_?Rv4IS_| zXW;abG4Dj0xNWfiSk}}65~5x97~83qFpCqhBuQ5K(*REZL?;y>lt`v+OtgmF0%lX$Vl`(>*&6ox~2y*LOAW zmfV6mt@^;+uU5Fd9$dlPGlADznJa5{DAcN3JYyZh7WR_ypabSD6Pv446B zAY(+)9m!VC2XBdGA1V%86ui&2wdgmk--)sbcyGOptK-857GG=*K~w00_smCHP``tO z`n=!xx$(kVikkgU*za@|9qAGb!Xmnj7S&P-^XjcZQv|y=&!Kt47MCLa{VJuiQ zh3Xcu$H8TE%rYSL);5F1f&$EaGovKFS@phaDGfFd?41!i7KEs07W1))xO(s-BtCC~ z(Nn)s$Mv2d4rS2DlYbVI&L;e#<0Cb7%T1cLTJM{@;Jv2vFE-Xo9!Z5EMPGvr>@i1N0YS-&>%7p zC9G1Kl>~91b1RpSO-Pf>SZOy@f#|$X^>ji;T*MhqF1et}f2ECgmJP@+s?O1Q~ z{_lV>sDd=iy5Ip&jnv0>28>%l2)o{D1;%|W^^baGdw@u5V8?M!6Z~F1$R+f-WR0Ft z&1Uy2yIDz}Yigpy3AskV?$K&0EId+)N|4|oK}AVyuChQY>EOSGgBqt)$(l#0Ez*8r zH`$WDnOp_QU*Bxl3~nnU#J0DsF_xpI9C45UHN?9C%NWiq$AdmER@kXP96Bv!mG)Pf z+CpD*35lNv#Wf|_1vU9r`ZL+6qf~V=e_HCY{UvA6Ac&^NB~KZj&_L}$V2+6geK>)X z{@d1VjHN~ff>g9X8s5yhId*&#=VWF+^hXj!6b2xSQ!Z zx&ODjRv;-Uvl)*m@l_BU+C)#M0?N4hyr#y}A5YPjabL#WRnLRd1+dFYjmNV@t2ii< z9*1pv%>^;LBk_Y{T43aM77iwf{GEDMJ=4`-bm6O=*L!$kw$JsjFq1TcaujxQmRnC# z@aImbVnLAi-`A~ddV3BEI0VV~3MW(=0;Nzgi7+}czT&+BUA^`NA$|)!r+Gfoojw`U zP;c`)2nGrjIM$ZNqWON41AN?WCe^hjQ*!)IQQ|*skZPxDH&?>&qZTiSLjW{Uv(`@* zlP0)~F$^9EV=$x(FBLEvzYqAXDUew(%QoA*85Kz?vvNRTgeF6bsN9VfYt-6}xGMKb zkcOg2et%nG)Y{_V{=RH)gGiPj!KHECS?kl-C1qa?FhR?q&$)^7*Os6<|98M8#_?pa zRTiMhr&Rjz^H^WBFwom)yKNZcEm`dLijrE5nhChZd6t9fw!dM2F3Sd?+z8^Gp(+{WrB{?)7sQJp_FQ!{T%mP_ zFv*@>@Lt{KhXU`g{T#063ziq;R4GLRPpC?zTlXKZ?s3ZkX5}HT!?0w5$`#?9hAoX_fwUT7PbbZ@Zt{c3#E)>I;g(cp|s3NeIJYaxsWj z^TS19!hsV(0OI`{7qWRV|IfB-IVM(AbGjUfD%T2;twXBOaxUWm!tTfB!k<8alG@Tt zzu%2Q?Y!uK59;)nD+QUvf0ppevAhN#K4h6e96;RtRcNugxH3juQ00>!6SQN|Qx(wF z*`EE!Py%-40cZ(17X;Kyh4Fhuj&dxi=rs?9kmOxh;}UPY67J2tSk~NO;(!`{;vGtzIaX8tCwr~m|vwM$TWS$eyOAKxP$W)7twOe{9XDa1pu5mNxa_Q66tOE4?0pWIt}&B5j?C7X`2G zJpNcwkZ(V}dD(m{uySBC+4wa%vEeWUu1S+Gt(3)slAGwv(;OW)C>9A|ow??g7qU0; zTIf&zypj10l4rk2unBo&x zw$EOzc(}?WzH2r)TnV?p1ookw_~J8Q-hsq`b=~lbPfoy`#=Ix4ItIAKaBnolyV?&V zxE&;hZ(b~LNL#ReH+V|7GyCXdc&{g!i9J>29dLxL*z@7GiM&>olI$Te#b(uoKyWhZ zkABnP^e_cV+;6hPLixFRQl)G3f-}mGeRF;l*fessZ%yU?Loo!%xJQo=VVY3v9zKLhCK`ndLwXLPpQ;#o9Y(sUA|}f zxwfJFhxHCM(9^rUruw)3jT-WdMQ7@URRe!FP}lylH->PxrrFwH8rxwz@2ACK^EUVB`wIv9|*4)hk@IR;tM9GUt9^lW1=hyUk z3dgX=3+@WxT%!vRMC%fP-5brS43oXjOJ|7X;4l=81NuXBJ*;VkD;t$3M}8SV>jlk5X7P7Fe26^;>7z)K7RHu471{p+_^3S+2SVHPSqIU?^zG z{bKBBCdA2ziwO+U8}hE{OAMt52zVhPj~Tw4l^Fq@EiG(Zp0_1U1Prw2Rgfjrml_$p`?5E ze#?~Uph%>OaRdk~TUhk9)13lc!mst`Mz`i#26^csRHjr(wP8$9HFk!5STM9a4eXuB*GYYj z4i5$*^MNhGpe8>WVn!>;bRtH6EWG(iRp`M#rVa!uVmM_oB(m&>9{E0^G07fx8;n*M zC+_>)kN_4n!h2NS_(Ss+grr#JO{HkXO$DhwHrgH;d&1T|?3FiMg+zl?Ph^7!Q+93;iM41z*fwo0v986t?b@T-S4B=8Zm(ImxW z4pDdwzQ1#;w`DLo`V+>|;sUMow}L$bgbi^xBvfXOUdn?Yg(I_7tL8oH295OExMNks z-gLW}b@{s$q^@f_BoUf#myx*O$K@&0+9}*(#%5KIyNryQF510Rwcjz zzI4v|*}9ibhluCQdY|crv2zPYuy$DWR2fJy>WjYNyJ2H<8r%Ax z-}Sz8%@>$+=A6CPUeCJk?oxWpMA`u^MoskS19WoV-`|`}oF>VECYn_@oCQw-UW)QQ|W9$-L3T@EVw9n7+$!otjyz`u*}( zbqLj8$zP89ut2|7jLB4{<~KGcg-S>Pn%LE2Lh(!=-)TQieggONNj+4K5n-5G#l>Hu z#Y=)!GkU0&qH!zZUZR&$yL!d+hmnR5g<3G)U}$?sK^x4Y1%*4`NRL-(;3 z3OZbK@OKi;rbMqDuA)j6j%8Z7QXx(u&_LaQrbFe9qB}B_S^g-FL2kXd?b=pFt|znWDEIR2JlZ<~YTx$Bq|F*ufg+H{F;jW={*dO0if!_7;~8yxd5SSv za2liI;cu3Jdo8j7A|+NkQE-va_*caOnIO*)iS3ULnazBuu&BszSKUq z@dKyRCVm9;v?5}-J5Uu)i!qfji^1C+q#pO%j#+K;1mfxeee7X7pryakSS+Y@M;tMX zIMXEDWYFG3A5bzkn2#Ee5}Desgp43MJ;>zn%EhufkIY$mi3Cf=w3p@9mlxel*5aKX z`fZ;(mPHJV0#U5yFTRgtG!!>66AaB6lx5A^#fM;?bG7T`=Lrq-EgP!?QhdK_g#X0F3o|Tiyo_iz3mK`;jD!$ z;xggTBCxP!j1s!t%#%}|oZtj^G3QToeDJ_Y-M-?U6>1FY)LKr$QPX`Mk#P`Iz^@kl z!7!jv_`RG+kKa4c3eJ?%W?2%^Y;{5T&?k;>rVq9ak-sg2ne%Zzgj-jcl2Jz2n>zv7 z(BV_>f)5aW=Bws&*<#eKlKoi`?4VcT1Yz9#yF?!3_X|vaA^NwY{!iMTg)uK?g(uPAB z_dG9ut6(?;GR}@`9Nkkfv zBJfIbR>srC{GD6*5<92ptQ-{>mZXFo>`5Tb^yp>Mady9kv?h<46D zZwHCg(6y&}i5m9AJ+244ydEJF6PRpT~PUF!Y-Jj`iub<$1BbriJ#6T-J z{d^Ops&fvcJ@P=|sI+Ju5?pQ{BKZxwFxOFcs#lknrOk~a>}WRqWF^sWSJz1_f%f8S zD@%#csODo!D83N&Upp`3j^2O>ztHjkPzFLo&Th&)2I1h{7!*;NJ{CMEKizp*wbbO3 zkNH)u_DptLi|U;C&T2HV0H=IgYeHoVBety&$dy3>}1F(uNQ2)b;`FPxw~r^+T&jvKS=H z>~$NhCfHtgjq90ermTNceejA=>((spQzZYG*52cvi66GGo4p`WoVt5k0mgGT#>Wg#Nsg zWR<^mdEQNGRaVX^I11j=9l+cPBaw8{1iXiq8^xc{R|a5~%CXGysSmFP@Ia7Y8ELMN zMrOl^?;zoV*o-o=oHf6*5mWDHsPWsZdjZmc+jRaU&+cw_)00}l?>0kXrSf5tM~qB( zV}(CmvV3M%R_eM*1tW&8qP?#0g{Mw}dYBujbVOCQk35ZI{Oxl?4W9Q+3f^AKE!ORG zC^9;yP(B+X;eQ8MoE{H0-H+45UEFPE#dGO#S;FWvza}u+2+?TdsYuDUm*U&Chyuv< zNNoXQH&?L56Z1Ndm1&IfaA*0?-RevL0?p*2cG?U$TOo{G%KdR!7Ogn@!u%-_1Tt@u zq=G`+zA4Vdv3Z>RbN{y7eb#xa;H#MVWV|2$)#&7*%TK^ZV``NVYBcM*V94uKN}3xdQ#>K@%EUAuoZs(B0i$j=%X`4P{#-FbD=rh}q#O ztR0~~(_^2Rw08H()ceh(nt)9bPbT9q9KOJb@1t464EIOm#fZ=D&ZY1Ju5dm74)~0z zOjeV&Y+#2T^SI6IY%~LqI+D5iB3F6FXDf zeN+F;|1#L`Vu?6==x7uDCE>SX=+TA+Ye*Fhly<;aQ{@Y*$7o8@Dh$&?rPiVltcf@i z43Ht;F#_DSx?LT&3h*G3?o*I)f8Ar_4GbHufYNoG#t2NBp&9fvS|3!b{mTRZW^?%m zfIy$$qCFo+3|8g!M#=v2;*&u8Z(Yq{zy7&B3$&1SMx-{Fpv6FJrgXj2wb{7KH@FUf1AN>j_?- zYDL|{EPZk2`sc^?GO>2vAHWjJWSnDO{jH?}9-*3C)5-}ZgL6=z!0AEL=lin`$nW>M z<93(Uz9Yhaa@3UMnLYM|%att7mAR{=xzIiKC${JEcJE+;>csB>AYUapzClfejr#D+j%>$M7ywCHV}*0upKoBVJ4u~NxR#&LNXq?IcJ3bGGm|9f3#6B zlXm4e=^0;52ID=|6U7SXC}E=%KD%3i>y)o?p+JSxU)(I#uew}*YVPE!j7x`}_{{w? z;F){%VgP{4CxTUNO>H^p7#M>TYRZsG__r4tNS%^v4@Ew&)c(N-=$;KN;#LUE`^yBj z55-VlmR}cuyPHLzTU2VIrPE+B`9o&9Ua43}_fKNn-;%jK@Z02qIM3U|E&$ud65YNT z&td&?T?donlqgPOhJMOvy6UWQ#|Srb&8<%_DEyBJsr$q!JiW4ZHyQ z3mE^lejQ_M_TE*+$YV!g`s(r9PoNWTHi}xrs4XEZZ_VVgK2ox@fzovCk08B<+$EBv6K*klll=#>9BqHw z#a!Dd)Kvo?4lsvT$KA=%km3HXAIE^(6G-O+m;u#<$ao=>a93@ccaLh3;WQjVtuWU~ zOybvbj!6EtdXtYY`PncQ<4^ zGs8^+H?`ILZ_DoycSyHLH-#M}6huW+E*_4>*$G#F?i&evJ& zLxKhoMjC*Oo*Jtkd9vz1n_sjC3ek`|tE zb+Iah8{jR!sTK`(Je@@R?@LevH1358z}Oi?rg^?yCfG@k)TT9xY7hnDim z@1!aBvR!ZLSBN1AtIxrVSCblCSCN~dtH@&s!?L@0E+8^AU)uy7#Oags)wg-Akcl5Q z9T^ed39?N{z`#6&`?F2Q(X@#VO`f`D7!qlY;CC^e*(*yI$f8d?*DGxAP3@I8w4j7e z`}V&gK1FnB^(KE|Kn$~f7S~nAL|`(XXL&deh5G$Cje;cXn|Xf{CK(Ib$t95Uy7H#%lnQfyCk)tP`(UlKm?ENt_sVp4BKn{ z5g5`hF7O&zJM8=)N-mezOsh{)Bml5A7h=G|D$4VJ1s&HZbsSF)Bq-_Y{aNya4?zm1 zG;$$Yx;*7U-t5#snBn0hfztW4thg-n10(M*lu6UfE2MX0%UWVVyKlh0;$`90P_r7L z7yk-Ko4=0AdC$%uum_qM0fkA>_SP0C8DyRa1tP(4A?(xs`aFj|4nd2xTF!T83)?B9 zgeU#oW5z2ivZtC+52xOQDQ&Nzxc~77ND@PGWO`r>byArP+y@_FMJh!f38*@cRx~m7kcnsUsx>9p9X=KxMX&OwY-ZrJyD&#ReWpX5Z>#&r(&iq<2a?#Le;mlEKP{5b*75f{6=<`@7~3U#g5agzo?X60b8CIP$He zCiB4XK&hlUv^>1B5clj=Vtk#p?$RO2KU|y>^3vU;|&NQO#Di~m|b39 zBTF@(%@$*oiUrnS|MEv_gOHMdn!>AsecE=DnI3>eaIjqchSqv+kjVZ8m95pJ@}CDJ zd{;Ym%oywk4~zAyt=k0HoBlcF4$v1erG^HEQ`hb+L7Cy9|~C`>^p*6H)2Z}JCi`qt=O&dXqL+X~>O6G_S~tMVO96~53L z4Izr`b=V6Z39FSVC9|KO(0jPg1Y9}@wBF|my!&``zw>CR^TE9OIl`EkXbS)@GkF@f z@KzAeB9qPkJ3CpYSCO*m4c2r5GF+ev?bf{NYMZCI-lA1F@bD!ts+}821nNl4&kUAo zt!qpQ57Az<)1h=j^iZNNFU`XFPlz0nL!H~e1H>Zj6A7s(3)ciS`8xRyvnICNv)bzfe)BLeBGag?*-I#V&+~x?12v?n!ggASiay0(L^c&IKIM!~V7{e0G*-Ly$lbeC=w)xVu<-h7qx3 z*I{)Jx?C8LSN_*EKV81KBboa1=Dkd@kl^c7j-OkZ^=ND=$qHYD{Mx|$|LsnBTMA8T zHboCLqQ1~;P|k``nIV{oU0pR8^#)hl>vj>*FI)b|MwC16ZO``n$Jo_zD#a15{kOh^ z@zj;tteB9oMq6zqYnO)W9P5G$2J$cG)rrln~O?zzZ;| zj-^(UWo>-2%PTTBJFl})Uaq5v*NnmxMwyvPeOf9IeK8J?C-Gj&)zpup=7gP;hG28h zW)%Qkg`8&UpchZK-81X^xiJQvUv#%7nW<#AqwvvFef~C`gL|kDi0XG~SJn(%Gf~^| z=!)nLyy9X-aD#9xpqXNev|8b^pKB0}J<4>8E@I4pg?>@=O4 z$?APeAcpPxp;2YiTGe1x`ZW(@Rx#65o4fO0oB;|dIpTQJ)herjU9Py~t}D75Z7D70+Qk>tb(U z-Mi1-29IpjF(-N@_hHc_9~2j=9&}1RKxirH$g^)Utlo6rtmb3i9MYlv>bVvmtH@OPKT%sx*r$% zQ0^nV4bq8dt^%|&@i@=^^~7*B-4Ve!H)K9s$&F0UzhC|l31G{L?E@nfZu%q>Q73x0 z$QgQ055%Cx_7|K#Q+D6%-b7j$ZbgkZYqBdaT%31TT|}Pp9&<@z-DSYCD&Q(tn!$<3 zR9*tZtg#>ri?Gk|Ukyf3Cy(sNa@gU%wLuE(F@?Wc^|^;uHHZYYpT^5cCd0o zSUY(0t9NbL>Uv7MfjftuTYE~U+ITh5#b2=}Sk7`se#c;S3Q6fl-EktZS@b-8AuS8TnrOxBQf>M?Z+ zGcZ#jt(?YLnvn3-|rVcW-ljCn(%E^$(z^x zbi94~jZ1xHl=}Di%+XrcKyCvuU)lp=p7?X9Z+mAnIP28gvK^QHxisJlAzbTt09xFt zh)=!yZ{<40QfZ%%aIogFM&uAL<4GWvWXkr;*z$L~`M6Z`OIdX|RL}z8E`M;^8kB+} z)h8GRO7U?+mP$2WR1nF@5wH_&ad!m6aapuWMched=>{_4rHQYs%fuHigm<9iar`Ho`u0 z>e)!<%Mh!_`yG!uN5$@<$fQHJZFfuC96Q2lKm=@(M6o;Z2EY5o&436P8eAKI-{BTd zts0PTTEWtEzSr7_>KpPLrJgjSU`Z%8N9bEBBgm#emt@j%{#WJn1XcVl%eOWx0gWuu zjPZ6MJsr5c0r|QQv1f$09fD*ABPm<%6@7~b4)6Qk4Zj8K@}`(-H*UMT*4}S692p?F zCBtvK6-?0fVujPnWbc1C4t(xUCr>SmOZI;R`Qp)Zc0W!P6R**ImNKbFIHYKakEx<0 z94xH7VVAjI3ggDQe{Pp&Xn(v4ldu1V`B;3 zp}VewVf&oe^Pfb>!X}YFk%PB8cwr49@-vdqpQ?_vCkP-5zP<#ptVK93fmem;U~7^L zM~cjgj(R{Ud{yq_bFoPjjaZn<9yPBy^rBW}^Zx4Mey>{`j;Rzyq5J`dMBKU@l_>u3 zZY|Y@ulZgH<+~EG-~eyp`#=2U*#?FL1tu}ykG#XNusZKo{S+)CNPa!t_jq00aQo_# z%uUCfY{n_@Ai+vA4m%`4LyAJ*6KYcf^nct7=taKE0ki+Ac7X_BAVb3IqBi9UC9+q= zg7>}iEpOA8R2k~MI0S|i>cEs+hwg`u>|APktOB(guiafNEp9$Ve30dzx(rLOU7ro~ zsE8QqzCEtlbdcX}w`=GdYi8X_G73!{@U8khvx>i8q7qaVTrU^wQnth={1oD@is`d| zDfYgyA~S6>(5Xndmjtbpc7*Jsd&%ePH`l;}_O2hJq9&=r4(^ePhqVcPzg>!81pNY5 zXkau>zgC_K(0(hHDwitfQPo}Yli?XuwH?uzw!#5F^hRP738NrxNrb!(A+KfdlllBU z0feRWqnz()16l+v18**xGY3hhG(YV(qUU5{{^xNYm#dk|1;Y%VEdtfzh}wqa*H$$M z|DEwd>R(}WpfCrCD@(U%KS~$zT$yxr8lAsCE-3T+7Qen!cOfWNQHxXI;DHCw4wZl1 zkJbw;Z4}^12S|S1aA#E^+ixxWy>3^l|NE8uZyNiT)cqm0V!stTJ)|FXb{{~{f`1EZ zT>nK}5tNuFeU7tPVGDgg(R=n5cyAHfmK0~6pd~`+NA3iza2pqnUM<>{6iqI zBy2z?g%I!-yznfRStK!iZu6;d-J|;pgA5Cjr?nRWqeP_&rP8R3%7vFPh){>=vM5c>Sq~N@vllIR=TjQ^;CrVI~+xNoa&8`@}kH4B* zJ@1)Ak))`fzs*(OzCRR7drS%+BJStxea1K4i)dKr7i~3jJKMF9>NqvVVAXe9m`@L< z1b~tI94@cXj1i~&u@gm#rG`DqLT!FGyxUX^OUgRmlmy}Js8vS}e9%E81rZ*@JBpm7 zD=AzRTAJP7oy_4h!qYqalR?)2EBoWat?mkU`#QS1iG>fq>Q^jv&1yFOgO{T9fs>nc z;L8r{moLYhS-a?CI1U8G$}~D*Q%@7V@I9ztE||TDA6Qc8qat}b?So#$&MzC?P2E>3 zPQ>n3_NQni3gSgtNWD!Fg#xz89>;01jQrTAJb&UC?2%WLwGyGeqGy3Zpt!HrH#fzG zEMNY$0|8F25K>dcV{ZHIho?6IA9a=@)E2*Dgn5-S~s zl|JI>;S<>A+SQC?Bm-YWlP(pBJ2+b2xqUTjU7o6j+zVYwY_C?QvR-?Um8jqj3%(Tm z)iLtUes4!qs?Q{QvUu%MK3(jq1NTOA|#E1HxI-_wcF34IK zOwIlpa34Fd$ina_coBzEIHM}#fyv19KSgt9Dd{lqQ^;C}rH9G?8L%N;qK)%=!$JnX|>^7A=o|}j?FOyamJ0L$P(|xbo{My_Hf+p{x1(=>P zvBim3q6$MOwc8{0LPDf-L4qMrD-L|omTb@ z0k6Ms0i|QeVSTUyW@-gWo!taT1a;{G=Fp-tc#S4s#WCZsBu3q=KgJLVsM+;?ZTO0J z4@4X*iex1Uj6xH4b=XBGfFf$S={lOb!QA7EVCZjWMiwf)U%y{7dwW~l&T+1BUFD(L z@rX>prQWos{1LX?&2x+HF-j{VT`kh{aCvMpeoc)ka9(q3gkTZ`&!#8dbq~kmj9-6f za9*ophVCh7J2h`y!280O?lxus0&ta%ZEvUIrN^py81avbhj7+W`BbjA-(*tgoPN&- z?7$1MGr{@(Xm!fFTU?W8@+bt4E^2cqq)eLgpC2B1y$m`sRQi6R{$o4ivwqu%%eY(7 z&G(zn=TUDHy?r**4aMynra`5EFq2_WxTOZgbr(+u&w}cd=hGwaXO6=A)J{sGHEmAE zq03H3Em>%XR>y#ZZ_e*)`0l8J5|~M}$pk9;MAW(PJvc0VV~2hV*xzA&0Z|L@CQ85U z%n@yUJM8V!=G&YdFlJXCwjEORv83?#k+mxIe%smA{y0bv85tx~;-vc>Aqt|^FHJ^6 zk~dRCph_{60{Ia8If^m5tLC~74@YE(6H(QQAU>?Lg9arT<>mDMYbaNz;q}GkA?D{J z>V>i^k@+L@@uek>=G0y?sTM(BSftTkXi$D&?v*F+7K>(Pe1W^46b~ZAdQ^`|_%gXH zPg|-~{s+(#QXKVSNT89ZJv~vp*j0;ZIYZ;!;b-$JVNCCz8-o0hpIKS(O7turVZUPjg zZSnJ%jKeU&6F$e0p)PA#VG-hNFpf+2ZC;kcCB>s>6Wu9l*>Emoy|ab~I$2 zx5AW%J!G&B`B7siiqD!vK@y^z&tC?T?OiO@?|YwF)T#rn*`rjX8gbTqIz8sfVyi8w z`5aOZCbbb(Nc;`gu+a?sn=Id>f38^R%~f{>|6pcN1?E)_?>BylNf|_+VJ#OxXN?q0 zdSAD$=`4PvU8O*!?d7*{AXwypGK43`ZepK)@F;(LxoE28qja$18YIUOf}~BJOm$;D zJlGjGiGxh1bbacdW35WHCq=grN^u}au?``PMD%6HNfett#VgYYw*2%{Vz&qJUX)#T z%A57iEq;b9xh;-B%N7&cpP60MU@?OMJ~rVhPfmGdA_H-WC*MI?CH5)nxrjzgqmJW5t$mD^z>_9I~{q!GC)N`HkuKk8m)| z7=b_Df>+@v86GvhRGDr1N%l0%|8pS1cQO+sT!YyQd5hFNOv2ALTrF15u~Ca_HD@r@ zm&t~|p85nkjp(vR-uhn8PA$Z{pE~&YM$m{1;PJj0-LFGj2R z3BRIAQvD1aoRvPq-t0M9TW?b+@h&&J*X3Qh{)asNa8sZty(DERHl8A1in4y%?S^iz zN**C=rDSf8Oh-VI@vy+U9)0SopCUn#pp4i$Uby$|a`Fha?lIZ`aIA+hXWfNGQ>XrUB-sI;X(!guvJZXIgo1n%!7C9o9&P*ft$-IRRRBH9*WKI{ zzGh(i z%jCYw^O;t)`#Q1DAtwpZrb{)l@o>F%q1F0ugrf2mmtAr2%a5aoXk}e5$$so4i^ubU0=IR&IeMCt z)EfsP`BARDFn0Qx{H9nSwwu!R&VQacJ-q7}E*MVK_x~iD%l_-%S{wJRA5Y+Xb_)AN zb-bq|PPW@apwi|t+HI6;X+ZjIqRQ2_BFhjCheAA%LR z@aRH%tgKKyZn=4Q%ut<@jB8=w`=_(?y)N3wT^1O#kK5GKi~K{Xj#sQfL+S*>Lbg&+ zX`gnw6o!mvi&@A+M((}ZjZ$|`S-SS5!s4a;Pb#tXk>d%T>)w=ZrEBPbslmwEP}o>f{fov4 z={k>&jMTA5IsOn#D98xU#T=>)oDId=%5%%Mdh8VnJU^9+H&;HFF7DZA!!ex>)CN%- zoOVCo%>+AR5N&+;HQ)sY<|Ua1S+(|>pmaTqGOpjO5^q(dZNaF|TD|{E-^um&I3>{h z1VIgX3u|)feys9i2pN^t5<5TZWiUcD|yjK*H=3E8` z6tLJDr0w`IgG?(id$Rh0XRhMw+KzfLE*D7Zt(E|Xx{#06V&QZu3nFtsKi~Wb1QfOQ zt(G+3f(b!@@H#fG;mqAS-IoNuLV9qVKj#CJh>+k8QmURbGlNV4TE~t@U~*fN&kMj` zv(~=PU8GQL>Sw$CRk7ft5c=1{+QmlW+shy%FIl#>M`6sbW`&rkV}C0=`(ajw_?^T> zMVJrdCrI}roLq*2`X>_@O8@M4b;+COiCb4BrE$H3`t@5Wg|4Jmp$WLqOHoNyd~)Ifw`yu_FLUd_`+I*cgNR z0)o|vn$U}tVrJA@A%PDH3OeYTObpc=1~Kg*Dd1E^BylUII+91ZXD`in@AuR-uXWw= zRV${N;SZZ@K{Zz8pEeUy@zz<~tgim5$n*RV0!FnM|7^q8up8ZFJ9q9W;X2up#`MQ= zX`3uJY29D!r{$lg^XI?9CSCWQO<&Gg&ZxzcKGR5c9WM;PaOBft>?ULv{ydBf`b0_W zem0T6YFZH|K~7MLff)R0uyF6?CA8F2X-eBL#Z4qgGL3~kOI_aot&XfrW5O~j5nYg8$>bv-HHE42eP#0I8KS0nrdyza;MJwJ=Xq=R>e=%b|;pj~ zh{)1A$|le2DSc`byPO1e0G+wZ?$}N6Sy@@6Y-ebxn!5~6_FHe$9Eue}yLd;!`GMDm zKGfN5MJDSH)WsbHcZ?!n4&pU--4;jc+@{V$ZI~^(?}B(a$~1kcZ-)|KR^RjIQsuhl z=H`7&<3NqSpd3Rl3_>L{nc0*nA!wlkJ5ze?U%!hC!h@z! zRX)vbAyjm+3G0u*vix#2ZBU*tbCB#Ggz>qaP91Kpe%-R*K5KJs8>-cOpi45g@4gz^ zk-Llb^rF3qdgvP8fnT-nZjRO;Nb;{zT3>`{jVOFrTmL+<1irF;Ks6TgaYtdJ;E1Z^Ie|bqK>k z8M>Z7Qnjo-GSJWJwRI2H9~>KD#zoLT6I#2lz5HDVIql=v>F zqUnjHK_enTL3uYh0S)V`7ZipwG?ATYyv^pCistUk#1<%d1>;hepyqyH>)Egvkr>PB zXxP|YdVm0qAtg0H&J&@C{ed11BAlWpJY0SZ)*V^zdMiWO%nkk0B3Zp3jezq)bT^m&`;O4LwbYa5MqliS4E@7uy*?0>2-W z^Sfp*rUsTG!rUg+YLne(>mIK+89$V3<#`ofb5v?v*Sh3)7=BbBCBwZ+GPHR)C+2+; z*Zq`av?_2@wiV?BQj&VgD8JcR3eEbW`=BtT)Xt?>MSo7MMs^KU3W!5Ee-W}8MJv`9 zMGE2dBdhkx>t0sKcb*D8oscbb>|5AvWRdOFdi?jzz~3d^M#e4&Bfq`aQ1H{rB~p92wW z@5RMM;cX2A8rX>o13&pv8?*v4(bu4;rHgmH{;aSlxIehNf2w1rCJ?D2Z>J2614DLP zfbav2Wh)NBjBGSz!em^*_3oMR#(Jav>eQv$8$xTe)u$Y309>EdMearV#Lcxd_jQVd z7qXp~2~d+2r&oX4EwN8_I}Kx9sM%L9Ja_luD6iMj_)VWI$zL(D?z1x7T{}X|3MyBn z;;*-cY3#~jB1=3DpysJTat9uo<<-Ng@LLJ=iRDU!mI(S{im}$j}tS zlAL@Ha3IPd`MnFNIardVqO1IW{z9okM>7xIO)?L0LXyOAgK`s;;!42GVPpmqY}Kix z6Sig>U$u`d@OIsPmMXiaqOD+X&%PDyL&hqs{G_d&Bn=* z{6H=)4l*!>?QEH>Pi6Aa@>mZgmJW$7)PQq$o2zg`lK7{AdYBIfZ{3&CCN` z8>)yNFf4L+o06qAO_kP=n&Bk9Raxiod@D1&xmWNH8p1*Civ_Xd30RSW1iC~9W=T^% z)W0jMsHmR@1IGyD3;@Mzc>KE`E;lHBvEDw#jzCMPDxuDOT=Uqz^`uP(FXwa^fGu>L zo7AN)$jznsD~@0rbE=WW;t&vMha@tZ0W&m=9r&tQ&+~?IHBCBIR&pJ$N&h5l+@3GT z!YOy=guz4qB$T1K@|YzCz#oJ|$^O>b&p2fWZT`y=cwIRZ*Hyn)x`)_k1}kBHEQ1Ng z&s6pFM*#Rm>?vew*g$^{q*xOXXD9L3Y2i(=rSu&J_h6K#eZoV(GWa3$RIg?Tm` zw6)s<7=L7HHOq=x@sg2&FD!f1jCQ^Fw4XlxK=?4!l0X)fFICPM;jp2JijNA8MHQL< zS+g*+vBB>pYed8xPAIqzwIt))sdy{n`?(OmI?al>TzO0ZgI=TVYxcPCu~iQ|NEtr% zk12SmLXSGP3X&K$IPUtiTj7^8o~xedkOYBBtHH*Kr$`LgG1+5srpC7iEmM)0XF5jgK7%F zgIvYUo2}`v!anC=Q-;1uT(myf_6HSIIZz0jiaw~Xfw4-LjGZ2Rk?xn(=e4jRXP=kv zPzpNj6%EJI$GBmsxv~hIH)Xw&jj97p8m+c^1^fA0*Z(-r@{GP1_d>@hu6S=TA@NOp zA)C6kR2*4(63U%66$~HDbHcn!qbWVG&%-dIQBGNhW%Nanfc;*mowaGiXvOq5k*dX- z8j)S+pwl`NFrm<#X?IVwdgFFdBBMAbHxreg@E5iR_ky|oX?sJM%gIgDMOjl(e-Yfh znyS5D!>RJxLXKS4cMD)rekz>Jiqo%02cmb1cfI7-%bgv-mTHSJ=i;~oC`;S#RUN~V zQbmwPz=Q0&TI^ES8vJ!vM_IhB=q(nC8=rThWR8x=KMY1JKn|klk-!S@c83Z=xbc!I zWBXXL>7+;Ih$Xkt6K;)tZSad$SY9A!6T^tmJPAp&b|0_J=Aex%J3cSf_hqcG#JEbI zY(XUNH1G=jR>uermbRQuwky@ALA8+Aq);L}*?7UNe(yR%;62>vnC)~cqz>HfV%@hI z?(ZyiI;WZwqaiK$5?CUVYnw7x6((Hh^?si_7j4yj)PLW+UPrY2A+|w70(@xixHE$- z>v^3jd7qOE9|x@TXA*@0Xg#imIT-$XlV#2(yY{tq)VixtSry%#y=U1FL6u80RU-&A z$V`c!Rh@qYJvdfmRj&3z+JTgz)mchJIZBBJ%j6PEuughfXqjQ=YDTFvL51#TfZ_!m zf$uFOYFE7%qF-z3&q@( zk+fF2P*sb3kBD>!8nfDHBA3Z73uH*;s_c&2?VTXi#JycN@&?Mn3;NDwzy5cAuq?{y zAs&w__Ytl$ZH#PT;W>J$Xh{`(uhRr$Wy{zKH>nXXk39*giF<|g$`-*_j-w-U`f4VJ z3HPrByPQo(AW)!Cuak}xVx1KR(@*leH-^RA1J}GFjPI5y+Un%!i%wF7iiL`;edWE% zbBxCR*zIt^(OLYD7oS-6;VifvI4VZhTNs??SyFF~ej(2X1GH##>Xz!4@PP23TJukz$@>0eoSW<+0FU}~oCJj<*;^1fIWOiAx#ZOx2UVs8h+}NJ!FAl!Tz*+Yxb8Xv`pvcg~ z4{Q;>Z#`tO+u;lbdKCw`5b-?i6Sotl?gbew<1ZetSIJaa58KTS!#48IUrI&Y!@|?; zyH&T0c?_z!m#8m$)wSH3x^3d}^I}COIM^&DsJFvvX4bgR^!@ZDukLm`X=iL3@Ru3y zOjm8?0B~mx5KoBP=^IymbfW|@RL+_Ji~GaGbXL{OYjGIm^G+-oSb{vT+>m<|2iq6A z6?O<=)Hlf0?m;DFWT~(SO_EJZk_wv40KoYG8(*`T)mu?H%VLFX-KYakcd6tsg!lWm{6DN+ z?`7q$YC4tA!(;7oWyN>Ko3^ae+HKT~s_I#rvnNNx==ZG2Q68zf$jL%SLpOAjR#bRyZhBtIV#bGG)C1wUEjpC=fpL{q1 z1NBl@jfT7sLJV^VTVVYv$6tlT-%`bMIyySoYM*mZla@!hQ2M9sg&P+%EmrXs;I*iR zxX>qe^jno_s0*={pw##CXu01Fdcml;U%n1SFsDVZ7mS+b8yE}iMJc996NnZHn?#>; z*_|BAaXl&@q$NJ$FB)@H%2b`{MOCbx9~AB)SQ)|dX2YMgv$veDI54(I$F}U29m< zy@rZ)r`3iIjpYwdBAxOv-^^at8m#|%hZjwgA|<4Zm9(dT%>xa^+U;d9PvSH;DABIz zWFFL{v?!l{A)5?BlSGQmD=~rtedB9sYHAu(n77U}HvC)&anrPDM_?L~zEDeoKY8{B zKPXkQ#tUm;frW=S#Wj~q?&HFhu#i^eddN!O%0~Y2n_12FO3luZ~ zuTy8oKF-7rh0Scqt=2}sO`Kh>@Mr&Q$F!A^qW*& zx+;XdQbwmy8TQPvaCbcC&?N}R=H5b#*j%_143fr)bh<@872xW)e;C1N$!|rs7;oMM-tn zAcl!`DD}U?gG(%eyNsvrlU4waOHiR>Dvzm?xP4SbO;VEiLe;@pcp=h zJPlTqtYU=|L9ju4^ygDfPied6FX?UWqRzWVCr;&PCXHZwMqkNytl5J|C$k$9&vjpL=ToZD@1^BmlW{$#kQFdF>SGcO(0B*R4rg~8EB z>tV{JchD6qaEcIfC?%A`2c*Ym_{V&m_0PdPu3JyEtM&M5`_|gYn%!%o6j%-YyLlf} z#e0-4Z+fPCmHL4P6(xf%lW2O71r3p$klbitH{uf@4q>IR63S-&_&L^_e%fdGh!LD{ zJfxY;@v8S)l}hgB$igto@0%oOTZO?JtdiHZ-r6_KOJ#j0Lup!PWqE}$gV8M@AkAvU z=;2L(3c^7XhZUR*)OXg>YhY$(WnR?eUg20tVTzIro{Bo-o4MUGPL!fpq#xa=VfW}? z*-Mi>n^0kYnFXPe+*$Ft-j(VAizc?*-LQdC5LIGA)Yb0*!ATHT4pWA3W6Zc8v!2Ar z(P%x52I9$_?GJ@hMqDshm?E_#p%@=qxG4bBEbQP2#307p(&}5=D%l>x?$qz};SofJ zXfXTH*Vp{-=u!62tL}XJ!no(HPu4T8IWIv8?rBiBDmJdqji$T?4+gGG&k~#Ufi{&S zNnj2hlDzZ~4my5iWK?TNV!mGky8<)8ntRFlve!S!YF3Y9;+KYQx^7NMb!;&E_qFG> zF4k!4`_v!I0ghMCH5R{-)6-dU^Xq>$STsF#-EQ@w$H>iINj>ybXCcbst7wIZ%DNOHVgYEil;B5a4Qh< zc@R%uIJRg>`5lOReiI35fHDw7#}Q5w#AktBiK%*x1C_44d=k{ld1B&t!Vw`H4wC-k z_SZk5&862t_5*5U6fVn98AUh<7Hl^yXy}O4v3x@70b64EPDIE_KGmT3Fo!F6@Z z`E;uLHZ9wO6&!1}A-oFme6vQ^)jZMb7mV6f<)MaFz<1c3&>hY)S%=JDYzihX^SYDf-(Y)ZGBFP}@Sj|DB}=MVJw9=qbakZ?(gnkaC^j=4Ty+c!q9<5)hD*M!LI{?vzGSy1S7c zx}~JMyFoex=`Ja0>1N*h`M&whk74eaefBAIq5+r~wXV(8*2A_qf9Gw*IDvi2dVH zz@t&_lhbqcnUKo?t8~2ALtA&23dtz{uqMl85L*Krc);b&uozA0LZz$u=ZH6KhzAGH zpNNa|m%r@5v|-33Fpdl1S=giboKr@OP{$vq$n{@{9w7Hp#WMZx#{yA!aA5X8`7_2A ztbFTC&7?S?Ni5~Ozkx{`Y`LE@B4k$WS=66`tYLGsJhV}Ym5PN7v2OSww0JgAU+`?o zVWeNl1`FNz^sRc_B!`)5a+BiEXb?FptQ^u%O^pcyq6Kl6SuQSm%u%%QMID4j;_C{? zehBoIXfUw0nRI2M?c-KxF>+>0@=G(1VfChWZ*1_h{7i2Blfyxk3`d-)Sri7Plr(;e zQAZoqH2wX{ld@zTyRLfWG3}Dn%7E%AvSMB2I02rGDeUbqZ4|?dK$Iepw)X7MK-d?% zM)o?Ss;cQ0={m9zGoJzL^${q07zW0C@yf!fP`+_WkZOb?@Q!;o~ZE3R{;>MO8=#lkl{DisWlEj;OThQ9$)~^RO1QFpVeTcoaw@&!=PSWA26ITpP}jieMK_x3-ns)3^~n zDvk}d)`n^_An!g!%FOnXFvR2|ze=PDP7#EaqH+)gc{n{xN??$$;Y(ulFesM>x}eLG z=S(30ysFLmr~yK#;_<;n7gX zs%kP-rf1OzEv@)cpbm8ds3oxyC_!#{s-;i35PfMnWEq>R2G=bbKo7~bH?plZG(asj zsmcK%#i$Lh;VT=@+FND z79a&ixa0+`9FOQbh3fDFr$4Lvfk8${dzm-w6?f7&hIdW`2DD3{;iW$f;-5BXwnIBpRp0RIn-TELxS*e z3?jl=;cF7lS=@EM>4DGGWhSt>cyg6PM~Rq-XP<*c%Nj@fRSN7@Eu&$2`|TL2+`SE> zU(ksnvJw-MzT`1t{uMFr)y7Dtwf~W&j})x#J2Q?wl5QqHU9?}J{PB(3EgOA<@LF&}X#Kt2jn8NlMl1U@}MrFJm z$01IF3r*>1Sbi84t8ZupfsE0Lgb*a8zH2Fm;6TJkiHGjZp7ee#%vrV0(qGOC;jGlB z6MW~%L*k&|Q})W|+^as-un`;>%lfNO|4s1M$JS@nnsClt;#^B#k6APjTd16`H@(0L zA=zO}$Ky`7Dp3W9LJmpohjE~$CEK>3Xxt{2PmP<^QlpUp9EgNlKQCDeL2S_}nkED! z!9e@*^sXup;H}><*((O%e^kZ)T2)1o?uirua8Oj_6r0?vqJZKkH9&NHd>r1U3D;U8 z#u0_-=ueXzY^RzMKUtQmgtN(O>dg}{?=`>$ks(3o{d%i|w*-qB*A9=tA<7>SE0nb{ zj>y54=WsSxs{X2eEU;T&IYKkk)swT<8wRh>Z>4aCGtcS3>^K*u)Rcz@qKED@^t)R^ z$d99aKK;@`j8Jh*Cgq0@!*n6sKcBOf6f5arx^)nU4efNU=*w~`)9|XKhf7;_{am5! z{GZN>SRcekN6}Y2^y~jLU~SSw@lsDYOTGGmT&j&F`Tm% zBbpGVQ^FY$(Mg2FOudTwSo3k6H4UrB`u!FId7P$XTsuVt*6_$qXV?2J3NgN`uqBfs zPzKMQU~8et_0kgYm{T4kCfl$M4XH zK15UnXsDbjt4JbtmaaN86jF@}--4#~-vOH6VgyF8smm$7Ke4*_@x73;-Oe`?J9;R? zOd%%PP-3-yhGxZ;wPLvbEN!aA{kH)nNn7-YU&ACCEzPc>C=y)$3LM3s@~zkY&HFbK z2l#;b@#Hzbd>!g|Vg&v6Go6ln{p;x9T1Y1%Y!)U5u`!-NQs}KcLe+If0{j?V?6Iar z)l1fFY*Gve=#}tR=E%l7J7;HS+X*-{<^^=|YAe@DNjn@CNus*Paz$UF5eC0ZUkNen zU5gkesEP6T&`8OLH*kY!ytdRFocxu~&2^R{F^;%*iI-fZIEE%t zJEaSE(pzr1d#=SnQ1ld({Z^$m{RJn|`F1d5+ReabtSY>gU~7n~_GN$RmpJga3NrzD zEHCElec)iLCKNdKe*XPiy3;6-6b~Y-=E!0VV!?&=o)^b5kH3$w%Vu4gQ)l=DakCIs zGSP?igV2)LM`Z*XL1*6T@l97v>wb>y4oyO<%+k8g<^&~d2{Etx#0Fe zk}E2oFP4rE)B#^>5BOt;blUU*8h9B#LLZmc?%;haD&?n7p8$8^%F2pvB^Uyxy31Z7 z(zEgozNc^%%-eJBDmY1Cwopi@|z6yTX2(lS{wR?LLBV~3BDZfWCFvB{ z`Z(oJviiRnlS4qjr4lf!zOcS~x8~RAT5c2$aKZ1(a0a=p_Og`oOFaNEzT&M`%;FW-7#@`24~k>xSVX6Ug^yg<+Gf9FlZ)Vf!~qG!meV;# z4Fc{cbwBEp^YOiU_Ms~wAbe%`?EN3U=EJ+<$R8h%;D759Mpu=bR|#sKmi;8C6l@h^ z!@RC%j0mi=YAj{KVf$29WrJi>0OxSJHg^ByPh{ zbe%KEw(a7UHS-^sQJ3vW>N5pnv$RD-hW6fTo+tOZ6@O# zVw_43IQ6hQ>(=9kv?ko}g(l}{nPl(;QoZhJRqw9F* zyK*{VQEfEuz8W?$G;iorY>!ne57ER%-sxsv0e$&8RW_3WCh!!q`01AO^emz$W}zKi ztPU?ja$Pur1ak3qjvmCNnsa$fT^H?NuQiWFqqOy@O`fTjXj)K|O^^Nr9}1*OrEc1b z3MfBN5EFl|RzEbKKvMUgM}-b-`_ZNKK2*CmINNK`$B_zf*B)EJCJU&gC#56{tW>;X z58630wtD%0h~YzB`uN$2(ATqay*KU3TGj^C-+|)u#yhK$QBbllbTlh3g8#_9n&@T6tBVl1$rS9p1a7w6Iau2r*bNs4BKc&OT;EUU^0-n?j zH8oX#f*;*jO1*+7o^!bq>6AQgM)wS9pkfwdU&w;G*up1rQGW%wuj()!|6EO$x$eRJ zmr#f*{%0_1$A|QPe+H>_@uNc*HlZL8N3K~l*O30`Ovn`qmG#(_q?l<0)8~^TROBHI z7XCWXAK%X_SH716Z@vys7@n1kWXa3b3Kn^}3@#tgN895!YoiC|EA)T47o9&IcF%0~7vaJmo%G&-F42)a#0ppX_6&ec(Jn0+ZQ zEimO`*IvP@Vj5rVr(GJvUmo&j?h@W>5CFa&%FU{+YY;s;6RK8Fv z+kSYdLe#+H^|Pb_#`P@`*!@;j8)R3339W#z_n z#tAl zBS5{vgAGB%#Bgq*i4*%cLqz!uUj8V(#5+iz?y|==+H;`dLsKa(Ow%J5XMO9^Pr${d zfTzF-IoR%7Vr)UMZy5DXF9KU1k!2R;4lrB-TxRyZQrwK``i4Bts|~^D)kUy^-6Ly> zetehb4sxER>haOBd<*h$k#MB`b_XyVq&6M)nnZ^mJ%SLEnG? z*3+jck@^=vN2ke>uu@AEFy4NGX@gfm%MlR>;*TX0C=pU9*PXB} zSt4-WjRpE%``+%G5=iQc7wywk4d!`=#kJmQzi>%9{)DC#1~LT#*o;V(ThNT_a4DUr zNvV_`g)5^~pV<3VY?IT+^C!BN$F99jQiqqHcfa$tpj!i1`@ws%du@I- z;D-}OFh&7drTu1evUl&QH5lbIJ4W|qhJ(Z0gNk(u6XGQzUSD5>f`R}JS5rG0D0qY} z|LZED(U5|pVGJRH6vbib<>>2%ro6x0qdk#CH)`mUty05vN(u}f{ z92In&lXrXUN0L>%Ykexm?pd&thX*TeK(yCE;GlN<&#&#~MV8ov-B({+J99g5lf_Kq z0{-?C*N)QyF`%09jc?Nbj9SJDP1Ns5SgGrh6brC{{h=59b}s8&{}#sIQ9Nz3{$xd&pcd=rmo zTB%lsfrZ7Q*Id~|1_D$5&`l+ycJbQ%#)=<7>*mi5kBdV2Lu2D=*r+U7zBdC{c*!{p z@v&ji>u}Eyj(l!nQHi0ia3=>5Q*&4}e(RPKc62!k((_~it8l)k@A*O!>eSAE1G>V6 zmt7XTW50ka6=%hg#f+}qTQI^mjJSZE!;KPmWV;N&?@PAiZnYk6)bSkCLTT)hrd5N@ zd41MB*5&4Q5$P8y?Vh=zy3Yh5ZLISnCQYEkVTZ$ot|BB*;w43j4=W7Ur&1gd^ZPij zW=OXPJeXOWa=5rJdEn2qJ&TQ(gE6RlpNMt>*f_P;Q}Y<>^8Rcv;tGvC4X5W0FEaXT>q^{7C1+- zFrNN?L=Ge4G1+kOciG}^JS^$NSk@t7_^tN2-d1X<^4<7w@+KiB_#$}cuC)-?3!qOO34UuPwT zEC0wh()Ak#Fpk)1RkCRl3Nmg?MV&g7fp3%mmlw3OAcK!z z(_1t?wmg&9>N8j6HD|OMbE){8ba!|2SAsx41ZKBnOrfon@KK>5l-4XyoW%1P$+$tp zY!M>~bw@CcN&@c8Rud^>D>=%KbuK$&6CLNSHAWV3m?(ImG}g7BAPJb{B3-cDckh;+ z25w#`a+3lTP_PmfbDEU))V>5#!im*V<@Q(*WD_}a21Pbrx4Nz+oJt$s-vsEtbZgi6 zO;yCNo~2rZ6&R9F-j`vo?XA1GZRe5geUJtPdX=kQ_ZW^NO2K^@K%l0|3u>I^#!X=V z$(Ab!MW?lXHV`(Nn_>{3YbW|g12;pf^S#1ayaj=zkX+C`%Sp>&Xdh@pEoYE?&lDoQ zLEPSa^~)}1HSE+2ju?(53%MeoDGnc@Wjp*WgD^?JPH4MfV!?b75r`p9b>Lp;`ynq5 zkLo4z+(aYNjbB6Ct#HFh?_bmnh?|<5AY(XcjiY#cd%H%&3s}*bFvvBT8&s3hrX*Ul zCVR~f5%fH3;ZIDGO>-+dZ+? z9~LmSUB_)HIUy)3>g{O$H&KD5gf>dVYi`xvdtHpjlr2zR7uJOT4G=Z8K+RP z6vEebBsN%1CCkw;X)Q*PupwX!CMuX4@lyVgu=oIM6nGk9Z!9#dOfaC+fVK8^)}f0N zaOHW;z^7NwpkoehT=tFxO^J9vPFDqA0M0|OM}~$1pG5uih|m0d8|-<7cA;m%-?z>U zHJ-9E&ajSd;0fpA|!X2bG*4_P@ zmzVc-psYkX8%4t!_oNegB{tkZa?VQ)q#CHxhv4>+@Dy$MSL})F@rReWh#W+98N>+% z{BsdS@d*o27{i$n-*!B7?BkC^Mmt>GjDJzCDZ|~JYHG&iV9p^1Aq2?AHugo!zC|F; zdAO*)4%>U3S>uO&Ly=;3yzF`0e}z5iw06lC`GqO4u_z4SeAH`R;%q<0Gdk&n72k+n zdAEEpFc;u@`MG%1KamvKva!4FH^1cJk?QXZlB2xJW(=`H(utS;<11}u4fu0TLkwDP zB4uO%2U>z$xiIQ)D#_`$e+#7J-k^ml(5f@1LZ?;nH5pUd8|e<#&_pCTQ%fK}-PRxf zrUL@4j(}4V)=v1XEJCzU;C%RC(B3#m2BH9!67b{7ND*ppc{rKAdih5L8p~0FJX>mH zfdjzOkFM8MxNDxXjKD`aHhUd5g?(qgar|gs{GlXJP(eUKhL+FLyEV&qedr{>KSr@Y zvVSKQqd%|CF5sV+0hYsa%$+4~b;i^bHzDMZP2_Q&oc#HuAS1@c9##+_8r$#9fbFWn9L4n#*pM&L(%zLko(!y~)eESStojkO!$p zOT)vS$D=xj^U@Q(*ucMIS6HERRGge5OG3dTR#k0~=q*oMo;6zKH_5wi5l|`Ea(3-d zLFwqyFB2IJS`WilYZ7_JMZ(9FothERJ4Vn_p;}RKF83yz4&?U70IHJ`{RUP+8&+73-)jve>|4TAE=idf7MG^J?!i zb)ynTbOXqSkYT6cQ!huGm?9n&De`nLz0$-ei-cn)!7kO(a-+PPzi-iNGL2vQlm?kJ z4WD<@#!8T^D(g=@L;|kIm5<~!PFva?Yj;Sn{mziXUmvE;EW17bPE3lvs_COu+~}c) ziG52vcwIey@u}gVrlD;$o7iTADD`UgW-EvQ*zoS2$Aw$b`{$*LcdMVp)jwYA7~o-y za@#E%dHdaJZ@gdd_*=&j%I?fF-zC<2nDXNdRs$KAD51{By~FT7lZaBgK!8e{6U`U? z9s#uCL0mex-XM5n+bF1u;Z$<6+<&*X0U7@v_8d$Bn0v$_OWWrvOw@$YgKQX8FhPfQ z^Sa2-(y^pL3z1^Y1QdNPsI%%@)eR#gOY-H9O0WA%o7F9Cp8%zQ(d(Q5BICcu89sSxE9x`%32iM?6- zbGfzi{<`nyo#7l^csi3fYC8T0Vh)R2#Jqialt2Uwc*CdIee^3ov+j;o(0K6A09dfTi&#*sK$mE}LZ zp0^<9^2ggx@%Iwfr*paPrfzubtch+ht7N_|>I5`@Xv#JIX?>6*o<)%ZVvk8TyW+Ab>*aCFKQB?4+Q1ICofpg&o9MaqI~z19KNRmV;_$un1- z#o0!9wWWffqPfu|x~ReY4|Xk7DB|BDCd-?r4`L@fBAd0>+}7T3wX*7RW!&@Wu_3?- ztHBx3Y`V)I1!WFC?2?vKP^ee^&hfl|fY0@Ne?lg^KNBT8*gBqKCc!2p_&&#!OGQT5 zQT|m_q5f6H;NZ8V|L>QPW>>upfE>~n%8UiA^xm4BDCYhR;N+z#C!hQV_Hm#DnmU)= z+MiZCN+Hg)Kd3ih1J&@d=SR5#Hr(H8dXga#9HK9ByOsa42|o@=kMGl+QQ<*R$aSO(>bFmmlQXl(Kk&7Dk}-&@N^pV= zNS8LY5OuR6lGS1cq$ktKVmOS9j;w zmA1#eQ%uG`RhU2^wmOSo)^FCy0FIyYzay#bv1F6&3_JBQ_x9?%&1Y*?*;AH~D)4sg z&_R@L=CvKW%U%E3@CtmG+fKswrn||&3=Uxngb~j^nt(dwH6cZ-7s*)CeW3tDM_vSg zIs)g*D6mTtQv318~c$M%KDW`H4Jguw4@S)qVL(kc8rib$QMnLdoIeK6_1COzl4b;cb>2O z*@wRfvR`zgKu8*Qe_2i9IIQvAs&*>WJch>r7&L@(?6*yXQY-LgCTz$d z?&;o6dO{|oXiuxnOqI&u%tLp5w)sXT5Wmx-K`wpV0JDmn&jw0jJSp~W+K6+ zh54LU&WwZgX6C$$`Yw;5DqyO?=kED|7q_gCj&w>3ipXnfewV7*w|-nPSKvY?Hsa{H z8Oa%U`n!nj9TONjHL)OPrEV0&z~yPj+FrqzchLTcf4Jw_>@2Ja&qyIK%X}k1`<#v( zcSN5OGhVg?3-;3g(<5~w;7W=dgc7E9-i-+-lx`v($-{wsULw@3YFzbXL7?@-PX=jHF_`)dfce)2P1{b8ljXC~FH#;wk_ zlc1N_;fv?O9=OdKq!kQB{HwjsjhDV3nrL)a{r>(onJigtSlCG%EthL(dU9iKhBtG7cl{vq%2rQ}R1Dn-!NU%V;q&`ev1PtC!?LA2I3rNL?ed5g;t5 zYMz|Cc)j?kKfFH_KG)}Zz#}(F!A#%^H$CJhjv8P=i>XEphm+vaAL)cupCF&*j6Gs^ zyFL$!zSbW+RjXh*)4Z1s3mYK-X6kvVw^(3MY``glVeA#JLCQugoHcgl4W?!8%KFPh z&b8+Fv-!jAYR#u<~y3EIN;uSn@Ai?d3RLwYE%8;)i_SUn+1nbCGi$E-_myM+IiU?DFTEp(2%zhrx3lS z{rc$<|Jg}dOv?DL4zDpE^3ZC1(U-61M!pS}R~wkH?U+bSvYR-b8^5YINGi3vBs>o_ zI--v+02*kcNIFW$@f1}#z=aS+pgQ(L)-X3oe=DjO0)sh`)_%PR4u5rp^{zJ+htwCh zvrdOB3-Q$!x05B+qO1&j=>ztJrc$ilH*I}T$Vg%!O9N@(_UXz~D=8^V zu8cm4)w8XVd!qezoQMc7E?H+tUerOmtQUY1{t zpI)C9@c@qB0a^*b2H!E1h+apX_?%^t3M5eO+FpC!#fm;_e|G9)`=jF%-t%@xcM?7aDn z?$e>wbFNvj>4lBwE0AOL0s$FE(5(S}S+uh*i`~^#G-a~DdJ*1S%R2Tc#=KNY&iN$e zxv18C1d1~!=&x~#~y8oo(PQaSNC{AlEs?U((iTQPj zJ>!A9?8=xq>Dqkp8^6Ol@3oQ&F0iMW^Y27dI_{yF$yC`!$sOoVtKkO(3J9_2O|_mP z|85ootq?xyluP>3%dio#h$9+*X6er%2nJ$J8_U&I4Yw-B${Z^?9EVhFY3sjlNsuv1 z#*|<5U&3LB(}e!VxnZ}Y(nGX4pVXHgYuTM;X<@l%=g;dN?JYp80a^-Gdb^YDHXAl` z(_4t@*wwA?v_35!B;mm8AowE!-q(?bwQ|s}JUHL2-ad&uwsX|Ft1opQHTVt+3QT#> z=NKWNxq9X)It8u+hGK-+9WHZfu61I4+;oQ3AWCP8s5}PE&W=U5=MBLt*tyMa;x+%Q z=u1X+*uU*$X*oZDIAWE}L|6b8Lm?P|S zLhF*?G8ugYIvM61I@x%^ZOj^#08yPWHnsoK$mbto);v0xv;5+A&q6s-dTX;sI;JY5 zuc6KQ74{2xC1CHvq-KmW)`4IYJCOfc`}B9;=wbCG;E7C@IeQ#w4fWd&(=d)2+a!_c z%~um+n?!tULSRSZu%Zi-U5BFl==U6^>sAr+#CMCb9v&WG&e9c2h=7*QT4+ z0XF@X`N|RS*2hv4_H4C|2!h~141=#PPsT^=DV%ypz&g4yLwzMN!uHhTJ0S{XvNeri zs|ugPuxV3?M`^Es`h$^ge!Pqp_Nupr`1gkdEN zC8znpvO#!PRXp$;Fa3l}(mo*?!Gl{4`lw04&_4l9k=CtVr)M=3kUnbPH#2=I z4}%uxe4|yqkRw*``S&8`Z8pqAR8gF)*M)|5jlM@4X6t$5^ZCUAQ<_~UH2^zPq2-jF zn6e?{C*FL{W02Q?sl8DVH`mZCGiVXL}r&7J<|Kx4zOKpwQA7mCLj@ft2|tp*tKf6 z2mW87stQZ{>HPTramCx)+mqFz9W34~gDF7td!(Wp+-z)Y6=9*We_lYtcP7=N&Giuz5>D0i}69NAQuY+c{+6=W5fjsx!k(6JGIdRv%fPHoH_kW z7|N6^WwU{HY*FZ6(jgsgjaWBRR^4RxQbi@Khk@ z4OiseLSu}ec(4r3PEg%thPY4i3Nm;;BbCVoe+aHE4drKbP(z%ZLp{)C1YdX9hg2%7 zLNO4XiDY|#je)iE4%v>^$x zdM90URFi|1^#`{f)PKoj{P_Su=xZ&(5|@7K;;)I2PlIjTB7%(l?ky2fyz^@Z)nxQw zHCU$sh$iy@I#8?Zsc-*HO&77rsCvxsOG8l^o%K!wD<5MS5dDearG)jVI7R_?>@ME9 za?cLWei`G1Dbl7R2Li%~&c#XZt?28|X3_h_R!hEB!w@t{Prj5oiu@VY)v3TDNO-^e5|PILXtze21souy(zD9YlF8&Q+6y#FG(+g4qm zQNT-spdPstMd~6oYj~YudUv+2if<6F%1`(w9vW)WYB{ww1`123&UQN$fR@oH=C$i= zDHz&@Gd4PxM@O_v-Se>_gcYms?EMb7na>Wm-dOAD(cZ=5eE$GiDi0P93v_(Wv*dyO z*>n}{=FE_#!9yiCmuT;H#7~2Jc;Al=(mbv~-+K=5w>yf-?EU@C_>FZkYzJG|=Wp&i z*?rD;N!2P*n(L9m$ab!0)OSB#%6S+3BO&ahZw0Ti4?@f&*!!q<>~+SHD_6ff!S>YQ zy-Vp+8o7g3)~aO>D)`tMXTym&uyll6EiQ*Id8;WR#oh-#bd%?jiCm3nrFWKnM9AN@ zsYyE)bghFMfvB>bp$>yHCJ(}LmRrtidNl1+avV9bf@lwpXdX5_IT6S3nUYw^v}_&} z7Fyu+X`ZDRbIDem|F!{%Ou!QDtHb6)biWD{1_YeovtB>B>1VkLV9wkM|MbBPdgr0T zd*625@yM?J^hk=}Ynk7=>Pe?WiZoWUA-1CG?p}Xgds{iZ*>FHJX{phd;{i-uwQk0f z4`=HgCM>7MAkdZ};67!tMpjo9ErC-K)H==Y-Cbd&V*9iw6PJ>$I4&sidfRN|TcDeg z>s3TJN#L{mFHe2yW-48@e2QPL-!Aa==pSkBKh)>hMid$Q<|AL`H>4OI8;p8WGDV+dhv4fLm89`UT5?2r#{CeaG=nBON1SaibRGT??95Nc3o;v zg(f8#K{jzL68f#yOT-_V>=NkXI5O+H47xR^C2FLUb-4uy)I|R?a@g zrKtUdR%F^GAzOIVAM$0U-{DvH*1^&pf&?0;8e{ffL@TyTO^&~4ibyDY9H?vZ2~RkF-0xo0z!1C|DApeX%`=`Io&}q^ z?9lege8t>858*jm1z}-2XcB=V@62qux3@4%@HE8nyo91?URFp zLZ>r)dhtSTeBIB{Zo8g4{&z>;avd{V+i>wJVLHppjZ`lyG3wkHab(A5FWc2p00p0Y z$gOhyI2EkWxRBDFP--c~S3h|mJ!%s`ip*r8jeZLzMz)IzpOVgxo4*<8$4B;@Qt?2e-iD(ZtNSd*$5GQgm+_M zKcDCN(4=xF?^B=lzeZk{L~M}8K?@tgK=rxE-8Jj$CLmdoFH|oxo3W&VC{d8o0HNE8 zc=!GM$D6OyyqZ<8UC7!VW2 zBe@8ulN9QjGMx{(Po1xOE$ZO1R)|rHs_owh^b{oWUi`EijT~3pjO#vSzk`z`!IBHG z5aF)>jiRlaQe3j}B`p#N1eoS*V)fbf9k*qN31f%NfF9IB&GMbU$rbBsE?!)>F_Sar zIvUqpI4~Z1y!LefX%aWgBm?k>=|60^cp=jYxZ=J!L#U7hNEY|1EJbkkJs%E=KorBV zCij-^FBrui09dlovs~g4A|?c3@9UG)IM`LS zDC{lM;Nh))Z_$({g@H0XfSnSGQvv0d4bVod2Hbq;TPG3YZFrR$QAz733%CybCl$b$ z@D!s^kbTZs!}|0st8I}brhkd!jGtrC$+6M-Pv#U?8Uu~kC?|RA zIMI4-H^D=~yR;x@+{oSTo_mYUg^<~YK-+j&6K}NJsdwwr9QV=pr3^bl$cO<8{5FEb zgTDFdsw49n5F~N=^W4w=WEbj{KR;Ww5T0Oi`Ffo8I{GwA-kymc7aqM+^N_`c_aW(l zjGi_$J${{gsI_`gNJnZe1!ZwJqePcBaK+rn`>^Qj?A@@FE*#O}U%f+s`a2 z(J@=x9Hl@X)ry;whM{ZeGWp@FjP+#n1PNr62>w&J>Y3=;;)9-pOSK_Kha77b)9sO* z>y{C@&%QRy{cot`f5)vCI4Q`{?~qUbvdmLtXA&?gD)625Jcv0SX#U7#{}cfUZ1lPa z@bhv#nATv~f%9mib`oMDSJkHKexKqqm7SyxyVVhMy^a=5?B!zvXR#ME<>_v!o;swj z!OL5;+?_iBM7R${oH=N7(_!+>bZaM&gMH>=C`hNdv(O^)TrUbUN=$ zDV`?lTA!(~R$a_yazuXT!J_fPbMKs;lZT`ltaHI>DYoin6y>J}qk!mE3t1s9o<43R zV|eY*T_Sab`=3DTR-o`0=_;}co9MO_d2Ep;f0m7*6WGCF#|)w4JnwQjB<~TuC?-)L z%B0WXC1-0gE?FO*+WE9r{tb=&O#T=D-OG=|#We`A=|Gb_NVjFxeeZ)_^PB*PMZxJJ zm>(ns-MMc&XztBQGI~`6a?BdD;iRq>rns-L>Jh`)>lVKKC}{O;L6H+_OEYPoe6aXF3{Ni1?iyGB{o zTS)8|4M9{%iV|ZmDpLN+tey?>j!IPYaC=^zndW++du>)t0TBcU2|r^sU=H*>R z5UYCooFhoF$7QdFBL)8odgibNCpe;fhjoQJd21j0D5g9YpN zpol;eVkT_p$@6$k&%cW)jo7>5)dtkXoAk?+V^ry7upwu_P^8#JU4O~l%UuBGlXQi+ z`OapH_|R>@qfbfClW)1O5rL0!J|nR zU=$-j;f=2zFUra};GAC>GTwrc3N^j#HSArB>%ubm zl4?9A0_s!cy5vj1VU$jZyM@FM)=_{!4zp-gO@7>xkP)&nDFwRj*Xve1eh&~ZyCnDUEc8)S`fNcXziS-3`*6(%qdRAl+R8!b*3?e(!hA_Xlv!vhVG^bLY;T zL2sbz_~j1Ne2OzKz@@$ONU;}rU{CJjTQI$m0+oKe_DeJIzRwUFR!BryzdWt1zV8%l zUo@8d?9-4m>Dsyb&eIg-^KiK8LrnOnxe7-=J!OQcu!e%DQ=`ttV%6DOkaF69so}TT zD`AvSu`qA|P73qoA+5T>Z4n_mR!qF2*@Bi>>{lE(kbq+u9KhHHI+=M|Fpv>7YIbfr z{?YHmKrr7y_7JV7N+J7p`saL;HCwo-SO=5v0DIk>4wvI3Y+!povXi-@J?n zh<^~DLq>o{2WS&>p|>VK7cP7hrYpS{59_URZF}(pA|#}N)Qtxlz*VE5?E|RErq!Nl z2dMGObdQ9Z8Q4vyp#KayZMS|fG*AmK?$50$=RwS3>M07LQhE>-mt^0 zNV8e|IQpUil2u>Q)ahcSnn}w|58Jty<_)FXZ}*{>2@ayTPGSZhq~T@uSFi$xZiwKO z53J&81LA~MlZ8NGLAcz>32<#dV1(Ks$rhGHig=pYU{rQZY)M&^8(KM7(eK7O-{MIn z%;@)8(@)=pN2{j!vh(182&(S_dXH3`?Q+gr2Y_lJ5p@^YPy%oj&jJc(P?|i4#Frb6 zMzw|75m~OmkSX;bA-D=R8eGKwgR_sl4{5Lae@%5Ov!1N?PjRUwxHW{YABNjh_#K7w zU5}Q!N#MZLZ?8!$k%FGXBIEH5`;;^#TIi;(pr7QMoCqMtM@MVTwrR8h24a-cj|(jS zrDV+v6}0pmdgY!yiR0Y!wAI)SUoRsn7bum*Yk!8&IO%_#%I&tC%ohJe*!+UqhokuI zKaNo#+vke~^AlI}yLSd<@?xfq7&K}>1g1&@cV_&bsHqhA%m3fCMIXz9VfPU`xDfk|veO3A+0*b$>o&$vBO)h{)BDwpBCFKX-?XLBMSg2>a9kjczb?ndJ_e=2x*5a-mMc0LMg214RuUmoE zj_{GhaKw^<vFm6K$H6z{ zKMLq-xr`=v6hY{Ceda(SxA|4`!l`1IsK7B8f#;%V#)}(jI?hV-U(p3 zx#m))_2I3k0hB*7aDq8SvJ~B&(m#{q|M2Gu_L4U?>^*nVF4q1ZZWS&^to68^e5v$~ z^JFa=gK%b;?O8pY`8Yu7d;dBec-M@D;;Cx+$6ZLz^xEq0#JNwH??!+weo0P!vJCI; zOZ#oJ_9kXPy87%dYOV8@5u3_^nrN&>2!ThiO6iv30@y+`=R2`UfXeTkZPXJZb zdOy~?*odJ2a(;fRVmq{aKu@YAAi{fqT-x(Vg}O91DR~pGl8%NjlfRW}c_-TDwbj$> z#lWh5`+CFINx711Gv=k|slvpHwEHPbyY4GpE_m@8($1VMRuM2#L|NpL@jBt1_wcVY zPWV3Q@ObJ%Sovve=7*)C|IrFnQsQq~T^yM&q~@~JS(A=h5d(v&{}MB`vXb$ZR-P53 z^X6;1ysj*&F`>_RU*NQl99V&DCkr<%Fr%fF?MqN)=E9J>^=* zL)u68#{x_INHeC4%XGAmBgSFmSDurc2QM7k2Xlaz;mDACFQpybpn!<_ws>HWpk6dx zS~A~7)Ln57NEvPCWdHUkG(|rvgA}u*g97fe!nf0d!;h9h1ckn{;Icpi|)xKKa`+^2hNXESxpFWrra-p-!NBj!zZ)L6}!q zwm;|=@&bv{U4GTrSQ7V++`3uR9h^!O)VUumqA!E=hO5ZM+VyG#%@~8J%32{(%4)Tb zaj~&)&_X2&lvvYZIe3xY7hn7Ko+8*i1>2);wqFebce?_N!6YWRmkv6`W6F{sX?JLo zSo)6^2>P@R-wO#96#`u#(!t+<(ZdQN9TTS~==9!(_b*c?&q=k@N*o)h=xu0ivf$x2 z5+?TRqwx7U;{>x%&Vxspr8|HrYSKrN-|W*8){*Vq^=M{U<9==}@Ll4|T|_`ya!1Tn zEuyNP(KK*JT;^B71QEn%H{(r%G@eT|RIez}ECw#v z#%y|8tyM1oM3!N;o^UuHoF$V*7jIlmlKpKf=f}HuK$Ic(MXyS`THM$hG}EeIWOZ&$ zx@eIFVGMD=WeihF^kAJzM(uF?%JUl_g`>v}UJZ3=tjYZISIVu+dVl`&mCtShP2;S< z>M7UP1=`2;ga5O8RiLtoRJK1^p5b;L<5cuvb(OMu;bO{%F3eAi-dwh|h+xOK{4x6W4#?)vh&)M)b9F#xiCF$8J((@=^b?&;Y|Lz$B&c(VRC%J6 zwZz&%Ti>B2zZ|9KU4Z;P6M-KM|LY)SN(V~3@6o|T?>*nk)bW>nPE^rzW#rk55EVVA zMVbZ6u(A;qmvEsTC!f7%$$tY&GqDaMMy-IX9aA0U@b^Z1rO?O!y&03 zxz|wDXxGL5C54SZT>%Tsct}cCWnQyFAR!0}NK||bdk2LdOtF?$>&xAU>ww^0 z5GUJBfXKnOHN$FxNzTQB&usCIum4KF87@1TMq4$x-8YJ!`)o;3-%E%n@OtR|)oV9z zi+NW~l`d-rcp_U+T#AamUc~=k*vU2&gD)Fop&&PnX?~627z_FgscxPC%hNrn+V=#S z!!7s*__E=y1l65Hcwdgt8?$Otg@OJ6{?VFHcY%gEh3pbeKXlOK!;Wx1m(`Vc;QEy? z89MN*EX97^_*&fW#*5FMfuoSGC)8HoqHeexK4tOdy=fHcVCChEyrIlZprNeXNcAUv zU}xN?HRwt4dC_I4DYSNB zUd!3oEF=NWW#~OVK>fJE*WitgwLk}w5ZIn4p&l=L!P{0B_e*(Lir4fisFCZF!Mb3o zLOz}kmfK8tkelN?#*E`N<8< z=(aD_1*MsjelIt?tS$ZvI%})7)uU+CAgVai&U~nsZmUC`4A{q~93DX4h(g_RmZDZDnU$HsPR9oi@6Fz>vqDlmNap{AsfLGZT%R;A zJ@Hl97(DTWL;wa3cmGvF1sS85iy^TsCk)=|>?Vh1q!?LTM7(^kP5&FMi~o>#r?#4|}~rPv_{7cAwzV!r{@VhZGov8PVjn4I)gLI%khP%|PjiWUA>2M-{c%Dhs6P)PjFqIsksgQwU z`sAsc5T{0afn(+hB9Xo2mVP8b!CkwV+lwUQq|G55_W5EQ4U7BCXE}OnMPQZ{zW9Yb zz~1%enKC-#qu32EzV-sRk0Hu#fB2iV?JJ36%Ep;zuI3%wjbD97YU`r2Nba|tNP43< zJt~@&To(tuNmG*`wtdw`u@ejTbYk#d9V!WRlU&9;=Cm)9Qq@M^wkZMKC9-pUwiisF zhLrp4nKL8;fsNw8-@=e42T!e9Z)r2WgRH!noJK@BeUP_-FfRLz1Ur9ei*yTK45(pc zz)&78lIg1JLJhSY$D&E^@G#gyT@3cP7xs=D!mjlL0a!CroC!SvC&NsB29?OI{(mg$lkI%e<43fuEB~U(+Hvd#&isLh>W$=z;bJ*|OO#P1k_WkIMGVH&Rjd8l z;>LzZDFh7;RAlHXqFOQ7(jZ{8%?rmX^>J>l>~vju*(0VRN9#PspV^3o;)4)hm0Yif z>D($C2n3Hy{_+zat$jy@Q};#WbL+hGv129#L<=9_fH-H*1c8E4ZE@PiAW(%Y_H-R}0I{4!nPsdU&F@WR-L3{RYKRlr!ra_bQl2o9 z>j?ac%6aqHLsSf7Xtdo$8Iu4XY{97drpm1JxFv_W5ut_kXWv#(3!M``<5)jCxxHkskr?m?0D`a?~I0Skrlr# zvHk|H*a(Cdd^koY)n!S43MGAw~5BnL#Xix7E0+kdpKt!!B9t(+HG2kM{2+3bLsoZY}#A)5}2< zRfO`0PoA(%3)tE9b5V6N0=3Kc{9n6lRAKdO!1x6bjt5;58|Z4$m5S<+kc4r z`SEz5n{qi8;&Jk4l<;6$cbL}0wNayJkpBtiZZztlH8=tTiGArxZfm*x%prqi(e1P< zqQS9erQJhYk3nADu8V3LW&%$v`1bQXHijFc+0okI>TP?nf3^63U|=d+z&qjGpG7Aj zC;+e=bw@M(L2u;)MRd8>qzP_MJp++8LEEBR4MoP-4D?(Q3! zzZehmQ$zFqxY??VObXwTLMyD5#u`klErKW__2ao}0-nktQ}2XEeFs$S;6dPYJaWo* zO+{))zY8;LLNKI=OjD0cRTEWdxc+YR#+4BUAMnBc{*;nYDfms3C*KmFJh;hK- zqJz*GwB85y3BK7K6~88Qw@vhMCTS0_K^ix_%f0!;OIqG>p5AK19=KR7Eo^XXRQCBx z4f#Ef)MYp@ht9uaOOCs}j|>Ts8N@&&A7nXsSYio|qzD1!uZs}e#)3a{bZ)leAcDb2 zStZj^iKJBdbfKW3?+A;l{X+vdF8c=E+*tG#vo!hS13T>$m}$VFfp+?6?y}9fd?h}h zNG7xAyvor(-4@+l@6J!+orDWy!ex~a;H!nVA}krl(7=NJ=?`Y>Y2yM1JqQm7n3iDm zg*Dx*I+`Ae+VBQ9;SQy@@$RftNDkarxNLBE=6*f@BQ8W^fdzC{n)Z@d!;SJU#$%l= zsT_vM(^m>#JKhJ1t$yEmi0?vkih6WYH}7%V>7Nkiq)u?=jkJp^8!OORW4#(d^#*&d zpnslHp-%o8C6>Q%TfT|Y5Y}}t5U*4X$6XIgJ!2?hAU~omEV2Fu(6s}ZcE%@vzoN28 z(|)7e(iYwj9jqEY{Fwg|T>C3VRGcyM%DUx48bZXrz1xv+fUzE0PUjK_5G8I{c0xeu z^*13Q0k<;RC-MYjmsTrs@My;d#M2$WvSXA!N=Im&y=^>J)I~1CYO0uts<0eLU%VK= z7R7L7NyGsKxNa@D61=r}xo@^;UDRYZvygk~3JJ&_d^b3c(8^!ATC$_IeBOJcI|Z=} z7v8ePdqn^REe<&rf^-I#`{!an_H#raQ2IBz2oQf(4KkGgHMgY!F1WamD;U2tSUeV^ z!x(k!zGNlDh8b&Hkd^Q!K0qks6|QXKMyPoU!cIF2CCWdBxcp4py9|!a)>{hHsqv&X zisw$c;xLx8Uh1?I5j#3oq}j;^*!p2g*tUeNYD+){mR6l)Pt&41MQJi8hGZ73p3d** zaHFVh0)N#_^onor;;LZ4V;j*EAr7ltu9hyj0jaqIwuO&Ad(m*s0C#6ebX4#Lyob5@?I(D4fXO;iv=Qs7r9uEM>WG$XpJpJHPLP2=bi&{bGu}o&dBP!028O4_^8910S|%`)A5CXb*yg!*Sae zIIvL!nyG2ys-Pkfsldh~_!H^Y-CRaYF_GMzP1uaI@gyiqtCsQ zU{24U%RGGcXp#WThvr3PdmV`%gKTMIp%T;%O3amNOs1x$?njQ$fn_^IqOjZWk#~N{ zF5kSp{m+9bL4Dbue&H1Sne0At=L)Qr0^u_5{UNxtoHK3NePN~kmBR3}?QMh5o9PV_ zr8VXqaalyNDazhFV9Ecco z709dO(_aFoeb55}vQY`M>L15e@ic1 zQrB+jS?uoeeif@XiqYC*)!*}R++k-e#;L!(vh8G{#^0sEjDuc%0;d0j4mWRHU**7{ zt(ViU_7q604XNYWR&d(5U=VQlxG2mMC1EOmQu_KRTP%So=zO!!=RI}2CLo%|Q0q98 ztg`jAIRMzVmNOIKg0t5al`LmN@U@Af5r(Gi%Tt``8bqTJXfpXIn1-#2{`I=Ns3G(o zZ-1}f$pDOi9k6F*P=idl`VnZ1h%^gN&+N%J{by~C)#$xA-b)ta`+jRW1sRSA1TAzcB8{jbZBzi`08;JWoA{x--9?_NZw=NE>mYOj_v3*H!KL*F zRYagFMk)H!ZeB9R^U%C7PmcW1WGra5v+|K#`laX)Ke4nqXALxizhre_ngE%af_c|H z$H&?tZd=s!_;t1m zgXN!^j~5MEkP+;)8~CGf-@A>{W`_ioNDtW9Py%|%6Bnquf)bl zFx6v;&-n*@wJw1E!SwT_ia#}wTd6Qd5QZ=_`J_JkXQWot_rakMfJ1UjMgQul(Q zM)A0`+-Hn`fycP$!cF4%B%L5rI^mTh3NjOORjM_>22D@~XY~6e^uC-pVq;=9x3<1B z0IaGBd4Z%CAvRDQaBD$`82Hldz;2Mrs0JM{lK;5M3Pfs~)s%J%Mu=!Ca^_gs~)Y|ZeT9;YjLanetq)ZOB_$$?xi;4 zclj%K7Vkl;A_I4$ostwNMpfk2C{vL>VaAumkh=cBG)vR^N!vb3I$LJ#r6& z>o+D&);C&4#t|R%y-SOrvx!k&@4?mNHvAflJpbLXkmFF3V?VkqpwL)JNrRRJO9%rC z%!R?l?(Nvzb9egi;#_gsReo4ebNWqPUtK>hrl|9=y7g29y58s$M^Iwl9%rv}`th&i zLOfOk!yCHUk;qQUJWs2+>6t0oVvCgosvsKghw0y>ikbIQTe<@>R*M_mO;jON#fKg) zfUi0Y2?+_OoT~%00+jM$`zm!ra_CS+jR)@5M^0Y;WuI?}9&dd%zq3em{0p3zl#}ae z@7a@H&sZGEhkrj<$zSo+%ftjU#-obhEU|Ds4R$7l&X((p2e!j0C)dhCyU5@f5{ajT zj=bhHUvb0nmT&yr7u+C~2-6j1>bH1-#w|uQIv=m2c1i5Za6v|MijHrP_B_pVx4*R# zM1hmlrRjFNDtGTenO)vZkjEF6+6=6P6?JK}2%{cfV`rwnR zFIEvm1#TlYV@=y_q69gSl@hbs(a65x*88|1&Sdpd@z>|Kn8FN=R_b?gbIu1E_FDGQp zk7_J1%O1+%eMgE2>N|{MdwNdMjhfUzt#XV_W!E>RD^B6eDLQ!1GR$MYRA*;RV&Z_O z0ZEovoo$--e5_cLS%}ATwm(PL_-0v!R&q5BEjB{`+|49VtVRQD5%4B5U`5iz(5ec$ zr!4xm$Ge)&({JbfC`luy6|b=-)bI)Vg1OLo7z*eoyP0-gELGZAy~Z@7WG<9PGI^+C zwk4-px3xyUlTgQwFdmFpo<=(E6v^ExDIz3i z1RYKZY_O%#_IU73RNlbb;(j4%u@>>&J4>OQ$0mEnz?oJYZqO@OHpk})GAil%$4aRj zYAl%3V&BPl6h-ykyyxDlJ{=YhOiU-OTe)%`rnjY^zOdpNP^FrC<)%r$7oKt@8?Nbd zCVLThir#1bB{H=JRHazH&4_9oh~!E_=C=cmHkLQ@>^vR_9i!cAMrGf2jyMDuM__z8_ zY}_BFKweuj#6L9`so&$E_74xR5UF0}TZX{8n1rjv5aYKQ=o`pY>~z#7W%JY1-;IeT z(qRw+8X`6kviusyI^w#AUSQ{RRNMP8Lk`cq_5r(|0Tiokd#KZ8#F^1`x9NS>wi}lU z1a>2qyT$>@mSGPzEt`W<{A`UK6X}g;1uGL#1pR6sZ)ShwsJoN;!6ke5IClH#Z%0~o zo6Sm4L%P>t%lQFDHAEnyLIXmj7%@roQ{a?TJtus3q%-*xh2<)uK39yvXl) zm$DXc*C=*od5f%jute4hUE}*Qe3)1`3KS)@EM|#)MChl_R2333EQ;}64}U*J!sMQW zbGCeLDDg%@LJM&vbz|~naXbmBoG`@P%8m))ioeq_uq7-%jtQmZxW{a-x}#K+VSfDJ z4=Z5@@8$D)@=_HN06q$#+g!S>llJ=5-wVacUku#{#VU(?BidY{lZMYbnGl+py;;hg z4Y*%e6rE=PXa?~SFJVc%=*EH$964FBRXF99-$p7;59f^Q{ z1df#P@ZUvP53m9tri#54^5;H01+~eFq2a(2jBk?gJAlO)Ln42Ep>27+oSZcyd`EWB zC`>&w&1`5yA-pN#A@?>Ynj_QcRr*rWRQua=Y-v;3#foXY_uXk_O-JGc?3CW=8I%J61SVlU{SkfgCb2Ivm}G?2A+1Y9KjYHXH0)LTK7))WlV^qn3~4L{`}wCB4VHo zqru9ORbm$+Zm^#7{S7^F^~I~?Ak6hxS!;Z?ywFj(CE5 zpVKAnXC$uBK^i;m2s6yB=2JXH9JXU=EO&b~wPO+g^_A(ne;ahszChsEA z=DsH7N&yK`GOr^N<1EZbN-&awgXgzldu;EF*#TA7oy3L-4-CL35%@Tv_N5LCe=ky2Z6*1DvBNhU5z%tMLR-Y>7B%x*XY0mQe*wnWztV zrLt;Y5yS>=_w-1gqjqB@cdIbzXqKGGmrio8Lp1ud-#5O%{l@NZ9NzNyboABu3Pvyb zaFC?v0HBj@s)wU%}+g|XFW%XBW|M*YN=s-ZA5T%=uf+^bnqZ;ETBL5212DA%e}j z`zV&Vt8o(Z_MkqG82gyqRyvHIS{_UgGomnS?Nsv*76`>!)D}5L@Go}8f8FUduwoU)7K?8`pTiB5L1YTlg>rPRPe4ERxtfrOAX2fy3EOl*(+>JMxw30q zreotl$!xL`5h5EHK&3g}W!x-~n>+9nFD5JUu&c|G*kVk=FS^-EB!(EkuNkdxJgSfa zmptMBN(_fdP;(NYSNAZwC287j5QkhVSEaqD_{LJj0^c zImtTH!k<5=LQ4Xd3M1&E*RQ~d1gSC;PIJ(4wdr7#xpvfyGaH>-33MUp2%k9e7Xcfv zDrsJ7$;3hginncC8uRA?A9@=bwdu9;yv869yvl&12xe@U1{W?(MPlzolrGs=OQq(QREnd8Ni0-Rny`#%i%@$_8foGB(0+Q{dfYtPJ50Yl^qAn`pbVr{ z4v3jhTrzI+XW*B>MV|c)zD#B(s)tN57Pf${`}M-P_W0?v^K8|&cEXHvn*X4TdB!vz z9Tf4;E0R9<{_2h9bCEg_;@4d^IWu)JDbTPHm(MEu@;++#`LmMLmY|ba@7Fwp`MNCx zTpHw+pZm;?SLt{1M1TY^U|AY2*}R9K0Bubljc|MjF*G28K^cIzfy?*1x@GlwB!L8| zYhhs~r4K1pCxB_u=t`gN0A<0zizuyjT`jxD>Y_J7KyzYv=4&3?9aeM#1 z`Z42eXdg<=`mjq0yI--)ixo$ygS#n7a_W`SI$vvop47}w-RFiN-uLt#-Nid`FfWud znqI*UY|oe+Hcx2rEzna8A!gbw3uf`9+SK3M0}hiJ5kUEYdn(YDj9$#cGaT`JcCq)I z2j$+cUA7yh?$(8o(u}-%5;U?}Ol4aJZCDY%oj0`i(r|t{XP;VYtfGYwFzL!=`+1g7 ze^F2RLr2TE;Wk&@x~ahd^B#Gc`p1FPw;LVNa$>jD^U}HXVnBul3J@#GE@;gmeDo9I zeNh`;)Fmh*Qpd#wE3flh{gV>yelp$Kxaq?m@0Kc99nSj{5-}*krD*6Ho~DpNg-MP3 zmj4cC#)EvxS!sNxsS=K)IQP;}u3XEybpmeI=yL>ss{>~_G1i*$9!IK+ELLK^s~i(e zIIKMUUaHlB9l%)REo3HZZzymxO02atL?-N4T4qpYj6&)Nh~7n-`(M$#HotK?w5->9 zUTl5Umt{0>$F|0pTG77V#6P0)rb}wy&$OlZ_OF1bupeGL;pyb|v4MDJ7oow#^^z@EcDaiashI3Qvz6Q7L_wsUJVoD6NJ1Gi5vj+<*X<1mEm zcA%4}!b8%>s;T7=P?OP6R}10y?YjHy!{rl4Ka!yS*#Mz!PyOY6hGN35 zdV6lh9T!p85E=K^!clQ^q>sPCpnUHBaMQj&1Z9u z9JIZcC_moU#=k6`1?dIipE0OKq;IuY9<6pJWi-J_wB#EWCzGYVr&(($&UXC#4iTOj zBCtGXL~0~`Tb&e@=iDem#{l`xAV+P#=66`yT25s~E(XaaeVktvf}Qs}etaD`GM64n zpq7;Gw~xxo?aF?%Ci)bt-LH$-I7wJQAE1Ib=XKUJFv}*`;10ze_G`7@y3INB(Y*7a zWC)m~?m%sOxm`1rLw&<&`H$QZv0mdaOnh2|nchJC*M}KeSxueXCcF|6ZeFJ6^ZGN` zJ~riZ>%>wm!v5mt<@-5Zql!O*agiqdBckdYG7z_o!!~oSw{VOkcZkR!?Q#NDS;nY# zdMc0`;Aq9Gk|W_h@E~?S4_xu#;(BAc>e%ezk^KZmCX=8cG|9 zGvl0X5$8r;`q`WetVU~y;r-*xhgwW{&^O|~+W%>>#ePkAYImQDL(h$4x^imb&-k9B z-<2*tK7Msch(xh&Up)%ll!##Z=->|cuw~itxdJSXW-N0c%S=tKm@YFWACD2bR!Z@F zsCat+&tLA@`f9uc_=3ItO&IJ0k{RBzyu|vRD76?sEi*J8yT|*H(Lg2h2JkA?9<6aE zA32C1hzOyPv(6i(ejZt93)dy~cRmljulFmI`>#zrT)f$`@A0i@G3kw$Q`z@?3Xh7C zRZ||d&!lOriCRP)-PdT((Tfv$1;&VEhIXEDC8~5=Pocf{59jCX+#8}+HDTCG? z2Z{h|A~H%vxc)WajrAHd!om5D0oaww+vjzhxxxhMw-07d59XCd6H7wFKFF&|OG-zQ zEj!$zj&XL=-8A-Jv>YWr7Y=Yf z%cEq$H1(VZDPe7Ks-kbD9!)U`eaa~`Ko}5ijCbImTHlkSY#U-p?Eo6vJ*`JJ1+0=Q z*OF*%u`>@JJ$6L2NGsF>@IZ>j&pOqXG}-s$)+7?FZAb6W+_G2jl_QdO1y#5|>&xVe z1#*1F5Yzf3z>)7lY9oLL$jaIR$f?mFU!>noTr1I?c%mB9$_?|ds1hN|3c%9QaJ5U! zb*_NB+WKTAM5U;Jp-tB~r)rlW33Q`_ULY~SA9m+hI>*Sc^x~lqVk4MM1@_ru16GGB zBX_=&47%xb|Ll?Sw*+z&w!EM@q7NgArpt(80z}^h?}(oM1_zI<{~Qfg*DUt)V8;p! zfDffE4*Vz0WxSl*#4?a9u@kgUiYw5<9NST2Q-fip!$eKuGI>TIuPuc<@zk!-GZT6O z#sw*ZsqN;@i&EI_mg?*W*h9Dp5B{WYwz$WGBEeX8=VK=5cdL4Ap{i477@!u~iijP|9< z8*I4RskEf~oT>2;ya)zn>wLg9kK1NhtewTK;GGPlX_`@40R?XsuY^`!Bo}?min^F( z%pqTiG-piRbVCv6eW+-LyP^R1)hfQYGV_xs^3sce|I5vi{@oF@)9;y8ehFmPx!Vd1;w_}c%?08UIBC197|`}Vl~ekaK7nxt$ddUuio=MwRCvXTj3~|vTXk4bfpn$YxtCDzb4^U zw)>7I%oy8ODMq+@%H?OKa5k&-fUmsG%h3%qY3h#!t7LHhYn zv-j~~%AexLyWawr0oR7vWD0v(XDu`8<#`1*A7aaIP^`pX=1cr|`5Q8ll?bH8sF)!H zjlRWuQy6e~wxPZm$5y$ft0JmF*8V!gqSwJ5NtZNiVA#dZ{-GNim%SC2Zd4iKt%C??yo zAtKK`Z1)Ndh?rpcUDEY`Fmz~tNCiN_==QcTh~TQKMHhrKbN8ahIxMQ2`X=QpA8Kqa zIw}~+gLsCVtkSOMFuA_xrv$2E`cjMs@~mmKoXV~Be<9`g{!6_E3vu9#U-IME_v4cr zPCtN;IdEDh__id3ANq?MdV74dYq`g5L1j`*PXNb7H;+&XPl|VxK4Qa4l6SbPe#Z@0 zj5O?5sbqnAl38vLM(_$@z1aD|SvL0S=lpf(hBC~!Knd768HL9a zT9KhmYV=`I=74~pFX4Fnk^%N{=v8d*58zYEmP-`$2~K+)12K3<03)B)`&^vNncCA2 zdXIPe@5vA&PNttsojuyY*_J(H&qNfdPeO7>0{k_XCTwG_F}3AXzX|c@ch@LnZp6_J zyB{}NwU(iW0S{a2jasSpIv4Mq@e<5+!_1rg%G&}|Lb#BQyq;oTCbY%6W^CUaKR*Br z4wOF62iOnL1nF7v4s-!-_{$w%$+AXjn?5t7jX5)6qXA$aGTML6Mel02D$HzUv;}s7&!B|IG;}&JznyTCLkL| zyV(TsIgq%OPkuOvxloSgyrT{- zF0O20n@kH4;^j3VP+a0u4>m|ecB~;ko8Wz}?`_7E|0_EBQ>Wk!oa%Ji(>-ZlfKn= z!!2ErUh!BG1>xX&H?9=M%EE>=>0j|tVjrXx7FA?{w)2htwgnyp+Pd>na199wrY}~p z_VoN4M(WUZ+(0?B3x)?#izz?e(DPeTP#kB3GSztF!OFA1WjyN7JtE;76@?^ri+# z?+qqsJvqF$g$nHXXZGNlujeB6=4ffS1OX?|>aRW+3tWA4Vs|SgRr{>jmr_YV^HN{`~`+&~y3| ziR09r&V0$&VwpTbQ%>6AmVXUtMQTR5^S}^OkCBa=L=Y=iQ+cBRXy8gp=tOVI6=Cv>N3kukGc00=JZhEgnW_GgYAy;|lxxAFVaj(i$`_dXBe= z(u~jL$G;FrQ_!pz55xi@h!~hOzc1!+K_F2|i(7PVgb`L57V6XSz9w8Tea40=^c|XB^gr`EKl1lH)zv*PJp2cBiv1=mzja{Q_?$s z|I^9ZXTFDznI#o%1v*}Dt#XEH;H}-xmR3D!@Don>V@cI`P#HsaJB}4LynTy{Oyxzp zyvA~lqK#!1>LV}-;*2d-tk)VyxqdSj*JXhNG3Tl_&gc_mVz%zL&;!$@>FDX`1clKU zq}WrMv2=T2NVP1bcFoU-!WU?vKP^9At=RDjadWTNcNi{f!kv@z%&c|Q3N>rM2Nv46eVG-Tuv7QPIoeEt)Hui$x8Y4|1{3kMuk6+Ha;<9Xr+?Aew& z#Ac$kA{n?BHlV3DeY#!LMR!l&&J<;zJigTK;sQrRlo->gpmMoc@h0E@>Nk83wtyrSK-~#geooGMGM7SFkTKguKJo<=| zX&+XcX7D&H`s%#WU`{^v6cQy>iQbuyB+F4Pt%boWje77v1cI?2@%!A(bc3V zg^vOhyBEo(e$l?Hi0O5DoS3J*IJvnYf{KnAwi`b+?R+)X{DYqcp>;y|=T-(FMc+f! zJd3!ZWjskqS)jG=SC@uyg(BfAFc#yD6q#!msaaT+ea(t)0mrScdI+~L;Vz)(P#4l- zPSquqWN~k(P4pIR(*dyBPL<>hsY#r^sSwF98xhz@hY&@%3sOj&IKSAR5Qe4A9zhWD z-f3{y(7slWu=WIYzZ>uWFb4bQm75%v4DVb(e%yilqKB$iOz+kH$*`?~uT;+BU!2hC zeX0Lrsi)@-5uUj6{=#tSR-ItFmh>8khEt9!MK(bN2t{Z}ET5lUM|6VIWbw9VEzaO^ z3+p=&{fRS6Fq`cDiaLO!q9NSNu>Xfkox7H7={Bg6f!}fCsw+-}FpIPPmm>)rZp2TO zx@SqYySR_+HBFQ)$*%3|u-C1;jP!ITea(nT^Vfft)1sfr`O!NwKXM$ZX71I6>UV$X zE5JKpjQ$kNvmAyH0DxA?*K^U@p2x%G28jYPd}1?Cq^(6 zMc-wuKFt?FB#n^jBEL@s$u?q!@< z`s=CP6<1;e%bLP_yczlgm6F+m;t~Le*#{Q%9I6znaNOz>DCQ%h;4rFOK_eZo37+P% z6z9D26!N$4jK-{4Lr!bs zCiF@i>YngVTkv@;PYp-M*rkO9#BOT1@I1iT2as~SPxbM>b|xn9%D=ee1)$Oq7uY#R zRR+XzW(DoOAh@Erg9^4!j45*O35SBFfBYd!Bx+~`f%tFNQbVrW`QVz)Jvc7l;i?CJ zSYIWMwCHJq{b__;HZv3WvI%}%TQ0#~?za4Aqzq+>6Ild>Qt!0U4Zzv$Qx-??iEY=6 z9aUAE3Wcq2K%lx2MM&3?rL~Igry_Fk1Ghs!2lNVv4&;jt_l71(!Jv@T`s1LqwP6Y( zg}?jPNC_(I)1`|VQq;2dj?5qmw#5v&0u&J0Q>Ho!m^#A9%}mB2TQ@iFXPjhm@t^gn z{?F=*xKAjq2Ot6({o#dyr+-XKO1(i5>#3w_960rAmuoulOpt+3@W&nc$4+%PKcgbA zy!y}aB8hsDQeP!&g`A(s+3fVv_}HvSA5`-muZLnu=MK1287T=>nOq)NMbj6otuYZs!%#8db&9?8`LiIr7Ei58Ak1tg?a0cntuUSJWVJES|LyGtYm+ zDZgRNDB>WtZLcCM)MmA@Q5reXHW?IbyaYX4UMF>L*?d`lv_m4oVIspFK*WS}&251O z3o*RkN>&UrvFHSf6As^%C_>>QquZOnkfyySWwo)Qv?=}5e?axY=T21Q|G`9*Do0D7 zVA|rOA8DOsQvdE7`2-1qJ)Zrn@xKD8}sHU-=p^@>H3^x9Yd8yPbj&^zOaygP5`}Rq+o#TBf^c zCjWGg>fLajTS3lCR6GxN`v6W^IPmWl78XeyhWB@eAXw_|LaXE=dQ2G|RipODU1k}J zX!)#+5AgwsyllZrT-=0SdwP{boyg6eZ$Xwfh9EX`?u1hRMrDa%9{H$VG|gupi0cAC zO~KZUoT3Zq-8xZw@&uQa@C_URkcQLH$+bga+;E6V(tX{c`~1!F1Im0Y+klCnaegMp zvN5emwz3gjOroaN_4^VMD+#PZ;=4baD2VU#l2eT;V&VRwyA7&IewOumicf=&A>@x3 zzkD5}WDbGAdz|=eJ@*bD?mK2}Yxyk79kI}*V8$Fb+bsKQ1UL+pg4C_59I5id)DCMhR+)thL`quv!1s%(jZ>p zF}Bi);Zqz8YFq~Ci5tjp+3}rfwsP+l?vCsAzl~|oQZ$N)A@_Ag?lVH|^9n7&BEs_v zv)QKzgk-9G{E0kMq71{H{|l^0uCpgZ9{@qtkZ8Eyns&iFf*g)C8Q;qxX!`W=dk75G zq}1%Fn%yP7_aMv}%r~QE z;{x@5FcWZ5A%Yg8m+W_Is3ukq$)sNrVS!D{rKG0~4#R_kr#?$38Q_&@sT-8>Vel7X z3xCU6`j+os=RO-b>+bIU@^t%Ux%Nx@<>sa>^0zQr7@fIbdd!B6UfZ(QEALtcif4f& zeZL~Y=ziUq3OP1z(uJg?r44R555=ItP(G?%jn!oe9OuTbMPDEP2Efe8NWe1SOGO0* znbBpfS-pSPp+LFtIwZ2QxmRo*Lz6TbM8yjG7#Y7dlVRId>V4{p+z%kkF`wQ z?4mCx6N4i5=x45YOMPG=lmuS`HG$c8R`C07Z%u#Hv1}Lko<@9n zl~&wk>p#v<1pv*6h&aIhTLY2JeHBX75#?BU0%L|il+SeSI1$J?!=Z-Bu?0@$l~L^1J#ld=Oi1Hif_T5~;CI-hXBce1 zgZ)^p$%)1H%b+U}YkDlm_E`T@(}3&o%vDzC2OP^Z*wt#sUyfKUV~F@nanOhScFt7U z)TGJXF(X(dYtJVpr8rZ=sK(H(^-ruqbo1vB^)N`q{O`#_xexzv4zBh6hkAZ`MPaf8 zo3PUsz<{wgU#Y_hOEXyGPSEhQ9@Xj*2^B37U7XB674Y5i!yVA(MpVH+ zwe4i*32WUBG4=nae)=_YTOwSZVcbS%$OP@9qh4MY!Iup=x>q`~yU8~D0-Lai=P;>> z{DSrNHi9Jfx3>-uMXOX^dSWD2uJcs(+D*XUq2aVOPf?6%|KTzG_s zZLICL?}aHR4#XVH5*e5bQdJk2dPl=VfB4;;zi#Z|)W#HcsP{djpg4c2YxAGz7wq6P z14V3eCa7UgkCBQ=OO^280=oE0F@B`!4_*_GZhuRuoKRXUm^Z6FtoaPn~ZEvLN<+M2sp! zNM7w~lhLnXha$KXEsp2Yk7MG9eC<8Gjp?+Msu_)%Y->)TfYndxHBS&`sZ$2HkBFdm zBHXE3&+1m=%M#x*p67GOJ%j=Bv4iH`kt}pj;3aashI%d9n*t4^{HY){9MRDs3AWMS zVPdx{si~>8+Ys0@=M5jFuqTA-@fct-NlE0rN$}VuQ z8bnXl6l9*k;TcEN^wLhnD`Lzj(}J$!l6#^mt4n8`?tfTV7eNzvUg9!MSfDTkx00My9-wb3JzG7E|}k zU#BtCmYS6D8jZ3S=Ei_oscx`?QhW&i&hvOj{WII=);rPrKXLpSc##0k_1^^NB1(RH zU!Y=X0rEF>=8rGUZpJITiO?{V@k`Jk|Bhi(x(;_UJ)#Dzq-!HpZ_rU`AD6wBxSxC* ziG|}5@=;)W+C}8KKnHWFoLlIbOLIy*EmpX?Z?+MW6l+0sdMJbia*-KkHl`y73U{tVAweKBTcpX`Tf&ZV@s5kGt~4c*uTR6X#G_)_F}QE# zH$SZr3jUT0SfOqZ?yilYV7=paSh~%)_<2z<^ffFr6gtMU(R!jrh=#$5>&hAECk|Xd z{0oj9J)-AaOPHMw{;*(}@9<~O2Xe<-6q^PJ1b;z1?eQE74qS#@t4A8fOd2_t{EMLa z*fabkMVJ|jos4chF40@bsrP^9kWd-_EFAi+GY*-bhph5*T(xc!MmrA^YD!Hvivbtl zPYv&%r#AfomZNIp`Bkm2-t&YQgAZE)fvG;-9QsaiM8BtFPgY4K9W zLZ1(GJkN*&n0y95VN8h>Dp9?{i7KxP@AxZ)BK>X$vO*KOWz7R>)yR-6(v&bCmNTFb z#_1=0x$o^D)Fpf#=&a#eOlKH=dAEF$abdf3dpbG_h2Y)ZyX1@%(7wZ@43+Py1CyX? zSa3J<;fgXX@=4243Fn*9O!xevgAe*T@-v)Y*HXBRYaQlEPtZDXVzghV6_Az99&`zd zh)`9}c1_^Oy$l;)Gf}k>f}+3`wl?Be7Fq-~Vi-+F&dkbBf#b6WB%k?#Y=b_t&=hhz z+Pkez_~}THm0#=3%nXn<9D@ZAxQn*B9{F;)c$L6=>Br^oRL2RN!ZiXD@z3%|iugD- z!L$@rG2XeYTM$bU$)Z&=#!~&6U_!1SQ}r`Vq3@XY##qFIn_?sK;reVB_?}wk8{Br` z*CV)S7^B3VvUKg>ozlT-y1&{woT+$j(M*e9N-QUIte@BVK1S6ag3(fIl=835AJ*?Aqp62Z4v%3k4-50H`h%6XG;~>7jkZNvMoOp@hiG%Rh4+6@NJ9 zNT&9`T;Xz&mTqg@G|4LU)4m&dV=TjI&ch5O6m>=9&c{d^_yCGsCylP7w{ zgg6y;P~NYBfMtCPokUSnQv+9f!-wnX)kB){L$-mo>afi0_te4q0`l5=;*TKV+Am2x z?3b4`;tjCkMxE1=-_b#0#kTrA8ne_($Zp%P2oF_vTIaT zaGhvF(WS8B6Ece^qrMgX44&iAczC2cu@{%?`Qkq~t;FHZhdHs5HD31?|N8?fv<20w zLQ+8~Fg#ox8Ug{s=DXZ-)Q$^|GbahEcCMnD?&E)!*jyK%m$O7=;6r4HLsri1=4A>L zPc!WRn*9$OI=ilm4WCU-b&n^|;6t%H-5CG)#$@+{MZFeP%5Bm6#|;xPsx1?gk&yxXBJ~3e*bCGPZ*&s|v2)W? zGQQYcM1N11;mu`n@cV+pLV@Lzii?%700tIX{%*@mso&d!fd{r`Z1PDV*N zZl5`WY)N{jvES+DWL2i*5Uoi98DDek@un`@&V2oHL?H@V;qWH9ZBFsRY6EdZztlrV@4}{S%F($|--amcp zN!14Vhc_~7Z~`&j-R^yk3i`+bA-(%YlivyKIDJn@BU!Sv3ET=RGy;tv^3vV^5ulSm z98(r;Y#eTjfrGMG9c1sL`X~YUtCUJ;D^BwY_y!PKAKC7D;m=l!`cTG_s7 z-gJ~kOuPAb_gY}w;CKyzD?@$h#q5axxkj0{xlczsWW!fE2qb5SX#6;6qaqYz62|== zLV#le`|pMC8t2z(~AQ7eq~ ze?;00st*4@m5gV`)@wx}E9!rgtgq;Z#9LYGFS*uVYM4_o-a*k>M=w2loL2w1&3UmR zP>69b(y5H+Yt40j`V>Z9{4t!>QD-IfTiy~oLlTVMK)?wA_oKvLzeAx1falXz^RnvI z^FvTk&E(>zi@)9up($e_emx4*sb8OS43N!Ly}L-4=%RlmBC=@zG@27{jQ6Yvs})z1 z>_1cz3*7GG)1zG_*-RgKfth*4lX1-~$>z+fLCdm6`~9P%a$~Z6nF}+^H`&U8I04uf zF%GPwGdi7bIEkFtBsdiBub3d<`9P|Y5$ zf&n~%*3P+5WqC1Ra9+vbw<8A%vYiKJn?bDdP9Igg)^fUG9TCdtqAu?g12oQBC&H|< z#6t%`3*-xS9`$Z@QXjA@J`Z4wd&QNja)^4bpA~I5qGmNw2ZX=An=UhaC{Xgd&JLrd zprW!Zq^c_K}i$@CN7c4 z$oS)*>dZrP`LU+Fd$hsfmGm$sb68yXjT;)F9&#HnCZ<_xGZ+-fC zMeMWj8a^*2hB)tkKV5p){)hIxI;v)8{ZB(V1jSwsa#DM7XIUL;Zj{YT49|`+B+@g#H=1aOZ-*` z_57p0b?4cNioduVx9htG7lPY?LRie_qOcH~%_Ql(`y1+j6Y71|9Jr2ju}t&1=gl_Ik)nyx0$SE%&s8B ziYzn%=H8BqU?s-c>yEf~%t0B}QF;Lz=MkVK;IpR-b_XVC46^L*wQzA@mrQ z8EJn#0U@f-*gDnvV<}=3h83(VJF5~*i-5{tWXc9PjmoSpd|Tw!%%(#TTsaBq&A80{ zYJa`@w51wos{poI$T~P9Eg8n04f~h{OTIJ+?-keuWmYyv`K4nejr6BkVolk<#vszd zfP+MWuLx3IJ8}3dly2a)zumOkN9eoC_abezoDLW>>#+tUPx%#GV1*PSBa(>HaA3iV zh=_|DQ}SIk1>&;z+1v-TGmM6XcR%EJJ z1?EnB7aOfWXP_!;@EzckM6v*7_O-_N?M1D70T5lGCIRBY{k4n}8=gtxjE)vzoH55z zf*7F~w5q?RN43LFX|{X=C{F;eH%k%P=44v?(bT_03gf*r8hYvs=3n z)Cl&rZQD&gaV88R>XKSGEv@pB7P3{?is$dIZwtI(*-wb>_LJ}5O#qB29+Ke#Yz%UU6v5Ge&XGqhYZ$&r6yHCJYGV zw*6YyIP+1_koSi!BcU-K1p_{DC?_4doXC>MS<@h-;4jQ7ert`Tn-;E-0 zLZ_jm?7(S?h0bWkcoQfcPfV`c4}KVmN%wvnK~K$y>cr;@MgJK)eRbe+yy{BwMFXN< z(ASOimr?z($W{VsG4(fNW6~HAeghZO=VsFHa!u>My-?!CfUK>p?M{nbmwMdo16-x; zALvs|DM&;fQy3Iyw)r3=aCU5JX@8@3o_mH=5K_Tec}ELP2o5!5(h6c_+8O!%>9F-; z{^kII_zp2KbvNmw4zXb%3@wk*j(fd@WqH=`%;bP6)uMQknUQfnH{I;sMWQuR%l+zA z@IIZ#`y;Zsptj8NUmFU2X`fz1T!X5a2>v^d2uAaTyGshb&EO18PKJFmp-9I|YR zc|2>0=pf7_7O!?pDo!^z9@g|d7SH;W{Eu7F2>0~NUgtbQ^uKL6{xKmJJG^+;IT#&F zJneuXMqK*QV(|ztF+w{nvz{b#WisTTxJpzK$=X*yxf%BG{6a9ZJGQ^{NQWW@V|2nO`sltJq z8aM}No>pvUKDj-ZqcYRk?CtFZ5}!u5-Gi%bX|d}~{uXTJ#H5)FTfv3aJ_>HIk*lkjxA*2Zn!Da1{^e7`ha*B3&EiqIkQib1u<(IY ziF2Zxd0@Di6-~YCFrA&J!R0$dg|BEEVbP*WrCrbNvi@L0CDQ9jPffMnT|mkv&D>65 z4Y-P_W6-d6crQ6$oN5gJ`5@K)=%vGrAk#IS$z>UkGqV_8{SmoX;1(7xHVvY3xltEy z%(_DPF-<1zBv3m48zHLt&@Z+vf@sVXelw_xvfYclB~yX*UfAFn%k+JX6>)YSz7}DS zDI&UqQaI>`bBLSet5jG03m_XE`amdpIu`365|0o9bOrDmStS@12I zlDUIl_2rQ!Z>r<~WPsDQDDUR38e;xx$-(-VyI6XJ)GA)tO3rb`S1oToABPnXGCl3rw-IA|5@gH`oC@y!$KWaw}mKP zu2i`i$cwQ+hUd(jMkE$EKbchYUS%!7ET^6j#p&>R>YW+{J)y5D{$ zLBxHj*w(?Vi}g6Wt~MF-s}ND!zIL}JS}P85XdFx;=lXyMK`axQPo~`06Jkf@7wFPY zckYDQCr|&jt}4bKzt-q`0)RxeK)jgqvfqXID{{pK%Q8Z_2LdsYz4u6Krx6$dKO5ivt+o5UJe~bkuls*^=Oo2K*1C^}Q9FUj9ID>c;B2gql@wtE6Ofa(IOSJ>e zGf{ufkt!yYq(h6za=tQmaK7f6J<*%5Eo(dgz|2&7z&+?Ob0|!QVANi>SrQM!gtX~# znKd7DP87v*-0hINEhqh6v7Cq@N5Ff~B~eEoPoN`S;9JkSb7l6p+gPba+Sr+qZX?uj zG_uh{c`~^7(#n4cSR=hgt**$1xA%+d@_a3ko+JPQ@jdyv+^`zS5=@)_!W)Oq#D22| zQZ6UUUm}4>Qv8Yi8x%Dv+7K*MF~gmNR=>7OGDVl3%}Q7!=1Ep?xMp3Y@)|qNweGqz zC$*5`lXXqbYse5?wm`~tB2KWi8j~t3^&LwUvo%ImY6WjR4`lP?N{_wKVrb{7-q8xc zc1Z>Ihzf(P_Yp6A@uc(v^hHkZ&mH~A~B%WPt^$aA77!6 zpus;6{F>q#aZ-9`P1TG#)PK5-S_Pd1 zZWek7qCh0OeJzKw>qufMAOXVt*jGGe>Wrvha}CX6qFZbcB#0 zY+LJ%RcPtcX_ED^mRBE{A&zJ`_n$q`iqS8_3JRF$j@TRo4$){l9{?FmQ=X#o#7_O# z;Yt1PG0P8_wIKmZ;!nKb@BxVA!J^2i5VS)3<$C2TAF#9r?tX%_5f%^!B20DW1f2u6 zFh+PYTQVJ42zI;TZ}9BC2?IeI!-VMe;}+2bS~qn#-(C>?ZvpxFp-37r!q6dHV&q0w{HT{VS9I*HO{ z!j(LrC@(L1=rwW!%I-0^s#8Eb``I^{3x9-G7pufy8FkeD&Mx^gA~bQpijJO(kkf?W z_?6;W8;-L?jpT_h^au-XN1+TBRd+xYjPW((-xA+BXr)`wrh5 zlu_HeC$5T7_L!Q@Jg}Qy*AW>AKr4K;N5(dXo{sISBsdnPy3|-~lstwpBW01lq6|)R z2z`Iw%{{NaWQn8_B~pObtNw7ZEWe*Us@VTC%hZ1p7@oxHfSX0=hhTkG$<1A%S0tR0 z4dXi}LiE>Y#>`4n{igQIs&}@f5hG-+TnNL8=Z5G3nfk+Z*~9r!2chLF>v{LAv^5Uq z`eOYTcjVf1PG}H|>h+O`aEvd*`^JvRk^_~l(HZvCS*h$kxF`15K{qxzK5q5&km%kjT-R zPj|1*3pa*UMk)w&?OEjxMUX=4qIY`W?8>amL`6;QFVmNw0O>>u1`E>jeWehv2V&$Ri+u8xN1g^Iu~15u%__4Uj46gngPX zI@Ao9#PR*T&NbdeIYD*W*9>B*wvULI)F!yhv>1RG2S=JZLwA^`tthUot zyPH4*w;=zPr8A2WT@<)E&F7IVofkWJ%=+sji%%Sfi`mn@Cetlo{?7g#$Qb}buCGM0 z6H?uu`7M`0qq)|C71Jiqu^{UwSGIgUokY?fYp5-8TrNi*r{w(3tcvjMl~xC3!hFk zrb5`1n#ZE#oduP!eJNRn)Ha8WULB6gm>3tA)76I9eZa$E3ovLUDO%2uw}w8LBzmyn z?U|S#@~Eo{c*RZO@N8C3(~D}L3}Cn+l4ZDw$v@si{nfvPD{D{uhl8=X+Hnp?*}A`sj9ZT79n!3)0nA zrurODsB7F*?J_mv1zw^T z*v%{WH06C&jy9|F>*Rn9kBBKe>8DJT&9!zH)dGzG5)&nlW1G^EGdSZ4CyP$2KB^YM zqv|b*X4Rl#K(3>&zf&T5=*OYO^I|V7D6mF=uqsDog0lhd0sY)~2jAv8jamY^UV!PH&o9-1q1H@!^0D?R9#VzDmw} z`}D^6X^i(3tA>%rbe0{QFq<9&LL`CY#B*y6dFZ^3*OwG2d0~oPgd(Cjwof#|Y$wS} z#=cMVi|ztsF6QPcjK99wt6IJq_DFMV#~$6VflD%COyamo*)(J^I9Hp#*-yz%=eF_C zRM@aCgmirlhN5bSVi2L{EU|3sefeQV#KPvUUb{;YwAs%9QOjZ)oTWH^-6MNAw0a(Sb-VI?rXHL3n? zos+kIi=Q+}T9bEfZ2e7O}*1J9yWCeD8q<>qBs&R?$V z5fym-pg|oxfeE3Zv!NoliaSrRpL`!Xjd zGrY9rK6HS+5%kA$Qw9EWCUc$BsVe?>h_bpqDK znaH)yaQ*c*;P2CbzpGC(J9M<=yT&N}v%SQNF!K+$bjUc5AFdU*r8=YY7s1Ti`hY@1D|K$Ux=_p@(0lv zD66qL$;V>DhhDeoyKDZI`uje{OkxR7gLa;Q`hC@YEAJ8b=>`8I9HY0ig^#KkAc35C zm9uGwHv`EWVxZy}UiE9G`5yN@ds$jZin%SNUhmDo!0-=Rsd+!Wuv< zA|cEh3J5Sz%X2l;|90-Le0CC@2$-=BDU6do5$dxu|7iL6zjinZtH4AErpyyh&c62* z+MpY;2tauZu74$$>bT6!%)F)%8%5WTnNyg5q9p8eGE{ea;L+j(;=;gp+43sgRhRd7 zKRw*orSUD1g90swYeku%43kE#J$;U9LCVl3#OmRW@HMEc!=g8PlSN7tDAX@6B5|@J_(@qYsBKLCsAeqwnUgqPE$&TzxM!;T8DjgXui6? zIb5zV4TH9y=#5uj&X4Jcd|1dp;Q{?Nv~T_O}YvHwkBB=CSBD{ct-% z6)Sw)n<3&80?pV;%`GtZOI!#G>Jx+GgO3a*xI^8ivszW&72r{ zqe!#Nz^|W)@n0NoM@Pq46kb9bDreCsW*`VL5B{fpZz8mD$_MzEXw{rADa z`x;0ybyZqHx$}dQE+NYFr*#g?&Bh&};Fh`QbJ#@5C*1sgI)+od)=ntKjx3k&py)Cc zlfom=0<1|i9^4XaqviX2A2I9>lLGaZ#0jbyTP5JQ_iySt^-%dNfZsqaL8?51H?OE) zpEqNUq!y)A0O&ZtZ#&}%Zq3U94}P)0A^}oZmubv&$oE9o zs`To7N}a5eHRRP@CVzMG_yN(fElDVH3dl>lxoT^`SObod^Hbz#@?7P#;(bs+Kp^SZ z2o5}1TmoSsr4@DW82E0RE;8mnQhp6V_(O53^z#lyacB~dI25ekG@GZU4vq$4QhRP^ zhdp!ZBnm5e=XTVe`0d-bT01Qfcy)^_VMIN1EcqbA0Sbs#w4blN1L+F1AkdtDJyr8b5#mbSyOw@Dk;QYa4bgHC5kYm+Kd(DWHSSF0@$~nA2^jGf5GgSFr zRVK0!if(725sKY&q6BacHr?qw1_GT;dY$V;y*<;KJEK7T#O>l@^PFFdW*>qcBiu zk?yF`4>^w}N5q00*bB5}jw}DSOcH?N^x&?2~8&uYt7cMY`hG z+}TT#!c&EP++hK%Geuj~o^|K?f`Wp5fc4H+o`DRKSuoTfJdMwOAq8M;8L3R9QHU4F z^@tu8KJN|YF#r948^|r}CsXl%<~7oPD&AGHBxun)WNqms3TqA(1vP{2O}$-3@BEEJ zX47I#}aj+bq zx!^nn-fO=Dt8Ni%svaszN-7#dJ{}%dFlPv2Kj9G)f<*!(r1Q1uig{`U>X!dS1-E8? zNz3?>!A^_=49!bmeRp=QpMx)C>*Z}@^9LLc(o<3Zmj?$W`5kL_eTU`^q)&=ePi`b!ikuJGuHT^0!dy~! zx*2kC_59>K}O-c zjCJeV{QIWaSZ2S%sL?>yn2e#P;{)wzyQ1t)13RPl8xqJK%kGWR0r&qg)61_8pS%5i zcPeuiZ8ggn0-D;rCAaa`kTByWPQ1V7ghk@;z$)$Lozet)3YCZ+zP;0L> zt|nB<`w9|F_WQpiVRZgVHK0Hw3~XqZSN~(HcS9TX6hz^(;Di5?u}&P z+RIg57-aq7&&Z@fhfjG0~e(S+hY>u-F94CjSHti*?&*@#15 zN~z+|g{;janeW0_5u@XIuqhOkBhrkgmg<;@*gYRwP)C`Gac)^SRF?>?Pe^Sns1A1k zfo`Zjtq(iMn$zgx8W6-x#%BOt(2%csiEs8snqGqWmN{}V^6so6p)3-uDmAy!;7An^ zo8c)VTVBiM#JS0jbV1&wDH+hJ4*4Iny>{OVUqA8NhnBK! zHk(E1^0NPoxZVlGg=OZIaer+HQwQ=DU z9LMV@XDxbsohy<3S>{Dj_=075L@u&r$x*rmtJZk7_hYywaA<0?R@Ssdxiamcd1|yb zA8;?+hI4qYuce%& z`7z50_4q%EvvBIgu2ja`aI!_ZzlsAV!b_ecQBuQ_+DH9qlb90U@gH>3Wa4Q0W4`rr zZ75xa^^Z*QM;c{f{_}4LK?rnC@Qgjs4`pW5veU~Utue1^8C~;1n%5{_KWDuQ#NwI)#V8wj8;U>VBE zYfBQVVne^>>7K^Yfj1=?)m2NSTTKtL1=~q!c?8^=5`Krwgkbiqc6fS|hoX=ymJ{;< zgi0p@(wdR^Q9mSFuXeGzthS`}di1TKMhiQcFBKEL(Bs)*BL^~vXIF`ogFNn=f8v~; zVX}CA_G4-C2cGcas}ae;r@`MV%E5yGg{;%&LpajDnAY1{adnG&lS zScY$hg-LAZH``V@Ww=ek38%!3no%)T=N|T=@C=gnZdddQm@(x(XuLbqa{soII7=^4 z67-)o1QKY80sEtGk*)lD<}EkjH~og6^kK64O5AxDf~Uo!{Yu{|o|Rp*yf?i6$({RV zD_aX`Otz;yxl|>v{FH^MmbHgs= z<{(9mC`~OoFf<_EG<9;-D6!z7P;ARr6k^0(ujVi+uze9(D9A_k0@wVpY zi1BB4cCt+r`!tf{_O|{>WprER#RtnO`Wp&#X;d zjeV$^Hh!gkt9hR~)`0_I33{GCSn@GdU8Q6|)R5GXOUyo5ywS;|&wCZl^|jki!1Y#D z+xSK&<>JkIX~aZo%pGG_VU|~mj$<8xzToH#<5JY`5 z()&M|PcMvDr6vfh7Ly9USo=|;$58q46+PO1cUre12Iq%ttqL62VJN?U!{(_nncG-> zY$Ou$gE*sBGpT*FMsz4?!m%sdYQ1bEw3NAq0>+hxk<}-H0RB3vD!Z`}GW~d1P)|tU8pgI?@t2U6{F3XP3I%VJDktT%E?rb^OY-i}oCj*K5uc(-!cj+221+AqSwbYT-{Ak7HDN<$Q z$sHt8d#P^1rly~mJqU9OT!ocLKY#DB+}Y(ZNNf_hI`!P)vu;rQ?qw8$qMn=xMx-#f zmdKUu<9*urnJa_yq|O37+G{x{VAZ!Ke`ic-R8`*6d~mL!62(*JIBSRN&}XwPpEJ^e z=g-)0US0Z(ncB4$=eoe{%+t{`I&j|I}#4%<2N2z~yTa0O@HomZS~I+^lFu(S)6!jHn$?b_-qkkImaP5k$Ued{^x#)rV3gHC+($bt zA5_*ZPjlzx$JG2ICzt<<9%3R4B`~+k@=cavQ=18_KVbTWiYZ%pI}<)c!#`W}Lk3wx zKup4~lb?^SP=(Rz#lqO>riv=tG;6jn<4Qc2_(5Z@jd@^QZ>TXQR<`!fLAEeJNhD5L zMm*UH-J=%X7yKd1kUx9%t-M{dw`Iy%^`O|S=LupaTO<`(ixG-VO+qP&?Pyh3x z%3=(ulr#{A!4^R+)lj%2pYn1BVcImXn0D>9owL-nU6`QC@EChYTaSM92U}Ru{TFxw z^NXG{`)NXvARRm>%x93_wVY#OQeNLYQLPVXKbV{xI+FN6@qu=%Vh1Q)sXS^xz|Cmf|)VT$~)JlxF!(qFF|~Y z@;F`_j{?EIYWh1$k%R^&;fWpfI$}6t)>bnw%ISuArtT-ZAnRNvr5#QSg5p`_^I-lj zkXEkm-UnEAGtNSK>DPU9r;4KP{IrFlZpDgP*xRN#Vv-7Ko+Bqo;bBQIg_R;rbCDp; zM&>PBoMlEFh)qK-N}%I+0F53H0W!kS~LZxY$91=0RXulLI|(<9uopG0Ad*>L9kImPrnOz|s& z_dv~SrDYw27QOm^1)dt3UVWaq72}47cwh7q@4i8@UYRvWv#6g-k2zCjXgBY20G~>iaxOotUw%ATUM^rCxgne*z*gBD7DH+6p|cOu zAldfP*iF+D%-2uE6B$2s+(EOj`zg$FEsMhGY9PC2Pbs|rnsl1>XY=>0c+5>Rx@pNe zp6M5eTce+`w!-7Di|bsAr@LEa&)!a$lz6;gtY8&?F8M)&p{&2VlMPop@o%-&HgmZT zQ2Lb47QA{lo3UZ0ONC=l=~9G~7Gy;BsIj*&lz@2(laNZOl3vL5cGcF#so#A9=R>ZXmC%ZNWL%UCPkdNp4i)xyeH{iK)886Es209=weg}-yTAF&#QZ~cajhx?1+y!Y zW#z^%{HQpLqk^{sUKoBi2~2W3MvNDhTeZ*RI|cmemaV2b++~Ae>}}R~SH`2T-s--O zN`b@;c3r6w+&(LtG|$m|zx!uv{fA|`=X_52heL~pY^OH6@KdLbf|0ar*|0+klUp9q zr<=<6W6rphbv}FG*uKvUlv>66H ziJxxOR%ZNT4G`R=C4 z_xVMjTeighcEHwYSTY~ZU0zC3C`D&zRPc9Ig^SIVhw?df2qw*Y+VvN~{9kg3j0ILr zo$|LvUJ_7={bPDbxqE|no%5(%`PmI#YKUE^6CI;9%U8d%d#%$oOcjtF5DqOw1h)`Y z;$m^83Pv{LX&PXXyyKL|UZkA1`yEPzEks^SPV>q9yA2iHCQ|xDvMOz;t))Hbp|{sO z^tx`iiu+xpYzSoq?OkjU<+$~6VYrDEnOqRB7*LL#hM9CGbi_>P#YKXv5C*I7Cuz-# zMEVM{cZWCA>W(UA(qfbMP0_-y^V3!4C8`>8V2{Gw8Ya3^MZ82c5o`low^*jlciraY zCfmGXIyEaanGO2G*XGx|cS09Dckh2L1+VEJ*Shr>4K`M2bnH1AxO=~)b;4eyJo=l~ z^|6cL)b)5xpI9|0xi4+A^vqP+(1o8!wI~Ja=KZxE&8ryYqO;Jm$SU4ujzG@)?&hJu z{=lKY;lMUiV1m>@=4#6_{|6>_^X`lHr?f_k{*=DdwYJ;I4n^uLY>pI+ly2?NWgQEl z6Al8fm7>lxBDr|?mNHu)ONN2SifNRxvbMa{m%MTYyGu7L|00e?05M;1>(rce6JiQ;O!m5#Bb+{*E?fY3xPnx zaY^T0!XXOpmt%>Rw53q)`-{sy#>y404(EY#dxlZ?@ zO+I+Fm{ex#;-SZ+to&mkz&0irBPToHEv5)EIS)YL?sCLSmUf3kij3e&H-&9zYxQ zgzhW)lXhMh-5b^0TU1Ugd-Xv^1q=cr}x>nBXuB7;)2@Pv`&TegeQ>od3(vGH`inzy0v|gwL2f5&~|4$gn^F2vY}u;QC_Nog@of1pBc(9uPtbM9757i?skCR#r!ZE2~xefB+Lhpbd4@1WE2>T5dA}&bIVPRvKerbeO7l z<|q9JCygt_G1e8Ps}_C-;7S<`s-eTk?hEby*hl~qLi`OdrTe~qK539mYSnr-alB{^ z7krptp|VFHht-hh@Sj$#+rVxnN1@Sc)$Ig zjOW2T0vq)+(Vh9P;}e`wLt+H9Bk)1>74UXQ0EQzg2xuHGU$U3Gl#UqLqNCl+fMFwF zIfZ0B15(GJ-eFkDb;}9^nXjZ`B9|6%2kI!nj$**kAlU~xCOB0p!O|TvZm2k107C?GnbB?kMuznzHw)#o^8Rk)?P{~RIK5l(?|ckqr-eR~tIXRH(C2Is=? z!Ox*bFAET{ubR_KH3xtR!E1S$ZePq$o5M)qZhr#W&VXW+VJygJajRR*Wx0d^iQ{k9 zE}ot$VFBdsnA5S%5KjSt&i>;Hl8xV=0hmZSn5E@wEKm`g1#}i-zlICKj+1|t3l&r< zxYK^X4PC{TaOvCa6b4vWy}brD!Q9*w-+L~|<0~ow!odjS28kdGlb^An#hw@&9RGCQ z_RP;-A#FHjl4iA-pWt)!tbAbX>AeNo9p&z z>O>3&p$Dy!qKM+K4U1 zl;EYfM3zhobWvY05!^s17`4ZQhtV$K9E574TGAPPJUM9q0}9_Wi9_S{lWP zL4HciR82#bwklgF`?=_#=C74$Cf8TQBZlT;p^G~^b+mUmr{|vO=lHJ|35H!=m`A06 z*3HQ1hb)^4g+ps6UfTo#!S2%hyu9i`@hE42dDL;R)pWP1aSSq61d#eQ*fh-F=8oj3 z_Vuw#Fjlt#M12qpi-6-k_4CGYZsmFXUxbJU*D)V_k_%uh7*d%0=WhsRy6CX7GXw!l zBGO1;-J>>m{sMe%*gymTEO<~_l)B>L;j$ou3XR3RuL}JLjwTru%aZfK>Wp1Ro$CFV zYz_yk7L8mrn!)-Xy_T2!aIqDAC;}XQSTjejx1-L@ek_=gOj}?B$nrIj-n)!(At&=P zbx#+j4rX{w?ltH`C4?jSO|FdEovW_`sWDQ|l>i?Ua!}2#GU!-*qlUB}#txpmS;d>9 z)5?Kv;&;ur%$t_Z((+98L-m7K=66qS4Mdd#90^*G(SQNW9bfJ01GJehpg{1y`sM7p z(#CxXs_p=~$NEqovi$%EfH zr_&FnVAO6FYaxj1yv@W~U;!MPGR;R=CrHdA@|7TIjQo7)Ae-8W!>mnMFE*Yv*bKo?{*+st9+tx^tv!e zT`&xwB=&#}OjiRa&@C*fDo7ogl$>Ivhue$NlLoAyTqN7#0XyknRckY3x%025Kky=Wp2>$812_nRwJV5Kb2BHD9p2Wx2_GP=`e zOf5xS24!0-m{?nf6dg zPPj}tDp_8IYC^sdpo;)2<}zK#Gzm6qQzr=Xl-YMkFA?BIl#e#KG(!j=f+5oFp66QI1IOk_cWn9{8$iQ~GH-E|3pllZiD zz8!W63H61Pp$@~UK8rs3uo*P&5MX3QkO10y$dKf58OsER=PG>&N7&Nb>l-K20k2*I=%UgI6l7Q5PDrgtL#By9H4^~jQXDfO%S=9_ z{3yRhA|44HVs&^`S=QhwiaI>yBMAVp*aWmIFb3FbiB4DY7BVBV(AXe=a&X|!M(pW- zZm0+doJ|5&KDaM@6qxq&7oJ@Yy@1(|_=a-f9I;*jRTWW*a4XZYnoUNP0RGXWX#`VX+*y{Ji6n63Sz@{DLsIp#MvR)GNUb0|Y zx2i3b+OP}wJ|mxcij_5#jF$!!-kVgtMj&6Q01hS7mmMf`iuZjkt_;47&+AF+^B+}y zr&^Fx8Oe6T*I+~vZ3%}~(fM}ys!9ZBV4)+w6mXczZ3J~8{ZsJb^eNFB>MfDWtHKev zx;IlesYBUW-M6+TO~KkHPKOn4nkp@D5~OsPq5x+uxh*ObSwjsd+gYaUu&s2=G;I_j zq$kMWU$UA<^$r>~cP*IQ`-Rh6R;S4wxEE&A0RLDsFQxC2QYsRF=^VVP=g<+^OA45O zmo6pPuTXj7G62tc;eLC#_`_M<*B3g1y}mFY)FK}QBJt@=C*@naU$0M*O^;EK)%XGP@>v>7@N`@*yYch2XB zpfE1{?q@K1_OQ)O6`S@3dZr|&4+XMOt-gUo1lTFENkp!y5XAyfh>WL!OxHz1ENL#b zy6bJse|5$XSbXSPAzy!RpxIVZ^;ylJja9p-H64>8io8VZ#YezZA1tnsfQyn-vG)fB*KDBYG>M^>5JpMP@I<<{UV!}nB| zAGQ+&Mt@`ug=in*%KMku&?*@w-WXB%b-DgmwNe5{1u!edof@%_*qSydv4ayA=^k=O zcGoGpeD0`c>p_@=`3Knw)&LA8j;ATv9pQkI%JNh?#wJ4a?OwvbQ6@!MRrrPLOxD~M zvE(89+Gf9)HI38EYxI(=jU=;w--^+^0FdstLEz3mi{M>u=ev=**9e z!gs3GbwxPEl7VZ}%DfOxHdy6q?|$YL5U+vjv8D>jA;- zfS_3qHo|#iUzekz*8m9CvQTmI4&f~M*}(v>e2SJ863kz>F(Ck0p)s<|8dT(;*2T+F zdo6}HI%aSEsG;9K*R3#PD0+o`du$bIB zOwFjeaiW2|QbH-K4?BV_Fh#rwxn%cO6_|CM_l5J5?+A}Dd% z#qk$gizZRYp5C6)2V7HZxXpE&Ty#mVmMJ^VzePU%uQY-er7s!0qqqAF@~Up1qI`#u22A$mCngE-k`nRH=F42ek%#3MGdVF3%Szz6VpMg^g;6Fi z?k`~=wR>p!bdhzY!gph-RX^skbdp}2b-z4-1!~JxaCd{G|NFpuK`dZ+yX{7yL_;>6 zc}_DYi5)m#`3Z`m>OX2HFtP zu-~sd^7Kp^kH%rfsZ)SIqwkKG9###AN{e|edpgq5 zI9Juwp+kW<(AW|n*zOfR&vy#F@*5A_kTE|!)bCfbq6AOWceQ?WO*-5Q+Q7!JA{7jK zOH(zl8#DXzS#G%DZJ=(48~?0&*-Q){qG=8;jt}x;2EKCP2h_2YOuxzgDr{vEoPI_= zaO{(&pX3YiZPHvrWt0y`0|sxF36U^!!WII&@X$?^N#MlTDjEWCf!FwJ7d8v|at~1@ z3!@6nf6>T>9J>kZco*qSz@JJg?`vaVK-M5agG_!}#lytEHy(tVt}Bs|mTQ;oR~fG) zJmC8AQywM)5u|Bc@UhW?%fjT=HoR_7z3#KriypR(8ly>-x{`OuCQ9wI&)E{2{HH`K zy`tSgiqVgFN_R7gpc)N2Ya}$U`F=Z0@7+;PbxK?L@H$Z&{XkRw&bnT3KP^FK zaa(TmIs*rYMhI7k{ja*`8P5o)PhGD!ab&&~HM<7nbQI%NeZ9!dj*ij9B{u9o3vI%x z!dVE3gb2m*VfB`(@j|GfKx-#2&T0iuC?>26F=ic|^khyRzlAwtPP8Q^}Q^eVg?7^8u_qiWS_N}JjQCjdS)4sIThhBpj z=Q{ZV7!arg^3=!NhYBDhX}S-VX?C9dN~YXIAf45aO_uS+PAf$>P0=Z`x2?YGtwuL` znd!2y5uFVuL6Xg97wedEi+^fVWDv+vLIA{j9Jvh?N`7RY5~ur6{)O*11QzP@O4|4s zZ=XhQe2i34{*)0L`dNnqV8C;4*bXkEaN*mTo&htHr|S?laH5kd!MDLb3R<}ZcU0Lp zU5i)7-jT(Ee%PCL8^op-BL~4JHV>HX~UX9^2ch1@e-997QX~aOJZXR(lRVo z<*=hnv`fo)*L4p5m>hP&A|UBVLBB{wDe_&BW0jRHpO2o zM%yg-{y?x#kgyPKDa|}bOmZA6P)Q7sy1w9j>B!*)?wH#_gE2&)nSgfrG7Ex1)Q>|;^Cf4BRL%fFnjE8CbQX(9&hy;2esf4YS>MN6C=_&RO-w-I zX;rsx1ru2quH_8vg9x8Z%*z!`s@0*tye@xkuEtovq9Zf#PR}Hc+7= z;v*~+2Y``;1v1SIig*t|&JSS^d(}$tS{d{^MU)0j&ZUNtb?YgN{btPSfHDiQW=%giBxxs zRUM?*;IQ42(|w6X^gocDBoEJIrk0ASATRHuwnzkQH2*uOe0 zt74vG%h28Ky9YO75RZF0zEH~ajz6YA5$6muSI`MY*-oc{2_^t=6qB?2yrLK`pQ1*8&HMFL&IpBU+=EWWSkd)| zEGP%VF5T)Dj$>XX;8KM@n)?|m03*C78nEw3#k<4-RwV%<{;I)Uhv$rUC!2FQ*$8gE zn+HZ5tYo?|oA!4O1U45NgbgS~8Y(Jy_un%;+JB^hLatC#{HS|r{rhI)%O^Uz%w-H< zB-tQOZ}+1cU8=ZMbWv`m+wfn>%Hu`pu-#_bd4u=n1a!p(pC@Cvfl9Y{a1t@f7B9pk zuO;|~vv*%zNF71=a8B92kDabCM@XluTh+=vs3{it-2ltNbbeAaP#$O>{aAN`NkgmrZQ~&?}7OnVh_`)DGY(iZbeqj6{wS00`hU*G!%E zpf>^0tO=CkG7i40$cx+0myTNWJhIt%iV#j=nlM_~ZHwqOYH}ZP%*&X#j1qJlZz+^?)7oA(>p{g zO^)U$413&!lB?KXC-gM9Tu976ok$sC;jl4I%(|4y@(ybqAe3 zpgxiTR4bzotH!&Jyh0gN2hY!s_iR6Xv_RgQ2;YbWX{&Ic%bEF)@~3qpr<4?z3t5ZZ z8mm+)arE8qP$mJV{UV-gCo254u-@#{rPKB1zGHUbt1;LbVD)JihiDpd2fU!I%)Zhg z53@rPa)4PWabm>hINS|SU?uJ;bX@h|MyReLvyGaVvJLloLYSsyzrJa%1+&7!3J7o| zyQn?2|8XD>D}=y4oGMglpbU10?n`TH3?#Y*rZe!3Dqv@Jn^q?=rqIRTtD;%0rwM*1 z)^cwwDHLkKA{FN4RgKF^mTJ&g4aGPFr2<<9-Z< zP9`<$VJ*Q#TMBUp7RVrz^y7+Wk|Ts)B(&PWbuBHtBGP=G|8@Zr-neMz1YVukjbM!Y zlumcd8A3ay1)E^iUmPkbs-!|1ft665_VEiFE znG7?Mi&*NO29?c==2K%>VG+%rlrt#0m!`7;aONSv{;qvYur~d6zJ`>*jNRv<`IMO! z?q=4`I{6Owb5d9{-zXrgUvryzAc7fv7T(K&78K8dh}3=Umzs$~t%> zXcT#|K`{4w2PW!mbf3CV+qAjVp!?OwaaFQuC&A5n>7$G~z-$q_MtkXWf~|^g{8#uo zzAHN5S%RW%xHS~ArpT>yKhZGz6RN7MISY*YMEzx?b9ZLf)-q*C`gmmFd9H^!Vw;Xp zK?S%>LYb{oG=HV7S7pHH{*;!M<^g*Q_%<(w@}s{QIQ!=zS@%J3Xk@5%>~q4$KHU12 zc%ohxx5~_BgAXh%?5h(Uk#S?8IK*T9ZubQrvSs{}8D;(Av!nyRWyu6*O;!#!HC1IW z%K5U|YgN+JUmP<@mK|4VZ^y>YIx-}px05dSw0^n+qvplauocm(@M8kIR_`EvUbdD0 zu*;HAXF-y$nf(e=%cqOY*mRGYlfPrpKZvkgMt|0R5kQSd zc(2(lN&aCscfVS2HEtz~rxx0>EzUJH$R?kq>m11c>2V(Ca(q=yOZnP7;odEIe7@0p zJn)5;9zzH6{`Rtq^XLg5pTO^DfK}udAF_CFLjp{PG0Gh_xpom%U^Ll3En2HnLCmU` zroH?Z&Yz}FWp@egWH;E>+yhhjqAM^FnQ-{I=lA5ApzK)k8?&*P~_p z8e4Dx*23Q`TWY>fLYLe?Ofdl9N$59Rub|H!1x|xI!nPSs(F1^>Ut36sBG-a z#R%ZxvFTg&^Z%vkI6}#VKt{2}pJFlooI6~`1&{ixP;W?R>L`IZt=j6h$hZN5JoFag z;8c?Cg;4yQNgPd*%^O+QF1diMvcZ3cFY$#MgRVwNboMrt)6Ada5Px(NzGCbK`Z22YnZzQ*rTd@_J{I72oiu#cK{vTp%lb*? zQ{xFK8M0-dlA zj|q=AcVT&lr=1BgztAbE)5Q;$i zU3|?_`}0z%s2U0gE&>T=kq;I_)Mm=9Nn5yZHsuFQ$`#_i5R4AcN+9tYV*C5%O2lEG ziilCYhl*-^C4p__N2>sz`rT4n==OAZmj`K{neEO#4{?B|<$E_H3a=)>5drK4-R_0$ zJ7}vbj1E`1rX>M<=c5jG3yl0M!2b&egAqVYa0E6;n0b6EjrlGZ2z@j%YUhEe#9&Ri zEIc2v4h~7Lb5S$g64{Fgsv698-Q(cuy z-!m4Qy~XCq1VV6#yK?0(7&*BQC;S5CvfoQ?9*GjXTC*pX0*ziosHigw!a(0{8+;oG z(|rlAT%m92N8U%YahvyXs6*c>%+Z5A{>v7Cv#a+7HOYdx6Uu8tgWq>`_l^kI0aaF{ zVJ#izCL%8QBz7zK#?xVZC4ctLM~5GGe77jY+#>aGv?d5o-(}t}cSMdSbgX<65WeBO z6UY=#h^3d|R=+FaJQH5Ymn)be>NWC*_NOPNJs9d78B2*dOp!g+%zFE1^u+(<;XNduRj@nK7QRYlsTv_R~AF*PWA4i;deI} zG2-bnu(aQaxD096<9A*zpBs%9OJ+Jq#U_A`VxiezV7`ej0)gXZnoKd5{1ohcFgGDU zSN4L1cucu;RN}t+zQI>HA?q|`NXB7~0UXT@^#6z*%^`nz9--h7if-eVkx+laK&qZ! z5M_AZH1x0r-^zGOc-t7cqhiYI*Yt+;Wfs%4sEYAG&^Y-(bMKq7)eQ%lV$xIwM?FT> zT|?|eqq$B)+mUWR_o8MhtZc32p^<&6%X`e5?M9`Q?qFq;tILsrYRpqhfOr~FAMaxw zSf6t+Ng+TZIlEkTaU6Th$adGr!0Wa8uHNT&2i2>IrKJ^(6U_;`A~{NINuBFjGQU%J zp2RtOe6>E%E?Su8uB6y5W^|sc_%6!#;?-&Yw79E!vfoj7MDAGJ`DN6*p5dF6dLM`_i@He6woKh#2Rf+B;UTrVWIVcsA@<>)ZAT;V#(7A~ z;%3N;$KEam1k#RqDX@9kX!o+Ny&Au_D}ZEiA3R-LK0a z?(q=3OiM4Ze2C^+e0lCaoWF1ITmR&PH%GdPocDN10Mg2mPH3u_lWzhioApxROHt?*p%19P<2po#}&HRFsErJCc{(D~loUP7Xm;@dSX0act~; z@YALBRyOeR-h2BGHTZ}XhN+lLyJQV-`*eutz|>Tjg<-xd{*)*W<6xCv8uemm^@dbipx*z z(fXNUE}K4_EteAe@kFjwYwm7y<1EqNU1+sIYo+qX*_2aBgGH*z-71g*?oEoT$FMaCymNRvOna&c412Nz`?p^?q6Z^SLx= zA#jsnh6kC9E1g0JHe~$pQX0FZB|zf-YL$39)=|Xg@gWes!%Iy3jksA~p{bovz^a_T zg$U{9f$u+`FY+)DWcnF40NMd7-gxi%a*qU%!2WGc2coR{bN=t3JZu*Nm+cp^tN;KN zz*xqYj1lIEfs8JaeDvb+W?u~ND{akRZ4PHqi0$N7NVV=EM=0;a1V$NkY$w&xTVAA4zLex7_N>^+}2?@YlB8jc7#!ELv#utlD33Y-mK zighXha*^JPkIc6Ob4gm1vZL0ZTyCoK=(q3Y*1_+jn=iPmh%<}~Sf4?tg@8)W0Kbm( z#*gNmu0z`blPdpG25J|+O{#ht@Wb=}elBi$fvE1Y4EX`x*Y}-S5>}qh~=NiWrb|7z(%ji%e zw5&@pGtN)Z%Zzub(u2<n-+Qfrp_cRao# z!Bgq|6KC#|22~!kt&@HxALA!ytF(uWryAaHUp{MFRvDYxMjwPUuFAKxldGYTEsjMGoI;1}$G{6^@B$qyiRbJPm29f75 zFb)-B&TppLnZ8?4V#)Gpgi!y$$jSW0;&uCs*?Ef1C9t_`lYf#3yC`)2r2%=i+zJBD zGFtegneO;~_Pt<`_CFKvF?ZDLeP16}X0`1`PEqpT5kFHtsr_B8|n^^Rf|C*Bsz#F$u zY8Q88foQ}14a8#X{esaVdvtfLJL4dKK_6dAxbzfWgr-udRX|$}Zz~wPHn&QLuoiUc z%I!(3+(WteSLHXq!)njC*4@jBfp{@0ynk@4mc_f^T+`=75dz zwp>kJDt}gMyqWL$gol~Qtropp7Ex5=qwMuNrtE#IDG!@#v2%hybbqNTm>7P|ivHQ` z!2U?3|8CXKOfykcd|Y5-qNe(3(g2;;EzawpAz=^R+K{--;y~}O%5tI`39Z3nqMEFo z_OE${c&OVGB*A|F@bV5!K2CXUHF`&{{mLe-LP_3->*de{xx3#Q8B$Tj{Y)8Y;_ZJJ zS>s42=(hV$=Jt%tN-q_jY1Su#3jejHay{X5Vgk$ zO7fHE3;F ztV`eQ#pX!1R>7-`HESw;r|+D~>kiLr&XTX^MG~yk@*nKfhx=5fU(2 zd2UnY6aASJ$5}(~?dqc>UfiF)Pi%b@a=o&*Or=te;AY z4(&+w?XIiF3@}}iGsirb&g#Rx21+Lud;if7hT5nTINlnk4S)K(Uo5sbs)?B`>!07V zE9R2#(sIonc)Xr+vW{@ECPtv%3r6Ju`KDPbV!+gW3d|G@PxEB#>5L`s{CuLC@jFuzW?0AecnR*XudAFh0zGk8V?oallb3OOXqPato=r-0KCY6 znb$y(5f_)yke!tNjagp?>Aa8G1hsO}ecb`KW81m~;}U7E(!M)C>8-uY$sd#|yv(Q0 zn55&i?*a*f7x#6n<;JgkCnn~vZu!f4=&Jm;vjbEozfW0tt=NWznw9~>sbp^h4hd`F$`j=2BT#>{JMb5Pt{&dbC^kGsh# ztzCurp?@Y;pV+K<24bT%GyqmIt48Lf<|ze25euvimb)(V2_v*IXgnJN2%NhDdT2!o zjEh;ZV6sZX1V%&o2>+NLf(|m*a$m6gFfDdtA`Sk8STx|3?0 zWTyCW>3e8}|Mhnf4u?C#+L6X(bHZI`ftJpNb!A`r;I#tR&ozs6ACr<#o&>(+<9)4H zo=zf=X^Y!crTK=b_COwVMlsdA#H)F0IVqRs^XuSkyr-d+x?#^m74O0!o4!lvaCMBK z0@=(*XlV{J`47*xirJ%F11{5_F%LsF2S&)!u@@3Y2o{o_Vv4O70&CZJF!f7KwWdnU zHT6qO67I1M3cga{P2uf+imj4|jf2;$zQw1G0`_J?E8bs^2mTm5q`uFMdBlR?#erlm z3WYu^=0{-zG38-P*nnA3c_sTovERDa>}=ID8oS+Oyy%$kkSU&_bvLi!G-t=RN`-O)T|3LDO?OPxv_*PzE7;SrDbK_J%wV1cYyRrkZ zI)L^%zTV`A3jkbexg_{#G=%fIN99NwcQbV3sI1kr#XB-Q2Ai2s*HzPsz0Ne?k+!tDXDcGHyNYX5PD*mk$%mW#pcxRJVepR@`c7HX`_BU!w=lS|f=K(i#(Y1fP z%3*JKK@tT46VM{&z)R5*rEP2P-ob-7I*q~-sU|;pHca+cZ9ktnE`J?LCpkH}!w#@R zYh)&?n#Zcd=h5Uu7XT0;p);Crn$X`ZEZO4i;by$V820#;VtC-v+R*D?+nRBDc@<4# zldtO9ke;Shguuavy%pCGOMtpq0!aVvZPppVT8htcRqHoa zZ|3;(Yz-Y(35E)f7Uj8HCRaQ8+h&{h)~m6ESbVAvRdz4cziU~s`Mi%-J!5%di~oi|hrL3Gk=8rzz=s*RF|i64WOd=xyK7`<)e}|t#QN7N zR`pLjyqXire$1ozKo9E_|H8oj9+mB-u|>aAVAaVthi)RhC)pbr^Ci;@D(7pHk3KQS zi!IYS#Z<Hdhk-xVdv}nE(?E0bnIf(NQzZMm|DO*{%cv05Y%pA0NQDWMsPSpya0` zuToYVrxsk(_zJB=3Jbet9#1NNNfjII?58mx#Ib>mG#ICTG*cL5HyKc#g*X5bZl9%G zSU9Sa*4@OvEHd%uMzVn3rXwcHJ!k_+2=sz(PwLiNueZ+G+|sI>^F#6^i;LOMDqlTb zm9MhywI(e1tiG1yIk^992`~`zu!eBdT4E7sJ#3O(OmsQ?_z}9OYc$u2mWx3j$;efsD8pEA`kOn#*e>H{H zR9NWz`S(0bm4dFjdI(z*Lr#Csw0q10Z-jQbI5XWzZH@`-2tsg^of1Y@Gbu3}_PnM2 zqWk5_)hv(W#Q|+yEp^7>RslGVzr-Q^36ZAt1eFU{H;47(k8n)<8@9vn%FVI#(w2mv z(^K>OTjGPhF*H7nWC~6azz@iJIKJNeC_u=|*KBt@^Z#ffB+(L&2*p<%dAs^AB3%<+ zw5}ZeE0!j}vz&Zg-_{-6ZQcD&IK((GQ++#NXbrrR`uZ9;9}Zis@|pU$Nx#oW^!eNO z|u@Al2&W_q!1X=%u7th0+-A+4Q!eP5g; zG^QEUH85a&4|aP!+56|nobjtB?(zBKe(@@e>9g%j*QyjUG~N6Q&77(k+Q1@k$nA|i zWy>qlA%LNj?LP;GT*S0bN{9I42S5ac1^zUi>y+afAC4v2RX#l9n+U^C$Ku>@`IL@a z4fvi}KqGK;(ojps=mvYl&^gii`I(A8|E17Cu$kIvNyLp)6o6bm!^3-doS z&xLpX3fMCJSQT-R2bw1t`)WHNK=`YNEs2>YigR(ao6?HRg6&|R>dQ zCt237ZMuC~h7J$C{s~pw-jRikme5ZAeAfSd-C&}Dd);zN+G9)G$iQG!Mj<&ld2F>n zYpnF}o_oI0pCj$n_zlVEt3Rs=G563|KmG>x zPsg30Z4J_^uN%2>$38$IMp6xlT3qFobiyQs87NBbX&0BPtsBAmr5R3^$i_dtAg|e9 z?@F*E94Xfy5#?i@F$TTs-B99aO4Lo?LnNsK9Bj8X04?2A;kU{jP47| z8~0m2^`7+z7*|y(P13l8u<8WUs-3`7ba(WxG8{oZYl9mZB81$Z&Ta*ju4=7ap_tVm4m`otc{!5Otmt((mx6ja=&5LJ%4F79Kzw@v9 zXeZH0GXWWAJ8ffpq@+s(kJEs(P>|#k(l;%AzCjMqu|y3#H^cs&=+^(_MDn+-cdn91 zh&=oNX4a1s7a(OEjMV578KzTwM#uQc8N%E+g@LHqWiAG0PL;obd3mujNO*`BXSc?q zYoF8-lRyj%_XGcQ#gD;?fKy}Md6p0xT%w!jMt20Z5pNJ4WIJSI0RZwqRRzKYp;xI3_Pu<`|DjiYe+Krh(h?;0PP!=sW)0b2CJD zQO_cf+JpekhRJ0Vmf4ujm+d3xdLAf^rA@><6eMEZ;1k%m0x2-WbiH*lza_TF?Oe;A zA%wpXk1l15@=)T)`|GuvXN2@+Pg~`eDW)7SfHvA37sfL zcCqNS6y$rq_eNqzsMg@t!NL6$(Z+(Y-O`q0b}Qa3#Rm8tS@@mor8x^lWu&|{;e!reJ^?~YR=?W3T>iwI!@P zH#6L+ph`FaYfXy0KTRI6b_tZnBqlMdXARtaiiy#`qQllmcSpdT;em zEN7H_XSpO0Y$)hEiqj$9xLM>sn|7zk770?zpb_^OjNTg@VK+@#@DXn2YSlOQRk3-b z8gJU$8i`$~ySk)@{QB1>e-uVTVZqn!&#x_X((V&Dc;mYEwT1xW$@5+!FVbL4gnSMm zf-(k@z*Lz#`jd9Jm(QN_o-gzHih4&N#Q7PI>-E|x->eHJT1$Q+y@?pb zZ^=IqjD$@$rhquzetX*Z|Pg*|MZ~o2|ZaU8u zJbtz9pgB#Gx5^NiVKVeRb9GaqPRxoo5KYzS46Ok-eS3=5m>}2$-(UiYts{b+2pyQL zUOI@3;~+SvA)#rd)uNzcZ%m6?=_`x*T*5tt2N|bK|0^S zR)4%PphP`)47Wd)mXr)smgR^ad+9b}c>Mh4sQGd{S1oRgSU*U^Jn+6cNOQ?0`0>en zJRyh;1cWXuzlv*`i^Z{eVwK0$1!b?hga=EGLkoCIcOU)j%HMtsg$s@2!~2HQ*R@&R-|tR+ zTWY)V9`|b(UzM8HrM}7#&mv-w&iFqxU3FZO@7Eq<5(1J6h_sY+NyF$C5CO>nlG5F5 zAc)f4ohmT}RGO)D3rKeelWrIrd&l?pdH>t<=bq=>=f2K4*L9u5{<@o?^NUB5;x9da zdYVO5(;YMm_4m1kYtL?}jY!$4r{c1F1Y(K>u$K|@p*d zHwTeZd-E+SES)m8IPPlRAN5sxwFTLmSUnFbz~CUSCFery#qL5Ay;!cCwQDmwg#Nkh zVlz1v|GXbjlbu{6p^tr#t*sUaV^6O=R}m(9Z-(fF;~gJ%#0*^_V5qb?Rj$#&t-{;E;moMR$q_eED7emh#o;gj`S9d4KYG1%4q{Vg$lU5g=k)>TJ1y-Pc_Ie=h+2 zSt6R;y0>S3@%XPkN#=3si}#Mmn=qPQGBPPvqPO-63JbNk^ztVYu1ET7W~FkDIz9Iz zciY0AFs69;=;@8pbs9v=cfy~(T>xZj+yF@8)}}o^6KoC0h1Q%6vWd6VF281j;`DW- z6nGVyS7m%J5)T1gHB$s{x~}ZwOK1z9Z!ixSK8^@}p`P;7DE_3Zd>ZtWB0ncbvzIoh zT79BIVo5^*cgOo;P~wY{hu6%VI9Pn8+H$;}(fGJ1?o*N2M;^&kH72~YnAzB3Dz2+qZA&pXcoYl*ZJTJAt}43Awc-)pc4g@WF?13s&N&N(pp z^8BPBrHgrl#^LGko3XvBvUtZab|f7k3k`3tyqsu_;;(lp{>u5>d&2{rqSeyCs-txK zD62&LP?j(j4RIGPYTiW4PM;x2}flg^E{MvIp_IAKF=5 z9BJ(tvA7#1bqRVQw>WmtIF!klKKylI@I%!I>{q(T6T0}rA^&f)?R2zS`9Z?Jw(9BX z=)>DXG*j4dV=jLnaIAi!q2B274SMD!1=H%O?!)O2;*FCwR~C(YX(5rud3SRT5oe|B z>=`aA-DB~HGWBtn`lNpTtJeSq!jaM8s;~wh7{h zltTCbldu(*eZ^j(FILz4*B#+>ciIwzF2mZHc$Om>7+l!}3~w-ISMN4c{j(h-71Efa zYbtc+21zS}BpRRa(9$?YAedAEwAg-4rw`9kqdJ5@V8afsz8>d3(s+O_y;MPJDj~Jm z$;r*D5!w|p!?X$K<%OEMRRtf^=IKe%4PV&dQV24=sehlg0{ZOfq-|wQ;#S7%%KiZm zN90dPR0*Bn;-u%#vR`5bms0fI`;IOxC% zEH?Rx%7vg_;0V$eLGOrK$?)=M)<0I-A$RK^d8J)csZ>fFC}iGH4Uu>@ylNP3oY*{R zX?vFFyE{yn4iyCmHQS_RY(D=jJm#ZwM_J%KuD}xqzG4nNw29kzYbm94G)@IF@Boc@ zX@JZZUfJ9$0NEO%+-j4XXY~b=>c8|JwOATdxf?$CM?4`*{&~NoN$fxu+?RK40LR@X#v`yLby8E`6LWo4=9U^m`5P1w+_HPVFvfrFTT`dhu-Wo~kbQBIhJjoO z&OrNgg>kZ5U^Z(hR1|airl`_Hpo2~6pmcw&^RuV^w1mG4d}M~ToIRUGm_}KFV!LdB zS&pqnTM>n($*!OsObo=GcN`rqDo7HmuM9|fg09#!n(Qt6WoU+Hm5NyreC_LGS(AXc zW*(#kG4=ICTT|CDgC=CXW)x_(oSu z1}PPEjUz~rGDYx<1I9wr&nh_HjXI3%FcAY|h;-@UMC}<2HIJx<=@9Sm@qWIr<~$=+ z?UAntm|6zMzg*Y93@f1AN#(t7^z37nJxR^pQOgt4#(U@T*}1mFiZvMc8ub>=x&c7O zO7u`JL(O5d^psdnNFs=6l4Hg=vyL)NcQ$3&e>335dO*Rx46TgCWa(}mrr;`0BpTP` z!4$RS;U;D>ji zlm+i_ekU;Y;x>*~LWlCD!H;o8%96Xf_#;5w?F?Bk6zm?G`$|87)v-zh`8ixA3}i5EMXo0=qH;3k7b%Z z$_V3C;%|bzJv4sRgEgn5GY^D-b2ZS;0c;aDl7x_UyjtWJcBxfC6K)x*JJrgRHVjYf z=fWg@5^kWqu@`7AJ!B80u76W-dYGkdlc^K!wzj|yMdwct{pU#Is|;A0c#hr#g;YNT z-6iOExA$C}t?t>3lCw0E{oV|aAX1q*%Bj#M~q{urWA`;L^} zCV&M-I&39H7wDM-r~XKPjZA%dQ})5b_4)8wROp(!vba6ekMToUwXU=B-e|TtoI6157dbV-7>}KWV!z^c1O|_}{po8*wV${l#a~MOwFIOvoP6+*+03PV%hjAKID@sqIU?t@yX(mjr5@R;l-Z~$yP>#7bzy-I`q7}5zfVZPw z2VdierMMZycumbtuX%}+Qw_iF9hW*g%6nml6&BeK)V6S5Bzk0c=?wp~<&me^WpH&oRmx^+zR9aRK`W{XuGi8r(J zP|yhg&G+$ElJHk&Q9%pg^>{x7A0$$6b^Uap|6A87Tn#Dd>nN?fG{1JLs%3n;(w;5y zg@M}UaIGN%9ofhD5!Xmuq_9rDh)Dl2Sw3>!>fK?T=fWm52VN=+@Z>ox{UWBIP?Y#c zx~2|8yKxfc(Rt?Ia@tejyM3rQJLXX|av9*J?NVENNZ4TKs&f>BLByZ-r8&fZ9Lgjh zR7|JAsa!+IBQojO?`ARZQ2JYRur3Wu;L9FzVThB|`y$=^R!v=LE_P=de6g#_XGE9i zxY2I*l``L}n1f07mHr7X={FVx_WVXs|8n!ms#=r)}}SNe<0cG_Lc2dwk(WH9U}VLYyfJ;u}365G(=wd|s7y$E5dx*KgrL z`Ud~MpS0;M7}SJ%%GlGt4QKLSb^xtjUB9!>d-o$<=_(){=QD8n21bvkLt*_<-_Wze z+SqTKUjj%6p(FKyE$ctMPTgJZRKRTPeV_1vo|xwxK{*)Zd{w5-nUhCNhwURZ3|$(0 zg&nkd>OU{&i5hhzE70j0IXz{nsQBJcCXn!c@V`^)3c%Yk-8G0*1SrFVH$*&kj2ln* z>|Y%d3RZ^tSDd@sb=!O0hn9xiSvZ|hOH)va!)up`f=A$kXWI#`*kB`wEp#hyRZgSe||BT5sHGp&cXTCuMdKv>eu8%MI$O?{4gEJus zDlzH1=%EC_p_HBrcZ}EC$54^OCCoJvH?c#`dvt0;Z+3c-o$@Hj#SR~tcmP?`-l<(Y zfP0z_n9VJg(^mJVa#!^HRe6A(4ZUlOkW`;e=guAf@;BpzGIvDvPrrQ(s`6elV-a!! zeOm+|@SC~7`&P?2#X@59u0@p1JPVoM3`&+Q1BMEE<$0Xq34znbPi^%=bgsUS#*BTt zO|qY*Ky)wZJ>AB-yYDGm_;_}J?@cYedemjVye&+vpXcFW`J)(duf6J7p%!!ap<*z0 z@nU?iYP7t&Th$2d=;&Y;#K3+C_^Q~){As^En68w#<9PS)Z^5nCZ+3@h8Rn^O4V`da zU{63Resn;Rd>q6XV}AfJT9rj!*e1vf>( zV$w8C>F4j_^CJc2dEH-hR`%W`ozTdiIm!e-zfhx3_%4u$`!6d*;5Qwt zP90YcGW@~eiO&wy;@lq(If7Qm#_G0CLaMq{54!rPkw2SBkZV^YxLC4EE5Hc;pJo;aW;HY^sU2} zhMe_;5Ii6Ds2%oh6t4Qmc{`ck+=o>Xz6En~;^cQZ2fk}4!%pg^ESo4v&M0SgV2to= zg}+}$y@qI|b!10|%f(-vD0=7-{Er=b_~36h7_8)w1K)w}L@)KK{Xa##A`75EIJ^E8 zw%oHi z#L_+C2ePNit<(2I5-SZif`J@{bz2<9wA;{0N2~t75flSZ#^)*DsBxSzCcwS7lyKAe z8CsU2gi+#!lPSi8nXt*jM{4Qe1e6TI=30VLdl%B8U%sl*0j!_QTK~=Y&o2gF72!OY zi9hq*Kf%N`UQnmc^(H;^<16>&P-!o8VDR;L#pwj?4MF!29kA+lfkuOwh)9 zr&+p|cj(sNImGU9h>@P>RjGxVsUA0kx2QQECIG`5;3AW4&zHeTM+zbkiJ|~WJ|Xvp zF4o<7wa|Qz0NCf8qh7|Dny_{)^!(!S2L%ecFpxDK-5p(ul^M6H6_lfuh3uz+8^VKj z993X>`ap+S-d95o?;)X^MgcugkNE78czoL+CC0<)bFbw_KoDi!8jOT||4mtS1_mQE@o_5#DCSG2yQ# z^%)E=LpHxGS(D+A8>E0Wg^*k9CL##}0+CVH8X2ffVD$pp~6`@1N{ zXc+&U&tHn{+je&N==Z~V!YRmq9fRhb=%Sa?!)5N3o^rEO(8&X3mT(re4rAhj%w9!j z4VF@bf|{*1xqj-;HZmW?Vh>yGCMj^ue_YT0*!EGBV75Apo@Dq9(XYi6W%Y4}WBSgb zwv)8eZ#8?+y+0;@_Mi^IYTNUUN4%*Hal{&^Y#qJkAMn)LZxx>(KkOY!56=AASl>9u zfTJ4XzE%VnObP3?1z20&51q~;lGK~hVZ7nSX6!Vb?J((?E`=gKgQT)PRy6YE0hd~Q zcyYYV`&Yq;??^%Db#(U@BJ~X{=G;L#NOcMBXH@^J^N?h3+*psJc&?tZ zYCK|$=$Lc4c;p&ailcU{6YXZ}0%YVODIesuf}J?c;r(xgc6Qct#yoy@SUA&@d}{0X zh|YHkdn4p+_Q-ZrjfM5Nl8Wut+#1Y;6>|4x1s`ws04&Cm*bPrH&JSjdYUWTusu)2k|bg5kbN)ts&#3v*pG9G z0J8&6Cwk%v;C@YR#aY`2I9gwxz|SEO3&Kt$-Vhbnu`JdS=GdG|8$vpVkM4#mj8@Np`-|gL01b-e7B`&^0_E< zr+ZgVZ{x^ny??>Iyv}wFfB)#SfH#Z*KM?ZN%c%f-bAEZZJuZh=L94@;0~IHLQJn&V ziuO$Y3B8rb91b_XVPn+{)7|oMqBJW|Ws76ZC-KN$U-c2-<9zsegWgug7 z{rdptzlG~D=zxmi+;`4GEJF0GC5h4JI+{Nxl=A-c@iS^nu%dKwtvryUj4`d z$Ig$^eK<@GwTzo3|Pp8bY#s#70ai^J~;|DWqwsV3-mlHo)6Tf9FY?8++ zlyz(&qscy`3Oae(^$aYKZ{YAQjoVx>XxlKu3~5r^p$g@-m~KLU)aJFD_iTLWZpiv( zzm?N|>#Nl829KHo1(~18j!95^?#ni*wGaZlgU&1^-kSs_?mZ+K9%!{9bjsw&)etP<%#q%Z< zm9>21gVOguQxGURGfyAJ{I&1tbo;ipxPR!#9L0IRCpvv7Dx0Th7b_$VHhDT5`}F_IQ%0yYjyeD`;oPgk!GDlWp0-CI2O z+qiGkC(>P)a;^@)DwICCVH5jTi8pJ1pL4z;^La8A#d$Jy%9X`obrys6_nv+t)|I!c zx$(BE#;gesWMaGG-opN|KWH$9cVG2Td?*tiw}|w}c=DdVJw9-?XAWJ-9mu>itaxM&;67;rpuN0q`+Cv=KpR# z%7~Q9yNIW2)JC|T8sZx}OWt)qK|#ayUtBlt7jnGf`?v=blXp5|?$OV6bFXT4c2;b4 zAnZ$R-DTR;+yZ&Y=j@N1`#@G8OFfGctCt?UnIqNzA@I1+fQTr(Q8Wb0zX@Uc#C_S# z7`QugAZ^8`pu=oBFT|VmLaG{=Z|Sa4w;+3I2%k=>eRwyD+){bMf&Q~((XQ)R+%D%R zRaX&S{Tp&rokdVq7Mw{ol_+e2b`GU{&R4k4q>vBSl9CU1QdkW)XP0N!9!cz{#eZ;* zuY96PuS&W82cNy(;56vccLm2P8Xp+&rUQxuuHW8>GSm@=fSH^F4zv5Xr$|8EO+$ky za;!udCDjQ<{qg8p#1=Gi=UwBp$8k0=J-KEOmjq2|ZlZox*rGBXzZG2hWky&tY^-WW zS(QTNbNpvf$`2iS2PEaxf`zXhe|q~5^JnLmEc~3;L@d@X)i~=};Xh3Gb?#cQUKRsm zL#4nIE zK7Ec(W#R4%B!q0NaaZZ6@Xg7$RX^4RGgqZ%!o zlKr+`47!cft@bo01Kf>41nisR(tD(L(0OfznvG$u{BHjjd-~C^z8c%s=O;e!=)=cx zR`ta{MT?lnr=`~V^aI#f^gC*_ljBhU7wgJ_X*4?0`T*ITwKRCmywUJGOe?I)T4kUm z)41yD-F@nLJ#mk{qJgllLG@ko_5T>#Z7Y|Z4_dUr>e<4;pnN>9B6uMHoDB0`7@hW3()(+C>!z4g~W+j$8 zYvPBt&QiBci?EZ$@5m%5$`Cc7&RoGD9t2N0- zpg?S`d~pE>yegw`(|OZPb=OLqZsR-IW8+=?IrUgIw)Y{}@z7iwmOpB&3^;ntik00z zeoo@{Q(~*UB-##E*Zz6+xNv{?6<--V&FZ`{N`xSp@EDxD7N~Gv zEdl|ibjp6l;KXR~^7j7%zY@j29}|}o&#($cau}c?dRbPT=>qLXYSPg2n!4+vbb9D< zdSp>rji_YKF=XI{PbsiT!@>1X<&n*ZBgUm(eVl@FW$`M+m$5E`lah8sli|hK#c+{r zvXAa@nZ>%rglv&-=6KLq%hr5LS<8KRO~u(({p^f4`A)nMyQ!v&Hm|czVZ{SuQm4R~ zA)RvJvvMR_HrLVYSWi3rY-l?lYHgx@p#EN3aE>{Cy(NH8U{H5GI!j~uq%EimWydQg z_^pcWB4dB}UzJ%d@~jM;`sI(W6xVx5RIMVQCag?ht&Jftu|xUFFgPiiGbQl{BCBKj z7{=GY04(?T-R|J%up#B5bRYEj@exF;K74&(xTJq zC#*U76{^UZPA6>NO!w)x;Cj#oA4u%!`9vU*m99P+$KrAdFXn~P!=IvyVF+zsn1~b=iXDs$brllf9vtYZeHaD&nvD zN;o=>O53c{wV-|VBe#hKt`J-&CR7KbRm@_GTI%^*Ier-t_GvPJ*VolJ-iM^Y*)?1` zLH9w1TeIP|Az488li0ftqRY4ydzZMeNhE1$!>2@FrFUA-2MbVzp5t6U zS%&cs^agVC^R);!UGRL?7X8te?|qNZ(Wf5u5(w5luwTaL^@*>DfWYiTf=slbBsOcrG1!sgawZ$MeViaO1nZhpaU3=0$JKWVEYp2g@e#JV-_3foc%*cdS@KtKn6(q#W3PEf>?MQ# z3)P$hXSJdL9>^-)Ny48tuqJaJrL??ITF)UxvJk;=><{Zut8s_=MpNOZkMbvckK>tB zE97IkFZGN4;I%9ZC;2DVknVk_+Om8t z+qxYVkY?1qC&y9@H@`Wo3mW|szW4$Jk1c|V!$RABy-sed*xdBJz^UzLvmoW2VaMN3 zw>mLKSwym7s2AT#y$GJS3*#tBxDP-l3$y{y$yOTJ;&O!;i;%Ez&Pb6=UikTs7^K|n zB39SyTET0#sqO^ce#b`VCQi+^!RaA<*z}o7vKkV8cXHFh>!}k69mWd2p2L)mcU?26 zcyeeidl(lFEaa>dFuoA+=q5DqDk0?w&~=!cR(%f%;k%1^<%JA;qF^v%^c#At^Z=M9 zuvI!!J@}<*2M&M7rkA(zy@O9lAy!JUFnX~|7}OX~Aj=$!=bZEWM^*0&)0J@kKKFww zcv~595SJ~1(A>YP9xKrS?VbjFI6y*7qu^C)Wg)QeZx6w-7F`|{JDx(bd{2EOX3bKF zs}Ow5U{0|Pqa&r->N=8^%LYLL^Z`^m ztFt{nbMnw+7bGsB{`|izj1OiZ1>ggOV{cRR2+`*TtTq=U)e-|O48Mwll$!{&r6#`L z!J-%Pn^19M5Yi`({mdYNf@nol>X_=y^B>xNJT%<>@W_TGdg$8-z(GtvX{V0H->!*| zX)X-+xD^(L48r-FCF2@LI7p@m>87muhEVJVf3J`=XcD7;Cd_ha^g)LIrXla13yPj- zMxBe(1kGa1GXmc4kjgil*hYNJd4&ne6}Gr+i6eQAE+pH@rIb!H%5Xu-`1IaCLVI#EY{+9f}{)C?6a zjx^54*{dU9ltlOl7+%5}E}7t1ssaV`5_!)Gq%*8SHjQRyv0O;Z#geY$Mm{6f0Ikh{ zZU!eD)`;HZto+ek^0vvQhbnR3y!{2n4ruFFsNJo7BGLAJ*U0UY*3zpTE{-GV29wvD zs59L^srNZz8=IEkiy7jlA}*chFBW#Ma|mdr>h1 z1%4}+?i1IrZgfR89*c|@lZK+Z*iQ9W-vTAx61~o|>%2!_nZ0*CvpODL{R%l|o{d9; z*XT|Q+XFM}tlBx>!UQL(AfbiwSJzy~ox$aq*tQL4PzL8L${Kc{D6G<)~^t5GR2X|nt z0ms1;YrGoBgmt$?$`d(D9@ZFjtQy~j~(=@%%m^ZN6a}0xVhf1llPz)-9j+pZSdHE6baT?~% z%o}U;J36wNA&MIn`hlu0p_L=6qGnY=(u<>-R>*GtEwqz#x!0elw*(xLspkX~w)wrVP za8-E`?0J9PKI%X4PXGf40!RT}+-wvxJ47s_EN&wsBbDE+prV)yC}#Tf{b zct=uP;;gWdv-L0B5?U;Z+6gEs|HDAliP5mQ@>+L1$HlQd*?-Bs7UHXPs+ug(~*r-0`YP!1?7|rdHt0L+-Ogn5ooXNKZP&=;MR%y5eg+aVt=Ug3MU@PWvc=(Y<$;p%0GdyoY736hXp zPw1(xVmI}mDOO>7e|(Q>nnal_l5-k#zFZ7FvK&*TQ@R36dgFb}hTjiheZXo(_3B$^ z^iU;R_XP|5$CCCrn{-Fgy{|2is{`_;?_HA1)Em;n@72sg+RqUVrw4J~P!osK&0ao* zor|=ZiRt0%!U2cS7Bz_UAm_iGDz{(5t%bS|V}a)S4+spZQkkp(rA@mt?Er$K;yfN@ z2__n4*e-MN5^$FmArGuCw_YRCChjr^$Kz3}Zw82nlDejcpLXmzgh=%;81R)$bKYr~ zKl!>1W}^dX`OdY>)TTErMZr3Fdqn#YO@9yScsiBud2~r`669)yx8*X)ho|G%KLs6_ zeQ#9JZFzkGMUVPIWI_JKKGm{b|6NkXtQ1TD^Jw2mYNA~w(97z}xoUU?1dAKEC|@-$ zU-@iJ{J1A1&(#p`wdwKn|#7m;R@&Zo~$ylBk^o7ZX_K?n%BVCppK@aW4d^o=Ai~ zI}}W>FDNrxljBvGuAw)Qu>S_0RHi`QsI2^u_Tk?|7J|I%LNk5C&AzCxoyNn~I0fIc z%Nk$RSnu_~UMt?n`;w*)1>!f^wAu@0O!HnaTfEe`-nyuw?oFy#?|t^4l4u#T8h{*x zcWU8AOw^8%U+We>nT6r(_-^Bq0JeP#bH})VJI-n}SoT}v00AqV&Z|FgT$ElZa4?{C z_2K|EJFJ#JQ%c`|Dt^+tRs*dvGn&mJ-RDFCUsr{!naKg&omS;w3MHCCU#ricMtz%9 zl*Db+G}Mt+gCJ{dB=SI7W_M5ky)!hYKpkxrutKW}@!y*EyIvdb?BNW%;LuGEJA-YJ zpM6U-4GI3_)g{tjMy!PLaN^IBrct~pwtGui%=EgL=SeT0pefZlO^6O+xtQlFNzqzK z7T9z76R{d(D#CK4TptC~CAt{gB0ge5cp~%?fMVexLgqAhw#JOrJ}xgn?~WD26rD03k+sU!gGcmO>T06Jwlt2VKl?=da+G%0i9*hT6rExP@p> zU|qL86ft&0rOgCeW1(cYMO2D5vJ*4mfor0nou&Dd`qOK&+^ChzfQc5i3ALlL5+*i3 zlUmebX=q}hT;;iNo_&iglW6=(l2SQUt2r8G3B(}N{V7&yt0 z$Nvx;Wyd{yZ)$Ojm8>L#7SVcG*K%>4>Sez7U)x+t!PM5mqI?4px?my^u>F_-X_7Z7s|!Sf8^Qd-nM3c2Ro zPA0pv*%0|k)HXg11HvVT7+9#iHM*P*Ux?IG)=$4fnnjy9KRk%6olf`6&;B63#kx1V z=p@Q&8T_T;BNZ?4GwkAa$F%ghmONCj4S$Ka2iEeX{$0$PAawFF#|F|S;B|@gxiUi( z;5_?oSti(XoONONMNy~T_k)FJyu53=8DVP;-sZPS7I);8aP<~HH`{GX%m|cxk!(4$ zh&?lb=wdIR*v@fy__>d1T%hdmaDS!A5TD7LyE5KsBbV%lFGOFAe2hZ&u1WW4WlFj! zvHYT4eSQb}9(Vj1_A`00yl}+AKaR-b(Gb=Ih2Y0a=AZr0rH6wp7$b+X&dx03y2UKN zNfFT}TJl=hInnthy&Yujj9i9`Zd52ntVGtY=+J|R>nYxbgn`&Yw-4n_AGtuqh8@yE zGW?|e&q;yxvfy;=5jBbP@h;^NUGga9aT=UFe(Lzf5-l#n&}EGzf^(g7u>Rua4pqleJTZ9bxyX?5m)M|^DO z=`4Wn757Ge3}() z&cQ&8a;+&5Q+FhQ+s^?glc-hpV&8lt_Sn^II?yDAkRSue8V5@e>!U0|S2_O{gR>;#-crGBEpJA2 z>DZG!+Zl)(9dhCNQ+pDE6(>{*XXD65596nGpIU_2ELn6!s(*+}HAW2?_eT?rYq>no zEN57wlx7F?ti(n{E3E7}mtW8z%tSsR1iY-bs+`eC;XX{LJNX4}?9p^Is1|lCCGT)O zeeiyKG!au1k-3QeDgf;a-~E!?o~L_wzl@Fv0J8dRd}mMV*wG@LDifqKLjcOJE9k-b zkPu(B03bADdiQK8k_AcPizKr-?Pn2}rRA^0tUnqFDndkLRYa&gFpm}2b4x-rM-bEm1K^wiP)dT1Mt4pgWO4N7i^bjak+c54)G#wl zobO&B+edrf?_$`C?|xnQE5^GdUR{c)9iSD?=4UF(90n%sXW0YecnTD9IiE4V5X7@t zsE-G$NM}-Qckq%KxnNUiNy&|0Dfj&9WP53n8+vX*Ckz44?6sf0I)P(_~5-TTvNtqm|=BU+bk&9dyf zms>T#eE6qGV~r@DXyelhjr`Nfl?x3kBecC?$Da_riGKkBXW#vm{r7PA@Mh?^p%5Bm z>Tq4*X)(p_dKd4}%{0PtDJx{%^%H_@p=MDVRTzYKIyd`N-(aC$5wub8j?&uVDV0t` zy$(1NKb|>T)B|=B=X|_on=2{Jm3MDIgim-b@v6tCz^bC%cU_8(c5zIDBt-n_gCMOz zOSur0OTDAu%twTZh-H$&zmsv#sHAP`(55$+<5J)k9adn&!|*SsY&da@Oc)v`!Cc1N zT;K+D^12cpi_bk^f%JE3*dE@#EZo#e9TF<*&jJAU*%RSUOc?%0dHmjaql-Edgl1T^ zUuv02e(tn9;*yEw3Be-y3^_kGQT#fyEe1wWC(7@@{T_&_wnYPG*g|*oo-ZDbM|te@ zD0OAMv@RO)CfGhER3+KRGwG*IY=^N}jW-$I6)tIdEep-oxy7v$w2$E5rOB}U^X4u8ul4B_+y?1RVDe-ZSpvl6{&g5T zZ)I0&wXD#faC4;>9tqepPY<=qtbh>-DVufAr^L}7UNvuV=&sGDz&dVxht#^|1Bbpo|v_((G%}mVaUDcFF3n;v$p|7rkPMZMM zCMhO{>^D_X4=EH8Tt#886|Of0cO5$~o1=h5T;@P6MKJGS=CT$MDKAJ15Ape%t0|lc zC`CcksAweCot=1;_x<{yecCGMh$Cd}sCVxH{Z-!d@9_2Avr%cT8YW>P0dbN+bAjys)a3`8ORT834RjTduM#Yj|^}kjiz>M&t z7+r4uxYW-uPk2Wt89u2{mtkdjiSIy)J<~ccqSrGh{Tcgt_J;xJz6quGs zlP>(@=+(8I47jS%g_%6*+0I4AM&)56cYlEWay$b!{6_4;uGdvTx)z`1$^sYuY zd392M`UMq`1N0UjSV4(cb?U8uzybOOhydH!JlgI{VfUYA4A^$++}cC-7JbH%!sgQ7 zfsHY*qwpKm9?P;(j>lfb9KwFa#dmgkl)NmXc}(R+S%{d2^8@j+rL$F6n`1kz*cQwm zuj*_bzfIhj@Cwb!T69|Op*KBL@aca#BA`XbLUJQvtZepsz&Gx6KyNP^aN1&ysdBsS zD~S{0#P@hb$30IKGov2Od{&A8aAH0Nhjl+~^mAO-W+dU72T+sL)3f}Au0K{M8FFyXqvKu!P3TmJ-YD6v4@4arz)Wef&H-3fNF9}4fVBKueBRJx?F^~F_(Q?Z zp2Gq^2;5pE9chMt?8CwCIzkaVN2&6F-Kr{w-EVmw8SQ;i^VR&}TuIE|FP?=tRmr3J z>I4Kh5zppqqZA>=?NJDBu=QHkdur?!_lw4wSHuwpZvl-UmBpc<_{HEse)m;u1})^9 zV`1C(fkl@F|`z) zjm^F5Y!F-ltzv<}Dj~)<6Mm<4JZ*UUD{x=jmXzMnpY+m3#lgy#;HtZ>q2c{g9H1KM zdt*#eBJ*T{`1W_!VyxNu45 z5`ynQSh<}d49?%(=9^0WpXrCPRgk_ox}erKq~EA>oq zzaro^DdB@6yh#uMabwd-D&TxHIDg3C%5f9>vA6K^LD*Jn9SV9`sr>w^XcV$%bHEx8 z6$2937;N-)hBrwc4|I?&2?^fOH zT+EiMWmYw6&|Bdr>`CvHO&{D>E(Q>feG8xbI2>%1`xod5Bm%-O0>I#^ z&Y9ivgwX*jM2G1#X?BzLL!0l*<+VT&kWA4G2{#qNWJ?s2EuC~6iKGj&IX>H(n35`) z$bLn&GD{>?Z5vN}W3DorEIS|Nz5{}I$rJ+l9%mvj8*fVb6hgLCN=or z{$~5Ku8X^$I%@tENW-sHi2-$HoAu|YSAU?UI-MuzyvJ13ya$#r*!fMIM!*?#b1I2{ zog07$J5()s9Z1V!q9~-Xsq&?Skohzs$Y<55@7nBInJT z$87%n1pI7WozY$E77L@;?)72`2tx?!ycV9^Uc!4dnG0T7^U=j~W#==;z4M*%f%>rX zZrbw`2=?dAbyjC1&JSazZaLW@TjuP2{pV0P?Kn zBJF{5W*wXbkpjJ+P!j$EtA{_(>d6Drcw-k>TgUVBU0YHC3 za;VGzc7`?8+Vb z@$%xL#*PKiR_Ko-bopOM?ECKABq$@yY2{@a`%eG_9a1<23OB)jfEi|z@`00=taYlLE@5;Jq<t#efB8l51MK0D)g<^-}A*kvh{3T`%o!Yy@1o z)W4CI9pC#u0Lwr$zpBP|8wqdD+%+TmqtT^$7J9`*x2#is2v`1oAoD> zBMftt<6XrgDt^xVlE{cU;NV~cNXbhPJROD#7Vm(}T{mRmp5b#A11pln=&&$x?!w)@VZ+!Q4 zd-{nd?Z5%;-<-%%^g$K?Ar;jJiwFkb3zN!z|Fj`G{72P6X92IcZ6d-B|5gq-;#jrv z6?)y>FQLFvgjft$g;>^#e)amQjQEN0{!~`{Pkn70Snfs`up||K1Oo^VN;fbNKmf3T zP=|3fAkMqD`Z@+h$6>@uH^LB5Zx?pjMP~ z_x`8xqzg>t0WOVwYO}88crrnjam!Q`DvWT8_$q&#m*Tw$9?U^L59toU`_forQi-Nb3HF;@9jLZF1epg^D$01Qe?mE|lUkbwa6 zcNE{Fws%5j`ab{6bN1a=Ua`CGxXZ2nXQGJ6l8-a}qOuSI@Ln{N*9ZiKfx-0r^gOB# zI6<7c;KTtJ;I|n(fTI&x3K*Y57$M;}7aF0!DTfpQ! z$874sn{E8o12#6DBY=(ot92hefLlE+o^^!|7DMLW;~bb~VtF2A2TXw-LKPRR;F04Z z?6|M#hrRwepj%p6vX4If)ZYEayY|c9{M!Eb_Mhzf^&3foA?>X+RZa;?uMz?!1bPU8 zQUK6H;FnpK5ZDO>>U>|At&bczY~OzN1$*x4XY8K4@3j*$^fx^{)tod$75o2CSBLWt zb9Zw%d?2DA3Rt1cAqw&%4XH11bH-mk+&6<=1OqU1!c;J1q1WLEOk;rNdqpbzZqVjdc)mDtZZvhx};>-sS7Xq|IA_G{t zvdFuG0H{)P&-ng5Hhx4#0*}c?z{vwPb>DHDkm0~Fj0P5Qi*Y`3=3m7?t<5#n9Outc7Bwv@KO%k)?^3b+O$g2D^@ke4R8v82(VO;5l7^qF|MS+Ld--##vKzjl^h*Df~3okjp zG!SrusKQvH!!vo$3GQABUs+ssW`F(EIqk2#Bm;lfWXb=ARj=qMrnp_8D1PZbFc6?? zXt(shGXDUYuI)bvwZ?hdY)}xPbRz))`uCa;--YW|xw>rQG8{Pep>W!JU)kjChi&{D zMl0lzE^9c=o68{ivrU7WiTa7|53YCGYAJ-FvSr!kHmK4QO+Ho$uyzAtTnYc2?= z$`(U5=_Go2DU=qSNsy6>HdF&po3K4MZ* zKMPyegj6Gw+8Db2#=IA83XFqC<$)jg7`!}c&g-mtS2R#3bdQ37EF6HV8*tPIgeMyc z7#9Ta(mi^YMx8T3}hfi$nFS6(_ z%l_3xt@yi9KZR|I-y;9=(*QsqsQ(YD>VFo%U>mpk&VzGU*%0OX$U?wZgS}%2j|q_p zFIFW8s7m0uaYiQy$e^h$uZ#JY5ka;ED2Sgf0l?DpRFZ__LhzxV9N>XA?{cJrbSmO- z;*%ctQFC7BSaAU%!Xyg^V8*w&Z~#pZqj}gdUx1jOo3|r}4%?o&J$8NRhAm!QwB?m$ z*90I~tt~xH`BOrmgg`$+ptJ$dkF8Rcsf0iZ0)hFj`ow$tt+&~;+Ux)Px1O~J?!Dh7 z@DniiSqIb4@qSKB=~@4|&WSV4@G!4DH%#`qF^1EV6(a~A?(TGtk3}_(uyeg`0WQ6| zl!hbWAf&XdqH=9Xmi%>u-#e#m{iE|by6?JGZ>(6AXBV)?1(ocu$_4N!7FC>lA~Z3flDO?WrH;Img!u0bU+82MoDj z@Z+3U1Owp1g}~bBgT{d1vTHZ4+1F>!*)M+kYx}?d*MHb2pM7TQIwc@LmiBLqQ@)iD zC?U{45Gc0*^iNxqB`G0LgFv87l4(?v==9W#j`#bf{nP8O+vATsYDbS8vpuu3weYnE zCfq4ePY3`K0=@Pv``|0q$|I%bmkl!1?~#zK5ki6)U~A=Q5(r3N>DQh2NJ#RWb@icMa=ZsXUaGU>+PXs;Y0PEK0g^1`^&dSzYJ^a^$>+o8=z=Q)Ia z6Z^_g)U@9JVf0)eFa-4qJsyc&x15eu+!XP2t1Ml}2LKBvRj=TA$8VER;83%{K;pU6LrvpK&k=w7YJUuqxjSCh4`ES*r+Jz8NN1?VWTxKPS77Lu z>Rv?%pce!wd7QfY>D#m}HxQ_>%zKGL@V9^Oe%m*<*Y@w(fVbX$+m@Df zT8-cvKwAm|BFxHr34wlsK&b)HPpwgwrG$VW5GGA#Y0b}4ML)Q3(4K$pIs3tD-?RJf zxyKId*WO8O1!!>2%xa=`b${KN)bkvP!_<*{B!WU#d7+2X4_E;w?tm%Yi)sse99|HN zdT{uqfR=i!C4tJT2;Az27p(gJIWwvI8%F&Wb%dW(Up>LRc(nw2_rOZ^n$Cb;U)MH; z6v5ZK5)F3F9QC(R6~jZH@QyUt^+}hW;lucu9G}#pq8N$+cVS2-?bNmL8F0~13IV)> zY@t&SPIKD${Irc9-DeXg58C+MM{M%0BQ}0mrwPm!tX0MI!Qpg2ga9>xz$W?9zHvT` zczu&7L-iTh`321MTqFpf(heuxnGX;K@Sbv2&52j9FWUPbeqg`-{qO9}KmOiMeRRr} zmo?wHD}?|laFvfG1o{F31z5E&%2pPlguuu^ATytOz4yw<&e5aC?4kP~u~pn?V)SJN+rHHCeOmReetOC5+G;TD*CKAKVsz`9(|Kb4D>@Up$_g2Mt8}}900ICA z>5ID!h1>?Ju)vIl9?_?(Znl|uW7v4c5;M$5+zFxF(*$c4q zi#8@XO7maFBJR>75|^VaVRSo~lzc zlt}fM6LUb~aB^5CA6*DghsT)W?ccl49=Pv*X(sNm`MvWtw`b1Yd;dMVx+r@Oa%qs} zfxVtu`BXxngg`eSPznIL0p&7}u0WtZ!7#HGxqg>Kha>(lvUB&HcT3&>5&M?*{D1TQ z2W@U{PV;bPc4oqcp{=cGXTC=;01_BF7?GU)d@e)qS`t1`j{b#l6&x-#F=CHGo(h2m>vv#Sw+P|xB_D`_}wj?nB z5tf6|19k%HAmEUmgNLqZxkT;+}Q%1+4RzA=< z^U|CeKeE>*ZoA1QA3R}`w;Z%F2?5-2V8rXOxyFdBGNNFG9011F@x7jbYY}KkhwmI| z8Sy>u1A?Uvp5C{W0JD7P7 zSAD=Ymu(CpA=mW|0i^4LF|FB|8GG=72kgf`d_z|LU$#3nq2@CGsO_QOUARehQ z%s*0lCgCCwSJvGd|8TK_q*aH{t@tN*PAQU4XZZ%f2>3N$IIFqT8@)XBw2G2EJj zl!H)j6+djx=;2_Es0kpBx}zC%6e=23l`GPy5`NwIMBAyfjmqYxY<$m@giXTfxLkNC z0ft;`PC%L)js)c5ckWrif(ICpAK;aSMYh`UTNIk`J;Io%g7M_!q-y{ilbxc22M)Rh zz{QJ~Y(w)(J#g>nEs9*a;e$Y$j!xGu&@!n`n*ONgfEM_k7iPHoX=wu>(;3Rw{)Rvp z2G}pSa&YRI0hOBo346^RB3&Vdp{85CA67hfy;Ess^~V zatr2isUm8yT`v&&{5XpcVz{?1*KMSrRKBRpskuPD8q`m4OCaj!`YcWqtA zb@j9`!;yJKcxjzIvj_l20HSP7nh>tSZpr3@t-PSsQ}+M<*MHfsbyC2WUwvt-tI~*bH{8rsQpxhxB?ttglbUHY z@4_F@{~|t*_oAYJbLq~I+R)p9KafolrZN$dMMIz{y+QFWw*U-EOO0?jBZH3wnSmTs z$r_0@c&_bmU15Mjn@3^*App|A2@$Ze%IqUQerrGQvj}N!&mOx?NBq6?-1GLzOE23U zx7{vH09pCZVxBlDv!cznq9TILoOpo&*%oNpU)gS^2;l-uT=kRZU$^^Qxu|5K1EaWlr#UlsU8@Q=fPBQ7005hlap_R-khayQ7K`R=&`n zcZWx&;y$+mBSqjefp}bC5)2w^UMB=RD7ygr_wBRWZn@ch_2!%Qx4*w@mo7_iE^NV+ z8eAz1h>$4nTZ2I03*ZMdEOlZLeF;At^mVwlmR1NAkO6cMZfuObMDPPVN5OV@_5efh zQ*Hqmf<_$1Dg*{uD#>E{xz;fdj{R$xbjcp3-;em)g!zw*tWVZp5sacRAg{7C1n}`~5$a5FrHA&3R}#;S;a$;Q{!cDi<*hHQ`p3`B z{&v=?pGg34Nh+z!?D5abWH9^~Yp`yp{<8(3bi0QDeFj5{qvk}$JEjZ*R*+WzJCTYi zWFQ!OU)oTbFz58XG<{hDHCI*J1MIp40WuU=kuad5EdmGyIAx`(d2(#ebTV&y(>CIC z=0|Vx&X z;$@xrcLo>cU+-LLN3s;wnSVVykyFnr9dxLkfenNQDxNU@T)-|1pIB)?0+Z{8qI3sN ze#t)vT&VP8#lQOA*H#tNZ|{C(hT1=8Sg$D&;tsH*^spR}`48i7fGgAoMGwPJI4cpR z40aFMK0tjHTVoJ=CIjId$@f<)1<(x${Cd^MYBB@?)umOdupR5XgaOzHxW1-Ee|?;p z48{V}{g7tnLeA*}l{;|8Q!X7g_=LHHATChW0~!U+?2cIb)W!)gh`>qiygPh!=Q#t@ z2?+t_bUMJHLx*f(--1A2vBkw}+9IHKO9`Dz!B+y$@|J->q{##0ADI5MMsPBIGqHC6 zTIUJIWc-qTiy$E23j_e+c~JXpxdmYNJ)}&bO%RC8|C$&pqBzLLBef28Rz)lc zNAlOtF3vd-H}^|fLE#)$Qo1cbfR#B30XDR^oMQwx*dB1{x=jmBkA35&AOyfrZf)*P z(nJWrDE0YL-zW8qntv;}JCmz9+B=LAAbh`do}_whyHP(Ek|%R5a4H(tt|_7KoD{IG z@Wdmy+_)Az_Ue>?`|r6|&ttZ-ykbks%XaC?C0kpQ5C@z|R7ERY34sU!aCBUShc}3; z@L762hHvjZ>AXrbZWl%nurB&>T&IkTORzO5nw_!?q7b0e0BCP-DO1}f1VH$~1ttUo zF-Z5|MZ&jQfEchR_b@r7Cn%(UTbrrU^}yKRo;|a6{Ma#j_2pOWU;gPQ_Q1Vj{^xbZ zF{{_nku>Q;xH$2m=c4?`02OvFCum|KLwW}euIkTS@NuF{&qWUSK!`Yu)UjahL!> zz>jZI>92lv$tu78*s8z$$n3-OR@FZLK|0Q_v0}wd(nIwzcKIBrtA@EC!aTydgs9>!iH~LagYg|?&LQ$Ib{b9 z9I#`@j_bbQ%>UKJMKM*HcWd)92^BYie=L878Ul2M$oQ|XulqJ&5pweDeYKh20#6XO z2Qbgk-EsXq=YaZxEdH?|iI0JfZ4aPqVrsi3rxL^!A4EKe5AUKN#Ad^d& zU;-mw-{)VPe)>zuBNzk0Vn3I1PX}put|O{C##&g|Z;w9oh>ZQcU{5^uxSc$H!ltL$ z@1KgwGmLHA(;g_nRDjeYjP05(U&ToI59=dDS7iRFW5l|7^#$w@b*$LK>CzzF3taVr z&MJOaYW^QzFgv>_1AihOuWRo=X$+xbEUZh|=3|esF@$yOW|e@at0wwNR*R0-%|i7c zuZYUfmYR^}RD)5r9+amjOM1MoCiQE_7jARHvSD&4s!i$N1Od$zk(oeMf1@cmOC*nj=}XZDLXf2DWX)oE~b#;U=)d>MKO#4W&R z14lk#tCfxndC=Gm2EL01a+)s)>iydhG6dw{bDj8sDQt8N(5V6C7J#kxs4`sJA;6%H zhCISPf3ir;5Sbk7b2ewq!t|q>&wz{b`DQb9-~mIjgOufF)Qy&^+@ci(ZR?ccAXjC5vh&H2QtADI6s-Kj8sd5(z{ zVuSn8^BMvI1R2CwTYI9CrJ7^ve8oEDUq0>(0x*=o()-@dk{F6uj5da+<5Q_iPP&1P{;HZ| zgn*>`t6=HUsC-q;0j*G7I(Yex8pJpc;xV5`;Y(n-*)=U8K(yS5Fo%} zG=9U%da(Z$nE#~1w5Wb$_*s+-&m7$c(b(KAi;_VQ068860tf*_AIbPQ9Tt?3@4|<` z%MfQn3xiXBl32_?KVEFJ{nYe>e9XZQO4ZC1+&ippOgw2cizJl z274Iz$*PGWA3h$OibE|@SVBFQ@Cg1L1fLwDxXp>=`>48Jq~sw6OR0YE{8 zYp~G+D>6|4kQ3b1LdE#o=a*&RPZs_Ea>lAs^*8+3`|rY_oQOl>MIT*r>$&Bzf9+^e zVFYJ*9hbEu!2wSLbcXZ0?px56p%ao30=BQV;1|ZzM zt|MEQq;9SYH#RFR6&Uy|l<6Ds@_>3_j?QZgkDh#p-wEr+@Q6Hw4%lGugF7w{HVzB@ z5RPZUq{baI*t7%z`}gm&x%qipmsY^F>(^{qwgFfH4z!df5~7k%>4p{pVf$}YT8V4} zj_sdcUAj`Fd@*RKuKzUDo52>YaUa}afHNpz{Nvzkh_dCE zdfGu4z+i1mlkyaf_T(FafQS@GhwNjV;6F7vZAXtDv1gxp#(wbK@B5j!d*|k*78|6K z#PBG*!~92Me=!*kko-`jxZ)r0>F-1JEC2u^F61O}uYXY;LAt)`uRFvZ4180Ul7|EX z)f*!Gq~2eVVLupu`>R&|&s%VGYecxcEKAJWhq!(QVyJ5#;V(=su?FRRhY&#UTUiuy`L&Lq!#=?3hKvMG z*_c#!$2ew}c{dAdCsL9OeO^u`=j7H+?B8z-vJJ4IRo6uc0hX4Qe1Ry0jr3Jaw7d@+1cJ(c&HHm-c zTQZk>SIY~iB!#z<&0^T_#fO$_b`n1EA0)TqEl}{ap0P3+Y{~H?^{3Emc0mN8gZ~fF%U8N@s z#?BTn4Ad}K8 z)d4I2YzJWC%)?)#y@^VDQOO3%p9X;O$B+ulevjjn%JRU6A$zHJ79!2GYf+CMx(k%P8Iy?<(I+L`~jI^PZEI@0x#FW{YH z0XY&d-vuE7nt}DW&HX(;ZufjM7ErpEahcO-L4bjcgb%gjKzuP%G&>k>CIKX8nl*KA z4$}>vFt4KlKzWb?Qg*K90Uzpva04EG%nlwpWG_GWqE`N2v3u^m+YaA!$m>)qcbo)~ zCbly6N5WhPz{xisWVK`m`oRe#0fsxkZ{?iv7d)H@Q$XGeTvd#}TlUvJfBQ<7{m)+y zmi-ID53{#-C$aw<9H~<>|7~e%jQ&BuL;+x15*@NcfK63nw4zm0R4F$Z-5snwcmiQ< z2ear{(v-U*)Yzj5v19e>gM_sWfa3z!t}NNcmy0(3_b+YYp_4ZKxK0ev=>bjiQJila zp{g?>xJQs*wDKJVH!jdtc;<7`7|4FONqc#{R58^2;V$T|+4&p<0Ac6G0TmXlM$JnR z9?*YB4;^vifY=5=An>za{ld$>anSs{d}Se)y``^%|i? z(Q4JA>jmob3AQ)eo8w!mCr$X;F*i90Y!6I==}9L-@|o(ETpT@vF8X6GN6iCn0U4IJ8=?+ zw$QZbrUMHLcH-Cxc2nDx#jCcmvMg-?jvt7w6aqv@4Bj2#`G&2^=yDNF4Wl0y1yT7& z17LIJKM3XBE|1a=3wPm;;c*u6!WF~CEqu)7^rz5yTjj&Iv^GE;0c2YtjPIZcz%b}9 z99{|lvh7vAw;clTZR=9u;?9+IVQygGJ;pr3-uvK}4cVJ_$`Yh^b>xsmSku!}cGJOw zZsGs?uYS)S{pQ0qKd1ADQSA>nAlxJKA6E?Gw{+Ej5zIgH94b*cFiQs?%x8egrP4JZ$0)V*kAHT)BI+q{crQB>Xqt(^Zf`ZY$;=Al(X4Zq^dz_m&V)_XsBjthkK; zj;?b9fy`Cd2hf!?LOq#Um(qhb5FQGj*G%}47U7)HAep>?_z!_gKoRPjsSnlmOFx9; zN)tPR(WHa`3lai=GZq&YZFzM?#sP2m7B)hYKo}F@t^6Gv2n5xCnD4YeCtZG+f3Es| z|Mb`FGxIRQcDCUR1uf4FU`@I{uoDb_s)wq{Es{k_s*@@vc>ygBnPI03&# zaOH&_JU=IZRBX{wVbx!70zP?;`bT)M>JRgu2NQj(%DS_CddVtpown-lKQlYUo=*7@ zZ^Rud{u?r`Q(FFSS4R;0uf7Oe`77P#5QsxuWKbbdwRtK#3;|(CI}LbKF(m{dGeUER z@F=6Th5%hfE0dK=*KOnURh=5J7#?fs_d0i8qYS z;h~CoopUNkSB{GTgiXUiaX2C1piT!kaq|hA-@DfZ0hcac7EOtIWZLx)W0fvd`P>Wv z+OBa+aC4Zj^3Q&M1_{h_a~OOgQ_m_p^bH2ag8(27Mrif?xY0LS_YZ;q(!mg6+`k_~ zHTSPDjDtUmgd3s>f(Aj16YfDsfM7v2QMe+ZLSwAXzm)=j&ck?wbHLbGu=pQ@0nMPO zpWhkx{Wpi{C!a9j!(2wWIK%b2Q%?&|8!9 zc*%UoN75qNusP}kZR1XrbeP5Wy0^oMMF9c;L&7C)dV1P!S~zGYjvuoH9T&KG?W$e8 zc*z;H7$)4CnY705;BX@}_#CyTL?Plwyt zK%(2#uee1ZGVK9!dCWV0v7E*E)1eU%P@nz~0+a%PUXO+$&7Z-kajrobE&exGG+6Y6 z@yFO7QojCmAccrj;^0Mi@vWzywg2OP{7-w~ThH08CvUM?Ed5946P(0>V$xb=9`VrFWbg>8I*;H-#a7w06Dg_iQ{vZ9=z-6$J;H_X3>wi5E95g>t`5G z3xLiEgj6vs0D!|G!oY#&O+2pcp>uoY?D)~+cJky&TUuVWvtOUh*aRS`+Qs4M(aN7b zg8-}lYtVLesZgqf7}wL*4?pG)Mme7^VD1h3_@f{ptk|a#YRhBjuEAyiqG;2m&z>4dP|`7H$i-GTE+Qo+P~!j00=1jGp@3t zBV#@~XRB|1WR>53YW6oQ{9o0If4R>;11f)YX*()ydBDtzQ2G@@fbLik&I$M?r*js@ zEI-sCAh?OhV+BoGtDG31<72C5bw2$U(mK=DfT|eN z@qO6)QELRkqh2%m9rIf3e|*n|Av6t%Kl5YMii>ghJ}fT6g9=0TWnmHFP=k{^0s-a_ zV4-Msdd7|%I&4P{AMvq$?Z!13l3Q_Y(=?cR&0M9+Qa%R=FmHx!h^+oMLj~raJ^xcW z3c85-4;lo)sCZP>Z%(OwK0bWCtU>6{U=7`q&YE@M8>fd=wdJzdRTFz>d1(~KYWJ5? z0MKrTmgxxs4DvA0o26g@=VKY4E2_r0e}FJ2MaaE#^ohjfA33Md+SqM|J#>Vm0ISwR%Ub}OX;!@plb<` z9Qn5)se2wsvr)?T))2txpKqhcB8Jw{%C9L12phKYVq>&l3mp)0jvtwU1*2gz;u}iuF7Ru|J51IOTYZUR^R#3 zHZExOMgHTGw)*K2xqv2<(R2Z{!PpMl07|zz2(X%55dq^?!grTc8QbvzgizQI*?xe~ zTDalv_kqwXLayzHYMEjR0%A6+V*WWUu&TL<`F?ChiyFz?UnmG*o=T1ybK)Sc`J7Nn z^4TC`lgU=!^D=zNTd(;>P~(nKKu<&As!)jcyEj92`)vk;oo zc}YOR4g?;Zn*ThDO@B%Oz@{K718fHZfdLPO|MKFw^j3^?SpDBAW@dYV7(yK%0&f@) zL4Ag(3jxAFggFeeNM4DW{j%v1g28&^$WeRx$*1gf8T)(Y@h9CfZIkT+82)Ki|8*5* zO9lu4hLQPawubRX^8vv~kpKjss_nc1*#hCrKb{#U7*%hq+Qz3BY*{M*OTYNg);~FC zmFp{3m4Qjxnm#p&9SQGF{*y^$^j=|q_+#~-t8}}EKotTcnb7OlX7}liatb1oe8rS| zTb(zMM!>K)l$gRS0oqnLmWj27fN+9q16+}zz^|@Y<*K#^sLzen;Rf{4vzm+%|M|nEy##FvJkfjV{piVZT3ufL;%V(7gif z>BG0FbJ5x8b7(?0(_bNYReS|n03n^m8h7Wd6aaJw&Lfly%z0qUvICt?k7^GFKAITk zEkcMiuSaCla4QUTeCzH%(G8(170A0~wir2$vLi8frwnseferovCUF;V)J7qqUFE z+49dnuw_>L|N4cm`eT{7MhrO^6Cr^5R@)hg=3)(m4fA6~UFAKVfVS0Ji|Ij#)7a9*+G4uz)0|T$s zdpGtM37|AEy&_1R~Jtw z#B(VK$U>yk?}27VW4F1}21Y$B3g+jR@H+x0ci#%+vo1w34!oI z0DxtF7AS+jfO0mMHJy7XEAtHTLq3h)!y8?f`sX={56j5k>#x0Luf6n&-6BE2 zBx>#H8QKl~X=3MiJ5-mFxw)Y=W6Q zB^?_mjQ|}PSdmb`bX?%rycQ3_1XV96ofaCwFWOSiG$lk++C@K?BM>I@JsoxspMe98 zyTc%-e$?6N3=bK?CXamLn83p~9kRoR4%zIUJ+>&rfQ#3zB_Yx`7`F6#`P?Z8zz=Z2 zz&E@4N`0Q{f6yFY>j10&&9T!ybw-{o#yXjU#W<v@E4S=$S`04s|<2mO5({5y2<}qv#h=VKMo9hSjA696) zi+DjkXdg6}sFly2*;%{olq$pGwvwH?e{x)mca`>MZH|uktsCwXW;>uIbJ4Ca4=FgpEzL? zf-hSGE?>TES0n_ueqBZyOV=X^z$VccfAe zdwfuprP_6*%p1%zSCo|Dw=)MrUnI?IPLcdDEW+SS56l=c4B*DHvKQ(8d+xLEyz-j8 z`0R7qTXvh_*WiR4d=igp{|z6L7m@`SQSCo3%pm#+!8!pF?H#ldetI00HWD1bpsakZN!pCc zSpww&tVMqnnPdNeYYEQ|wu^{)AGibx!&x1m`H?y~AQ0(mRQQ`2 zU6}Tuejirr8;bxl9qoOEbQ-^hw~z)JzSGjMiL3bOkXzFLXyi=-uK?emVa1sARsM9U z&CgDMs_*6T8HA=jzMznbN?5KU{v;K&Bafv3pd(-%%|v0$o$<&ZWT3&G9Mp2QE1AoK zB!eg*lbnoF5qypmGZ+kf#MS$HfPhq*(|F+C`|V$U^w0KfG5@#Re6t@nQ;(%5q|Y*B z=zIQ))c#?7xv*707R}i-BsnlI5FD=dFXAHrIKSuscTII?zrSlg5G)G>wWIb(uKoE7 zyYV0I*vea<*~V9_m`LT{C9nBO7x|JUIWP%0LT#X|iee3qKjQ)|h;^yjmB;?=zFlD? z9jC!m24u1OM9UO%As}T-U+oh)R7gPOMvR_}{jr4k2MLU#gHDB@95KAt_f1XL)*Lvw zykX<#v=S{rKvnbT*gk0jXaS*lOMn`ZKI|F+p-p|R%~mvN?;@4>Z1914r~Zy}RmoV- zrV8i-^oW>02UrS6Lj%ld3&8Qi$84VrHebAW(KseB*p5mk1ineXm(T5mfM}9+X~2k{ zYo0@4-h-jOJf@ksm(#wA)$l{xk;e4&wA!&~k#8$o02&1Vz##GuO5VF-bNSPdq7zk} z?xe$}`xJFUh!C_O8Yibv@1QjR&Dw2O3IMtd@gdHSfgaRc8Y_vE4*@_JppD{IZ28!x;q7l>&giYTI%3=P4FIG#`zzEkL#b#=er3 zdKn3{6{-AlQow9R!;l)#rI`=8b1~mCHnJ{`7fs9VcqjZYIB>7mosONUJFoabhry)* zpwymiaLLTfj2%6E*bW~)WUDKycKPZRHw>6%=CbtRFo3Qf>fm&V8_-q1&p2o_RQ7{i zu*Q!;V1V(zJi7rgY*4L7JC}5_-6MwDi3UK7OPcfz?%ow!&;mYpsUK3WY=4UJ^R!UN zi|`G+Ml3*w@1+2s!vG$IHrsk2de(8{JZdjnLFk5TGa~m+K10f#CBOUE`+BUwH zksc6zCU`35mLFjRTw_axgy4es8U;taH#E_rYS#uZjsnP9))AwB!SG+nTDAmK8$U8q zeYEBfP_46}$E%%M5NEC957AdZA)HqV0s5m&ov?=v5sfhswFRLKuCLk{Hg0rmVD-|H z1nZ(L79Dgu33g`e9DX&TBq+^aC?o`-bO`IO>jz^$c&}H~M+qH_w z7zX5ofWtQ(_R|5*UzBm-TznCjy%@c`?+yYmyl7oyVI7#|uq_z+JL?d>!~AnEa?toe zvp9d{KgQdQ0Dv?CGarr!+{!!?G@bLtUg($Etq=hgXsrT=?)Jab@@52Ac`qTbeF!kX zvx7XQLIyZ5ep?9qq5 zY4_Z9m+jrRHyQM)LPoWBnExUNu43{@Pe4Us2f|-#Mf*_cOY_((@IovNw{ju{m;*-mVu0+bWuLHe?WR0)lGDor79GVa2+QfE;qT zwW^-uGsbZV&Q@S*`!~?YnBZO=Z#1UVGNZDM8o>h)hac@V*11pLpg$lTTp$xk2k)=y z&R;yQIkOoyfUTYMYN{9``51VE z=Bfuey%RA`kcpRQZc!5j6cXB8XasL zbYXx}P7utz)czlT_)&ZHrI&Pn#Eu<3CTV?UhCwpttN*&kQJt*{6A7!{T)1X11pk5g zRRzJ#fPzVt=UDe_9lYMj%D>k;Re8a=3gMy^kM+}+ZTWW}+sdCl*Ixh2$?<-vu-SBX z@lL9fDmO=q(NBifCGbZ5e@)W%yzw`*b?;ck*fXepn?oFSEXy)shd>;uzF5l7rY_=- zc;7U_&<6m9;mD~7&7rSyUse$4Lx;EqWYIhXH?gv2c2&m&N+2)>?E$SZtyRqip?lJn zk*{S=b74TH>K;r#+~XBg;xYjT>^0Ua7CW!*e2aDFIfW9S0j8&>Y_I6OJvu6I@!F!D zJ$Fuf%vaOFk^~q(Bv88DKD2S&002M$Nkl>=@|I(r3hr z8URK4$HhH(i_Pqc?N$STGEheP1jCiR{6&s%b-_fo6Ty1~djyT~!?tF&Nat-IXbjul zw^9S3z3?2_)QY5H44N=_wnEHp2Yd`juKG$5hyxb~tDgKl`fK=V05XWNNLq15|K7Xq zHmvUpG)P?%(qIwO%Unsi`*qX2;EBq>KNok4t{xm@c37}AIZm7wnI zYlO+C-gRL6b5+QHL#rGcAD^?8cTd~u-@erGev1KK9fVU=?aO|6-v;3AR#bt4dJTwo z0J*l`Bz<~7irv@649tIGP_C~_I(&r_iR6_6Kcfx}E^p-mx`D826>7A;IVgmVw0q!_tWO_e z#z{Lq|0-atdR*PECNxQwE+H!?ykg@j;s%~Ef#^R#cQ3krTqv9LCFqm1i}jA`FER=m zPGAawU>4ch`OW(u5aYjQ*KaJ@AKv<-Ew3!4@CO-Tuq}WvoqYMcT?j#fHUOnz87s~4g^jkPLo9-QiFbmFNL0CWPX zBbO!}w`jmRR`&U*0NX$$zcXM7f&kL453+hrb;2+VE|`C|4Y11pzk_|dIX5fuLwN6>G;WUvEJONd(LVp zhW^%1owwC@#Q1;kwKM=OTJ@S%I=bL!iZyAZOiZlXWJQcbmPqPC8M=`L1ToA%N7a?C z4G>U2b^*XJjOA9~$PVj%s;|mYkzjF51iaQy10HHsm!UwsEi1Uqk2EF?9PitV-lpKp zy$E7WLV96fwP&`|Z9OAa)!@pQW(GyeDQY8bN%*0PRWIsK+)sePJUn1h?V6ORxt?)%S+Ulen9{;|AhAHYN8ImdbWq2A5?CTKl)w~SpaNhH82 zwk#*RX5?1UfpCU-LldJd|p<+_MU03u;9hIx=L+9@J5P|NhVa zYOg-`ZJif;ppiiYr$BJTbMA{O0Q1X4?2~He>(tpCRB0y`G;uPg7BS3#k_mNP57gg5 z%TL{*@?VjGqm_5g*yVX9m& z>LP&FjQ>T7u@~j&LY}@dDdS`~H@4)f7G21A3t1r4Vx-cy$3<)vja$O0#j?}R7IGN= zAOS6l@phEgeY{7@1csgVQU2o+=J;`K+lRfY3eyinORQ?;mUJ6uob9LC=@=ONV+)!a z{@YGwt_^U-?9vUZoWEw{7jLoZ14nJ_@O&^77}IikOD_`sngrY~6kzK>wyB*6Q#cr6 zreEe@1Obf%QcQI)?xAznSNG}US$c(fJ3XTxoht0ciIYwPBgfXyXP$Uc^ue081njZD z{r%mHO@P?LN^O8nbc#Ajo$au(Yex7t=T7^4D?Vd^e)_PQ~?`j^f=)yG!jAY*_Xkiox)AAHDu z^!gk2$_p>r!Mv4!Eb<~zN3u>OvW{dK;V{9CS!6>F#yfQcr-4+td&A`vo#sXI$;Ik} z*S!t^)cq@3!PxlZf-V2{V_W{shqfjSfaAk+#`zoS!t1Zj=ICyd6jQDGB4JCZ=jcKiW>)OJzGb?mlb~SjYyE z3oUk7QW*8C5}HepUR`F+)jRZv34!JGU7U{`cR;I-?5cZ6jvtm4Xfay7s5VJi^8E?C~t zcj1&S_PpodcELYv)6MUs;Dq$)UYnI8?z?YpS7L^(2w~s;R`3&L3gyeoH{dsHks$zp zm~M2GrTJ6@@}+-q5Z)T!cU_F~gxZX@E~;Kv3)P0qV1OWmiwtkJA&Zep z0^)!b=U(>JZUG!d3_@d41h5lRs`uI&%wi7*YF_2lCbSFMEq**&I*j9>J}Ym!e0}jO z__zT$-MfhR=tr(jt+q=$+LFGv9fu7U_WJrf%~T~E&T|;6jp=VE-A;w2b{suBAx0aa z^Nt-=<2YT8$X)%ys%Le7_?V47c--v3o*)eHHdR2vvZ{|Q9-a~8^NqqpS9{h2R`PM! z1<15f_%I;9Ni~9SggVP3+s~;tLXWsb0{SJ$|eBM*KS<5t5>ht;`OT; zu#gc6m2Nu_plb!q;Ko>?iGb&BV7i;Qjpf*nRihYp=cdl07Mde@BlTZD#(NBsOFIU4;(Jree3o18uEWXA%=&ocU++ zR1N_%H)cg0nJ}rh3qAB4f$a4cxhHLa%K1eZ_|x%z?`g09r#fB-<{yHj4_rl){>Hcr z0!meK90AV|xX7>$(qz-uU{{}psTc)#!u(HbhwT@f=BR}ZBivKP z&}-~G3C;-!IC_@%VhALVDP56nMh+!gbmss(~|yS!v$noFzeIyMlYzylHl9Nw!f0m-P~=J3`- zZI0G+?v?-&*Eh5YfZ?Fq1@L}NRa6&@v*p%V{&YFDOHKHZ2mxrTg?;;U4D{VD09agH zv_HN5mJ9=~G|dHoxEn)AB^?siZ=-YfU>&yhF9iU*?-OMTeF6cW1HCMD#KV9^73aQt z?z4Y+<45+)lTV5HKP=T(8PXE)Z4B6e|pJQ{`j%2{^hie_mlLTV~y&KkxjMh?{4ge69GgxqI%XY z;;icxNiu{iz zZ(B+7>}b8Vm^fvF)?n}dR)ri}N?J5WmF)mJO4lyQV!ez7jz4~j*{!l&gqxspEfHgft^tuL9xwvsjq7I&nnqVb%5gft z;X{Y)xu>3SVG~+)Z@=@`!qWjrIf6WsOZhqy5QrUa@GDyi00zGe%Nhs*bvmFvI6>0W z)3f$~R{nqbqo3G|&pzii05B|7b4-Lxl3aM_VRn*alH|hGvk32l^h^C%1b4>T~nju`h3zK2d^4^Rbp2DM?(EyT^?e0dU9IB9@amM0qzJy*G2fQ z$|`nW4BAu=0jE?%*ttD(&6Za>7|v>o8~17?Tj zZEQNxY;(}-2lamJdhiatgOER4DEz!{=)2Q5$$PuY-i`FU-pVM_Xpz22=S_bbS3qn$ z0SmM#8Z-g+yAS}FA$ zm{#}hD4O6}@^@DdKz)|0ks)CqfCNCMOxm4rk(kJ)%}Vdw+`K)g zmH(go@JII2bI;pBodMg#{4*JF6*2!*iF&~hH&!p3W&gj^AZR$$2!b3W1)iq9YkG*x ze+GGTyx+zbmu>CcGq&=Zk8DlH{8bm1n+UPUVDtlE!t^7eQ~iQfrLdaV4<*}r8K?t9 zG=~4yqbADMqn9CbAOJjB@nJ>}eIzH)FnIaUpRn1P83{!t0LX9y;7c3~T)!|>S+Iw- zf66)3EtRcyo8AN5(#&+560Rjft4Nf#Y5zeC5SW9|=V}Qg5BMUeg2~UL8zNod`7v%= zQRCUfJM>O{rda*gLPEzi%vOc3qzPabByhj6?sJ;WX-r2UK&&GG0yf-n02bh-T>xef z5a1ufro#-R1F%Lx-mrCof(-ZV&`+1)sIk2q0y{h+nf|!%%k0K zJp0coM_j{=U&~Av1WP16Ckh!O55VsFZ>46kdg}FO;;Go@o=bd8wU$m#6 ze8LvQ{5PoOfw<$!KhJrXoXLR;=AX(8tWyOMaBxj}WmVst%DA8dVTZm&NqP+CAP}*o zc>%LPJF>^|^U`C-s})ob7> zwSIsn-FyVB{tML3plFpJ#;V9o$`$~c00?tkC{h%#7yiOOpkgV1=i};Llxd*y1c3|b z!xVJ@?;$?h8uL%?Fzpf)NE<5;2SW&>MjwSz&0r4n1`7*r1XXi)uY~}<$N|yslWcQ| z+v-CTE^E#`ec8seEugX}8v!e_4{+xp8{0drd0rEJoZktHuo(J<_DA^7)#UUI)Ik4W zI{-Amz|JwM12mhrh3<)PlN!|=@KOKqIz#J#ToSi$?_PWCkwR4NG(>aG5k z#SN;>El!u#QeIXXWPt-SHtNQcO znyq~#=KptE_5a{&S^2*q3&oo-|1qzAfS<~MKnhjo3GD@(a6?EP0(+a2^)9F%nE%cT z)!JP6K26S<5W%z=XZ2};cUmlsK^Uz`5xl;qI)`ZGJ1Wcw0anMvOt6eff=3YvL001Gr*3w$0#7aB;8JtMG zEo9v?y`6rfl!!8X<-g5fEi4nTUe!3o*sg5uRM85+-F4W;=49U}+A?AcQ>Ge2uXh;Y z4BTq~bm0Jy)HSGf0VuxLnTO=LslE!}+W}}jg(GN!@92MrbUgHf_ucPv!-WeM?ae>@ zE@Kmbgo)DJhr^fhcQXhCa6@c1j`Pq|jegm*ScOM^gUdE;@NNHZC~v+-L(DDUGqeEc zfQ^p6LK-2R#&4m+X8k(|y^ivO7KA=wD_GKv z5QHAwzhK|I|3O>H%fUG7!pgl8bm#+?iBEBH$lm$@ISQ^LXi$c+k z85i*lnraghG+4GHXCk!el{qnNbm?uDm8n8>LolV4t4DGd|ks_M#qe{gB0{VIzqw(+@E{onfB zmVfb~j`=&QZ2&7x;Yg~Li-LewVY>B=K8isxUuB|Sc7P~uXI*F#SN-?CP9v3WR}c_9 zvHI_V!+{;(TZDjyKE&+o?6j-*Z)W=f8ySR=NL9F1D~x^MU<%p3wOQHwpr`{;1fSB> zFLsUhNJvaL_%w{Atye$htzxJgnxnitnyTDLW&Ym(Z0e4=)n8-&+3($Wr%z5vu!cq% zQ*O_k+XawDo-_igmzTx6Yo6FUV`DR!H9p!5Apnz#7dQcYy5RUjFZg%D_OfE>g6;H~ zO3P87Y*pM(p3q4+r-8yF^y=*NjGZ`k+!iDZxO#2TE?t)GqxID+nWP9IVFy`=KJvf7 zFOE&_X~(y4!R#lnPY3Y%C)F4Y-@;2wBR_sULSWpBY?Fl5dbaSy^y)F|Poex_PDBV$ zuZ-&upwnuWa4CFtpc8<> z5QG4E1KpF@(oY?SkQau~)-{-d&|!XVE@A$EYOlQT?O^RcjaVom#{Q6e7cs<)OILvx z;sN&zs-rzN({ca_al9YQzZ;a(GxblojSJUo?ayNTf2$+@bl>>mQdn_Fh1v2W@aO6m zr@F%IKzwGAb-xrbmLoD_$_;|R~vwF?4*DQ(;agp6jAdWjSIJ1rg4LL`78g4wFFRp+%( z0)A3Nb^_aKB0CI6xhEuCL%OD)sr&^3fmW{pGyzKB86t1cINl@t;);779Es0&rQo^l>bNF7GyllL$j7t!RA<*XXASh)%&08dG6aeHV-XYIY0MH@8j$*P55C(4=*8&IL z!=TVg&JK}dNYgR7OVHNW*?W$f(3stF;-o$D(8KoQAG~2Ne(QNVfWg0XM&t?OAJyK{ z-(o`{G53}K1oKD#LAz5o4E+@eIj9fygz5JyQ(plZ)qbq}ul(*~TY2+iTl?^wj`)M1 z$b{V{Y}!F^ZZpfm;GrfU-hg}?`b%M~?th@)W2g@8nyl(!_e(SQInwShwWF~5PoIW( zozb=^U*Xs)0Wb7{hD_8t>1C_e#^+Yc-pj%cTHdttF5n9D4}PMb?A(PC1grltQYkII z?c(APKCCtdyuS0&Y$qlHwXpi%dTY&g(rimu=5-y>J9bg01V}SrY*qq*8C{|a8ej{s zXgiuueZiUiYyl#z#CNV;^p|YXU45tQfrt!Mpk7{|q!j)xkF>xe)tS&Fy-9|pp)CV0 zt=jpE7nNixP_071x%M*B4^`J5$rB_80J$cn#!*ARtU*kgXrg_J9ciG=sis6rtN`e{ z6#xXa|3HJ~w|6KZ+mXOy2s5)~aXL=?5j;}XF~`NY*>9bG&*%qrI=eFcVTJ&RF&uFb z24<`)g9iS3EIG?L`fu+Z5N#MpAQXs?s_m7<)`uQ=(4K$hTlUmrPs;lLfyTi{QT(Vs ztswE7ha_NZ8JCg4&BN*2D{K!!t8y%oAx!0-S5HdCDuM6!PbPi<8l9f2`c}ETWGjFE zOh^2kvW@dnDU_kV+Tsq*j5~H!vh6DRNW+?4x?oG!WSp31oIhWcN{}dR&w$ z%s)hLs@wOtNFB@GS5>t-30~fggC*0D@RiNU@ioZ_S+&?s3hfFjoCaanK&iJY?GaDM z8x;8N5Dp$+xG5m19VSvAhGL<`do>fzTTPh04j!jGt%&rkrngx*2>fx4t5D|6LU-|d z9H$rv{OYP33WQb~e_RcHWUm=^0bp`*w4EFJ6F%hFUuXsv4zdn^Xo`q5u?rBNvtj%7 z9k@_zXgr>$lKN)LOBied2qZx`LSR5MW}FW2z`ggmK=shVLHpv&8M}D-l7DafAr1+Z zj;;gjV9;U2vo82G!^_8)`svshb65WSVC+U=^M6Uig5{4p=ei-VDm z&+!{~;)W~j%X5eae0t$Z4S-%ieMs|U;?3{mnm;s{gJf*`9i4%mc5yJWy7kz@kJ!sE zzGRO|1K`%1Pr3xI7M8IEV}}5Mi7gL1kTy&)7cP@m@a*?r)eW#hwa0qq)s@MQz5c8a z_ykw4tgr6Nn((^R2Up+z)K-80sgC%&BIcic!1ZvuhD5-3yH2JZ3B;nXzt|CX-%Z*G z&NTVxDp&v8(c*z&+3tSjcI4dcG@$XPCZ=shC!*}6Z~NE~4uN@ad>aQ#mnuSb?5}3t z;IDM;hX7F3x_SP*xP|PT0QZSx3xQh%nUKmXE3Byg=Mm2>qWY)ULtI#zB^EY|yu?d0IHKnXx622x5{Qb>|B%^v4Dl zV1(K{c)r=;2MrWf`J2Dv6T-m!*VAZ(Jv@oe`MJ0u#B;3r5TG;00&?HK?M+|N7VPM6#&EbKx4Z34VS5tBN8d zrHb>!}}B{<(P1V6&?B4z7K0)>gFNAJu=h0913-mD0)XdcYtH0jMwa%hpp~?g!e5tFkG^l|Bmdzp#Iw&Ck!Xxl%*;Pnj){so z@|?vx00f*1(}Tf31Rbf#jL*1rsS2*X{h6)4bK2HVowLf~a`@1xU&`6stewiOWx5cH z>l{X4q}3;#uN~FOrr|h1d;ewhk1L>r zJj9TV*!&Jd#UAHMWlE@n9;v?A70`uMku(w4T)NU{_Yj~>X=NI9>wSdK&iuEbGqCCp z;XL80zPqatMqQPX%2t&|yGp66-Q5)^!K_TVwnCpX{}G&wU5*O8wrrIvn%i%z*!aUI zZS0t~1f;jVK^wT5w(cCy=!*)Oe&GPnk2A+`b$WJMuicQ2h3~*U7Bw#XXzmYN98!6K ze1Jg|0-!mW#6tkIcYd!u_2}cG9X5RX;5+aB&92|jDGUvc2-v33FK`=KhlVB0bTa^e zO%O1{iV7EH;Do>h`(bNRVo5q$b0YE~owYR`?JTlC#DOs8+MXkwpskSDUD63-o4K%m zKhlnk6$tgC?iz!y|oBZm$*uH2F=%>T3oe-ZP~goT6xwSS+y(zy@9vnq+o z6gZO!oM-Btbxd$^zdv=Y1J<9b+6u67=CUondCFFP{gJJIdcmqXN4V1tlCz(jlZon= z6M|!~l-}F&L5CrjSd*s9Mply6Pke0YIeU2B_@YDJldM>V#-R`KrP$$66HZ zVmAQ-rkRM@PU*H1miYl94&tb{<8Ujb9f~l*H(Q0Z_W@0sp+D@V_JIzVk%r1tVmNCz zsS%gn;`IQ3y0A|VZFX+>Ph7@s>=m@RNLx=1a9o!*z`5&A7mUqGJBSkkG~gQApKx(Y zBtV6}$U4Sm^yl*+Y6DQu*hNr{$l@E`)AeLxPPrK!5qRXLL)sFs*KRCb7X!Lxi`TBD zLZly?ncMW&VSk47ESf*)dk*GYoFC@_=AO-obu@$P`Osbn{>eMf`aVzx4~0A^gKxPM z-GwbPo;P#Eg@F$%_{{WY)>!Y06hg?%sYTSlT`Kks;z%{#n#^b+*beenQffCDq^6s zgnhkm;VBA&vi?1L7le(Q}8 z`tPLgr)E_ZW_q`dG~!rQuk;fsI@vFMf3;g8-+kG4TlbNQ@A!`z7sH)dgtl)-`*Z5b zRNSVLH;3&I_@TcNSww#zM0S``b2RM4Fh4-Q6ho;wUf=mZA7I=UZ2-3WsXn$Ky8tYl z#I}fcr^gf?+A8|R3dMJT!Q~w!ejK2CEIUPZJ}%R`)Dy2*U+S4SaO2F(tnHuQC;3O! zK0ot?UAip$Ne;Ids(vT@XgcN*k>6}bZE^u7Vf(NHx&~^3#`%Z%0j{YAXi6)10RC`A z4S*tsK5WsT^FkwVFKWAVR+I@kk(GbP2RZFZI&6Dp^*^sW5Pu3meGlo)QUH){)$+Lw z5MTf^h@vDeFUhEOgX&rRd)^@7D+A=_6F1v;UwYNP{rrn|-(7dxtdMTakp>eKj4_sO zeL&T|h6l#>pfmr}Yb(q@5=X8G(1>xXfKC|u%RqlXUIVj( zR5x^?ROjqc4Oq1bYG@JwbScka&W0g?x^kG@hq$Jo22}rDrFu%}5K6W^&Y3C(gkT8) z=f^0vC+UV%$Ol{>h~(~%y@tY!+zarIurH`Q^M}vvyTeSV&rizo_w@9Xwp?NSsvy4| zdM7n;LcU#E0E{L-tw4ec%0KW9W@mX#ng9|4XhC3%?UMUuZEQxG0I@*;BuyQg+X1T| z?$Hs}ujrwB44?Aq`$y`;WRX`N3ZJec;Zk)3*P@*|zh~a@*H>Sk)m8v$R1&VqF~*ua zhu$OB8JMs=C+ouZusGa z@C&lXaC~5XebIOj{zI6Usp}St(UxGP0H7^kEE7pXAQEtO6`b@}@GJu|P3#5S+kjP* zC=i5)w9o2C-~XY#@!i+$fqU+2Wc~@KN!eKSkH`HbaceiSL+zc5=l0@*S~eYh07=07 zyXwF0S)>u`7@2>UW~NFcm~UJb^Z(Xow)~3^Y+XWt$|YF_&OyehSRH?a=cYRvy|LlgsOGm|r7syOzK70ULHD=eN1oa&=m zJ?sEDA*88CC(0iCM0qcQ0CS4I`7P6T)Yg&l->o2xlwqW2#QbCZpZ0FQ^GbMK5CF8J ztBz|-h1GvFYlg%D7#U78(FJzly4pfC)c%!1z7B3>vJc zs9d@}!6)%ky}bT{74&4{z+Lv_{3YNa&?|c6BoMHZ}QukXw zeZ^LO|FJFq`jl;acHXMXn$3yVYe#$KwONnK6}w?nN9!gt!lbsL6A2(%OSgLn08iI$ z8Fp}L{Gd)fBj&$ds{dK&GY&vKsOWPdK)Y=LkS>H3_tNzW0<^KWbW;JCuPbe^|9}y_ zX%W6K|7-*3l#Bfp#?@EgcG0gWGeR{#t9uvebgHhqPM7(HtpE}PU&cHCb!lpE>h4@<`0nuV&^~tN=jZJB zk)zsSv1VUiIB!eKH=N-OA+o;#r)GVJ_1(J%FqO*}B=V81!^{N%7kS{+Pv9BwKtK?* zjGLM$6~29sp-kh_Sn^X#guBAZe^Q|&BZdyR03(r1lKmDhl*f(yw-HF_En85rC8AC{b1sca< zV1!|YLDKpkEJuBGVIPSJ2O$9OSx4P`)n5!f0uF$hI5*a`xMJ%cowt?W%F_RDKeozO z+MkB;pFRQz#9F_&xYC7|Cxl-&)IELDPV_&V`o+;RUOvpR1%rl z_3PJlJm7UPg!#-MiA9jbG=}*e$9zB~b-2Ykr`7vI*k<_P9|Z*A&bEN&v|^gOyNA4j zu|8J)gTcSR{5PkJuo1?;$c7W;MbORmr&zz{RJ!NWF7vD1D{-0AfIuJ&T%_hw8r6Vc zhG}%+cntj53scd65Z5Kgz}UBUpWSoUJ@$iFzh~dL^>$x1@Q?8a;WTRhFvlTU{1Wb9 z7_LE8`)6*6EC19eZb`oDc!aqKpRBRIi z(*i?GIGqT7Y2c;54d-)Ep@(UQaA(E8v7WrcM8hm=s_}4H@Pj{K$^$L|KPjcuQG3}m z8*|v&TS!}7K!srCAGQ5}xANNytp)@D>!Qi?1QJnL(Q50!)=JjCP^AE%eXuN3%0PfY z%OKs@5J9F@evJ*e#?j7xAOr+s9KR-p3n2goRqp)89roN)&)A)}-=RJKGmR-Tv7q`7 z0?uRp2@OJq0U-ItEcx2!j(Pz+T}%$#vW_~T^6$p}l8+JV>5hv3+J|5J5r1o^&RIo! z{QBD1A9d`Lb`ta?HxhFrRjz6k#;T~3WgBJt?=%^#&xRiIjKyp}#WDsbT?AOtl0)o3 z4NeG&H~}BZ!Sr;}k$zka8Pj$Zr@Y#bp`)(0G?vEZ12sQH9S?uZiE3vb#T}Evw&s^KJLw z{sVXO=1oNcJP`SsbwXR5f>h&D{G7#jL^r7Sp$78me~K7^CTz*a_k?KdmkBp2)epRP z9@^J?1`&M`ui6FjFt>quNLJWKVy64pG3pn`=7VfArB1g z>Zs-UgZdGd01S%qWAR)%x$o#OKF|Ep9Uo!cUt@5vmi1fje&Bxio$tE$WdJyI;D81n z47OgFgh=+sntwi71+Vz9dj6poXfdfWE2PNT;2o*M%&0v8Mcn8Q2+pWkHE7I6+%Mft$p{pQoEv2>z zG@Mk=$fm6~BKZ4h zL(6@nSD^IcsrTOh@a*iqi~fQkgJm9hOP+y|w!#vE@V?VH_xpD=0Q8&UWA;2cz7Kst zU}OLpH?32lc^rhvmrtB<|MlH=uX3*Fr@8t`rsYl5oL=?*8UHoU>t)d zfKQR$0L&-=EO+Vm*ct?VGM|Z~;ii@ZC^?`d1e~8T=S!u<+i#tud3&xAKvYMzp$uD2 zfH0L1Q510SnE1aYx6!NgNyudM8=_bD?b+j&m-n~}moB@j*RN)Hy+dQZXBx{2r$+P} z9H9?#6>~P9tioyk33kHwgI1VvhaCC-d}*y+8H|K2iKxo-`@h+aEU# z2-#1AHv$`5{VB<|DgQc=QNT0#+%oZ#XaLA~VewRVDL{w!ojy6xTL(OrvFDE-b$_RX zf8Ublf8YMSt-=CKR$)GEm4BKv#Tc&9T^xGbns7rdG6M&bOb&+wx1jt3Py$E1{?#kD z-I})dd-Ts=SonY0z5czWX_s1;u)^9eZ^X)ZFWY<+0_nOE)v@(o-UPVKrpNX+%PW#2 zOpFpy5NIY*s@V3=BBZ+8H#^a)!PCXtQL4%oabXId`T-%_WfHhL(X}ksE$koTPI8jj z7I5O7Qld%zN0f>+$Gp|=4-eE3@pvUB&K#T&*e9hFpbU(CQBpul3@B%U?bbA}tpbS( zpaixPA^}j4jUU<7<{z{+>e{#H&k};6EA$(|oh6@zIVU)u61XATE-lQvX(b1|dhMFK zbMKBRjEO3#!+a$-RdJ|)2EL4J)o%Xqr1dhAbszrdJ=j+c1;YG9+kVMf*vTWQ{D)(% z1%p9oH@|-ZkLACS>_)ObK6yrP;Gk{4tgUIS$ID9*NxJgM56NF6?$sW z`w(B7ox9OwdD{>_#4|aid z`hE;Co_1QSXNuXeBMn=l?Wi#QZFa&U(_5YF+l6e4B z(F#i7(&Cak^~M|S)~#Fa!NZ5P9Dwia!ZQLZX3C(3|YZ#c2hc;t}x1A0{ zf<1DDVN~%E#X(`;d`~ZE=oNj|`V;a`jsl;jAnWPgX@qfs2t5qZS+xdxE*by^OaHNW zdJqg@gbZAYSAaG&n3eQTd#Aqm{4w|8JMXzC9(&A~L6a~VH6&Ug?gO7bOgR z-SP+QpwAHm6+y=Jk1exha#uhNx!|n3C0y*_8OX zMeAw2H=So5rG6KTOlqY+1$gl=DXF#Lh5^8E&^eROtU@L%^r=Jp54yKx1o--!(?-Xy z&zv?zGV2EV>7aRg7F^vvN0$gj0K}km83~CpX%&e)T}`|vDoK5Y>*x}`t~Vkyx>uKh z46p=c--rC`o$KX?Ynva5VFIToJDmCB&2#8}yh!7>j)SCjI@x@HgNm;n}RoX$}THa6Mc1 zuL;=moF<;KLT;SD=~n*jYq$E_vu@+UZC6_vZsot6ugwdM7A9tvTsr5@%7*&unuOx4 z?P7M;@_61&2w_bMX#Rve8gzk0Oi8F|X3n}a ze;ti3(RD0LNybCl#X_4H=yvk)5ef9L=DJ}ID}s2jPbZX6{3kT7%3xgU1Z&@~Lwb?} zYDoaw30J14TxCvq0dUTQfzNl4#TakMHZ}+o(1vkH(!5O<6ZOh?2ri9DMnAwc?8ek# z$k4auCEqSAEJ%6X?JiutY&!$wp*cw2o(EH}&V#GRmvL-F&V3Z*_aXRRAm%sS%$H_o z3*9VSDf-=bz4aTK;r`z9k3$H4lV5z;KL(02ZTXi4Z!`d8Q6--18U?b(i688KyFd0y z{lWJ>(VnRvx#cBUltZz}LqCWZn5rwt;ad2H`QjZ9p8s}ZGTc*^ZzNKT8*r`=N|uBT zT(G9Eh{;*D|Kvq6z*U9)pSj^4{r4}-0I+dIp8sToKx_4(`(?^SNuX|aE6><>lEs?w zfH{F>1z3`sZI=YuAq0a7+DB3V{0LK2EQ64LpZHJ0H$u~Z1!#l7JYf~<)Aa4K6@xL* zYP(Wl*^uf5Jayj0c*>f$d-2Gvw}~-F1EB}+k?}O4`Ise%M6|?`K>>#M{Z&`Ft}X2p zX;>jI6FC8Z_&L`{O;<&zjQLQls2IBzM|&iFa1&ifcfmJ=i3} zeC*I6w_lF0hmTg=>2u#G^gYjMBhFQ<+4c_|nyVVy>MMLG@S%1E9u@M>m|Rt7THcTV zi`YK#{IgN`S>LZMwUv9TS|!VWdzgIA0iRpIM<@Wp_TgxUe>bj013)*Z7~9CCfFF;{ z7@G?rtRHH-zhC|AXYQTX-ZbUEF<=?E@BlpjAtQde2~++x7%>23KO%icNq!4A!r%D7 z=rWamA;fxrt%)+_KZRda6Y$y>SKP|KeCbwxd&bqYF0Z{svLvW)5sXHE*y4RXsczkj z^=I-9>9c*<2>bVW1xE3m^@R2v8r@(Wp7=o-mCp{KZ|O2@acXkfw*9m9g(VRz1eV4F z`#O8WR!P8W&=&S-hLRnP+eULEe4r7eNt^KhG8e{y=fCv4?iKQCkgRaGl1A)6cjz8K zWo}6h2q)#&-CMQg{FMb6BxkiOAXylA$Z00e;KwyYz|rFS0*ZJi zvcj{%$1CV8{qi4l5HDx>I)TLgJlFhnZ%E*trqUnQ`SY!g9B$3|#&`kTc>M|c4?LvY zeL5Nda$aXVy?qKWsC;Q0gcCNHv5YV5SS7{_69P&9@ciek z`!~TZ5S<5}f95MCHI8U}z2<^Voq`nNS@I!@qc%<9fVxAxg(xAJRQj+OAQrgeFq z!Icl&u0WJQqOP>N-9|JU!Q8hK1z_(_&t=OPGXKZ2a|-y-bI~#}PIlgE`C32-Vjby> z3;^t0M~eM?h7$s zfXR&U`bMv@xmhPYtZe~qhfLqDMFT+JNF5&p1qiPr%m2@R@FQ*a_ntejZ=ajc{=kjG z$U*Tp&p%ex#+Bv)BiIZ8?FVP`hLRkC8)5&!sNuOp6FAVZ_5R=omug<(xORWVt^VhE zxB72ix%Drvx#}%>{>?g>s22}=Mgg&PwOun05uu_TW~59w5U|%et|803HJ;o=0a@#r z+zU+9n{FMl69`4@qdqmIwf`n$W--RWA?3%0VAA^_!X?SN1_xE@$r1v6Hs*5CE(PUa zelov^KHd;87!Zm+)lZuOSO>n}6}>aR9tv47?6xSd`M1O?401fzD8&yH4IrmYWqH=` z4v-vT)Z>HmgE@4QlB{#-`cy4b(b%y32iKJd@%(M)_4vgSPJyuQe>;&~ouM7! z8cbz4Ewn`-xdGg)!2wsZVtk~`c`aQ3u5Euap6t?=F{!@Q>cCL9`jBp+f4;xCq-;oFScCs|I*t5G` zD;fa0P0=xK&-1N3=H=WTDgUpYc-j5x$3Jx^kH2KhuR$(GG72(#%VGq`8rgXIhS~Ek zCT7;}G!q9e3;UNPJ?;4yfofWFX3GCRe(KggzvQY96n2+8Ni--L4|+fWKb34f7l1@- zy;^SoU=oUDhZGPk*+#J&dzJ4HlAhm=VvX`YrL5bmPcCbbNF=rmbE{Zi$3Ro20(|gI z6h1r7nq$fYaGzj-9Wjpb6^}jvvW3Uk`&0dHECtw2=&lU&B-o5)n-oBXF(ZHs0XR9> z9Y8q&S`0L>1M6%Whb)6kkH7Fc><&Vs^_JyRW(*OWK07*naRQKag ze(2tL<4rd|SKscBYH&2>UQ_;ckj3DbVDS9&Jv2p#z_1AW56XWUcJRT1&Q>xQHxk7X zo00JEuk!r=w@=;brBohPDfr|i9@F->B zFiz)e{4k#6{U5GUpAYo46R$mM{--pqY)j1&%`wCEF6z)Czi5j+g9G1YzPv`M)gX|7(X+Wc;Wr zt69~P=ZHQb4;C`=zXb7z`i>FB-Q)ZQsJE@sZ2>XZt?`f|{{=y)@2U_)l z9yXuZGn61yksUtVKNtXl$A5r*bcgRb8US`kz;VC5$)vKm6@zkicE)}C-4ERlzVjXT z&YN$!C0RQgqm>F6&samn+W)LkY|6hl?64{S7&R>6pYY0>4C)h%830nap=7eP-|C+( z*fxJ_f4%4v(2TgkGc4oDCLAeeUXj3Nl|Km}T(g}_Y1c*%3(@OvqWoLTe*GHlv_ zt1dMu;c!Ol{wKBme809LeC`-N>0rt#pHUa=49gpH)p8xh|c$taw zs#gu8HdN@tx9~7$%T+x8O7_Q^|4znxj2f21Nk#yoAJ`%h`L94Bf!w}5dt?OI<8I%* z(a(XxNGQ}QIkPzpr9JSQ3F>a658UEpe!gow9<`oP@@BD8`(Sp z2kk6Oe^@MRX|84E#3k%_a0w);m1X?0q$#@GlHhHAO6wd8XIS@90}_)oya-wlM%ex2 z&Tatq!gc=^&5o!4sZY+t^D;Ca*+}0!Efqg!PtZvq-Y6rU%1DjtiyF%aq0-#*2bqj4 z;b?TnHZ^Asnb@n1%)slk>MTrezhV(2#+jAd6JToPrV31oVGSo@t0iR=S426(Y8#xr z(vRkAqE#iJ3N*4C-K9(uWJ(WJjo{r@22_#MW5Y5@#xDK1U|F;i4-nMT$4Z<9CqjtM#XAfBHLF=fVp$;#Q^U*FNf6Pv?WVytpl5Z34Cu{C{m8H#T_ek)^ok>c#8SxwzsL-lc?H( zur!$caxOIg&W>2;G~yYZT9vK3Vad1J#YqtTB=+YNz@i8di;^*!u1Y?nt_qM?gM@1JF#Byic6d)Y(&+qk3)NBh?$^}cRJqsm*PEHWWR=gys9Ni923B814uOpL zqzoKcFW`C9to7B;ITNA6MiNFqGv*Fw04(rAf3Co(Hg>>gS})||)O^bMM!=BXozt?t zSD{>c%^=ke-YxgYR-AtYsmap{tqdJ^?4m`KE7H)iE6&I&tiq1?Pt|7QJ88fi~1WXco$V=8f=O(4POd6ed368+ZU% z2$$;=18YZs2@U7|_Q(V?+ZpTG*FNJE9-RUIG&E2;ooQW#w8_}C+xkR4fj}EyFlUXg z-iQhBoUQj9oNW%ApRM&cTfcd}E3URPrygjTVSKzSdHFtZK9K(#@auXk@TEx|pHXAb z0YH99w#IoQK0QT7V`p!-rpnv93){{HADf9?#$5p%xDDARJJpaPm2&SvAF9E#BI)Sk zpdw3lH8bTs5U#K64c%Dtr+O!FP;Gb-MdJp{5M6+@m~d zOnwGwAR<)VZW&?1I)d()RESs!s~tr9ub>(x4iTK@G1Gi&Uiy)hkS>cx zCzRhV7FeOC3bS+-H$lylrW4N+C5-w?mY66 z-_c$ug)FYNRN#*W3xu?8eW}eCWkD$rH+sH5(dGO8ttaFKT^17}4J%3XN?;G96(?aKhJ&ve5sy7pns=~)wSQ0MaHzMV( z8P+He>WfXt^^+GJ6Nc_i1Yu6Q6*6)<;P`LOdmV+uZ?%(DE9O489ro+v@D~+l*xsXk ztQ1!+u~ZkENmI(7?q&rtg+a5;nLyXFhn%a1?vQl~G!*elnil*@{kP)isCYL_u?%Qw zqOoYGD(*$jE-2@4L`vXl)giNKfQ%VwylvAeDKd0#$5Fv#seS8p_Rxd$Bdr8Y1j8=K z|2*w@mpLvLe#JIdc8y1jDP<)kkMh3K7R6BCEYyZ6R6Xx0!XuYimR?xkG(SwryE~U$ zE8XohwOzkIW9g`Y>WWN+y%f+jodNcdQ~J9WJtu;a#U#wjZcmCCIc+X-PE(hH&e1S) zeqBFuor<(f{249OrTa(AJL1Fg7sl4YpiAcaGn4b~^WYm---|)%{EWJHBE()<%+U0! zq6)TQq069a3E~P#{x>)PiVsU3-#FpY!!K`!EwHvGmEK|-txZjyyL7e&LX5Pd7%}9 z#TrF=+jV#bA>5Mu@4Xh;paDqn{(w4bD#-eRpCnO{7;uw)ZXU3Jk;Dv1?$hj>_#!PU zmq_s;^X2=|{#<-jpcJs3Zy_%rt4r^WgW(fT1wJF|CH)$0#0O(Z9NvU%XYcEcD^d%^ zM=)Dw#$kg`bR^j2cJWseJ-N{pdzGgALwD}qRb-8EjKOw)iKv0CK3yDyj4mCr3yk=$xFJ6U6c!a;N9_C9?pL_>AM$nw2cXy z0n=;*J=`@pz4Og09oH~fJ{fJ+8|g>;6Qv_8=M^Y(&N@g1HR2c`Xp*YZ}WOAl*E<5FgNoevY9s8^e~%+=E!CRLrVTg zry-qZ4U>8cNXpFliHtdLU8@~l5D7hyn6}6SN=Y_AbO1S4Ri&b%XuDJ2IG#>#mRrs? zpgxZPhneeD4fUVLsYqIMlmIIbn;ejr59O@)k#vb7;b`?7eAo{^JvU^TrPu>HsQus* z7X*C9wu^$zGHw59e=7-pmtQ14Dma8W4H^eMROE^}7!H9l;Bzh&&RG|l&M@v1_?LwO zM*$hFqzXaQ-YuTI?DC6^P&A`T2+bjN1~nv#0&sD4)XA?g`16S%8W57;55_0>T*`+$ z_<1(G++X(!rl~ploE+*R5huyF8wIQ5mj>H?$l$$sz47ZNgEB@|<4)*7!58u;=}pzA ziM@yQ99+NbUag%n^`(~;IOa|dqhNd9A?{CtS&Hqu`&K3?OyGK20zf5=RlNfftMQH-9!>BK4&Bl%aj5M{eP)I8`aE^yrJRpH% ztk5jwBM@zX9`t+K&W8TS6;_gH$u3mMtU!LV&%_S@Jxa>*&*-P@GfCT@iU~zH#av8HMZ|j7cc3Tw0ofM$5hiyLNs{(9CH1heV@>W)T?u@+==wCq(rhv{MKm|wgtRD8KR8h) z)eeMnAePhG7lT_EzmRapo+*~jN1(}Me83-GQEs73nQGM%`qV1lF*@3#!4pGB5|Iq6 zK;BEZi<6JtTIFYp&R0ldDZE(r%x887pn}-JjpFg%^R-Y`UU@F$NC*DP}t77HU<8$l>u@b=?CnP)Sl>mjy6X8S{@1}kF zb>Li`f&gv6^~dWhqOCufoM+K#NIol+jZs;BKiJzP=Kg#8m4pOCf&Fz&+H`6YGLLiV znq6>R3b?KEHA8tn?9b#CooltD+w<)+}iXLm>X3{Lvt0)z8e% z*DFQ5-HPz5D3j+G6}5*0<(9eui~w79vk)oUy=pxGE*}zi`bP};y`X^RSKgfqJTZVp z#jfJoln3|M?a(sNBpVou0Ps5bgLW09`unJbe*!Jz-q0TibvFcq81J5}4i}#1IOi-G|r1+3LsF(3$@DIEa0T9FL4{Bjm z>SK0ooGK4h1zBrv>K<9IT4|%~CjeUX{B+7V*sQ1-mny-&o+00VO-*(ojA$o=&>lBi zJ%x<5@R!v8oiIKuIb^953|Dr*w9`)`*39DFQESCD&Hj7j`!Pk@9_aHO2ac#(GoM>9 z0{L<6(V`{GB7W=DA)Tc7EhZ&CCptESJTKKy{8BXpb2i>+^t`Qah8B5p!t1qo+Mle8 zmJQoAfo8I?cA``~Z1CHQkx%zq!ZHqCfgM~Ar;^*Yo0O6L>!<0COJ^wNs+6oSzN@8U zW=&0vW!Urr?_2&%%fP_=?Y2TLao5wfr)$wbo9ueNm7^|z$`9RcX%;~M-_IoNZPznG zT1bS7OxyZOdhidk zslKPAv%CDgUI9?)gR0&TPywG@bF1I@bY@h%u+o?KCa5mCVQ>7!v;$m_j_y|c7S*Sf z;?LDS{O@Md=H`r9@1HYq z23Q9En6v>m(+SFVtufcvXF}QVKXl@no;d@ws~4*$#lEdOKPvE&{K^+Y@3fOCNu$AZ zwwD6a{PCT{_Ia^h)dqU5JTx$8EeIC)y2xn8*|I_sD)+hY(q12rbz60J+1SkZQ;qZS z+5YM3S$7t4Yy8naCiBSloqInWx}_-y(er0-6RcKit~C(}i5)Y)vz+Ix_4~E-WC^#} znmpVEiZ^J$p^oNpEq_=Pz#;M!_UD05dYQ7J`>Lq-)v!T$Zre1E2?U<_Q3rP3&}zFj zwz}g9#M+&FMm;+TAh~W=4qF!r5em!qvK$8iI?(gF{nQj4CH(lUs8AI*ug%Yak-i#p zL}h3r732lc4Wtqq7bH^!PvC*32=!qIzgABFZ+AezrFe<}qhS5dOZJTu`aPYO^jmmY zXRS5UM&*;e`gkq#_VW&^!Sj1n24<>dKx;oI*CWe2p{vZj2u!jg;+f~Ruf)q-?kalk zS;Tner#2C=>Qww7xVV-9u;xl3@max8d3$#MlFRlTA`csQkl3;IyyEC=r+}6&{$)PN zm!{Q`&y3O;>jy??QwVf zO3N*h2~^%JKC8|?PiIa=1qMsAv-7Zha_j1S!*vXn;26ce+Kcj5OrcQy{Z>=g!M#6k>C?+XEaj}*xl&*uJkT z3TGRozb(4t)G5{oa{zocrH?^+e4VD%&6*^B21_o2jt5^%^|a#@GkcO8?~L@yw$G~L z(Y?mr|9Q_xEW$cIQ1`s(o0lH#0uQRv`sO&1Kd;H#r3kSjiVp$!Ad z%+&wIJz70K6~*f+*R{`lE)Rd@x>T*C zm^;#UK7Rp3jta+lJ3zNrOZgwu*?M=Guq#$oS!zt{Szmg1_Gl^U)S^s*hCe}!Wijot zwY%~YEEdtc>P}rtf5X#|G=6o?9!zzL`MTvJcCoq6(%+8|vZs_w2?3bC5+sXpkTcC2 z8GrDC?;!;5&F8zHXQSVT+adMRqFn7K$8kVF#XD2{lNgqrfh}}eVmRWhe;F3mF!`ec0JW*OI^YUk zVCq5>+w(_3SFC)kX}S^oq$EZ30qdhN_cZUST#=l~<(08SviXogRkldf5*#U7_>I05 zGVrcfq@0>N!#qVZ>-e-t-Wk*X{q@D}r~x$*psVt_r?uDR=HY5m*v&O~-aR)zzRZpy zl`BgrObT&qFfu-GdiUowSn;L)tqVBa4%syOW^YqhXO;Dqt8Em&vC(odm*&n=LVQ@!sBdweSk^2@nfonwMfu_zdA*O zlh$Ya_GDjQneA%1F+A4DH%9;RTY_?3s&vbVZowfOk+1! z2*p}eJpV1~8#N}p@4dn5?e{zAPcy{xcPQZ1LF$J(kB{dAG? zoR25zrAZS~I)AN+%{FexZ}jqohmTogwz2j(28ccail?BO&H78*3`MW3InNuKT+lTC zSTpc$XF+m+FcbdCoV;w4O;l};4Q{kv&BorjDCrb`r=Z!GZ7C~5?H5(@KI84ML2}(# zM(&4=v8)Q+;h51Rx=Pv>;b&1=S8T&>VLV;~xj3D%iUo#qjMKGzip}-I=b7IR!#M$P z{?5E6k`eq;h`}DL7fD_~~fLZ}DnIQ-b{9^!EkHFa@@T9qAZE~o5_O|3};QyV|k!vuHpdv(s$iD=2ct0tzEPH<3V}} zoO7Y2{^bBvCjN<(N2V+_BTAXYNGhz&MvcR;G@T7|IuW~LtqcrZse^6T)@-E6>o2Su zKFI=gJ5`x(w+QqPD?eFmId4WvWzwH@GgXGP67{KX7_a*+ zQEL+icx6K$WNaG}0Bn#1#8d^g8_y6%@RcRZB9>iM@OGe-yV`TK6MfWa?=LS9_1Ui4 zYNoL~bcNDG`yCMLDneO)n}?kiP`%mfJ!{(U)g{m5>YoIadbJ$%wvYmZfS`j9eb?&b zf$xB?G7Ox~2K%8Z5BG)7WA`Cjhye}N5&wj+60+Z185++zH&>(BV--QvZ6)w&$oX9E zk%Ft<*;>rmys+lcOTV3H4?68PdF*Id9|s4WW`h0Ot^R>f)w&Mh)+U9cuO}$y*a@|p z3i;Qwjh%d9miM!46+FAow8K!cWai_+;GyOpp_Fc|0wo93e`pO=N5h<7l0puoGUEw;z5DCBiKHrR9*vlNbVB4!59+ixj7x8S<)!pAW!ZUNRvV z?AkC?JH5n%=rE*DmNGl%PLE7G)<$T6u^hHrZq&YhyNJ078Y=;>s7-WXQ~w^JA^W}3x$vCHE;ygO{V`UK3r`$IJ)JB<9#b$ z(qn3EoT6d`R9r`{QLN0M$vP^I%N!`))?FZfY>Qdv`0$%k zI{?4FN$T|kYpB{H#e@4(#Xb_mj2yfk9?RWF36b6>qiKtPxMHhMl<=cPuuHQ#w5yK8 ziO~!eV1mg%1t^Idg8-cSV-5HWpoSlza(Qp#p1R-Vd!o;-tm8VdYIxVa{Uqv(ijGGO>mMQ;wG6X4AIUARc(k}~03 zwT>SW__1RxOx@J@@|=aXYl{Y$_uy3Lk07uh2OPKb)!AT1JW!%=*HTyK4nq+)-Q{6} zU1{OX8$U#PTZ4+~0=f^)zF*u)BY%$^T|<`4nh<2?Wo!a>Tm@x}sjF3zX>x<}6)+RY zo}}6$y8uJ)Ep`k*-M1SKc#M{5yeBp&d|4;vO|gA1)YjPe0)>_3uS;Lfg*L}YX{c)M z;*rKNwYqL+=2Hf203NC;I5|mg4hF9FELJU?E=2$j-6Da%^IEEyf z!N(bh9aE_MS^n4hLf>WrCjk~Z+B=_i02Q0$ZqEloM|dZIhuiPfI2xjey#I$rF6$XU zfvC!UU+=Y+GQ7UK4OG~23vUx>c(WR5DE`|OtC%j*d#v3p$e}(2ng~ zCh27cL<2t;r*gH=qjSaD51+az{sRSbS#ne0t)_{t`N8vgv0K15%N0wkQX6$?n97tn z6-WoJ82oy#SaV>x;zwQ&hnQ-6-fWP#gaejRQ^UG`S}$+IBi_$Z8%yyOI#=ZKtZMzc z(XtR5WmQb#?|CG3EBkjaGeUvn1yl7NvN~21g%`$XTyK!xEB^r*|(l;!K`T(oCSxcO4=BeAFHc+I8=+UCJ2_i{_1v9X!6@ygv-c}X)7bp)|_ zP5gqPo~ub{jX^j)eoH;tE^C#K$REE+u1c?O&6aO)KXiv#53KN6*Eqa0(rjI6IDj17 z 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

?+l7 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�Kv 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 ihBCBv)8__?8cC&XLrk0D^;$L%D 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�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 diff --git a/osu.iOS/osu.iOS.csproj b/osu.iOS/osu.iOS.csproj index 9df3ad5516..12505e73e4 100644 --- a/osu.iOS/osu.iOS.csproj +++ b/osu.iOS/osu.iOS.csproj @@ -10,25 +10,6 @@ osu.iOS - - - - - - - - - - - - - - - - - - - @@ -79,6 +60,28 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 23419a0ec3f15d821d33e041f536236951adebd3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2019 18:06:36 +0900 Subject: [PATCH 0199/2815] Add drop shadow --- assets/lazer.png | Bin 77579 -> 191397 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/lazer.png b/assets/lazer.png index 19b5fc2151e570bf321596998437072943422c26..1e40e844ccb4f6065bbdb28a591af35ed78ada35 100644 GIT binary patch literal 191397 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

grX5kAnI7=yXit`@W>Pbv5O3$W*%9No zOf?v%kC^-4mJ~@K4jK_b>u}86;Qd1i=Vk>Q>Uq&^0~ngP*KNR$SoA+>I|q*+U>1J( z9nVWbIa_3CTk<{E{|@`mmvOLo{59wOg$9VtM%{1pW`3E#G7s19aKKEv+nwm;XmYE$ z^_zZ%15SqWPu0P0x{aAD)Ev#`U=AWfMc$FruW@rab`%*%0 zi`(+J(N%_>k|iXOv9X-K5Y-p!*G$+uL6jSK43>PS8rKy`QjS&Aql4y&9_Siuy0>o_ zbah*XmnX9E5nN-|-O(=qko|T<8TK3eAYnZ69v$56hr|~^{TVa+-Q}V2)#N=MEpOn= zu5*#x>FYbdnEM+WJ)32)O*%9O+cA>QKrpH$S3}MnV_ew4>brw#(%@R(3SiI&FPV$J` zKTmP=-mO@geEDO;=xEmu;=7ee2h#5efB}35dFgDclqRT-LkVRD0p?Y7jCY)R+Q6w| zW=NioNoE=Ulw0DA9aNFROWB9oIX^KWtX>cWttb~g32U7VUAzW0E;bH%3+M9&$gs;| z>X$lV>ld_CCFkt)IF3{|S(7e|wbNO0k|2ul^`8t?7zQW&KK4#z5L9To+;V1{u0k2I zwUus8?yBeCCBAvg0y|`wF2I3hqS{mN!$oDk#I)Q?HGk9`$BtsL#`usNgzute^>UQ! z+t8pG-Yi5bvwiD_84)y=<-*Y#y&{!OX5fKuvLh7#YO|hJ^Y#ElQ!K<|*Of$%g?n>A zIIXA^|0d#CAwU~_>P79Z(ZkQmqykCrHS}by_N`nUYggUntzwe3=+nr5p|3E136vNi z#_9~Xp!Qm|t#*rE>?HX4@ye=au99J0g+vni^*J93t2UD)_7qpzu>x(Tl^m)~W*Ccn z0rGNDLPFQiPs-P#0V{34TGe~*E5Iswa8tfiZ_)=%j3_G+;QPJ*NQCL`P#Ut|!~YX} z)*nN5@oXOtl+iRF-K|zc>^$gIBDOK`IOOVaF=Z3R>C+(vpSK}zA%xGG7rVwYL%;AOGnITX^GcVTL&Sot#58i7cATJ$d6k#M*D6ehHG!j<%5$6R;z zc?h=|J`ZU8<>FfvBbw9Q#4Te*!%u9cld7!I$r@UURyd900AaLmS?_0BG9r8jnVX#p z=2>{odYth?tS0tb>9WT!k|-&Ru9`3uA^_zydI%))8!XZPvR}NH`8FPh~hzLL30=e2gk4N?u(T3}aw=k1|I z?;WgX{dvvnLJOAu>sc;pWGggI2 zYm~UDsVqE#7wyXcez_7_Lid$q^DdwmduAMqr?tRg4(lWVi-)OTngB>UW!Lp4iqV(K zYoVYuVZr%x10CpMDu>K8^jDB_I;ZNHzP9hnFiF8UtX?zBKiQ9&ib*^|gcJiJ6?uxq z)%N4lh`gD=X?S+wU?iqSY;xI+y1vCDLmTCZ9bFB%&bgq@M>5bu{sO_nHjBs;W9epT zqO+~6@;XIwe&~o$$|n_~d-hg`eIg3&-n;AS%_mk(mCo@73{anh!l%Rp+3Om;eCUcz z?lwgjXc9e5&=tu-5%y_8CjZK?nF?8BqvVB>8O0~8@yQy*{DtTjxRjqt!@lFct1wc1 zb7A;?>!h<;0kd3)31H{InE)sSpWnC{v#tJ$n(^d~Ip@6dc4imHeGhCpxdTHbxY5|0 zhx1^od}5yAt!%`iXIQTZ376B~^)g|6YbyCbMk^4d|B13^Gn#~;$iQBU*ZQ+zYkL+@ zYw4HNYS67gBR*787kjcKtdDD=?V$GK87#U5=N*uo_`L&a?6-ueZnQFHlnFF$vR1uJ=5xpW>ZQL2RdiSXf*YnM z$K1lk(xA+q@;p6dq2Tkg7Tq*11M_F$c`wOdfgSG zOJ5g}73i+C3MLu6uWw2CtpmNgU+d9B{~PzQKI5PUh3LvV96BTJ#C3c&bjbIXf=Qj#VGZY3@nQ|5TFd zz55)`r&N~eup^vWR>-ktLEX62@XeUW)0%#|Ileg8RdH`EE8<7E%5i|y7q`y(WA+^@KM2{mT+&uj+AWa+GM~nM4FtYy%5Ewvf#i{4C@>{3{_qGa15-AAU7_ z0||~c^4Y%m9<&xRyh`S?yYx%iHJ^B1hPJyNLsR<8H#k0Akmz1dxhv~YU?yjsczQX` z)Weu9gtwMp#Qoe;)m>*R$BpM98-sV#(7A8zHI5}0FT!!oJ1Svx5uXREqglf`mIH(= z-7hodNgGUg<=QCnIe&0HW+ooyFAdXr>l8TNCWE5;-=r1X09vwOMqM-btgUEE=gj5Z^IOVt?5} z@hx!(>_dwduBXo+Z*MkEG@8LwCL83W;&C{+r@M!~VRCD` zRlf$qhxU=DZM%Nm^I@t7RkSFOI(;0*zY;cKDi<(Ol?>4H9SOo19kbA{B{FsGM;-e{ zt*%}ukBR&EIbLvHNb6)oolIzYYuMo%F-@s2Ho^*Ipln2z`S;5%+K<`5bxgLWfm-^m z3_h1atAKSBWE5xz7w3}di4y0A=_g@?ncdQ&1N~qW`aIg0j48TNj4n^?V&qWn+J-%v ztf3vagJFbOG5O6=&vCBU_>29~o#W2yHO$x;+?Ia{84naDYC zFoj+!HBfcI4z6Xqou3U%4Q&yVaxqSwa?ZtpmtWmlWmrCHR+zcVxo^#WR7gKpdCv3& zxMs#viaU5yNLhoK`j|@TMygon74X3r&V^O@6p%)7@4H$Z+q=Z9vnm?N52i&jmYI+$ za__s=;)26=*osvNfbAIrCM7UXR#H-xs>&E3@i3#IlYZLF6 z97;&xi%Of=q0RfGXIJIvWTW?ok)lWK`|u~ZfoJ-1nuQ-9=J&%yX9RHw)Ab}UFHLfm|k)Z`_WQJB5xb{pYb%UWpxuOu5xp^rfRKN}SA$oc$RJ99(ei{Mb?zd6F= zk*5U<{GN5_7G!V~mA}p$vvO~)znxwaBIc}P6v|X+y!t+$BbZ6$ug`I>$;K$G8D!fk zsykX0Q8HZ}OVm!FBFzWd{HPZucF-{?cH_nb8`>qX1!u*G&^~{P%JQ*17g*wASrpK;Bk!UOQpTlG{z7=h639H z&dAtX9GyZHV4q2+dvvK%vKvlif3J_ANq;H&I|5w#%C+Tb9+W_po6UglL%BRx(a6)8 z`$V=RmHTAAX5dF3Kt|GuUls})(_c{(j0A4R61ED!cSU{sb73=)z5D8$v)ht0m|({l z6+h1q#AgR9sdv36w@d26fE8+p=?IUOe)=4`;Ob2A&~8dDx3dn`m7*n4%M*L!T0yVZ zS9L`-dw*$Sqp@?6iFiAiOQs1(SYN?^|By%xnlJ*cdt%g<7*R+1e&JJeF;@vO#ZnAo z_p;L`o-`#Qtq`ry>M!&`n8`fbwETJ83r^^)EolYxjX5^wfjjAshE=R1xkHEAeD7Un zJ6sO(5U`Uq1zO)5)A?2JO&mxlf+h(g`ZoHO#}SCU{`UJa&=>1dI%4#EKR0)Y7!~C#@4eqi)&Y%im^|2 z!+9&fzp#~@i^&ym6Aw{A~I z#s_f&D;!2?OTF&W-I#A%1(_!drS`U8c3Xj-6O-SB3i-{rXEh>Tp9*> z7?XU><1U^-Y`rNZQ(5sd5R6~{(^DhPPXg93o!eomZ zyUErl>W`!rrews%Q+l0!Zr<4W{!$xl6#R&uYfMA{B3J1>SIF&-c_`aFEvG;v*jyFh zjl5ck+%4!QB9Ip=-D?`9;T6?QO+UUVr6+Y=OpmxZ&V9NC>SUgYyk9VeqW{dc!r$d< zYs7{wdO_dZg}hh2Xxx)|uK%WQLEXjq@mEMSV~xtymRk^EB^0=I&W=(Qj5+kl!Jfw8Wla3TsbLdDkwQEwPSo1-H>*%AMm&n@iqn<_$^jx=6Hy2VvlVr`G5*vPj+ zDAmU7gMfK7YvWxtSO$z-ibXZ`~Dm*tlo2( zYj5r!%3xhR*>$w9pNjv3j^(eDv;+efG!vj35_p}c9^$_jA%iDzApf%bu%a@p5kA+8 zzX30`w3;}^ixFI|d%KW0H~LP`&E;M5{C{! z%w~|+N8z~Qn|Ucpw~pu~p+-vBc&Fw)F&RP@^Y(XE&t`=e8bVRoVLQ%RyXYI1^jnv+ zvZCe>v?3PLp1$ls;FO3PD{9Sar35w?d~wUU=m5$;0`G}g=ea%EMINWr&p)Ui4J(bG zeaTB$e_xo$_*zYCNzjxOBM|bDEpgE5;wMuQjo;O0uj6U>ksJw|->ZpXm{x`%=vz~W(_RqE>T~vCU|uyw zRR7CNL%NpkSS<+0N@27=$#5qDJ1B2G0^%N)$f;@E(E~puer>~EtRqG z+GpNOMbDQPlCT6AyP{}EkOV!1c*&Nj<Xqv_U_oT#vvzYgK{AOCZ9>v*ntTUu9h0j$NkDvh>(Ts62Dt1=2(slz>!*Ck&Y(g>+wZ=8#f~$lwHLKj4C`3>GMo^d zxCY7B#Bx0J8=x%6(A1F=9i0*^R5A+~T?{g1aN{WZIZDTNx#7-?yH~D&rr5pwPGf1* zLHh|iTs|z{4tq6{_XBfdXl!9t=3X^hu0XsqCywIa<}#ZdJO1v94%>Ye#0i|tR2F)Z zcO8YW>=*ItxxA+EYW5j3S&M0p4#5fmSRl2_x9|iH8`{dfchY@jH*fAUR%|_#(49v3 zGRTX5oo|7VuYdYj3o_$%KS-%_p)b9CZH(%s>cDnB_pUl7)UZ=G%}u~ph;E(XGkB_S z2QEBK_Xu&25@B7ZL{=qO!&#{i6lJmLCoq1>vSuP+*@y!0x#u({0qLfm$BFK zGg{9FId)$-k#uXb1+&itGkd!Nyt2R1vQd5|Zg!a6YZ8XsEWfiaRqfnr>13HOXbBD& zWr1~*3KR8Ga01@{h307J;cO8%tz18Kk+{fzXD2rZ-ZFQ%6hHqNENj+2+-1~7;`{Z5 z0%*f{dTXEdfi@9?^JHmOqpCxSGk!yC1MGxZLR?=JvgF{cs? zT>gJ(`pU4V+VAU`p-Vah=@#iyNtKdrP+}0Ilu!X_W&n{EknZko$rPI{m4H zz%pUCD?9#+@Ah-ue2&6)AR@J8bp^oRIHfZ`-v3To-L?16Yq=i)EfoL9L#4bE+$L(5 z526dv9$Aq@qj?>M(!xlN>O7iJO1%fE%xoz5EU9^&m+n5*&+;6tN{d-}z~{BaKqLKK zRLu@^E+%K!SuVhIB>n!>{VqWMD<`-Bkah@@yDG|2CuH_$Gdx9_E-~q3-G||@&`vVX zrIXGtHmm1*Ghh0?2*=i4B!RO5yq(l};NFaPv4CV30#M)^yuVh?JcuMfcNdMHCY(A> z@ze)986MMpx_JK6->!Ec)=%&5KKMN~_SF}1OYwh5yDa|%Co(9o>$>!w7TnO?`!KWQ zt#`ECIOx3GhEY_&05XRS|aQctOhLk6NSd3#8= zfh;WY#gi8x_)0%V3}zI|sVsML({{yzyoii0L!iF-&M(la(ti!4bU5SrvhNpX(=gWX z`+k2+BbTIKgcYHiYh;OB8j(WoyThMcmynin&dBta#INF6t5KaV@1I=;Y`?|4G*q%sY4S{*9Eejg zWs+9Ypm=u`#aN|PY#7_}&MK5CcU03lkXAu1sNad8 zmPG5#nL;ZLNZg3=S_B@VYrA>|BXco1$lirYk#oy+e?#aWtStTM`u_$5!Pnl%OmI@= z<0g0T^%#stHVjQ{W}|~+IdeyzCJ`n?#VT2BFV}?1e54E<$)!C+xLJ`Zk1(8_6`LGUi(Sr>cj&(*9$LTl`WjP-o zruxmWA@rJYELebD>eSY>A?|v|0aP2`RsMB$Ct4$Ljl~d*%9uUgmsE@SeWKSZe*jLg zM`^wl=P;w=b%$9OV%%JI=%ifE5~TjL>B+^NIYBG<74s_?yE(S@c~$5IH#&Fe2Y$-3 z#uaD^rvGlr^JsDN(`svWaXl#8_F8NO2;X(4^^?jQZCSGdRcyNLSe*wb$H0eDW`NK-J$))l1)_j93b-N9F^U&YQedC?UkNt`p zKhI~}E@VS{;uo-6m}}V|z7^x%if6q#4acd8IDC%7bezr_{QrUOvKN-L^$i`vZ;A;| z^s!>nAkXsq4!=k;dc1xvi%hs9RelV~)`EiR%R@r%um}UZKHvO>7zk03La)03or?pDRbr~I$Ug@U>t?Fl(OwDA7kf_xC+i1vlnRlA^F_%;q zu2sS;LXm-;@xKQZd9S|9>!4CuLMP2Ps*}D12@+ z7zB2dX~`O{H@laA7zyWrG^d&S_K1NxYaHb>Lc=@H&%M%mAYWflEB#X06QAXtMb-GD z=9w-dj4L%yGuT7jvICI&$DNl~eL;c94-2YSOqCqGIH@!#ZpXpt;d<`9f?WANNPk}= zzbE7`hUob1pN7{}Z}XMZ(&HhM-uQ1A$lh=j>UZ&)41Le(kqG4t?reRpEf-s}tah+7 z-PdjTwCSUTLrO9APUcyGTY#yV3Dqr_<#ff9l%I6o+eBj%R73prSJ^3+e~|q*mu}+ zY*2QRgf#xa_(n;cwlJmK+0Y;n`Mp_W7Rk;c`uiZe>mRnrMCxM)J-l~F{6DjV|3ln) z!%+}eJjC%QWQ$_P(t9Esv54j{p!#-I7qA8q|duXc`F3i>P&?t)R@CZO4yLK-tY0S{{}LIh?s|s#@xo0hqz7Y0A4ZG(m@d9C4yGBe zof6DN%bvA!YQJeg8waX&mbdYD3g_jKQnYi}Jd?D)_yUMIu^@XY$}ps1U6+!?%c61P zAg+3KteU`S6|%ZMoTe&5!FXg;yXeYe+f;CD+?# z1)__tFEmlBdjtESnb5lP62q;P`$PO;)d%A-AA^E=a>tWgs(G%?nQe11H6+->0f6Rl zV-^_CTO^(X^R^Y8QE=V5Y5<^^2CO1KKe3Qn_vlc5x+uU<_Fzt9~Wl!C3 zrA0;ceWAkJX3eXTM|tCZTHniUQ173QsDy5vbN1FkI)g&Mn##XBf3`If)o2^YGat)2 z|JtfORP>ykE=8}!q^1ai2lSwwT2Kp3z&RD>KV>EE=PQZ{tw5bWoImRwyf=TCj5F%_ z$6~AI-?DMiD+=VFVc*aOh1?w%5^6h|pDny8{w#V7NYq`sd; z0;`roDz9pqp?`FHhYGIq-~u*}nCZipjpr#}D+WpZM)xQ&lDFQgnTa&Ru*S%rg)N5t0C?9eE}_R?KS^O=B-FeI{pl}y)KN~ zuc$6RV_NwZ8=B>e=jM#to5CG3Dbmy9j|LvhppUf5!a_ooey8a>U$Sq93Tp?*ND=GR z-twY-2I9&G+rHf}n>&E&@98UMTML}|uN}lHUPw9A3n+ej(V6gj2gWxLh82DLD-3(a z`X~(d?7`}K9DR%0+^1TVS=%ej~kN`cJR!bh5tU+O={3nO+UMD@7#NG-6WzZrMtg z_2*^{-e3xReJ+{w(3H59*DpQ;%8P?R z74C@|ML=?C=HYTGR^Z-G;)zyA)vsTV$MS!Bj|#{%$f|z+$a!C6!eZ7$iEqrd4)yXC zJPa&2uKYQ0W5>?e&6M9m%X8yu?J`5}0N+ufd zO>!S7b}lJZaGt+h4j43Kjr~A?&KNdz&F>Rh(VuS8dw$~=DeiG-#?BFVNIXkvIP|9@ zHo49VxXxBijk=^SUpEMk33A%DkUTz^>W~#B74VM<&AJ4l22B5j$${tT@s)R4JVcSa zy!|SwlBg3iGlQKk_HU*2`I{wVFbPqcZ`U97mEUKV?3E0p4}Y;LDliWASCa9TE}wFs z_TOsS>Tb4f^dS;}a2c|yJ3qlqD~`dw z$!5b{XP}n^|AAnr+ABi*AGiC-7>j0?4QgKaKWw@I4%p2;L#UwQ?XcbDm2uJllfH-)a;5Gjd4 zzO|7yv%f6i5+3tk0Fw zIcu8l<4+`Yy*2B8fzWEdK)}r)J)J@s<#V+6`&t*hlrlz%Hf%RvXngLBlfu%(LE5TU zY>VO5VvMib!^%DQb2BjJ!}I9%(Q!p=XKQow`wEhBAA=JLJmzAEqaq~$xK7N zK+TKuH+BG;@axuSv%C4&X1XRStn{O!y=TZBBONl8wBLi5gH@FJ={z?Ova+*;I1P*^ zr@_;!ZnrXr(z`+{RwqI3+)EEAWAC}STr&{y9Irz^ljsMGq;HgVVvk_%dsCH^;LL)( zgv5~XheBi5xTys$ARg0VgW51ZKSGir@;jYhB$s{8!`K3mF^C6OjAVZmYBLxY0y3Xd z{qPLW!)317RXhpNwc^%eT7k<;+j&eMr2NfxoN;rpywxSUIZ;*K_<-$IyUaRxa-eoL z=#jgu*C4z14PTmCvPD0lY2%uc{hl71&= zmZQR67%QSB4H>Fu96jlDig**BZ%)q3^5>%U@?EWj>G;MH=kGz}#6E!C; zbRC>IO7YI?)m|J2sCQM=ZMqK%`p%k0P=4ZS9=l{5LueEUoMJ3pZ^DwAEuOC~Ov&hP z5GqSChmqLy`2XRq`nJu~h@B0Ro*`A+iEI}k9zs0Fu+3;I5v<=c9Y1M;p79i83au=H ztaR$15ydyXRo`f@{uU8fDH8`Cg;(hUfdy^vSqnZtXFn|`%PW22^edEQVys@L!qE6v z$wePbJM&M2>hsgE|0KhH|UB%SQH8Z_`n*1Yt;4ajNqbDvCg3j6{T$cwim@_ z)|e<6fxqevqcN4QKX)V1G+XktSde|iNqG7tV&rnBHB)({$Eyilu2*)}?5?q-uM2WV z>@WqK+#P%kG64K(iMJoa%&5IS6Z@z?x}k6KssK6={327$s{4f=$!a;!<%i56Gu?Z} z_Avt%xCg#l2G%ggD)sfYlEYdzSo?$CXr-pX0|s{V5O*Bx-JELbo!@_qZ(~{)2khMJ zqyfdIuRA~ZQ@X$E_>fz=sJOsPy>p!g5weh2a zR-oC69|!2A>@1M80zWQ0DQkoujy5vjgq_0u1LS!_%S+_qqi?4kQaDtayZ7;LuG|k2 z$|!gQ{_;xUaS#8a6vaNiS9GjcdvI`tH5V6G*0`*(qdsOz}U-0!4V%S z$qbnMHv{8vTx0*eUHeioLm+CH5hD?}e(mwIJ#!>c*a}Nf8}jgVN8hk-%CTtJkpJ)f zdjfMML>}ZlO;18my%H4x-gk!QIkY39y4P2H((}SuJ!tc~Z{uY3w(A?f#+j)oJ zr5p#7E{;6q`A4+tN(bU)*@G%HuRsUdQs2jAl8tZ_ibhUHlx_GGfBoBZK1=&nRtJ^( za>bit=O+&~Q+M6zg|DQ5LuG+UJP4U!lk)GB+UWD>urLC~rS~fe*gt<5=G6$s-h=b@ z1VLlsUqYz?(8C=fz&l2i+dkR>^CEiP)^|Xt7F>>_9GBO@XN*;r_@kPb2&|_+ERPgh zAr3WEc!@k68KZw*;fJ=k54rOAx>GwHjq=@QZYQ>J14vDuc#BwyBf)aPk#ktqcb0!t zpD3&8+^--ir=@^KOukpc1PSt%j&ohX2nM7VERX#HE5D-U2u@OWoAJ(^oN6*Q?oOZg zXw#*3F5vSexg{QVA=`+K+P$+{N){bas&tEdRR7ZK1kM%mdgqNbj#Q|WIR9~1mR-T^ zARAZcI=U^i&vI|fvT&bOX~pJ&HJ)# zj;hhR8R%1?EV0U*Ta%E;e`^xwWQ^iwx%OHXW{rS} zD|QI%;(82p`h`P*07Ue&w7ZZ>-q_B;I)x%$wl0X7|FhNITHE2g?TMp$YVG8$s$e{;pT_B76A;YjQBB(eVJn%8Les?36ugM z<{rW26%qq+a>Q=BI1r^a=on;%A)>%YFh<5RN!L6-NNw&*apzov=I|WMg_Q|x=P=|& zznYe6CD8MDqDP`{j4caR^Pu4^Y|%?vjK=QL@;Fo|YnFF+(KYwa?dYV<*LY0{;v|QN{v)pmCNWsd>*))T~b@$GT8NrT2+^ISo z-kV`i7}q&t#QN3e$x~xZbM03CskLy6{43-)dVw9EMshKqDlnJl*hX(wn-4}aw`JNd zY8TpV?P7F}S?&D(2J2Iln9*wgrSf}9N%iLGUrs6z31?A!PA5-XXZ*yoRthG>@?mqU zcA{tQ&*?G0kK3As*?z^3lvV;120 zYeh(6a+=BnuP$~V!VT^bOImYxd4Fm5j~yQ?_CyqN`^!u2xUMjP@zoYADI~5-O_r9J z2S7!vqmAk6K1#kB*Us_$0LxXvP7TVexK&02vj5Tp6Z&JC8Sman+>J}&#a0a76ifu$ z$A9LHD$BCQ76FG>0*IH!VI%{K)170#&fTx59}ZAd0y2VWxqXZ4z345KqHDkk&=AX9 z64%Ca@H&>F>q;J3OZRE=Wl9~S8#Ehz4D`3FIahQBam(k>%Vwh|w6KPO2bE7k47k=N zxk1D~Tjgk%c7%u)(Ia}0pmXbv<6yEzV86o{pm+gDj86j~#t(UyNc9M_Ba`ua^LZ&b zYLZqdAmHy`14oZDQNMOu-rE&PPQl}S@mr&=P?Li|?Y0Zh-kde|g;4%}BsO#t8$>vE z=ZjA*q?vN&&I2(ax3o3U=)rRyV-7&tZ-nSpv%|!NiSSq(@h3I>@hj`%x9!(#o5GL` zBZ?D8e|BVlIH2;eE6(5adFSw=>M2S=X1!NiR*Xye<3YYf6|`Q(*sC0W$Z6rYWz-es zb6RYHm=C1n)x+0E3V`A{lsSM{&vChuiqKiX3S!moA`H(%j-eV=av`- ze$Bgt_O(I)hhkv~Oy$aD3yQ_WP)q9zr8MU838HFv`OX+ds954GBeB^rR|IdL{}|W_ zd$$op0V&+}#>8?R}M9r^mx$ zUk2h-Ha^nh0tDj&+VMZ}@nVYoFiY0k1dh3K>uruX{@qx3Fn@?ZN0el>-JFhAQ-TF2 z7dA)}6YKJ^SGa?58lD9sY|jnEqk$or%q5JPts(A9TowdWJ@rr@x3TW!*C;ar+h=9R z(g*_rW%O}ZSiBm|yFre|hUa&#WsUt;`pl@7&2*-fr=+lhQPY68?Qp+KBb(YFkc(AM z*GrK^*M##5a&jXrX!0e24VyTjwbEqUJQ6Wm-TLjy8|HV}{p`-;hkIP02o+!gu`&xU z?ma-9o0}{9`)+8=_50hewP5+%a6N@{%ckP3N)E0sW#)rY2net29e(ofZ)-Ne?_>8G zc(I?a1EdFM1uh|y`RC=3C%04m#S6r5psyuJUU~TR!hgea-b8B{wkbQ_iY00vSbb3| z_(h@;@E|Xh7!DnRMN>`0BZRb2r-4FZH*6~5SE`l6@y4a?BegK%?;_Qq z)%zhwx!n|z;6OlC*TU|lyYfjm&_(kih|mh4930h?*-#5kv*15L#rHL}v^Yqne_+j> zX)gT*@qSfwge{uxpeeNcZqSO*)tR{_L+;ssuKfj>>6#L63$tf|qSv`?AjqCC56JnQ zIsBCTUxLz*FRe8rC^Lvx>Ak;20+yVe7G7`u24fDkkd*USXwa$;D5B(H0zV*F83f3W zB!Pg3loYvlYjeH7JLoCi_Ic!0ggW20$fK&VKVkoOur40%_wf2G_+g2mKrGcll!81Jy3r^siPd zn99$K1uq6yO#t>0fCLTCjls!?rWqzgc!c%Z>3d0-i!}Ug<2Nb**+QdhXaV}Sx)wBL z=~-wuwBU0(BGm}KX^$a+0Ylf2b+X_YiWG=FqEC0XTrK5rxJ-UJY4Iz{D zzl|)-o#DF8=OER*tTNDP%Y12L9!h!6dCxiNqu^HW{_C{(+o{rk&fK_k74_vA|Q1Td_5oNpF z+|W}fRV^gyNQA_V!G=3!5A!UaboE_xPH`&_>#Ee5x#3fRKB9dMk= z9flP21|RFCfR(J~MH4|J%Ks8s_{wa)SVi0-CCwKC^i;Tc{JKOKSPcKGm+*SLA1*Rv z0$|eSepNuTOX?GAA9j1Y*4E5%W9An~ZLAaO^FU;&?Ho}Ujz1B2h)}4xEN+0`4Ld( zUHqEyzD3|7he`fxG=P#=9(c?3|0@i!*xE7K4PxfJ)^T*rY8bYqcqJlM`Rq^KP<4@5 z>yuK;66V=V$$i}?XO!&E5a;f}%`HYK1WyGBP7394m^huSKA2T{deb*7*5ir_FUGc) zbizTNelXbj(s8bbz|XYxO7}#F$8$Lzm-XD+eNT0c69Ms*@&mR(5Miv#Lpv-v z*K>L}=YzG*pteW*izj*)_tp`v4HLh9ac!9^!+5to#U6WC{{g`;Or|8Fu}+aOkAvc{ zG59l$H3Og0EdS~Pk-eTaFeF_bH!oZ4C}P!Rf%QD%q<57pUeE}C2!a3b;v|3?E=##P z3+2ECFuamAPaU5si`x$r!xr>?9D?duB06eSTGlRsUW5|e@Kb33IH*7(q;&V&Z&;M~ z4yMLko}JYWZiJ_~>%Y-)Dc^Nv>ZHkkS37hyg=D6O=jGD^CnD-z zfmGb&k3zg`x=z^YC>aq=By_;Y_ao#mQK*3?{zTuYmKpQs*t>zJqSgVsM}b%K7C&f# zDvE%DxZqE~{|-tBN`+mX;T-j~HfIbg$gj(c`c0YRBf;$ch|tivv`7S79M-u57Mwhe zQ(n& zu%K@^t^Rs`Q4)P77a5R9l5zd$)z;nDFMSVL{_0Ro7Y6!)PaW$eim6-eo;6^@sL2Od zvjrVB;kin98q8nB^uH0|BJ|i^^9EG*{faMyeiYRxIIsXtCkfjs!HDjYym`v%B;uU7cP<&i5ygkef6 zPS2Fby1xSSchB^BMFH$3LDqwCh^(AuZC1=<%pDk`Il3luU=Z{C19mMZ>UeA41ByoB z*^J6YSWtU(1DqQ#g4j1lm__xVs9ivq($`@)4n}oe&4{Ac|B(0}W+xIFze5mEnUKU2 z{{y!1NvIAq?A)Tl}Sd2dz7 z+OK1G*st30Pe(f|0K3VT`s>N5s8N+~Q{~3*Nh+meyCj=A4z~82@lAEjIc3v?N{iSl z3F=7m@)5Gh7_-r5D!^1$`KB>J9`AW!?GY{dM=6;6q_J~COTR-A|M z1c*{Y8F>H2Qjx84`IwR$(e}6cOd&lb%*B$03+#vLgs=q4h}B4LJa&-u$mD)8#^cOA zDzw=Le>RFD{(5*J{&t5^*R#ZIuH2%;6zchM#^ z@H3Vork~|$AARilK?Ny5KS&5_5=JV}8Ll5w@z=Tm!Z>(3-7btTRQWpd6DQA+bErS4 zPDy8n&$}lD5Zz{Wa#_a8TY|xVg8%LkzwWs8n+++oBH719?}_~Ipq*WHHrTi3W@&=; z`v6%y=gC3*e}x2ZsGDU?G~jz)Fy37BxFTfX2zl_HV&$PKi|rT=sK6Dlnvoc#VNZXDz51@0tf){1-nLJpBe!*rD7_Q0!Z?%?7F&gq}Z`eM%Dx!^z7 zdguEsM_eG4_4@XXI_~dA0yQ98IY1)?a|*Oy7w*9IKCwZB(Yfkya!j2>crK zmEzCkG_pTJ?2ozu;*4M@8zs9eH3thL{$<%D*k1jNf@MC36G2g>&C&n@mOxc9CO2i{~qLSEb(w&EqBdfI6Em6>k2GA<4YR+3j0Q0 ziL-+FDk_iI5FFIY@nrF2sd$GM;~RTR_HG8GxszViHfms>k+eT3FwnQNBaXWQpQ{DCGy%rFNxE?JcCrD}5nv zbiOrM1 z6V(G)-YEnH<0U>O@mT%Qx^#9DyV8jvMhaQ6iSt7J-~Q`Dg7qE6l?6HRISlobZu>Sp zO0pobo(}8!8hP=kL21OVYYqgel6MHU_KM+?17sgpb#{)Q?zv0e`)Dv2^dx-%RIs7w zu*~AC%@pq_ao6um=v$CL-lrQ>Q&uRXS>+BgC~lyj`M9eE%Sr=y^8=d>TL^j5&t-RH zT}ALh-A=2My;5+)xqSc}TwS%^4d63}<=#19m&|?-Ed_#dxsUbA~cpzymR9JMa1q^azFOMGDP}gnS zEMQl5^wUT{Rli=s(5qI(5Vh?n$!?FoZPf0FA&c6z5wXOx8ix_=dT&4eu7cMh`$7Xq z*kn#HQFqX>p7h`6xmlfq5PMvKgM)<86)fRg38SXK-xVLY%x?8S$_jt#f6UlE8j@LfE_m`yG+nr1gk{@QDFy%w4T~WScBJt*vtnjgd$TdG#)0z&~~fz5$CJ zfS89V^ORez_g*6zx6gaWU|)pgrWqbJKvNAf`KH-dX7_0Y+RMY(o1 z2|X4OkQ02nmSDdWOOV$1((=!-<5MWxgm|!z+vn(s{st~+*<-Hk?}AjcFf6<`E<0$X zJhYJuY9RzoWd!_R7PxPuBzu+%&PeV&$vPig^z4lt53xT4gr&po-OR|}I}LTZ9@Iie zo%#yS#Zvyjs02qe}31!ePyt*xmn!w*xSvDvqwALX^1ssf=LBsqx{uYVUlJ}wP^*)4jtGT=X0?jy+F znamM0&F`Zr@V9ms=L^UVK)!n)2M25WH5Nja2NmJhZCRR%B5P$#Kbq=9yTTfAmwc+`ik9eC6E#&#_XOBjdDBf zseMFR=LA+3&vIve(($s)N$ALe%I%di<2m~ngJ%iH4Kj`sJy2zn377`X62{l#4ZciP zVeIElm$JGbwfX!AIi5w%_ZT}9@S_16q@_j1bhq%0HR+hG$&JOs4yzgI_PNoZw=Ycm zu;Sb+G;hln-fu9+OdAARhaa*J?&9Z)2Ylj4fFw}qUpIWFxXTPVO=nk<^6abQeork5y5Aiz>CKIGI7!)O zD}}^;{G3E|1D+v>%h;fjk~`$?a%sO9kcOiSwuIHEb}@e#Yviuy>p6x7d`DF%xIAUrLJz(|@| zcE^W2)>3?8iRT2iyz4P}ON*<*mNwdDjD4SczLZP3G$OyGDxO~(;kNSBAIHfiR}h}u zru{>%Cr>cn`ELgO=a_%}8Nl3zD=kR`g}eR)J$PkCDia(LE)-O*^K`q^+-xb=LGfzP zll9zD`0b7ZBwa7{Axr*PI^_8sVOa($$^2$=4u3x^i9PpNF*YKCEm$CHuisgTy>Qj$#S?j5epTt*XYzyd=wRsv9&{d?o=y zZdIlpHDmjP>Nj2Ol1;C|woVPI@oy;2#h{x;GmF_Y?<5DulqiqRYQlQ11@3uVk^T+3 zq7d7z`I#~#2>iy$!%UV|vt+I;uQh@()4=FRUd)It)6^nj$0LoTD*+XPfKN*Y^?c(b zm0vVI_vq^kV3HRJ=_9U+NtH>4XJH@K2`(0|Jq7E)0exs-R$3W{X+y*N&@B^^yf7{N z)o%SX0JUXwjVCHgaA)P_8o~DH561QvH-j+m|F}zHUSg$hROp`Zo0(h0ajx%L`i`#L z$w}?vo$TFp4ZyVfQ@!~;i$%5Y4%j|sy2(5Yysm?1KKa91TeHlQ()q+`6P8|zQ-!q zps1y4Icg_=e)AMl4G8rv3D@gd|24?oj_8hL65sC6eG~aG`6ZCpFhmS?s%t1G3-568f{m&PA@;z|-Y^Vp3G zFWB9mbZ%!`S<$0eEuW3jd!Hgsxs>P;EYsEF~n^CMKS+ZNs8V-bv1U+wB4pxND-3i6c zMHgc{Vty}bV9sUFs)luLo5wj%N05N8u{T2etBE1+X0iFi`ad?l+hjCg>EoHB!@Ak! zKTA%$Re19w)7fK9h&fW@8e_qeGkx=TZADP_Jl8+{^PEw_#7ydn2T9jH1Pra8+;dyL z1E0XtNSYjv1XvfJzXS*Y-;GH!g6P;cVLQR!b5}46vMZ>I_h5w*ibCO&E4&lhjeqbc z0@V><^_vu0+t*EdbvJNSxK*B_qNTXd`q?s+p?}!zZ4`o#O6p8hV%6YGX0PeR?_WFL z0FoN$QzH%3nvweM%=d9P*!{ z`eMH%1zNwDdbYihtl0~F^^CF`x4Ov2EFR!P-U^|lRt8EC@chToUcba&!qE$(v-X48 zM6k0FAxcWtN48uMw{`V>I!1jJA~e{%bXDI=)W)L%4|lb9y5I6kDKt=Lcp8|E#;S1F zhgWPbBEplE80vcLdWJ!p4%Q?$t#8!0UIW>Yr4@#0DYnPbY;j4Sp%?djt=aEZ^1Sh~ z{ANaV;|=7)Gra0P-x-IZS01?03B*YHc;#F0LTMi80b-&}JploA#5N}0D_=S!3FMCJ z7t$dCThp0Dl+0D%2V|fwPckalpZbPIidJB{%V#J*RwL# zW3vek+;iPy`UHfEWrhFVL9zojMVq*jsw|=OpYbF{Fx{fcDO#)p;|p z-I5#W|89q)+Ct{&(tN0Y}D~ zQMPfOEz=XxHlxgMSL>^rhOte>U($jVHPTdWKM-V~BGJi<6A4lGKKxFg#B7$cvoc5b zf7SN`L7)B)m?l7u<5cZTq)bd`;uaATr~Zc>D}nKJN$8z>*rlO{R|KOF6Jb>hg|-LD zFs!+RwjCTm7PA}O*bUS)TGWnJ;k5&bpN}_U>Fy+{GgrM!#q11VoI7J)bgMp~iG3?2t8hTe5YWk+E=90C4LHD9}T-fh4%8WyVYL6Z_lN6-8PLu-{SGank_uIMmKO z3ZrT1apaJ{0oxL2f5L^u4V+ceA(;mjTJ!nHL3e1|yKG5N!UWYS6RbOiU~FO*7DZz1 z^qi$-tzatXfe0+ZnVsE_Zb;y@bO!*gg~gjCHFz!rF#WL_t^efHXN5D#6cBQHa1qT` z5VG(5S`{D)S{9(rtJ{GY$0^lVMbBraE=b`|dhO|c=d&Bkxhw@^k18fJU3&iSsfl?R zAIq+fkCqI$JRS&G+jVqjn)vhaF%LlrM8k85!E5X*3s4Bjn1Hpk#~VjQQ(&uZ-sT~1 zTH%%rKj)fAC@5Ntc9U2;U*;qOu359#l#6k-4jV7mWhc+N_r5)A2Apq@tpN6Z99}Z4Nw`$ptj=``AOx@x>OgYTWX`ML&#Iw&*~t$IN5c zLTXCMYvz{apzut{+3!Y2@_P@(fWDu&Vhu&FB_f(L;~DlU(0U+b$;8SsBo2tp#6yLP#q6AgPufFl-|k z1$d(5C~|T61Rsw4LZ;uOV#=6inbu4?TdN3c5#g+nday;JANTRUnPIVQgB#M@vF6>+ z!Q&Npg_4$Y$5xFToBUduU28q;D?TI$@KsIXA1SssDc-6o3Ng^O_QWKeA{GNz&{4qN zuoB37oC-6~9wQxhpK$pctbw7ozHB6h+9vCN{a_D|$|tO+*&3Bu=-YRT20jul(Sb_a8 z{r-1mynjY}E%c(`t=NJ#MFgbdpT8!-!{A1U|`Xzvr zRCHAVE8^D3bXb9Q&kIgdr_K23DJ7`Y#;uFAH7-bSj*nH%S6_x4m7+` z`tJpdsDoaClUT*>$0?kk-6@zKU+_>}s@u)M7XBYeSK-j)+eNn#lG4&$5=tXT3=k9q z1qs0*M@kEdNNkkSjes;rNC*f5(j9`NbcZyK9x%4|d-;9;!LxVIbMHC#o^x+|w6L=t z8{qK)nM>K#zpp#lnz9v(bbNE|=GrNmh2&SDy6||!4zKs$X~!5-1D(_(d7hn!-=F|4;?Flm9@T7c_O&!^ zz3zao1dVnKu#$&Do$e;wR?XDg81cd2MO3~`Ogkc&mjA6zc%Q;=lpf^Y=7E&MhS$ht z+FhFaq}Frj`cl9EYQJmVG-;U`nc6@+RStiHAyk2KIsc4+)IXQ+D!V__Vkcc9r?#6e z195D?o?^y6yuJ8oH}5xgr+je;FAD8ig*NrJ+{_gCkF-7}q4q0(0I3LC_;(Fm?=@5< zCWq$Gq))*w+XnF?o=}a!JIDqZMwBke^_pZm^YA)sCzvWPR9Q_Q{Nxf__viI)TKaL} z*d^c}dhN8F$3d9lZ6z#wn&2fH`HfT77NQ<}2!C${+L#_{-LL*};ak^%W=^BCGf|t3fEv#;xLR$^D+l@XKc?CFyV~jj>`VY7a>qdHK)l zUlV$6Y=|X_h9h(q61qtT((d5+!5GEk{YST34#OWf!4yAZVkD-{+PiMtVX^E^ zmnZkI=xteV-pzZTAtdMZi5DpOxttZ|0zCQkI?+#1c13(Qo8Au+-}goAfs4|Me`1$r zQkdG;8DewYNN{nQCt9KgYT-~5iGX%Yk)qb%1GgkKFf<+$8uSmoY80#TwC;|#`X33Y z__T8`he%Ke$m-vAc5UR*-@*EuMq~o|kzW;7Lm@w9gW8Yin?{)pPcT1$Ol(Wl$)^`x zt?pkBiy_%x|0~o76udOc$DfOpIPWj{Q!QXyO%MFb=)y1XNxQ^(!d+qTOSY=N0aM3S ze_e`Grs{0*Zzu;s9Jvox6fADPKqunpF4UWCpiiNRzwRE2BGoa9S+;u~GQ*9xzCis4 zk7+W1dvC4mcdfFFb$(T%a!O!JvT{$0CIi}qAq$Vc87RC&Xh0~Zl1orc7o;i=^mV$M zW}xD?x3y23pDF?T&k}Ix(!JL+(28H(=oUvbF3qWCOFjmEhU-5hMCtoO%xnX$IzYj# zBv2kph?Lnn+6;ekuCUvDgHjT44}O!DjCDh)8=p>Bu_5MOj+1}rk$aFI|IRVIFi}&g3hO2S$%=?o zCyQ1*n0kRc3lhBkKnrqd45dC01X&sh#TJGZ_C5&@reR(zr{|QVm#W>vJtdLf{u)NE zV=dXo@+%+y-k8kexy=JSVJ|gek9~LWsNz#8Z!N$wmmYQfWk3I^yclG{`e@)DtHP|S zb9T2Q zvdzE@)~nd;Ce4+F8vcmTOu-dA#&riRHXi>vSUQ8+57)+_`2OolY4(@T-A|>D2mZL5 z<7TD|hU9QGy(t9Ycph40Z8K6t#Sbt`1tR}CS@9gU6eO_XEzeur==hKQ#y%19aH8fe zU%|)88ehH~Q*ouer!q$2b;`NuAsV-_CRa*$+h?fTO*GrOwbYyGpIC`jH|u(qtAS5$ zFPVaw<=7`EcFa>ks}`&|gW^j_T%TE!4;UsJ>ud*}Qx2z|B1MVIO{Pu{Mammp`ww1J zThWuOj3USRGX-c+Aof>%e~2E(y8f%~h`tpAy2a)RW{A;-a~N0Pp`teanLF23Q4f^F z({0tmBcjWr^-(N?>+8}cR$8$p^7UP#=-kKse!X?nBH#_z`BZ#yeJM`*Rs94#=ELeG z=gPZhF)Gp=A8l`LwF*Oy@5bW1DZqfEw#Z{}mbn$DIrc-1h?mDx2RHRGA^>+TB{xF5)M3;ATuI#awwEv$iP2m+{~=iKt!ItVMkAOVzg-#`clgD~J>FXu=sx(g&ISDR zVIMNSD+a-OA4}!jo7o7Kp*r7aD``6I(uBk+NdA$rK#LLXxMtansSxo%ZbQwIK%z#H zEn5fT=JeVEatr)(FTkHh_Ddo`wBm6=VGA%tvt$Up=^fk>n|1P6>^~_o%lEb9&oET^ z8oxn-zYQ99+AwZL7=dm}SzjRNegwd%Z!_|TE{IAA@cEx8GmRzdPa}Jm zL}Ew;nk@YTVY&}hsG*=0jrAu)}l!y zZoIcsu9$50{C*a)#=a^)u-S0yNbaz%iFGM~7{or2m|ev4Mh=TD;tfR0rcQ18+g}cg z3XX?ctK#(`qXwY9ob11g6#sXA4*}lwo!)!k2Yt(9iv%enc`@~%K#y!UjP zMItl4(LwcaoQnHsSq<|Gs`mFznYs~VK!ELG2%qu*7}7Iw##O5aDo;Lx?#aHpH^fn8 zkf#YYz=(Drxn^|+5idtODJ76fzMm!kxYXcmF3o!{HbfgH;J1(c-02y@S>?BXGyy{Y zxmo21Mm9a3xLe>z;ReiJ9qxLiQ*hDy&fz~~S5*`){&Nfu{?ZI|CzjCPWs`e=ueg|HMnD9dPongngJ5mx|bDWxE?%Ym1KPX799cum$(#1vY| zwk<6cEXo~h&jI5%ps(r{&)844Fv_n4R^fyTVsZAt4*>#9w~%5q0*Parip+2PTS;R7 zC7BL-_$dB>gL8tc;;;Av#n_Xko+)QPn8hHA;N}slmFJhF`RgfeM?VX*qWmHAdi4yb zZUR~mF=+CWyo&=m?}#;Qw;Bv8;_hw^&;vwT);#@HoP@*aHu=jGgCMU-TxQW8&DokeBjQ9u$L$n~+YFg3X~ZwHycnqo|Bbd}n@3MrK5P_+w^q(c~CXbqE{!BZrn&)g-=qweSf6 zw3{+6ZD-|kb7RK=;Xdoove{&kL(@);0$LGVL)E=%1VK7O)Gw|`I?LlUbq(~-yT+nX%pYRs82S@^s==;6BJer}Rz#S&Tl3)JtGeJ{4ohqqTJk3hnv%j-U0_lYqcffbHM`v|Ld`3|YLUn}X8T0!v)#r1JjT3SBlp_9WkpxMCU>f_7A|X`vikS$ zW1{ihYSiTePjS2Q;Ld(Dbmy`DddrhLoCmu{ryW-@uv;|U z{VmWJ*4zVsK}o*EWhV?#>#4n*yu-=xU!u6i{MNuQM1xm6n%V8%;s5GbmzP5^2pJ%LNpikSx0 zFunVEm3KUF7;TQ;8CA7glJ88OQUBp$){1fnX~4j5MBL?}JQ3fx9qu=@ec7B8HO%r> z#jFfLtCVog0QIZ4ek6NJRZhbKEPLE*0xW5uo|*i${+O^wZ^ryYl8yIHJa(2LK`+)A zpyIM~v7Q11$3zYUk5hbql}(y^|IR$M6WT`;sB~ssiMZFlEIuOX1(?ufYYYZr%y<6o zFwP^yUUfa)m^wnwV20MHVvS-l{S@(EQZB20m?|E*-ZDo!1rnbB+kM_mWN#H>UDyrW z*CRrS6GX9iqv19Z34y(t*-gbfR-GuyByz*FBs&u~B+}de-la zGlNtEBGecZT&VHsp=$pU^ubHy$&ch|jNd<2X@;v5m1DWasi=u8$A3``B4&pBt@mu~ zRrk0Esv6gy)#lMuZn589Nrl8hM6t#%pEJ@D)3dS*&yk3qf7InV+OpCa8O`9IHZ|MX zUzdZeH;dZaxqvAiqu*s8aD+QW`4TKW9U zu^E`Z))F|&dv*ln8r21}mY$SEb|MW|v!uaLgIg_cG_o{z)Y4w3in7->EG12&|=LKo5mA^lmjPEo($l<=JRHcdU03!`Y2dx)_ zCe+QY)`Xp=V~ZrdEGEgY@GRABX`E+xN1n72{(;r%)Z-orc70QEfA@zN)K;|L_Lm<# z9g=n(#w1lIcfdpLM%fw$>i_TDAN0@N8$4bT5o@|^UTuCqpr>e%^`*to=Kq$3p|!<` zGt8Xsi)hwGVD`>}Cl#M$M+Avvyj|@mhAZ+?F@Qx6A-V#Lm~_hm_$l|+C2e1O%jG252^ivUbKqHrBD2Kl7& zzoEWalys$Ve`0ahjCYFJ{rEQX)fvbKO!5@{uyRY+9%x$nxe?~-&dV~tr%*e8UdHY0 zd|y@cA3w2Ptk%`XmKinQc5D=NSPP~3y4ZZ&?@GaP_tMSoljB?W^_lz?rrOY%Z1Eb& zK#PmQ(Ak;_XJ{30Zr~`W)`B!dbAB!8|I)WTlI` z*Gv|+_##Aa&dbJx@zPV)vJs1m-?rlk>uEZ5|NSf|)o+7bc^ASJ7i@}|-f1l4@IMa5 zmzA07&B62@v*}*N`nnA9ko)AM8eypHa3>iNlomMmyR8>rn!HcBUo42h zD#qOke;Yo9qi!oaO|P1vA-McTMVhx5R+#DA<4-5x;)wio9NLDpqSXZd5fwLnz|351 z8w)=i?zq43uA|lk8bm1?9`811Zs>u=CVMArL<26V%rz26i#xu1tv05x_!b<~fXz#-Jg{}BB||w1Kc{O92Y;~_9R2GGp)Yy_#Jv@U+Ki4GSH0bp?qL@ zZ)D=m>Tlc2!aKgd`J}1icHgxk2dY;^#sCtffy>$-5GNUHME+M#*hfE$l7@7!=DA_R zL><*x>$5&0Sk4T0Vf*ypnN6%8E%ZuVcAyB}! z)2yGOQ==GM$vi(H+TNAe)>(UP_CyO7sn!u=@5=jCIbOTh3fitxE zhl`NK%OrOu;*n4`G@N)0ERM4sKoHBE*bOE?V@p*SCt_2W;)fPeMY{wE1gnR z)ya)zwqOjqU;ca4U!E8F0R=kPP$m}Z&B95t;kk?R?bVgF1aR)QhD5 zoHwdB32)|`$;RI?_vsivZCA`O&Q4=JXY`Oj#QCjd71xn7&K(a?$0K(l7-G+}hTaq5 znErs^X1banBmtSKQ+mcL6u@m^KL00(WY>Hmu9y0}oN2@)5)<4{Y`|r8&4G$R zYrtFk9jWLPx5ar{Qnrga&MJU9-Tb$sHmac%)y8-DcW=q)3mT}K`Ib&e^2_&KZx}N@@TYG};|L`QuLJ=_xzu{EC@kI& znR;>hw@nuM&YIPMqKt+*i=ErWB4W7YGqTOVW*|LJ^wc>OHrDcw;tL>0S=zOeP9sfA zE2f4+*_0N432@>qfvA~}Mj`;G!*cyD-8bmz(A#K}Rww=!@(4iDl^+X#H2EbhSg&NG zyANNocB$(&ZTQAQ>JtHWa<^Go6Ou=KSB#lXx6an{3*<)WiSF-5P12dr}nzVnkhe4=4%Lr_-lM{ZH-klSOJd1IrMn<|`iU+rb~G1*95b5X`e- z_FS?~%VG|`9(U1fVMf)pJ<(6ks>sT1X`(%bDC3ZUnC^V2k0VC~=7yY>0Fvq}-K4Wb z=U3v`-}HKY;gEh=Na^;rfk-QeHv9*@&~-bG6(iubSdDem)yZulpe_aw$;p?m&&@uv zVaFXKOo6tH%T+Ze&6gKwn~RSf#Sf5c$A)cIX zH1o!dZj0Wx+(M>2`26=FkilWgBWYwildZwy`=!8XM-bH3>Q8kELvF3LOT;5*7)kvn zVW%_6t+wCjpS}pc!?}Jb`We>Ed&r-&Kk>lQ3je&v{1;$|+PrhSdH36xZl)rS2rQ8Y zr+#_G?0iX~?UOfFyD=>nha#>Y2aiz;hvW3hsulfMspFIUMo z?0iK_nAY?dRP;E>^3`9qbLA@7l&ZdK|8e)zSH@HAk75G9qj3?j;K)J(By$gJA}{|w zxw|&$oq_71nC}1~l8WFntRMjQJG$ZN8}Rw51S*MN7ytGf-uBJ$aw~=Pwf;Wmu6hR-yR zs;8q!`dcrt_^1^`kAC%dM(p{Rv;{jUP#~>h1j%{#oRCnjpc0u+aIKI`rQE>V9Tb(H z-!ER8YPlg#&@XPfvHMQrnG3)2Uf%UR={mdM9}HYG*>CyOVqICJiC&^ZAzEX|%Mfsf zhiWt@N+^db3fuGj1au>(*AT%q^DQ<=ebW5^b}vrn@(1iN8py-W^J<4UP>^$oo&j}A z{6p}abfvdxb(kL-et_nCt5*};*`e%xKc`OBjl+GGGHH)-t3xy9LzfPBO(G`F#qRY3 zf<}^r6yqIy%TsW347aVAW0{P}<19~;0M|R8#2slez5{RSrrwqD(wK%LR1eLQ!*AbE zUQV%D)Q+N%BK?a>gMEXXqNn}RTw>sN{7F<^z~xu<>4nzI-dO2&Z5X1yjJ!YGDqN^W z2m8rZwZtEUE;IZ;k*lwnA2-eQ&pBe0^1K$`Z%~eC`ts37AGXVvOc98MBSGiGMBP*g z8tyEg_bgo6kSG!uAU+!7w~0gIhhABeFd62G@r{0#Y8SSD=Ay{i)D5kp0b}q~7CV}; z@MB)I9P*XdTs^_wqQ0BE$@ANHmtBxFp3O3JIK)<{`1A04_4Qt zZX^OoF zPL7akYfUa5SijxJo_B&TQ8cQlRCchx08+ zS}kxVcpOMq~l`bKA zvCppPsQ@>ir-Ii|Q&~Sw(4AjNo1A~g?xe{8DBHHphgNeR^}>yO=YBQ=?X8{4x;0Kp z)_C#xuN1=~{Yx9&uy_*cR>4q)Ya6zClJlEzAqVtN=EGe$Epvx->#pSH&!VF@wfhUN z^t2SiT_{tZmCF?brMBN_L_y0rYjkMN1q}zScIBL$?^Lw-Lw_+h5LBy&&&r0RCE%pe zuD|}i*MH}@ujlJ^@^lYh@(CD4s644GJgi#1elL|%(R=532(GZ&p{v~fyS75eSSZx< zKmA9_FZyP~%{>AXrz2+mAfvxk{OzVj_(8|ZEpYbQi^FdcjmL|5=oUTqF8!)O`m4+| z^^5Xf-gWE+WTx#UOOQNSFzkVfHK|!;i)IxAGv)RoVB zomYlHlucok&0&_;kktNHWa=ey5caoduWj;oQhxwKL?yZDgI7MQz|H#ZqCHZS#@?XB zML?54Coz@ccIFFL_|c}pW8Vq-q`l!0}v3P zTh5=(zNGl@q*0{{ws}#GlJgmIWEVBle@bn0$Uky6DO#cYVjZ~~X-rZ%bBBJm|Cz#@SiBNhwCb~wbrYgu(1??lSEn4(KMy0M;I5;$TOZ}GDpf4*8-o-nbvq@?%#2TVHj(g3APvRZYkYB}>2 zTKBWC*9jAt;I^Yf*?Y>iU+X}nJ|Le*EOXR=1%o5@GD z^Wy$T?gDzOAX!2c@LYs&Hk{x$@LBcPPYQ$Klq~Noz=0e8F3RQn^sOeKukUMetZ_1| zR6p}g+SPy`!KGT;UzgbX5r~)(Wse}VK!Yha3@koYkqn0-6I6=GV?x!xln;#Z9Fp}s zk;L;#>l2j^aQ>6(l}L&XB>$38~F0a#Ql@^2J1HG$gfwycZe~< zkP&MLE%fT`s*~(*W7~``R*uBjFiH1f>c}KRz+!j;ZiqRaJ|3w(dK~&szuC1C?3Uq= zSW?ix3655~riHFilB`#dspQBc>nU$%L>2DBxf7xB#YBOPxdEazLcOOHEAH;Aa9EDz zNIBW=o_DKl87%a|DY@;;%^Hc7Q6WRj=N9>SUu&&*$uhVuJ|XkscEOXSy>*y-mhsrU1DHxsQiM z#DwZPU@QCbBtMcgN5A4O^J~e7??AA)GPrN6iI&>@#+;OOvEdQ|$c<}#30Kn$xK+X~ zWC2{#THI@I3jYx+&D%{`Nr32k4i%SAtX!Sc*lS(&g!@CjQc$o+UG9?)%57L*jP={ay78&PfNtK@O%P7Mdi<*oWqQv0K zymx`q$YNVwX*QFlyGZtmJL`9?7m_2CJ^ZO~5NpT&&Z!%#e;6|GgG80eM2s&z50VPg z#U04q%3;}BS8oCK_^|0AP6G#D4_)T{@Y3&m+*s|SMB}@IKb^w7#Pk!bYndSDMcW9$hiZ0$f;i46a_Tct)M>Oamvm$| zxNhTWg9IEGUWwyR@)1lowT26e4T-4duIcj7pq|NKXA^OP!89pN>ry)e_s!hr<19tF zIPUSW@E_{NhVD)Ie7A&Rv5b&CEiVuICv$QIlol2`%}*iHgfHvcT6$fXDo(B|n7#-0 zywJp5Mi4W_K(Hduy`+ZHo1gXmnDK$luQTqBnsAcy;ZQTZXn=l~H4CGf_DUr(S0q+8 zeH6)$W;-C=e5krEHilZZXL$7O$Mqn+Vi?ILognF}q@11>ZKyJLWa}={BIvHqkbU(> z9Z{CN9|2sP72_Un7%L`i@49EhhDFvHn)Fm+W{1YsogX_18O%kH*5~g)@6tY#?RnTj zKk@odE5U_H*~vu6T%3hm^Xi23o&C!OKl*G|dSaB`SX#HD_9A-l4uhMC?NMxS8UD98 zMjBgCZJt8pD3+ay93*sXyL*1o*eaBt=+`aPwg*itB!ie; zVsk5N+j)@ZM0)t0gJ|m5rd8Qwt-AIC8jsH@gQQVFwz^FbP725jx*PJE z2Fxdg=vw#=YGoAA|Nr-ce1W&1%^7?%<$(c@nnm}R`GS?w;ammY;)p(JV!UKAcJ5^H z@2AHsU8^rwXs)UNt9#!1Vy;X&Ic;>@*}8dzMQ>|3v;m=x2P6!;Smvq?$;4sfMmy43wSB1H>X*v|s__ClL9!&JM0ISN=JNR`I2l^ZnRys#aC3nAzNcWC8PnQo zyWPX>i}I3{^1PiE%adjv8p^5d$GsLpCc?Znr*KZ`8?kzoBzpP65PG_sSY49E>&1kF zXG0m~;>tAHnH-Y#YMRqXH7?Y6nPWY~ENhkTqhXc-bY&!}3?kXp{LV>R7UF5=8%-o_ zu{QPOn(t29q;9wdLWuG9@`=oU;h9i{p1Rr76s>$-mvDW~9X+obIB0!f8j$~Fz$Pay zp1a`~muC_uOEx$}BO7^V10|1)GT7q6vTA)mLI2XzMiEt`&tF;ZJxb_>bH8lefN4DS z4axsbgo%7l+H;NoiX=m+Njg0(K&6h(i)}bG*k^Gc58)+-^zsEFbQ;>CSCgIhhbSfl z$LVwx&snjL(&aebv%$ze>We!^?bl{ru`1R9d$n_Kxjk8V!@plvhH7&*ed$K# zjv^I(6%EZUhQd2xJN-$ZkTs0BXE&@E5_sYEs|xvan+Vl1!~_O_SR%D>me0G$oT4XG z%S`3wOpSCTkBB3Q0GVqRMukYw-ceZ@1bv|xY(DJ{x<>o2D0% zC;3}17N12wNL;C6$d;KU%f5MdX1QFMJ#v!!7U;2NshGd6@bV;zyt&!t6SRoPSCxN7 z7ksPla#1^u_Or46$!2mV?==goG+Mf%0CjRA`m{|gn)#b64JL*cLrAlbffifni^j*h z%v?U%#SV!!q}y~tq!qy}=_y^}6lD9P+-nfBA+wLNz4s9J%HHxo!qX~EDC}*x2x(K~ z0bluwm{0pP5c5_)FwfdaqMVE%B?{~YDqZFbDB38>hLsiV@*jwP=}-}fy(ky>=}~$V zLzT!=k%OwjECv2iWGlWgOXv;vkiewA z?JMec46BBiORzJrU>n3fq9S0)7UOJ*y>FTO=OmL|l{o*A z4k23DKnInw=UH$VUHQ$|xE^%({C9xjOCn}I5|F&Xc{ED?7Ub7LS21oPDSqK(@hB(1 z5*17~GFgd|JB?8xp?eqOs|i{kDd?RJhlZ+tVdZE6TWUQL?rZV*zpSVx`N=R&++!jp z`fE%wn%~Z*B+dv-Mz+KTyX0D{iL*T0wHRYq8@$=R|E6C0N2)@11)Vh2@lEdmpNn_T zu_rtA{|-wEs-5pzDfC*u2V~MiipjqNI-?87N3pn}M?#t`AtN!X7r?~JiuF=rewIM_ zA?a5_*;HkqFyCM5BC;D#)BZ3#Ewqv6*#`E0QZLyzZr@!_8-UbOMTg+@fYu`G7RFdP zrd4?6(KRSVXdgI_s3(LD8n4^~6VR{N%T!=Li8^ZW|I&0@KxLO(V1=0v1`?iF9TMuM z(T|+In>8Q(Pm0EJq{?pKLQ=Hfs&wzdIm%g~_vsSbC2Z=+CUSqodLyC@NU7s3qMt)y z_-a|EMzYoiK(3`ec<&{}m;4TXTNnQ#qIy*RizSg2`*9k1NW>ky23_XM1RZ0j=r8b< z$k3U}%{p$pM$mi6>MA8T&v<$Z@J4Iankk>KZaB-kT+Q|g-471Mp6{lVA^^jKX_nK^Nj2f+_kTfFIi;ZS4nUq+;QqcogNg-# zd#XBQ2%UC==qz|&Dd^O{@WDxeg6o3v(~wBNLAf{z2hto^w_!;G3TO8XkbN?zHv1eR z%dD5~`)aep1)g>lb+M(e3UAe&VauNRcjG^>0dYrUP7Pn?lMtcy+yO4B1mx+M`ST*MgvAJf9m@PC@BLeWT2^$ zGpxmu*)xu0IpRpB%t3on!o%D_gcg+GvS1&O@Qdi9{Js-Tl_uc}U`tXc_pMD;$gL;d z&uyQiciezT%N|D$*}^&LOkG{w*R7k;@&#Ein~-QTs+js{?_${d!@Zc-+q!p zp1M3F384NilfXJzxeiWP2}ZnZaNFe!pneL4RITsAkLuj3%O65?KAqHNg-HaZFD7d+ zbs(NqLbIj*mTR;{6Il^l+e1$NuH}e$B13-``ZE+lkBWz=*k@C7U6}pr`qs2t?Eb>J zVV=J!9dP$k8-vqB)FYCFa<-DUB~OPeqDP;dRSm#aPs9SswkfK2kY6oy#=eqqWxHn5 ze++o0Sd~4*sR4bB2LYWXKCQ`<9TWLlpl?4NY6@4|WF%Dp_c^jGt@1w(92nY*Cat<+ zhN1oZx2>$4M&1L`^U_P9_rR^JXL&K~B#%WPD4-HmEq>KXsiI!Gc*Q#Rsd=f?L#)c{ zvaRVU|K;t^$)UfvZfQjUbMFDr!h0}7UMGWKPRSC5^3%Em4wPI)$s8Cx3oL#IWH_D1 z<9jFZga&;3o7aXXY5h&dxM-tBqtr?K49m7zD!kSSM0fV#kEyx>e}C-#D--hZ`?pzt z{>zMymMXrTOBqAy(&q!cKAZWRArH058pKtmX`Pg|_fzMLIg+GM!9ClngoIgPwD z>|Q{|kwrWU)u}md*`J%Ci#?wd-N3#&n-t0bdo4!P&@6E|m+E>2@3_bMi{zd(qq`uMF=uw|lO1GB7qci_8jJ zZFL~JY2?1!rG3>120Q5*Rd){%HMpADwr7Ru{*{joXDis6F^|86X~%k>noB}a$@r2k zV}ckd8?q)cuoDva!y?My*Eq$UYlH6!2A*yJ0+f zqg`4xtn+P?)pmZuhw2~CynUank#h0i{)=x#JtDZclX9k#kJ+;d7yOr-aIw)8#WJU-@3y`}R{c?#T(WB8BId=R~|y9c6_9CJs-T~m44l{d)Vv=l|R$i z91raLkKX`3;)aN5-^1rkVj(L71kz?$+H8e+Pp>=ZoiB$2DCMQ)hwr6emUG|0=tM*mP)!ZP5`(8zkro3@T1b@Yt;6a+ucn1jC#1ketnAy1 zWelgn{Z=J<#?0Jz`}9|b5)OyCOe2r#@}j5R|4jWBymxmjZ3&^(Sd^#*-Zte{JsPBv z4ZX2Qr>8jFjA9~LaR`EQ?r*B?_WN<^Jgn%%tPWt;9EkP<(f9OIg@-xYmM6A0%v1sxGazVE4 zmfwX|0vN>hCUO_mR^f5mg82to{i%6dSx@9nb)jJIAjS1Jkw&3~7~?PviT^4T210Y2 ziMr_TlgghU0Q#^I2?HqXvC#Oq zO0Gv%0Ikd+sLk-)CAB+o+?pi)Lwt1{m!0@cfJXwd*#9Tr;PwmxJMX0c)&B~MFTLBO z9g?qk_SNZf(Rr7XL+}(|wW1+=RQ{}0_RQ14`2*_%qnk$zdZYSCye=7p+c)^r(9cPk zSI%1j=H* zS^Ckz_+C)&gpm8#F(mhROL;>TgN^VW7gG1tyIBg`OqL-`bO0sP(9*4ALH`tio0U_y z7gJu3kOp0&ls!Dl6JFvq%l*#-MwE-1WgF}SB}D(4#_&Fz1f;5`aN%v7-QfxTXt$+* z3O<0xyztCsX1SX8+d&BEs_0VDur|P}d0C$B?NnB6g2-qq(LsCqRJg#R5y%}8tT~P|Q15DT| z9a3ers|(+@`fU5?l9BbUwf$N_NjV%^Kc)FiPIAg*fzyLKK=QS1?JhhXNhh1h*qnPe z1u!LX8mTXMi^DXg7e9QKE?x@x0qHsoj{W9shS($FKZ|dzL|5}|)-R{HEKOqzK z9RPRmg`gS1A*y}4`8>ut*JM5TE2c{>C4}ZYB$)5mRw$B+G!kbBp~g+|(S7Tb2LVB+ z&8r_?T)!s|3C98&JdiMGeBj$(OW}k{e}Nv3H^;FVUQ=6E(%VyYQz)=%n008g4`B#rXut<<9AxG>Wa4fA6NjK7 z%qS(K<2gG`_8;Kd(%pS@O-mvZ&p7(WR=6$TUTh=M; z!(7S2)X|IGM5N=F?UXO?^Ar`ehZNZDW{vu^NiKXQck&BN2e8y`_Qk{nHltl6Gs1em z_Y!KX)R+SLeuO?wdd!gPhEy4m%XmB%@>Ld^U)=biTkYW04-G3AS;7;*6mrq_LgB-E z{zYFOgGEnqP(>!nNSG}`RPlXG`I~A!@&tcJWIntOb5Rwj z`GF9cI>B8}7(fkG;UeD0orjjQ-L>w?cq>m%k@qfcIsc$E1=8CMm{tJ=RZK!nj4vIR zpNlJ-t+^7CTdwAh$$)Kt`&NCWOm!bI?%<)&0ypsI`{jnWOXSqI3X|K8Jf9;`Kh`wr zVq_rrLvG%r+R{|x?d?B?&m$8eLZ*M77?HM zK^x)3}6h%3Wz_C?uC)O?3QM9!Z1j14r( zj(w%bev@B{iXNT=HE7iJ*=7;LQu@5v6>3B~MmHe4Kv7ff;N)EMgs+}Jv^A&bbhVQTT>ewvuo0}G8?j|unbctOsI3pR~bv~R&tRls)!592PAdnsZS zuW0cZA8?M72H&Ba#Uk#0=Ig^Xt>M&>2u&Gt6GHNs$U%T7n>JOb7ym$}!d8^Xae?Kv z&~40XN0u-M)ST-V6>5`(R;{jJoTo@|ErgCN%BYlvn;)9pjB%HyEo<)MzfmD9d^4*Y z;yCggS@0+2oO5pF4BAVbfH8!6&H#6CU6ZD%0axW8x;xB2NgoqdD7G`i39CUH3A2q% z{X?IM@Y9BUxxU~9bP&7B`odiV;$u>T@nv(0x~WW2vy!pWyXeZxn=vt_%LpLe0n6s_ z$6)$SViPo;+k%TXh3uJP$>H)a$76d%9_gy)Q=bn_K=F+|RwG7e>}4E>Id;bhop#g8 zlGXZA;(jR3w^TduxJ~OP zg*`d%A=9@$|HJ@nE%T|&glc*nUs41Z2*dKT|7@I#If7c>d#=F5UP6viK0_QKh#)x> zrqcnLwnj@A#}px1;c7;~qg4%ym0pHlr-q`CSZ)ic?Q7%Y;`I2l?MBRJ75k~@1+l5m zn!kn!b5oD+qpiMtOq2mh1zX1Z={Rt4~>Nvy3q)R z)?t50zrtE?*o)~f2mzw`pPA;D4~n=Z&h+oogR_H#Xa~r7W5F;}>~Ik-;dVf^73#%e z%PZGzaM3_|lgF3*znZ;*?sXLh3&=J(THgvA;X55>LPje1133fRO1%nY(r$&L0A`&` z=3-U<{XNHr?k_zO&VIp+?bMk9hI{Q)bDFYU+$4+mnvF9&p56FiH_k+3x+a=)U6P>4M+m~WV_$@6)d+|n`Vu?DqIn=2s9%?IryOrWKR@av2ywTj*qQcbSwEePYh>vvw)iu#|ueb9Hr?PBX=Pz}kl2AS{QpD-C zdaN;K`Yohbc6@mB?}x?GN0aE11{;81f$qsEXP^3vDE{s#zD1&EG4SC&uwO#2a!n>X zPSDN9u=9a^uat_-Sk#o1y{N?_IYwsK1V#Ggr2CNJhz@%1+;568X;&TcD7uM0#?thT z%7b6?Ulce$Ko$N~tUd#aM4E#XdhRr^(8~mf%6@hig#SBmD&pP7Y?pEE;0$F3gZO_0 zjpw=Z8sDTCXuNGXy=Gp22hIzrN4Sp`i7d*tzl;Jbgd-^<+=T9xvH1+BxMac{d_aU+ zj&>esl;dNYR3YRF>e~MI=w~JJ#N|a>=dbp+prqnDHRiw-@oqtkO#FKbTT=b28d@)n zLU?m#U+dCu+3AZ;M6}WZzLf5vF@*k*9i%ajbT8UgbNWt3QUuvNb_-@*T8^~Nh8g0v zi&~crYetaa575Y&O+TVt4=ZrpgfEi&H@IjSmK`7VRL}VOGj+&9$E%Set)j^rYE3|t znGg7_oJmB&AmgTj+Zi}C4WwQw+qaR>#JJvbCEUiZ3~XantMi4uVF0JcaJIypi92+* zoQ{`gRPPKnPy|OAK|(kn6rE*|>J;$1-Q>b+aL#Vt-P&9gpk$Hq)D~^n3MqPlZgHAM zwyCag+eTXDv?~jC2mnq-A6qm+^%h>J89~}ih4`RGQ0na))x&07U74!rdnc+el{YB1 z`U21+$(#Hnap1uXL&FAW2w>3Ke8PdBwISto~sWZ$!^w2t`$OuNzLaj{&onWkC>MWB6F@!S$C!p8m zdfe%lc1-0(C=?>`D7ub)a@xo=TFkglW>R_KI-<2~a?y7HG~-+ZU`bSJhd|AcH2;AY zFRrQi+s^xyVS?)y4_lUN%=|2fy;u2IamJv*o>_(eT((rsKcu1y3XH!FPayM>;4}{R~;5b-$iGa?v(CUxZ?7m!YAL_!geP64GB zNd*b%UP`*VcE5eUXaAn(nf=Y3d(S=h%(*1AHk0`h%ctT~xx)EJprVMjdddH588Atn zAA*Ga76P}bvE8M1IfU>VP-JGN=C%>0bI=#(0ldj47N2y&AIgPY@#-=e$@$-{#^_@~ zW~A#XmDMB6?-Ye!q@&rQsjfb!{X!o|cS;L56<0UiS(6PC2>%Ops-P8rl!2aej}gFi zLz5-u=FGZ)CrKby~tp^5gu10NGl=jsD7F;Im7V_Mmg%ix} z>jgHf?+*ueqvfD2vP%cr8!7pgU{2Kp^D*09$@}&@$$taG* zGaKbLcvNT(kz^tBuuAmw7)6doaHb2sqOPgHNmztEyW@>YV)jHuOa{+=TS!dOf4?E$ zZG=9V#Mc!C!76~;Xf-jmLYkgb!z`G!$MAu0`%tT+8Dy$^3TBo7Ye&Q0!Lc4IA%{!|C_k#2bBj!hQl&N@ zRMZx8l-?%3@z^gc9eVxnfdWXQg*j=0euxU-)tl!v=|9ASAseW<;4=sHnmw)k#l^RQ z&R2V39Y;xSJ`%9Q{$&8y0}D45p&h@Wo1udKi7A?V=G0stpIyBnVOTv*p;*nK(J_)w zbX6X_?Cmw8VflHL9#Z`&3dWeNs-g>4cKu zvX%%94j?HU2F1toR{|(O@UN7XxX6!B6}{%#Y^00$F{0*v$amo!k1U0hROcMDRaX4~ zUw<&2c&NU|#Ey(A6OcdY`#v{E=RB+}l0QS4C44ItuPN z76uDqr5Xy2i?ik*sEN~7%wrG(5|rTz0-t6FR4|7(urnaWfLK5KS8m80^ELD4L;9u- zmmP?3F~#dQcssuKeTazI>Z) zSOA?*uz)RtAhH{k4+F)>pQ5ArciUwacQh`OX@&3FP{z_tt!mppDMcOAKLKyA2{P+HU!qY9w6a&Ze@vkT=pQo(Vg(gI)*)_EYRfH($R%f$W=?{ zS#IPV@jk{k@j0S1+f`0FB|wXh7H}N|cqBQGzUj1|rd^*B_Ub!%62X_C*mIaLIWkjT z5NyR;)DP9y^5B2?{CI@1FYWJ`y{?dIv5rYZ1K_yNGX3S+og2|+Hp{0DBkP;Y!AezD z8lwU5AhvG_nZ0QX6JM9Im7bx_v&bihFg|-8m==%;cU7x85s_2-KRtC7!)BWn%#=I% zt`mAoh_t7M>l78@!pcGSK)$o}{bomScGt;N*A=Z%Ehlt_&>t0;Ai)qh4K*zxDQTl^ zmAgi*GQPblvt!ZbZ|#4~^7pAn$r3M}EMWXwhE9)CKwoWiCN9SL8t zj$4jm_}jhHu+yr>_ zwQD(nGOE`KI}+FHIT0#yjFBv`&Ct89>%V8rCh^Mjlv?Bs6oAPA1vy3x2RFYJ=v55#z9L{#p;0)1n-TE(FQSMf$3#E{?f2Jp zy(n8v(q;S_U(a_{NvHB|z_flFDjBOguZ1-L#q=lefZ9|a-+O$Fk7XkSaD703EcrD$ zQ$@l<OZCOzIcmuN3V-^-R|VtCl$7b`m~QZ{>Z0mg(1~E52z77A95T z46(rg2y?`981I|8yii)q{&B?^=+5A$o_3-`r|;5Yu|SSe9f=vC1=s9-`pQFO%7V8y zPWrb~t;Z4ee@K@8qW9=^>b9Y(;XSBs9C&@#SM@XVBLu^*w_el~njDENscFVQQ?PO| z#j_H-qO0I(Q7a)j`Q)>F^sS{}=9rAJx%_7GxBf*{f1f1&8XxW@>jArIc=n{v8Kkt? z?k{oL(PfTK@l0VGZ&t1|9eOM2Huf+f+L7pBXTc)xhi;6mA{KLFpyixQ**hq%CZU3l zd#^F3YA%@Z3wxoj8dw94#KZPDiN?Bpeu!u2R&Pm?i&c5D~#`ksv|Bk zKSF%y3m?qt`oU785$Jz;E$D1{i=v+a892}VFyDE$LO#>}YD$rp;0U$*6dp95GawU;@dJfzZ5)I9vy{u;T$epzre9nY{7ViFl(EMqY|XORMlh`?HWr-Z>N# zrSK%L`-=5?=LO;`kH*EOjm=0?aj_ohGX#*DGWdRw4_E^nzrfg%mKE-`Lr-|5`7Kp} zpw6C?;t8cv6;~8jskkus=E|_wQ-0;H9tm~AE?T23#Ddi$qbt>5GB(A;we6SX*>dOm z-rNu;kWbHZMP)BI)|(*TZ*Ok;5(RAkzZW2J-x z70w*S$&UBgALMR~$G5Kkj3z@zNPWt`9It%YM-JHhL!F2Ja5uxcfbsU4|?z$Hn6!u*$9SRj~RAeZ)RJ58%cxB z4Qk)57G+;s80>Y?36Qo2c9Mf)QC+_D*GW@#RpI@uSo@tiu1ok>b!o+7PE6v}C=9Tx+{?>g&uW<>kw!g;kOO$$Wk*V?Dub{=|s+iXQ_}1#JOgS1mxZ zDeU`8f4+t_6X<=1{QV&67J7dKz3iTvqAm>4M@4cxDn*B#* zv9Bc3^?^+JMob;w`3H%{{ALayVseQzA>vBnXjgAY$woDtecCsuQ&plECVXC$-!pa- zFhwp-`94SOrQ^cN1vuD6Qv?Hm|Bwf6<|x41ZiB@c%ct^((Wi&jcm4rjyu z#6ul^TgGENX}+EZk;>bglFC!p=3aLWv-mzA6O>jjL8=_aW{G$8pOKv~4iM_dG@Ic* z-GfUwuYJ`lxwb480I{u93>}>$eYlsSUddJhnr#_*RoG=PxQp3=ZJ9Q%@y3nyffB zKgTtK70&dgDxy9ZJ5kwlIQtP?)k9wYEf9)FDB1h+`1^dwYJEls2l-nJzAlmXiFKgk zBbzjZ5Pyp>qPH_a6R$BLYQ!EP9Ri)^bMGJwM&!Sb5}-3JqKz11|F|V=v`qKfu3j6$ zLK~mhAKKFg_udIbHg6vj6h7JwU^IEf7PT}oa7KJT({+ai{>$6rG^p~ zp~VLDkA!aB1hPLsQKzTB^wAzxbS^HOf0+DHMC>+K7QA9PFLWm{z9D+^zXwzsA){V3Kq-*pB!Xh|Gj1R+5NTJu8Q|AEwDgYBc41 z@!l~$by`t7c-gD_yAS5?!VW^d#S1%5P?Oz!=liO&f!g9*rw7^WVa4K3He~#f zrE{gg!~thOzm&(K)r=&C;BSpsHGIrOBZ=f07K!hCmN`>(GJ>Lve|rjSkD0sF-B(_> z?HCkfp|$xy4%b##F#F2)Snt~5VYxFMC@Mw!`QC_p90-j{-Xr8S_dlP6l3_{<%3b}W zX6ej}*Jmg9QO9{OZJdUtvLdO=f0i!qb$Bv9YslyNbEZy{rQFt1{Ko!f5VgjmLaM6@ z(1p&D;HSR;$;Fx!5fx+N`Wp#1!Zdy~iMU|pQ0(4PsQD=6V}DKt$6fyfd(%m&Su#XD ze&}i~bXR3QT6nHJwS0#~kA)1J^fRjU7u@SR^JBP+Dyt1KZ?9G-7`_4JgDhMX#n%EbgZD8xn) z4vv6XVw5o9vlO6w5n$0Bwc8@+WWamI5^NLG@sOAbz!K$Z=VMXz`*=sH7mhN1vV`~k zhcKvXlnLs^EF`M{Jg|z9{~Re`2O1j-&`MZR`Uwq*J&=#dwSw4ilPG^%((Khs4|_LP z{Z*bl3Z=_zZ?aEz!MOZ`$iu;`cS!PR>g8+S0G?Z>)s=x^b3RS0v77cKDZYFeuU+rj z4rKdvzaRZaB_(usN}-O=K^Wq6Mb3A$boad%vtO_$teXF%#b5{JTH_)8?Ji}e!5jlM zFylm}vn@o#=F3HN&wW3RTk{^>b#=@Si@{#c`T1fxjS(e**u2GsJ#yrJiPi}GusWMi zQTc97bbpc*!nV>n{?sDNmHFE?cAdGtYJmuVXdEDB4h$@nqM!y37h^z1;&5U0821Os z^U#yLF7KQDV(L-F>a2VI#Rmxyxfd97uScDGW<{ISGWwXzmBZ@_T{u*7^ z&jhV}4ZkXOo|{p!v5sb?;-jL0Kh6xc2K}lFSAd6FDOQNn644#(d(iozlA>|x62)4x zOMomQ-xlqD0}j%2a4r^Hi?w?^TWvD^@Y~9)=W?{g$9oP{fqLrxczoT$`A3%MsE_<-}rbdF8%|H zofX5xbme~W4>1MZ)Ip#5m1D&|oxTH&rBMv+$QBw7DO$|`q74|j}b&8a;} zG&Qf*D7-m!j3EV)zG_z_sG3FG`AZg~zO(t^U-sT0wa;unA170vbh^cFUH{7uX#uV1 zHv?3>eFJj=hgZ#B1vv9@{{LpWPAaLD7IRcj3jIjq02%`LibqR1`>sDNgOtZHg17Mg ze4u1jHH1wQ4UpQ97W9=?)rL@xS9|zgD!%4ufHl}4 z+mztM0`N`nH4$PT3Ez5>WAcqHkK_Eih%9}NH^^yaK$(+_8?#6c=Ht^rOe9?x(b#lB znGyd?W{KO^ZC^jWYV&0QAeo?Am#u^wGwRNhlG;u;p<;#}wF9Mz38RGn8Q%AE-eW)M|2D#78 zu+F1&;W4#{+{XY4_nv=2;NFCt95%QpoGfJ}rD4w{2qS-n_$LLQdi0Gohy5)~GW-*- zAKE|zC6@)-@TC9w)S+U^u&~wV`vNe~R3YkYe0XL;>Els?3)JS-FF)*6>*%IEQK znieL0^_!^AdU@SKh>)0J2{;nay%&l~#?l0-4Z3n~LuItRlsEvaS~0pyAx=Bnm)AP% z@^ZfgFvYArv#8xFHeP01=P&u{g=Hvu>qs1l>z^8Ww+9+#V)(qCqLv7n*5iN?J9EW@ z+=7>s@%p~cH#P>Nj-^-Hz`MWv_lmHU@JT^M?AxPfa>@WrY3McL(dV+z)wZ(A_PyYB!y5gI;1Q-@me`9Uzi^VffrPoUO4H#w^T^5>M zjl_{>@2pU`@`Ir{fw2#`mS)Kaf9M`_!H3+!t_;dyjLgeo?+2*Ct$e3eWk&qf@gJ#c z`8F&*lE@b}B6z@WaE?p+PgO;5Dibg_CpHU+TDzi}er9P^R=q37c`ED4HsEldR>B}%VBT1`n}4v@@)H(&y0HHqvX}TgT=$7 z-2ER08vIxqG)yrq^|(FmJf3$j7ypkqU@+o;Mh zf8Zf?+`$=+)B$fX{8L7j?Z!d z$r;Wi!+ozNb1)g23qcvN<(xU67dN@9AmD)fgE^wd* z^HY8B?|P8V*2$NK((PVsxLeL^Pb-b9+zS?O@rLzWq;o_KdmeYA*&VdeOaCOvZ}1?+ zRieX&=bN)iz`rdP4~e(OyJhBG=f73u!tlv3eW<}-6fj{_ZgfR$ zUUoh%Bcmgsgc|)X6ViEe(S=`X3gy7B>-NMhftIu#$_#d~dOoep&IZz&m)&JxudkO? z!Gfp}SpLA4J93(R$aY2bTlwejl`D<4fyb3ElKS9xtKtCuv{Nr*l6nM8>=x%8y4Nxr4W^40QS1ODn&qhGgca027Yon*y*Y~s}6JkQ|sjx1Vf27wIQ!RJ(RbWd2YqK7p z=G~j+uGOrrtAisMU&|FF`MbB)w2;Dv|L%zRe2W$1Z43!hXB}!kZlH*Y*IPlP?Giy($rWzX#HsweNYmy9LNB0sXnWdU*nV4(wr5RT28eHANt%GOTwym}C&oZP#Z}8`zUrSy z?t-w(5{;4u=CmaM29ZbGSAy>w!}=KpP6W*Y&LcdwaVV1MR^qCdhJabAGSvFblzxd6 zr)C_g+HK1r)JZF)kxT*qJs@_=36z!yguLzU;=4L7W2sSH?K;7j2zPyC}Vock}1*a)CKZp+3H`zVLo&C4?SlSZ8TEg56+K+bqO#WiIjVQVI zk}wOSK={$wt)xCvsep%wt+VSjLP1or69buTQ^O~*iT|e}_3u`}6RW{UJkyqQov@>> zpy`*sDtucCXgc$Kp0D5+h;H?(E5ZF0l+3lcSLyF1Xwnoa=_zsPzmm5oB3!rXaOg#t z+$VCn2}w^HU82WHo!u1ZZh{b5hL~f6H8Tdgv_XZ)hSzWC%V(p+z&^z4PKgifVO!>% zm-A|+uNYy*W!Ha{&bO|bPXz!EbRo%(vhaKvosV-o!NCG9(j6UYFi+@~N6qgI^eP4L z{OT9Z1f`1S`u+wSB_(uefwTBAaOs<_I=DmE{O_qQ46?*LK=R;?$-4RTJP)J_#LC;z& zsUYKXt&Y|?pj3xHR9mv5(pURi=AoeyV*==4vH>HFx!E9mmm}`T-!0}5K^jC8)}LpTkW5f6l%I`2lu3loVeUI_2=QCp87XS;%Aq}g*>&0A?<^VRWL;4V4lkKLu!;Um z?DE-_w*#F+p{lF_Pqvsynp(|w!DDtT_SYwWrY{!`aA9-gkhv#MhtqnaYJ0o?Fr-ch z&F&BHSE|uNu6{tlDD<#W8@EYQ|NR9Db#R1Qr)WGQ*kU=fwG(cdTPAz!X-B(@;`tjP z?amk2*?q?gpHX`#R3UMuJtTim@OgaT7@Xqv^BpNL;gN)!R%~_}*UIHNo1AWZ#XO2z zR3AA@U{hU`xAtb;{_24xMg&K=c;$DXTStbB7QZ!x#MU7f{LiG5e-O5PEPwln1ez$BytIbMf4YM* zO|KLc@k4on%Zzte02KGkS$(jWqtZ0Tegc`5Qn8M%au9G%IKVbb&hla!sZ%uMT;>G9 zgoKZO<=X0_nS(=c$Q9j}_2zn(u={u0D*LiGMP74NVG_3GHl3=D_W9M#!^=AP&(auV#e0yu$$6hBtj@9HfjoDa;3)OxsyY`bTtko}) zd~&B}Y(m?r1fPl+J_Tw8tkugi#W^p3K9?CwAUOwHx-SEFed)Wo{s0}!j{+g>9K_Sy_`L3X85XI(BXZW%WI34MnqWzrhxyzxnUp-I7>@lc}c z^jnO!e8?d~E4qd+(C#zN3?7yE+k1j-mfmNRFOw$(AOGV=EHK5P#%J{qu_Zu(YWbp= zA5*IaIZPzA|7Z?tqW&%=jJUSZpz_6}FYC#)>oRRAmRzmA(Q=#35D6hIAqVm~KVSos zzHG3oyk;Kq2Ca3d0j=MD1c=MNBfEtc%!l7HB)%bmU-y{_F`F1%Tr9z_YM#JvH9Uob zI8Ix+?pF$mq&@46V+_s1E>1Jc3YJmss5O*1MrL(E2?pGHI}VP1;iJ;PJH>wcgY~@q zE>3s`Pt%V2sTgGe^qPv|cm;)eX-RncxU9Gl661Tuwc%S__|?rg9~k5HNjkpk#e*cc>7 zlc=?PPAknjM#24R!J4$nHS+48tI#OppaKwUV~B-_%#6U0EtLXU(t4IB2z@$!l)2g0 zIHVao8fOT7oUB1r0@04Zh3P4-yIDw>8&>|45mpXxoB%mzV8XSdjrJlcuQD6t+_?OHxL>nlF3yr! zRvUa|vHXHjcTjP*6Jj&Qg=W&FLi!u3K$(vKzj%qx;c3;NsY*t3U1h>Rob_+aS3u1nBS|B`d z@^}xOdV(lj4}OS|jw|x>OF_Dh(e&s4?8%gf3I%4TM`i7s@sLxG(2WT&Ykx(baAeH0 z7cR`?vK2g1DSkbG?z1*+Vp90N@Ud57=KYryFdhzKVM-6EpZAMB{UMut6((FC8MIZV z*NDz@K32WB&CGr~&L$2D!431I2KG>K0J=~M5v9Lubvl2f%L;_!t2N8={S55u{y~~CkkXKEmYwMPx z9Gbvubmp%^SU{=dsK~s^3S=RK9z6kzr)2rN?O#L%xC-d_9cvc1j_W*;zx6{yeA<|k z2<1=}B*7^+_=@4#wODCA2Sg6d6}u{3Hnm1noueS-_XD*Jhrj4J%*)Q9 zbdh566^}~B-4}n`p|7Tsqo03ZK7x@=T;Fjx#%=r|i47!SgK3fTVkEQQKoYquHu&&h zzpjWP`KocB?url}-F|Ehk{^C{Q!wB`?Fhq>^zC^k%OLBsp;)se2`T@tEaL7@uyjpy z%byQ>y3d!tfGwD$^C$O+`QiLx=^TGXmvzMsRKb|mz(gsJ@pZQ}FQ2_Qr8 zY$dvZXSmP3riE}`7W2!;sL0jzCge93pB7IPfxf=e!ST;D!bYGrqUXQtJ>?(27|G7W zHvW{$x#sRZSrTnzXu-DpLr4O3@2w*%it&faHb2>~H3=Q-7@XjFB;myC&a=I)3(QvE z%GK$y?dn(nQBCpO&>6v3g-Us}Wr@`j$&Xtjm+GV@=13T1#O2FVGR}>M(5HgAVFGxl zB+oiWnuh!ph};l|0^Afsft;ksrrw8Ap7=3EqdORGnW~8iJbaT!14N7~s5Nom(IU3F zx4xK0f4H${4oJ5leg*{3TH6yp@uki~$Qe!T{`C)AZxM{9#D@3_L%>Kh)EUlr^X{?f zXk!Ya-04e^{J6?VdYPtb)adL(N`~XQJb7)&ZI@1)q*%0VJoa=v`FEva5-eV zbSK}548Z+%rvz*~U-|G+g?Z~eb@GbC+d$$`Js`Fj9nAKBN zxo=53Ou&j`h@qHH7~m`P(*vCbvtl4li>V!Y1Dg8+*AJmt-8dgi?Fc|HiYaG)9n#u| z!sX#7qL`SHRNAp|2a5 zCC7yfZe{kN)nR$m?cH3TIv%*S*M+EHr=tz@+XjQj9tK?9yJV4 zEQW=~`i8=7u*09(vT-?7mC+S%2>2K=1RTq>$a=Oejn4V5ZsazqhTUjd$a0zorka7_ zvA8T?NX#muT)=i9=sFLZM%~ah&VAbiB{+c!%=tG;PB8<$)0P`2qZ7MjKugKSw!N=8 zaE)A!H4)JE&|{&t2BAgjPeM)Xmg`daBSN&%{ZOubGUD&eAc;+b7I^THc_$y*2Z5l( zx*n(B=MDJYJy7x;P#^WF!+GWO)b6CB;Q8Z8S~Qn7p6eP@yQx?GD&i)0uI!DWUe;1r zEF%?S5R>dqY`fsUBTRiJQDN2pO*x%LKc-4E4CU~ zg@3nEQ3=EwjYS>CqcZRym~h<}EoSp^sHxFVr=$YGgK!J0h`RxygT-w$M zRt|AZ^*>$=IJ4nF$Q-z*+0X?imJ#%0ZZM0WHuOF-rtru4wM>)Yi((gm4N&=K(zuJ- zOUZBZm@_t=CEEF+zu@@d`*;abUOz`jm(c9~Z&VMutXVW9S&qpR3tNTrB*hW;qPdKw zNVV*ftMA26y3%s7U+qrOSqDi*oir$YjFGGj1~v1QNrl3>D~S-ec)HQ`H~Heh1=|)47wG87J&iPm#QcuPPI3@;F3uRbRmK2O_b9S zB=~j7IX)$PFut;>NNhN6xVH{G>%yx#eZBC8$L5i+;lsinV#WBVvoYn0=48;N+z1RD zfPH~obEZfP55yPOH{1$4fKMf=Oay&Q(bf$IiE+%*UXy``I}W3YvX(o)a`AF}J^hRg z-yJkYe7Ti(nS`yFQ^>O`7U{tn)1+T+G&t^uT!SQQ;|Ri>2$^&!b+c2xU}Oe4(Xlsc zA9o+Qkr?yV=8Od?p(wm#zJomWM_ymGr7L_@k6r?Na@=N#g+{$=z4tD%ErP$Al+!)d z{Ixr#@*lSDUT>;bS5GJtlnq-OAJX~w?%?2yzIWm!aqV&CP>x`Lxm#*7F6Q+L3~?EB z@=eh-0!M@O<8d4hhSC$Xt(uO^pK|U8oLi2B~0y8bA)8^d`kdo}Jd)#6zKvLW+i2+y7YJ{LXr|AJGPiwW-cJVrXt&y)ZQI z2tZ4omWsSGCW2i4;}Y@kQv05LFL}8``KoGy{{n@ZGf`Od;sKghuCG&q%Ckx$E2YW3 z%}sJ$AZK{_Yez|uppFkMWE)>eMP%IfdK?KRoe<#@n699un~WwyzGo`I25hst1z3<) z7h4QpV7UvY`v)+&B*HnaYRKS=iPeIk1ZEliz`DFXJYx2gusMSbn2&2nKOqAN>5i>r zd6E-kLwCjpwDB50YA#!45#{(+qXt+P>M?&^O@`vu)Pe&#{U-38dtU!xi%|Se!np^5iGT8Bt>(l4rA zXGUQQk%)N>e+YjRKr}^EwEaEW`6O%RLB_*O_>cam406&7JL31-Az~$L^m-UAFBTE8 zu=~OB8~3oAy{!_6c}SYEmZW{nV~kYs7mKevT)xT`4+@A3P3|ky^(SLt3zVvJ$)0E@ z%!B`KQ-Ad$$7Fa$Gx&{NP0@L!;@7Ry%W^}C^9|T)lmf$YI3{9@zIEfHNaJncGX&~h zTkbFJiirM8`V8e||NDi?g`52ra|hJ+o0YKPm+_wh-y-p@DL_I#+YGOGrsBtgi<-9kF2F?24)eQy7Z?A9v;Rr&ksjTi1G8oS)Ire55G?o)uTrp zFQ;(m2eAGVp7{w~%mqqlJ;8G9jr*We9{wk=-GI&RE_nwnKdqoC*lb;y#V36OrgE}% zlsCeLpUw?_!oVr0BkUQ0+i(DCm~eP^s6mqYtuNvZ$yWJFfJ5K?=Q-;9a|OYKQGyU+T)dgXF#fZCX&(N#{l8CyKHMaUhaJ- zpH+{zUPJtei;EfUM)k>7Pog-1CM^VQi(w`>2a6U@bJ@U4vd7!Jly%D8X>4`}!Vk=U zV}gf%xs11w^{Hx8MY-uN7^?MVJ@bGZK{kPgsZ=L=!UBmy<75;5w*a*WyGnzJY5Qx~~%4b?O!_y6@RGeJ>-S8K+ zc_^>bIl2X3X)|m$Qi}(+n{4!qr~qeK9XdrX^XN}6{ya(YOwQ&x-M>PT#fkJQ5Kp|) zkBZ?q607~>>+a&$_vRZAKq6cmFI%$B8m09>WF^=wcxdk~*W7N1-oO}f6c{W0bjkEN zOr?Ay1o>@7kWC~!#0h1qrvwlZKXri11bbSoo@C{RVsaaU=}n6()w}#AFo8%*J546^ z44sCQhbUFx=rIg)5Z?g(=w8BE_b2}-v8*ww z*>-8=Q-G9QO=z0n|n zmCg$tgkvY=4^)3_0{rliaWb^-E05vg6CWYsW#RN z*JbQUZRS%=Y|;gKxWpc*so5_#g@R^AN>J6azBTgu1#v@PmLV&Q#wlYC;GUAo*?$bp ze7ds%EN_5R6!IwK%F)ix4N3sAbT~kIhiU+SSB}3+8Kv>y3O%gMutN?@IxW0}O2r%K z=O3%-DP3pWu#Mz>P7AOMVt%WoR9o`XQw2`yTc*ZvAin^Ar6;CSZZW>v=rpzs*uGH% z#0pvLdmP`mMJJueT;|#J7<>2X%zL(V75AO1tiAg;d%dRl^eN}7>A`8wKhcFy#tFL= zR&CshKPgN#nf(y!;7(%<)VxO6QqA|5^?L+vYSS{lK8@6)QQjo^l9v~w&_F?5S_m{O z(@g!P?;6jsQ6>{fO{ry^snHq6h2` zHFb>9JN=*dMVpLOXRB<~MGWWbo2=e2{Y|b+ZnE(pya{s}(BnwP6qEwrSf2pwhQBrNAO(GAIoDqRPRi6rf`qKR5 zj&@YZzBK{GrS=0agezBJbT(gPCD)7$$!z43vZvwU{i51#X-zxCpt0nqI)O3}`^~L% z2#?_7=KgZW^|ej5!2J;Pfg8nSI6qrfhnLb62k=-*{(b^-%&{pk{r*v{G!+cf3eP=1 zOeL1)$_2wrvC2s)K9V6MuPv2JDR%1MY&iqPGt#j^pqf46@s)oBkmFE_bb+BB7c$C2 zGE_qapxjmow;zJrYrv^`yV`p9qVQ>2TM<{Ob}Dt4^-IG%v+J_+?lRj17TH*-_y#R3 z@OcT(Yu-H4#f8+B^|f#Jxz|h$>$t-?cD5}$UxXPI=iP8eg#Q@LR@FOGCH7S`S=n@g zUFz9fbMWX5RE^2>PhQqsfBe@m>oK2f+;~BKYuYq-;cC_9>*_reuLw4VF^ zH8*P2RvG2vu%+uIoS@J6P4%N#AV1!r{rKM*qI~No+W|-1ZIVi8o0Bx$*-eyRjw3oS z@b=7P`}|-~EZ0 zEB7^ux#Uy7p&A(t*35Ho5yDJ=?ozPo;pmZ)svSn>5y@tIkX75j2TaE=ya~m&;q_2$ z#-TIt{V2e)tqKM}@`vxDX+bY5IB(5fi8IZAc7WA$0E^3#l2?4}n2I!FHBp#w49`X5 z7{ot}V>cPVf+2?!&elQ9hQa0kjztlSJkmxHi(no{$$CD zV4m}Ky$-J!7Wyh>d06N^&xuUfs(TaxF8AxmtcW$)Y@&Vqp~-d`dd?>0je)~XJN2pMy~^qdEAVSw~LSQhZ~ z|7w#2f4l{VCn-J74iA6G44b{){+d1Tx=L_Hrap_3YHz(zxoQuMq>9el-bV4u3;wZ|pDEI+Iv^^Di!un$)QS&(pQTh() z(cMx1g!-jHa{`eO;Z%iV8G;rM24F-*Y<02$mNFr_lt2uqe({lPC;hN~a5pLZvBqZh zzOc=oAUm1Q1vSN7JhleUoh{#4!aoW|2D+$sxNBnVqkAB;4wA#XmhRxKX5jTVuCZdT zaABPLc4Nc06_lZvchibPmW(Tvez(Hj%7AMf|Hbgb@tphjzERo1y3K79p*DLbl{uc| zAS0?szYf;@+iAM%8AF(b83=XxV;&Sgm^gd5J)&I=tahPg{U6d@E&6K5(p-Nz@l%x8T{Y{QCC%wiHD|{<_gV3J! zmm<~%sKQJFw`6P?wplxKkjE{KGa<>z@s}Jk^OHu`Yz=AV%2(W1c{COs zxHoE)y<%g=;3XlN04siO^#@U2HAon3ETrlezR7G5E*`;THM1;u2tpy`FrC?i7L^)` zYD|1pON3;xE{hra3yd@K>tzVI?49?!6@jq1o)4S~j>Q*7?UOtP`T|38*d9b=*BQML zb7R6*tlidM^p|_QA}otb2Vo1&@n5^4^;3rrN#lz{pmOBU-yAcwAGk2V0urV)ST~^% z`-7JCiT^)Q1(+NQDH)hWJ!AEwqmsL8L0k?h=Ns9gS-P#Uhx4NdY5FIX_P&iqT`QWo zL~a5w6&YaJI!iy+Y#y*`R2aDFuJ%7F+I@a~^rNNG`@nK~V1X%Va45olz0Cf)c6)Z5Q)`&)fh)fi# zG%}=+n%XNZoP|Sqh&+C$evEUkzdy3?d?rau-x1sIB%gH}Td9QG_80cvuw2~;V=BS> zyMO#1D`-iN;9-OyES*D{feqY*jjRz=KzpxC7;#@k@E34qeHHV=VGlmJzkj-@8y@%f zJ$?GK;=|TR8^C_VTW36TnP&N!pKl^`G*U7n*R3g?pQ6D5* z{d`9nQ1)Q{m^SjfxfziP9P()-$!=raC0W$RTO#Y=>W9(WP&;yEi9@3N8oP*_uK*qi z0o=z$qHc38mGby(>fmg;2>@Oenf>lt;)Zrhjfx= zc-MFAdvqaQY2)6%uhC+3^;wG@{l)eU^_gj_6??LyJ7N29W8J6=%6qC`f0UvGeMe+} zRXDIOxBp7`@>(pv#w`f`CyeRR^0VG#UTUxR441>`>NsdtGo0_}<%%Fa+jwfHvSinvu%Qi?hqZq&Z`7V2xi9j^so(1 z#v<^>(p-)(3()TG4CKU&OgL$t;RF&RITh#HUDEr>fB%WgziwjI#_W}58CUo~?hCYvll^*lJZMj1f%?{~>3U8c zGfBLEak7?tBRq)sqA&Tx%2JQycV{Eon5mo(uQ^3tu>>2|_eJ}A@W(M4vv0<*=1Pu{ zP9{!C2e+4ZjUUoYc+3A@D}VN6NR?r_uqp872u0iKUE%iHv4EEetz+M$?}Z6-@Z(weKo z7Ch{h>KP{?@SG!;htIz*eg`!n+#ngqr=NwV1f($iA4LQ!M|MH!xrd@)jR-~zmTeRpJsQT@NPVY4Af23hzoy+c0|pwXeF zAljwXV7`(+k1~0@u&trT8^B#=5JWratOFS!?3Lu78N(Awi=IOy9_PnOrF@<&=O`$pTV}r*2KkRM z1J67vdC6mhY>d0ujJi~0YD|bhaP5I4$GhHtTnjM+pwPzG^sa@) zg`0P7ZKPHOGA1Gbh7)dthhHw24vGnL2x_&YBaVV2uIUw|U*m2WPc1wm(5qj)Ct?~* z@hj$39m^R?+q@_cs@Lm10q+R>4>FLFx8L)Ie>1T~mH_w0t|~Z>9R~Z!<6vN#v?K(A z>@G3Uj;0mukVd}H`RrvV zT{#8y|My*}fB${(Z>}mKFzOsIoPp0r+*<_v&eJErdGxTdOW$u_(;o#Ac9@~!gv6CO zM)sTM{8&gpIUxzfnYu52sCVE7NkEzfh7;m~AVmeuM-GB{@i6dv>tMZk6TG+YK=8pm z@Pw^P%u%E!29iFN@b8;(Awxde=NVnTJO@zKsOeZV%GQvXMSkZDt6>CsCXth4If5QG zj&$4qG|uWPnYKoc2V5X2SZdil&jN^n4p*4f`iZsH@S)S#s)K)R1-RT9 z9{tJNwhe}546Q~UKOYHz0E6Tn0$3@cG{Art1)1>ke1CX#lqf8Pf5&;|xByNk!8)~9 zEw=4yqoZUh1WOJI!-Tt~221vJ9tx<*NTp?|FW7+puDn)LrNBk)FflWr9+Co_WtRwu z`-j3&E$|PCE3{uH z+~tn|;xO8=@-_i*-1l}`aRz=KVQVK#mH9MkY$XsyU5|+9;_c5RrisB8s^C0#TKbRR z`U9B};Qs6;xNqK6V73U-3;h$eryYh*8niz%LLCDZ)@hHB06@Z;(eD2k69D;~g_ilx zc~1y4y8=!EyF(xVQ1Dg8@RrdT3gi7p-5h+M>lgPd@LVy5TBvmP=~4*LIqP{I5&-Xa z0RE%{Km@=*+*rfM?pj#1zWRkPAA_yHNSKywv^Jz@8zE}ojHCMhcpbEG1Oc|g*>)SP zjYi8;FesPHL;V{FL73w*mclAYec+9QA3Aob65+`bNqtZ(^d3!Fmn?AWdq;xq*ZrTQBv8vA^krJH_P? zG-0qF^@&dF1%xTr=AlI}_df>q#Urr&7e58}jjIYgkv>3wKnjBa+k(<_XJxuSwtth~ z+wPE-gk%-kuw%w{p!xv)aT|`6ws7bII`h_lhxW?SD^0_ocbK}KKIe!Kz>Sh}81O}z zFEzh8zq2$ka2{+ag?i(_f^0KrKXw>4{^frJ_eWR7I$;+FwD}m>GmO3jz|?j3znx!a zeb_?!o8uhf%REy*pWljvBz4O^Yh)UA)9}dInT1BrM#Qnj<-JWX_RVz%)KLaN-7hw7 z!BVruO85EedF9{y=C2+6z5nKq-VpI~9uACvKM??Z!{=bn($d_Aue@*?wo~>0k#e$* zsim0&K+t+_A`tYi-Un7t`@d4DH1wKy1lzXb$n<+p9NqpN{(O*vgy}UBKihQ&RBM6h z{;nr`GM8R>2+Gf#RKOpD_;igwkJ&AfJ@`J0wF;2s4Zy-SP&{7qI;cQDYZ9eCpSJ`b ztkIV{q1N6l7y)SR+99BvK>!pI0e;X5lEPlQrT|7WUwssA-&iDY`8s8;hlalGTTH4 zf}Hl82B6WU$kLur`FREu5rB4F51L&x(w~p_#0}@b*w!2O@`fDg%NmbCXRPgbq*KA; z0*Vc-3ZD3vC&=LJY#J8&bsm17HH01^)^)~6329RNPh*Nui?I3N`U)8P=Q}$;{1KK_ zNi8F{xO2uiy5Z*Yts-`R?c=Xp_=A7;$Nw%BKgTn;X~Gi$u;b~I37Z|;S)7@je+Uvi z<#|$2ssm8b=IY!a)oU8$&LskuQ3rU@E<2{@k0d4$fq~;V5MXcYE68KvR5cIOE77OAy*U-j80)nTAoCXQYx!B0s(lYQH!6dP z+J8nZ-lEon@z0bxF|M#{Pd^l#ZWEfg);QV1`)UKo668pVlxai{#oe1hw$SZsAR&#t z$i=*tqIPbx8w;0ZD2hvrzunIdGmjuF}u5 zRQ>VRV$g(+z8t1}_iU|HWO>2FO8R8M|hn^6DdCUpyx6YuKqFYam#~ltsG-(bcHd zbVqgLTsyFvBrSMo)6rU#BE0fmVsvlO#v{OnegrdMVyzRo)XDJlN;^id=>;%~&LbRn zmo6ks0ndW&j~1%`7zzm+34VTC*h@qvr7}xcTHA%CN?};C(@YB7G)OV=xj~qLmMqob ziWopGgpvjJnf<_U?bALJfHh;sT+OLt*{7OJNtU-W0apYpNdOGPCyIG-VZ%mNk0Ol{cWdy-Q~_Xc>56e-%1+N%0)NOZd78L;*(3v`{|KLL1HnkeyBvlIo3Z}TTxG&w zXOWS_is_P~JUSwJJc#V?$IpETqLc{!N3#2$=iG69l9?i*JsAT$uNY~7AfK~9iMD3F z1+xq=6DtV1pEuWI(*`ppc;55X(XplHY`bX^P#eHu8WLZt0bkvg?Q=T=>M#Q!y@@&M zA+?w4VMZG7NN$uI=j72thn=_H{dp}m<>RVWoqFd?wb4;w>JEqiQ>?Dv`SoufgL<7M zoS4}rxZ;{zT9%S|I_(NmJ8!JY^hOy#jK~RKz)~tXh1?k>2v{mMklHEPAV3qdO{jeI zVVM2KvtS1l2D*32fJl1Wq(!go-B?@+XeN%-5a)^I&MhLFzu@ zs9H)K2=6t6F2O+N|*gaI~^gm@nsJF$Ka@Wz|ZC$WIwIi0lIG_H?p_T2NYz2r-VL?nrLgWq0jvq72i_vNBn z88fw3*K%|sz>UL7wJPw~QOMtGjf^b98*~f08cK@}fB=NnnUkl^w6w=-)T43zV#bW_ ze=>DB&AOtqxV5STxCHPGIHuJNzcveZ{!IaXc_AQ^Ni6`^bCcJ~N4NW*CLD6L1+1wy zbSRQ$Up^YvRr;;Ahqe7!_lV4{0ZGOC%!L?t{DbM{M!5n^vIUxHv=3k#3hO4oJSxw- zYNs@<58x@ga-KW}=Ds=D{Ief|k1f<}bGn0`aEGY$;fJB}@pEAdBCqv5Qiw4uz38~v z71s0zflBqKk$H=FxMRrtSdiWcqy-s7u_OTM6trtLOvBWV02mqGG{6Uc@+R!GI3^lH z2ydCp)(Y;dgK=_Cvr@jj15xYjdc7E^EQ_qUDm%-wwpgZl`h>t^06!pb-=n{zThJ3^ zW+6zkW@qNkCU;M`V%LnU4gg%kCmphEL5g)1*Wg~g4}QHael(he6c=r*{pSVUCrpzy zOeF{yKL=BQI?EhAM}}lbN75k_=`xS+ftg>v0=58^jaH87Q8raS)fuOA!V_15L-Gg$ zT8rbiCfY?#>t$r!L?l^ohkCY+W`y`0^XP7v{d>=V^UR4*ZGB2_b|@@Ye)$nu^Dn`F zme3!PS7^hs=X>YR<8GM2G1PlHInPVd08w^;8?^T$K}zRBtaHJ3C<16u%%xMXZHrjpTkvn!z1DRn z+kV*m>>LsR0krD1rMm&pVio63=oVxGf7Xc%@cqCnInF~ZziDO&0CJr!EA=uG(1{ie z4CFRz;NIJSK<*@uMu@LeDHYzTNNNyZ7C&5VAOIjvn^n_LvNoy0M*Yw z4s7>~VoG^NFd$XyEv1^=!I|^8>N1V?Lw1V*r7+z#BOojbz%mPLv9?|dJ2S-Ig93pw zWvG7P2{7iXu>BW5Rq2n@6u9H{ihGFJE2S6Dw2F~7Y}gJ(iJ-lG%H4iw-K8Enbe_;y zBI~l!nPT{?HtN(;O}Bu~(4_5Op&W=R1>lpio{jT$^kR$TN`^OtpAPPCHrf?;v?&$} zL>IjXVJ8~Y=8`429T_AMyW2ajGoFiNqvg zEW5r&m)24&!`Y_06NvNoZ?A`GougUU>=Cy8%M&ik|^^ffSFSJ;E;#e84YFekw@ke_F&r7 zR-{{W;aZ2v?hiI;_g^>QOV6WmFD52BoeFV0+JG3U8DZC!wJqSQTity&{zGrmmRo+?VtU4eS^bD*&vHL` z)gtKjQ7B~F8%-)1_Ny97Hdj`J%%m?}!j3B+ zJqzUzKdct`laU77Vf4#@m}1Q3gONP|_hql4Yd-2T=!r*w0J@ zf;(%nwsY8lU6gWf6(;=G!S{8m!5`7UdxJq;RL?Ah(mwT=0{3a)gQkZdbD`Yc!Sh%> zd!T9CpfMY0yRzoo^E=$muu}knTFlN!zt0Q6xpWj}zVsv*i&fzD$;|{n+jE{h1?7*O zZ!J5C#QyL;*+%2>h!~`+yoa9dsY!8_4r7$9YR4*|n*ai!Gr1g_zmW)_CN4?pC*^GaD2a63@g;XMG^8iHwybXF zSU*D@qf)_T0J4R0&h_3j=BkzIambeeKvqTsz>e=pVB6N|7N=&m(*V$v0pO{+TfCn2 zT0Vww0K;}{ErS*wXatbO^D#Js@(X7a_{ZU=(xs89MNnC?+S{A2BXF9)!BwAiXsZpt zRR#cpT*qqtS=|Ne%s!a;;*-E;%Nf9Hj0bkD*uNk}|6s1%TD~H8{>!Z-YJy;)I?+)t z6Y`s0tBy^uz74@qq_glmJnZIK+Wcy*V+2J7@1{}+_>N$4RB>`YU zp#TO?5wnr~E}a0R@G>}t!H%W~fb<3~WdaZZuwzq{hO>Kd7n`4(Kh{zcX|@5`Ixqn3 zc6H*{AkH6yI=;R=^z?sRl+!Kuw7}Z}{0Cec`dmIE_X zB2%Sl*MGE+P7*F&d$4MTx&Uc$K>5slu+Hv>>Sr$ltJsQA822F-a3-#``pN|`4((FH zl?gjW!M}EyBF3sRsWaVHIEtDvmg)iqR|S>pTytPv8&u$6=><+AWan$0^A=mxqh zJ|Pe^&h7*I)$=MfI(=?-nD+0qY4OLy{=w9ytFULt-8SmzH(4D5#N4zmAA!oN zj{?J;@o`syM!k>cPJ{L6VG(?|t@i;l0_?1vf3CbKWhW|)u9Ju7#JXQ&l#v>65+)v+DPfF%Z&Q}y zb+;3y;?8!Y{HSnCJuIv2cDh@2c=6fi_dfFQg?Y$s^~VVO69KSO$Q-@)&zw4a*q)uQ zK#*c(Eltz^S(~hvbZ>ImCS+S4!zuxYRQ4^~)-GHQbP}OK@PBv_>`y?BZybjJ@8*$RQ2y}Q*870|2Zbx! z?KJp&%rq;;d%m@TB$nOfLcI2`-lq-#MF(%ppxyu6;D?R1K;9HAl@0&t)279624?vp z0Z@0lV_$p*C^qdBSO$RWQ7vbuAK!z$`}glYa`4a|d=-l@XptfUU`L=o>*xMVrFuwA zjG2tpxOJZBvir}nL1OZ?`2T|~;T(o1#DW24Nu#!Z3IE}1l4ggXXTAdFYmX?99|!;O zonzb?ZUdUAy`N^WjTdC6^7F8^Z8oOi5MO{j4&{%X1N+jERuX7T0#{}kPv>MWz0nzH z59h-Jl`$vY&v`%{0fyw``F0)ndMzI~!3`$MC>FcTyBJ=0>B;ulT}-OD|0c{)QJUa58@6@YXmcHdzykWV{nG~g2TCN)difj} zhZb9f(u{c8f7b5@1e$amQC3FQgTpB|Us7y4PyP=2mg=V-1MB2o6$p+~;EJmMPo4nl z)ZSJWA8mNHlo-KfkaVu`iR zefw7Xc9+9G!RNrwE^sUu6ktvG_)7Go;wRO z-uy;Hj#>aj0t^H3F$nIgEC1c;nIuX83LdpkP`RTG$xZ-^!rbB+0CBKSLV!d7^!M;_ z_xjNOg9lpJvv$3Zbrb-Mgq@OEoxp1@`1dz~4XLj)?*3yP9@i{F3v}6ObUiR0+6&h6 zr&?Py;+Hdy5{%1O6y>fz?U;@U?&8^*0Fm-8mUmdGav}uN$CjY{iSsh0UVC9jO#Mef z{?bt}5ARkBc>Fps?LTX0f%En)6o~{F?)ha20J=d^D@Lj^yQ%{K1?R;DZPA7UjU9wT zV^|6h@Ekj!pzq!$&-tFzdJs@4z%W=DMF4bbtrtBM%mOG<>m_#v0N}nqd+5Z;C7@j} zA^?U!3Itc4czV}M&py8d+qKrfeVnL`mfagB)i(Lc=1?X(?*1bIpxwG8RcAHu6`lZ0 zyQ*M*%SpSUprEkjf#7pU6UC+>!2bi_8#Mx;!&sN|25nUPKV2i3 z5)r`Eb#k4AnJ38ipb%iLbr#=JGPCu~Mme^4+Yt9K<@FGugQ}xwHp1HjU&NKGF*QF|!1LpvhWATX>lIPYn53lK|5EvIs;}&3DDK z-Ew11NCRLCRj7RO5m^79{#v%a(JwOMz-CHNdirFlg9TS)R@ut@9aZ-~LAK1okeubZ zWzG9g#4TV(21w0F{}<2GfB(J;9W(a>%9#yG1IUU85wOr84L;|wBu`$V|ES^gq>7pW)2Iyfg7iM5Kd_J2ZlED-ctw$-?%z7OHl z%ssPE{m8?uWhD4=#%_xKr2&)g6ZoG5n~DTE)4oHt2XvGuVEj3|o>bK@J$n*(UBm1T zkAr>TAh3l>^19FmAZRp<7Q2<@5aVjgIFE(@mxpDF!-^e-S{u3@R`X!-Lw+t(MCFj` zdo%l^hb1S8HB6jOaBCI(8!N3@D>U*&7r2x4ptj?bZp>CAk3WTmleY~ZV|w9#mS2=m ziobfc`B+T)hxIK0RMO`&t?Y)W|#JxMoXLaEG0C`DFMm= z=tc&_O8^4SgGDf5KH-eprYC8-RP&5;LnZvX8w0KDM6isi|2nq)>;JkOSqjkCb%ph9 z4}yDNtSIFV70bme?@Korg-#XGumvn z@ZQdacfL;20vN14NxiRb&qu}}=FnP$_hcKm9?j)=pJndz1Fr&l%VO#ju1!72&pU!$ z3%eFuVkiUjwXrPTE1muyrUYRJfI%)ExLcv4@ZUftjbTE$RPN8Gxjs1O4}g97XshzM zO#Lrq+m+1V7*jSJ?aNTRo@%!=Jfa;`{8@(sf1ccJHC3w`(`dHh9M=We3$isHoQMdD zeP29(W3dXQ4?nCJdR=Pg>ps{g_R6f5mS7IgGXubkMPSE55Et29k@gnNp=d5xDoa56 z45Jf+*5*EE3D`#M|KitQ_oVIwcRdmS?A3N6@V~=(ie+l1E^Ys4jDAdm2w)cySetgu z6F~rA#&2H1?=S`_+U3i2C&#%mV}5CIsRF(4z)t0&MwD_sgW&UX^Umupzq$vu{bWof zgRu;A3pDKk*phOrdawrf*9RKdA^69-tx08WZQE8UnLD{YpMxO)e(8lXvR*m?=<(y` zoHm^$#;`|v*n3$4_88C~kgY|%o#UiH^Yh2Zr}GZab|xwXh}noI!1jeh;5>3Tw@B2` zFG1L`b#^~6Y-Jd~7W9vmjNO0GfA?Re-{q5hcAMkvLW@+8+j8XnOxC0Q8%=Ei zNxHQo+*oG8ZDz0)+*kqs`h()rLC0mFX2Q)?2(GU5<@Qhk9oy*Apg{w&+vn2gd2Qoc z0>^CE^OXUVG$4KJem*}M`0(uGPaUvS$`q9VbX%tA7GxSoi!73D+ts<5ndO#UtafRa zT!X7>7vQC808kNBUe^GoimZ_@l_q~7LP0>w0$Ut)*Ue7^;BB5OLHVV#t-GISOH~KL zUOpM%a9n^tYgHESIs;ku_j#L}$n%~Hs@Mh!SC2vV1nz_|E2A|ag6OIZun}9dYaW=# zm%zDr6l(v^&jdIt@VvbMto`#~?3qnm!LBOsntY5Tj>+^Av+p^sA_O zfM0uIoj%?M9(L-;ve&45jD_v84Avm9nkoIPDee`$=Dp1U{45swe%{a!;H?=A_}-e^ z>`oC|Gu$6tqh07;!2hi^@P2qr5n=}0aRz{_`={GJ-JsX?^NFzQW+&}p(5&^`%?I#M z5YPw=+PduXxo`of`GYNBd*-^gCB?SHMe60_-*r96brNE2TbP~O&lm}&5CPDmtLkJ{ zrfF6MuuT#GJVpR;ozQ>#$>F25TAeij2zJS*kq>!U{T5z8A6_plcD z+y@gVE2gg@)-EjTGMDESQQhgm$g;13JLU!IoV)r{af+2n<&ZEvJZY~}1gbY!BLYAd zQb5Y&iN(0goYFFQZVL3U)*V}}%ju^l5pM;T;{16;0Q3>8Ao!2B{hJ2WR=h^cG_oBV z>WR{jHt)w8>3#FyTs+e1f)FI(4sXV$sH_dw_!lXlhbKM_lTk zLoz!cns1FGY@g;BA;@H%?~^1bxcr+s6Dsm~%iC!rgFtB`!ey`5oA*R~pDk9Q{M;$n z{L}9jQ=^T2nrBL29o?PGREb0kjn+0oT54H@_5K|Pmh2!2m>fyZJ3z$FTnqgt|}Yp z<96X1DhU9(tAzihASaIz-s5o65X%7IUEj;S{ObV^H$<30vtx(OMV6ED^;#}K-B??S zib0u*G344LV`Sd(gv+J!0oDwjSjIthBvVcR^y)bp~5%c}nm z!hkaVL(EQ;ZN@nC6R{xI8}0s2Tg#S`%mVPbtJI1ByY$ovU`sP9*p5FNbZllukMtnW zS_QbFh~CK#dFa4VKofTu{n&0lqyT>A>)2xCrHZKI83B%|(#_3IDQisroG~5>4{3UD z5%gIZex>4w`xqosCJ|>patLaF^DYFp3ux1Z8RHcj%!7+b^?1Csc9uvg*UzTAfq%T; z>%#YYUekxNE=vjPV#|pLIEOEf0JtBL0=RX9Kr7UTrSQF~HtTr0~TQue4}gQWnJ z1<(gJz!HG9T0T5-$>#V&TUM`m_ zV3@lS_K~~BNe7C9HS1ARiw0lY>f7v5sd$ow|2nU`iu~Bx-fE`UrVYsTz&x}Itn-Ij zMZgfSNtN`Bpj_wF|4!gPVvG?uZMv$DYdr0Hr*ns87g#BygJhOBqDpA3Vi%mDRtCeBzrqKlBj5RY{B;f^3J51N~N0}^9FYM^wMpg{(JO`pI z0R-;|K8=tJD}`+`qfRyLGGrogSp41iwTXG;{oQr&Z$6;i|2*LThu0Lr*>BoD0|Ngv z5^DinWBTo?Eg;R4ojDlPmEarOIkSGl^+XC*vqH{Su5oS-0zzqX?Jt!|M%}IV@CT+d zD}YJ>@&P>drB8ism)!k{2eAelFEJ7sP^P{og41B5CLL-Y1prXOwtt2(l>`6|YzycK zknegKf`B|E2`(H2rIkh^+FJaQ9pGZ;#Tsv*=<3*tQCef_v-0SGOQo7yeY( zJ>RTBfWIs2>Vsx>gaC|&@E6PqTlk6Ri5V35T$y9bEEastst&N(vg!&F@MOtMr#XN> z-}2NEWyWBc4BCVwhcbu&6j_o17K;&h;#+TAgJ7*D&yhwp_>XG-Wd=pRX~~%QV^}1( zNF%nE<`e+dZ9}Su^N~V_J)V0*(3Iv%kLw-C%|MQ%cAgC;yuC%Yf0YD4J|Vzzxia^q zPk(mdpZ}|Wu|W@;2!Oua7sxDU!~DqeFCG9tr702BA-5E`YD@s|SdfQ+9a{j_{k{SY z)3m^z(%t_ihA_Y>IRZ;&2m;C$X){o|d^DN*FKeXDwry6pA80u(q(XWp<{j&W@$iCH zFrN+%;!5XOmnMFXBhwsDCC64~Wd(@j{fdZ@paUrYVL$U)ST`1tEG_Ys+PUQ(*Kgv= zYYf6&^pxM3u~mQZh8Um^#{NZc&g_G&n-7rVhmo=M!WK+a|4*bm^QK^3OGgW?lR0Oo z;fJQMUdyiKmcZYV_UgTP6TF|_0sqzm2v!96U)_?ml{{+i84rS^l7O&-h-&W^+wr$e z0kmzobI&S{A^>JeQdp7gssh_PE4wUU7m=i9Mh8+1e=bP~JZDc47Lm`1vSaYr@Dw2y z{hoM_|G_=*-oB+zt*vfNo#HkBwf@0Bhi2_R4H`Ql3)u9W55}85cFiID-}}7_d29&-I3Maopb<*0LToPvcR1&H#@gC z8OZYX4o(Nt%G%N5VFo}HU*Md^I-r~_SlW$psVr09`4rD7T#RHEfbBu8UP}@xSe|=& z85n}3c#mxsD!UsQwg0}a=Ok$d{-ZQv1Wj9Bwhcz<(Y~tZ!uFHyd-uSLxFVJ(_8=|b4dXqY_Vlx+PM`0O-R-fMr8=tIuC7p)=|5)Eo$xf*}N&Xq?29A zjS=3uyDn?Jy<~q}9)SJWVW@xqeF)aKLzVbZ5CBZ638#U6)XGk04GU&u*XJW>-b{i2 zkg($7XZ62%3*7(jJ@D_Y3m>|z$hxR~AeNUPS=H&eB4JpqCjCT21h`m_FXqwxZ6$Vv z@4@F?wt<%|;EUq-4le?T1Ls47WQgoP2wFcg`nNF;#dXh#bKEnd$hvE5DW`6O|C3wb z-&~dCTj9?MuWP-Y^ed?M0qM53ql%m|YW@chfS8s^ZU1Pb)_|NF0USWjUVz;}8|-ki z17ps5-R-WwL?e0MY99CxHlVas2LrRrI>kNs2yG2A0Hg*$4to-d`rMqrYuSBySZu)v zrkxBR0w8CUSgllcw+6oL%tu3e;?SD@F9In93f$Wn*hJ8ass9+Xa3a*?z$^eP14v{; zI2M$iI+-ny=9n4wqG|2F(GL8F%Kn%xUC$ts@b?gS3!s630#(K9Ke-Np08h;h0700| zmm!+QoA!o3_ujtU$Q&?sRpqX?b^aij`xk)CmDPPt2NakI;0sWUKSwZ?p@o6qjr&}` ziJO(?h|d85P|a)pOx%0(_>xrPufK6^WTz)f0QSx$%{Z!w&kFXqUKs$H1G7gW0wtzf z;l6zfw*Tyh;NN%GgpC+kreLydH7gi4GIC zxH}8w&{vMorUqT}5787ifOP+9w2WTBv7u2GKo1!J{bW00#*DA&hKaTW0P^yiHPzi^ zzS5-?U|2?0^g5llBvu580s?G+5Oj1C9RE0dIbKl zlSEdE76Kyy$ld>+y*FWUB)QH6pT~hS@4BnHy86B;QWQx^D``ffku=iS?9Q6)HUDtT z)@E(jWFwhT8i^7`4K*S~k$th7eO1?etzy2bxh39Jd zL6rZGk>-EMu5+y+f@eibOmQ+A#sc73Uuca#UJ=(H#KVOIlZ-H7=Gn#14k7&N`Fz?+ z8tpMnSo2RZZA)nueaUYZJVRaHIlxY_9fgrp*tgR4E6;I{=yM^U8Yg!1Omg-modA+< z2(C{1X5XEEoRezcjB82AjA^4@zYOEA7?InhrjFRteK;7<|2HNYYo2#0m%YNTY5lTk z!LO=4RjL%7Z8?=P0D{R&0C@JF1^;M#ZSY^7mwsD7X0U4Cc~NAEvP=uiE+Vy76(AW% zXCv;A%s(RU`yE;D4|$b;--GzkK6L-?Q!XY(*@43Y#c2i=8So(Zz?NiaiAqiA_#YnAVdL3_MUfRojsQ}`zmxHpivWhLC4W%*VX;!uYlOL ze}zb6!kT~lIcC`QIs==ZvZ&CUIqfig2t9^m zo<<8z6yLXrEEODcU#XssF@C7V8o?j2GweK9?Xt5l2@>mz>OLA=*I06S+8dMyk9t0? z1oV?RnWwa?P9)pWj(pA(f%Oys7}nX%jwJ;E&ax037U*6n{TY>^rPN>%z(iy?GY|(o z4)_5xsZ;AQr?s}^!hfTjdjtVa50AmSwhrD4TV;cV^Q|&u2BQ6Bs7iVwGNq}4FUzTCvpQw@KZp8wh%;&7i7%c`s@peC?{oohP5`1wl1X!KWuIkGHbsTJXm1HzC^ z#p5+A3}FF)^8*2KoUn7ixm0o%O*J&iU`=IX2_)U#nFLr;>c1W8;WMYsWAp31bAzjR zmjZhf|5Mfe8waS6;eo(OuiQIaDvNjj?lb8A)yI5oU8sl{2Jk49)(xQZ{f~g|oL`)^){?higab9v7sJ|~s{ zD6MPd%mWd$?zFZ)GtWGrovQZ=K!eo6QO9s*j>6`_c^WT33;ipeT4{j_G zXw@P3raAS-7RD)dSO8#>C8j#2>3{g6KUrl9s#)3)%6`4n ztUJkGR@TO1`zsdwClmHB=Gt|Qx?L^f4zw@=@75N3ZHf$A_F!!J%4bAPqZ$rV{YN8w z6qHrp*#Xh}?_WUYzx`4KGX^*!q~`Lrc%46gUu4M(?HT(m0u?f##Iyibg@EIF!Ypp~ zzVLV2HPL!<#`cJ3TaW>e>Y)&l(HkN8dRv;91(1?aAWOc~f@k4o$fQ630I9~4fB*@b zPcAk($(&+VPJ)lUpWla*?|%TN?A86p&mnrW%e!-6X2{ZLpA<$ zcz(=lnjsZa?7u4E?PnIoz*agnZ>IY*WdZ14CV?o7xY+2KTp1!XGl;DD@BY&laQf%J zfOz}ZD*y9E?Qrc9F8GIRCtD7&+>SCk3+7hx7>#J>S@1iEV~_}-qv<2#txfBULIG6I zv(cUAT=6!5k%sO_zXV`Thj_})(*eVnZ@RU)RsZ$(zKN~dZEFY%0E}E#6h<$9dTDFx zGM9vff^SFw%vQP1Tog^po4*_Jm+pb#Izsr*WJ7bdKg022d6_V9h-TluT0(cL*88r7|`nk z8pswnOWA-D2w?kxcfUYqFtPQs=c@T%Mh_}&b7-KwoR*>BZ5kb$__#1zd`_^&u}LZ| z9}gJ-R7V3$is8<6q|8F1v6xt(vbnLb0eak$r44B8$)+&}?N)13aOMk3##j+R#e1>h zcTfaGmjQs;|9Z`~{nG^i)|)_XZxjFkDuPeNx}l~y-b&;i+ggQ!Nm3}7>Hg?5h#&2V z47-T~K?-)*b${>)P|Q|m4>O$lVZo1)bV8~OT5`ZkB0wnb#m(`mxzn^em9A^X{fPiw zaqSTBF0bTEd1m3?$e>=wzJ@`_Sp!oK5ZWg$k&0N`~0e76N>bA^{w&>5Cfa27xq z3R?d-pdbvagFEeEYyS1uDpKO{ANyGPa|*D_UOOG}Z~ThiV~bcGU@S^EE;5%=0Nyu3 zH$jkI8#Q9Ol>F3L*tgcs+AZ%vsy;q z03Xvy0PfZbIPLmu*VrO~f_N+Ijr}Oq?2*L9X&j{3rL6O*lE(Zh1akxM+lf!$?U${+*7rUkT)EST{2h{~BR{zv5c!E8=;vD7_YQy<;ksg5N<<_mL7gP-2b zmn(3FLo8m!4Pjz1kSGe+@gC6GetH7%$2{e=671nRRicfO>`O zIIfu};jP4y<8HN_W;Wm9`wxH~bn^Q+kt_$QP}wsK1fc1vJ4b9LbU5qS_Rq72oqjOH zf`mI;Wsy18QPckuv#5Q%_Ma$Xq)wqGw&p_o^$tW|JO|=c3}t7BSiEx#!TS#Yu!UXr zfI|R4RR&<16VV5b=-?sY`f7QFYp39{AeE~k&nj?C;!+F3l2D|%KB9VDX#;FD%7H!p z2P{zZZf!vOkG}!!fALZ$Luc9@e|y&(lSxjNt+y)DmGFu-07jbhRU_Ve-oY3GSI)inqMFIPu_ptF@M3R z)&r`IXDNC_Ah96}{*%3v`HRDx0RW)A7{bq<0G#ylU=1S7q2fR^P0d=hbfoe>?*@>; z1yJnAedY4ZTD&QR@l@>!O+^4ytQ=eu*u`VLp}rS%SXt2M-M$2k@4W)6|I4>wx~Uf>k*oRA?%0D!HvI2srO7R>s0u0X(o9pX;k-Xz_y?7-o4VgT%# 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.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 VCItYF2In1!ZDDF{Qek{8`d_IH0SW*B literal 77579 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±JV~w(ThIDK>wg};C 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 Date: Fri, 12 Jul 2019 18:24:49 +0900 Subject: [PATCH 0200/2815] Update android icon --- osu.Android/Resources/drawable/lazer.png | Bin 39498 -> 39318 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/osu.Android/Resources/drawable/lazer.png b/osu.Android/Resources/drawable/lazer.png index 075a8e7184ad508cedd8fe9ae549d8cea696b51a..fc7aa8a092cb616233bc83a3328413740bb57953 100644 GIT binary patch literal 39318 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 X{r?ZpP_kcw2Ot$CjR&O)ra}J$c_$^Y From 341f2462232a5ecb1acc9056bfe7c511cd98ad8b Mon Sep 17 00:00:00 2001 From: kakiremora <32620809+ticotaco72@users.noreply.github.com> Date: Fri, 12 Jul 2019 15:17:13 +0200 Subject: [PATCH 0201/2815] Fix README icon display on mobile (#5341) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 590442f01b..fdf5c85b20 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- +

# osu! From 3c665bfb2970378d565669eee8b6119efb76a52c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2019 22:21:25 +0900 Subject: [PATCH 0202/2815] Revert breaking README change --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fdf5c85b20..52fc29cb98 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- +

# osu! From bbc3cbf5632fbe5fd8adffd0c9c29db639d4b7dc Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 12 Jul 2019 20:22:31 +0200 Subject: [PATCH 0203/2815] Restore deleted line --- osu.Game/Overlays/OSD/Toast.cs | 6 ++++-- osu.Game/Overlays/OSD/TrackedSettingToast.cs | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/OSD/Toast.cs b/osu.Game/Overlays/OSD/Toast.cs index 6572830d10..67c9b46c77 100644 --- a/osu.Game/Overlays/OSD/Toast.cs +++ b/osu.Game/Overlays/OSD/Toast.cs @@ -16,6 +16,8 @@ namespace osu.Game.Overlays.OSD private readonly Container content; protected override Container Content => content; + protected readonly OsuSpriteText ValueText; + protected Toast(string description, string value, string keybinding) { Anchor = Anchor.Centre; @@ -49,13 +51,13 @@ namespace osu.Game.Overlays.OSD Origin = Anchor.TopCentre, Text = description.ToUpperInvariant() }, - new OsuSpriteText + ValueText = new OsuSpriteText { Font = OsuFont.GetFont(size: 24, weight: FontWeight.Light), Padding = new MarginPadding { Left = 10, Right = 10 }, Name = "Value", Anchor = Anchor.Centre, - Origin = Anchor.BottomCentre, + Origin = Anchor.Centre, Text = value }, new OsuSpriteText diff --git a/osu.Game/Overlays/OSD/TrackedSettingToast.cs b/osu.Game/Overlays/OSD/TrackedSettingToast.cs index 454ba84d70..9812dcd797 100644 --- a/osu.Game/Overlays/OSD/TrackedSettingToast.cs +++ b/osu.Game/Overlays/OSD/TrackedSettingToast.cs @@ -62,6 +62,8 @@ namespace osu.Game.Overlays.OSD break; } + ValueText.Origin = optionCount > 0 ? Anchor.BottomCentre : Anchor.Centre; + for (int i = 0; i < optionCount; i++) { optionLights.Add(new OptionLight From 08014b1b99232e698e4118fe003661cdd7c6a560 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 13 Jul 2019 11:13:00 +0900 Subject: [PATCH 0204/2815] Fix logo masking not being applied correctly for showcase/win screens --- osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs | 2 +- osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs index d809dfc994..20928499bf 100644 --- a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs +++ b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs @@ -10,7 +10,7 @@ namespace osu.Game.Tournament.Screens.Showcase [BackgroundDependencyLoader] private void load() { - AddInternal(new TournamentLogo()); + AddInternal(new TournamentLogo(false)); } } } diff --git a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs index efe4ee92fc..a0216c5db3 100644 --- a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs +++ b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tournament.Screens.TeamWin RelativeSizeAxes = Axes.Both, Loop = true, }, - new TournamentLogo + new TournamentLogo(false) { Y = 40, }, From 0584ee9ce55c5a9e86940c66754f5722ba4d4cd3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 14 Jul 2019 12:34:12 +0300 Subject: [PATCH 0205/2815] Basic implementation --- .../SongSelect/TestSceneUserTopScore.cs | 97 +++++++++++++++++++ .../Select/Details/UserTopScoreContainer.cs | 79 +++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs create mode 100644 osu.Game/Screens/Select/Details/UserTopScoreContainer.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs new file mode 100644 index 0000000000..f7cfd4156d --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs @@ -0,0 +1,97 @@ +// 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.Screens.Select.Details; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osuTK.Graphics; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Scoring; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.SongSelect +{ + public class TestSceneUserTopScore : OsuTestScene + { + private readonly UserTopScoreContainer topScoreContainer; + private readonly APILegacyUserTopScoreInfo[] scores; + + public TestSceneUserTopScore() + { + Add(new Container + { + Origin = Anchor.BottomCentre, + Anchor = Anchor.Centre, + AutoSizeAxes = Axes.Y, + Width = 500, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.DarkGreen, + }, + topScoreContainer = new UserTopScoreContainer + { + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + } + } + }); + + AddStep(@"Trigger visibility", topScoreContainer.ToggleVisibility); + AddStep(@"Add score", () => topScoreContainer.TopScore = scores[0]); + AddStep(@"Add another score", () => topScoreContainer.TopScore = scores[1]); + + scores = new APILegacyUserTopScoreInfo[] + { + new APILegacyUserTopScoreInfo + { + Position = 999, + Score = new APILegacyScoreInfo + { + Rank = ScoreRank.XH, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + User = new User + { + Id = 6602580, + Username = @"waaiiru", + Country = new Country + { + FullName = @"Spain", + FlagName = @"ES", + }, + }, + } + }, + new APILegacyUserTopScoreInfo + { + Position = 110000, + Score = new APILegacyScoreInfo + { + Rank = ScoreRank.X, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + User = new User + { + Id = 4608074, + Username = @"Skycries", + Country = new Country + { + FullName = @"Brazil", + FlagName = @"BR", + }, + }, + } + } + }; + } + } +} diff --git a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs b/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs new file mode 100644 index 0000000000..05d0930de5 --- /dev/null +++ b/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs @@ -0,0 +1,79 @@ +// 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.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Leaderboards; + +namespace osu.Game.Screens.Select.Details +{ + public class UserTopScoreContainer : VisibilityContainer + { + private const int height = 150; + private const int duration = 300; + + private readonly Container contentContainer; + private readonly Container scoreContainer; + + public APILegacyUserTopScoreInfo TopScore + { + set + { + scoreContainer.Clear(); + scoreContainer.Add(new LeaderboardScore(value.Score, value.Position)); + } + } + + protected override bool StartHidden => true; + + public UserTopScoreContainer() + { + RelativeSizeAxes = Axes.X; + Height = height; + Anchor = Anchor.BottomCentre; + Origin = Anchor.BottomCentre; + Children = new Drawable[] + { + contentContainer = new Container + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Height = height, + RelativeSizeAxes = Axes.X, + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = @"your personal best".ToUpper(), + Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold), + }, + scoreContainer = new Container + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } + } + } + }; + } + + protected override void PopIn() + { + this.ResizeHeightTo(height, duration, Easing.OutQuint); + contentContainer.FadeIn(duration, Easing.OutQuint); + } + + protected override void PopOut() + { + this.ResizeHeightTo(0, duration, Easing.OutQuint); + contentContainer.FadeOut(duration, Easing.OutQuint); + } + } +} From d30ae24f581d14d2068fbec2acf69375f6ff620a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 14 Jul 2019 12:40:54 +0300 Subject: [PATCH 0206/2815] Use Bindable for setting score --- .../SongSelect/TestSceneUserTopScore.cs | 5 +++-- .../Select/Details/UserTopScoreContainer.cs | 20 +++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs index f7cfd4156d..3fa85ffca6 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs @@ -43,8 +43,9 @@ namespace osu.Game.Tests.Visual.SongSelect }); AddStep(@"Trigger visibility", topScoreContainer.ToggleVisibility); - AddStep(@"Add score", () => topScoreContainer.TopScore = scores[0]); - AddStep(@"Add another score", () => topScoreContainer.TopScore = scores[1]); + AddStep(@"Add score", () => topScoreContainer.TopScore.Value = scores[0]); + AddStep(@"Add another score", () => topScoreContainer.TopScore.Value = scores[1]); + AddStep(@"Add null score", () => topScoreContainer.TopScore.Value = null); scores = new APILegacyUserTopScoreInfo[] { diff --git a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs b/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs index 05d0930de5..21c16d1d74 100644 --- a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs +++ b/osu.Game/Screens/Select/Details/UserTopScoreContainer.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.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; @@ -18,14 +19,7 @@ namespace osu.Game.Screens.Select.Details private readonly Container contentContainer; private readonly Container scoreContainer; - public APILegacyUserTopScoreInfo TopScore - { - set - { - scoreContainer.Clear(); - scoreContainer.Add(new LeaderboardScore(value.Score, value.Position)); - } - } + public Bindable TopScore = new Bindable(); protected override bool StartHidden => true; @@ -62,6 +56,16 @@ namespace osu.Game.Screens.Select.Details } } }; + + TopScore.BindValueChanged((score) => onScoreChanged(score.NewValue)); + } + + private void onScoreChanged(APILegacyUserTopScoreInfo score) + { + scoreContainer.Clear(); + + if (score != null) + scoreContainer.Add(new LeaderboardScore(score.Score, score.Position)); } protected override void PopIn() From 922c3c89ae4eb9d89c6e5c90d7af0c2ae7222fd0 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 14 Jul 2019 12:47:35 +0300 Subject: [PATCH 0207/2815] Make leaderboard score use metric system --- .../SongSelect/TestSceneUserTopScore.cs | 26 +++++++++++++++++-- .../Online/Leaderboards/LeaderboardScore.cs | 12 ++++----- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs index 3fa85ffca6..1023294000 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs @@ -43,8 +43,9 @@ namespace osu.Game.Tests.Visual.SongSelect }); AddStep(@"Trigger visibility", topScoreContainer.ToggleVisibility); - AddStep(@"Add score", () => topScoreContainer.TopScore.Value = scores[0]); - AddStep(@"Add another score", () => topScoreContainer.TopScore.Value = scores[1]); + AddStep(@"Add score(rank 999)", () => topScoreContainer.TopScore.Value = scores[0]); + AddStep(@"Add score(rank 110000)", () => topScoreContainer.TopScore.Value = scores[1]); + AddStep(@"Add score(rank 22333)", () => topScoreContainer.TopScore.Value = scores[2]); AddStep(@"Add null score", () => topScoreContainer.TopScore.Value = null); scores = new APILegacyUserTopScoreInfo[] @@ -91,6 +92,27 @@ namespace osu.Game.Tests.Visual.SongSelect }, }, } + }, + new APILegacyUserTopScoreInfo + { + Position = 22333, + Score = new APILegacyScoreInfo + { + Rank = ScoreRank.S, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + User = new User + { + Id = 1541390, + Username = @"Toukai", + Country = new Country + { + FullName = @"Canada", + FlagName = @"CA", + }, + }, + } } }; } diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 9840b59805..713d4a25f7 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -20,23 +20,23 @@ using osu.Game.Scoring; using osu.Game.Users.Drawables; using osuTK; using osuTK.Graphics; +using Humanizer; namespace osu.Game.Online.Leaderboards { public class LeaderboardScore : OsuClickableContainer { - public readonly int RankPosition; - public const float HEIGHT = 60; private const float corner_radius = 5; private const float edge_margin = 5; private const float background_alpha = 0.25f; - private const float rank_width = 30; + private const float rank_width = 35; protected Container RankContainer { get; private set; } private readonly ScoreInfo score; + private readonly int rank; private Box background; private Container content; @@ -52,7 +52,7 @@ namespace osu.Game.Online.Leaderboards public LeaderboardScore(ScoreInfo score, int rank) { this.score = score; - RankPosition = rank; + this.rank = rank; RelativeSizeAxes = Axes.X; Height = HEIGHT; @@ -79,8 +79,8 @@ namespace osu.Game.Online.Leaderboards { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 22, italics: true), - Text = RankPosition.ToString(), + Font = OsuFont.GetFont(size: 20, italics: true), + Text = rank <= 999 ? rank.ToString() : rank.ToMetric(decimals: rank < 100000 ? 1 : 0), }, }, }, From d1409d4610bbcb4f58c6cc5fb0ee0ea8b8e6f37d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 14 Jul 2019 13:33:47 +0300 Subject: [PATCH 0208/2815] Add top score section into beatmap detail area --- .../SongSelect/TestSceneBeatmapDetailArea.cs | 1 + osu.Game/Screens/Select/BeatmapDetailArea.cs | 68 +++++++++++-------- .../Select/Details/UserTopScoreContainer.cs | 3 +- 3 files changed, 42 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs index 7b97a27732..e9650343af 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs @@ -163,6 +163,7 @@ namespace osu.Game.Tests.Visual.SongSelect }); AddStep("null beatmap", () => detailsArea.Beatmap = null); + AddStep("Toggle top score visibility", () => detailsArea.TopScore.ToggleVisibility()); } } } diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs index b66a2ffe0f..678b18e8cf 100644 --- a/osu.Game/Screens/Select/BeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs @@ -5,19 +5,18 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; +using osu.Game.Screens.Select.Details; using osu.Game.Screens.Select.Leaderboards; namespace osu.Game.Screens.Select { public class BeatmapDetailArea : Container { - private const float details_padding = 10; - - private readonly Container content; - protected override Container Content => content; + private const float padding = 10; public readonly BeatmapDetails Details; public readonly BeatmapLeaderboard Leaderboard; + public readonly UserTopScoreContainer TopScore; private WorkingBeatmap beatmap; @@ -34,7 +33,7 @@ namespace osu.Game.Screens.Select public BeatmapDetailArea() { - AddRangeInternal(new Drawable[] + Children = new Drawable[] { new BeatmapDetailAreaTabControl { @@ -58,33 +57,44 @@ namespace osu.Game.Screens.Select } }, }, - content = new Container + new GridContainer { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = BeatmapDetailAreaTabControl.HEIGHT }, + Margin = new MarginPadding { Top = BeatmapDetailAreaTabControl.HEIGHT }, + RowDimensions = new Dimension[] + { + new Dimension(GridSizeMode.Distributed), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + Details = new BeatmapDetails + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Padding = new MarginPadding { Vertical = padding }, + }, + Leaderboard = new BeatmapLeaderboard + { + RelativeSizeAxes = Axes.Both, + } + } + } + }, + new Drawable[] + { + TopScore = new UserTopScoreContainer(), + } + }, }, - }); - - AddRange(new Drawable[] - { - Details = new BeatmapDetails - { - RelativeSizeAxes = Axes.X, - Alpha = 0, - Margin = new MarginPadding { Top = details_padding }, - }, - Leaderboard = new BeatmapLeaderboard - { - RelativeSizeAxes = Axes.Both, - } - }); - } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - Details.Height = Math.Min(DrawHeight - details_padding * 3 - BeatmapDetailAreaTabControl.HEIGHT, 450); + }; } } } diff --git a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs b/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs index 21c16d1d74..2cc47a7483 100644 --- a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs +++ b/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs @@ -13,7 +13,7 @@ namespace osu.Game.Screens.Select.Details { public class UserTopScoreContainer : VisibilityContainer { - private const int height = 150; + private const int height = 110; private const int duration = 300; private readonly Container contentContainer; @@ -37,6 +37,7 @@ namespace osu.Game.Screens.Select.Details Origin = Anchor.BottomCentre, Height = height, RelativeSizeAxes = Axes.X, + Padding = new MarginPadding { Vertical = 10 }, Children = new Drawable[] { new OsuSpriteText From 963e025bb8882eb94884adbf40bb6b13cc0b298a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 14 Jul 2019 14:03:14 +0300 Subject: [PATCH 0209/2815] Make it works and some layout adjustments --- .../SongSelect/TestSceneUserTopScore.cs | 8 +-- osu.Game/Screens/Select/BeatmapDetailArea.cs | 60 +++++++++++-------- .../Select/Details/UserTopScoreContainer.cs | 11 ++-- .../Select/Leaderboards/BeatmapLeaderboard.cs | 9 ++- 4 files changed, 53 insertions(+), 35 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs index 1023294000..b33dbddccb 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs @@ -43,10 +43,10 @@ namespace osu.Game.Tests.Visual.SongSelect }); AddStep(@"Trigger visibility", topScoreContainer.ToggleVisibility); - AddStep(@"Add score(rank 999)", () => topScoreContainer.TopScore.Value = scores[0]); - AddStep(@"Add score(rank 110000)", () => topScoreContainer.TopScore.Value = scores[1]); - AddStep(@"Add score(rank 22333)", () => topScoreContainer.TopScore.Value = scores[2]); - AddStep(@"Add null score", () => topScoreContainer.TopScore.Value = null); + AddStep(@"Add score(rank 999)", () => topScoreContainer.Score.Value = scores[0]); + AddStep(@"Add score(rank 110000)", () => topScoreContainer.Score.Value = scores[1]); + AddStep(@"Add score(rank 22333)", () => topScoreContainer.Score.Value = scores[2]); + AddStep(@"Add null score", () => topScoreContainer.Score.Value = null); scores = new APILegacyUserTopScoreInfo[] { diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs index 678b18e8cf..cac1281cc2 100644 --- a/osu.Game/Screens/Select/BeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs @@ -1,10 +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 osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Screens.Select.Details; using osu.Game.Screens.Select.Leaderboards; @@ -28,6 +28,7 @@ namespace osu.Game.Screens.Select beatmap = value; Leaderboard.Beatmap = beatmap?.BeatmapInfo; Details.Beatmap = beatmap?.BeatmapInfo; + TopScore.Hide(); } } @@ -57,43 +58,50 @@ namespace osu.Game.Screens.Select } }, }, - new GridContainer + new Container { + Padding = new MarginPadding { Top = BeatmapDetailAreaTabControl.HEIGHT, Bottom = padding }, RelativeSizeAxes = Axes.Both, - Margin = new MarginPadding { Top = BeatmapDetailAreaTabControl.HEIGHT }, - RowDimensions = new Dimension[] + Child = new GridContainer { - new Dimension(GridSizeMode.Distributed), - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] - { - new Drawable[] + RelativeSizeAxes = Axes.Both, + RowDimensions = new Dimension[] { - new Container + new Dimension(GridSizeMode.Distributed), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + new Container { - Details = new BeatmapDetails + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Padding = new MarginPadding { Vertical = padding }, - }, - Leaderboard = new BeatmapLeaderboard - { - RelativeSizeAxes = Axes.Both, + Details = new BeatmapDetails + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Padding = new MarginPadding { Top = padding }, + }, + Leaderboard = new BeatmapLeaderboard + { + RelativeSizeAxes = Axes.Both, + } } } + }, + new Drawable[] + { + TopScore = new UserTopScoreContainer + { + Score = { BindTarget = Leaderboard.TopScore } + } } }, - new Drawable[] - { - TopScore = new UserTopScoreContainer(), - } }, - }, + } }; } } diff --git a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs b/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs index 2cc47a7483..0bc30fae6b 100644 --- a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs +++ b/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs @@ -13,13 +13,13 @@ namespace osu.Game.Screens.Select.Details { public class UserTopScoreContainer : VisibilityContainer { - private const int height = 110; + private const int height = 90; private const int duration = 300; private readonly Container contentContainer; private readonly Container scoreContainer; - public Bindable TopScore = new Bindable(); + public Bindable Score = new Bindable(); protected override bool StartHidden => true; @@ -37,13 +37,13 @@ namespace osu.Game.Screens.Select.Details Origin = Anchor.BottomCentre, Height = height, RelativeSizeAxes = Axes.X, - Padding = new MarginPadding { Vertical = 10 }, Children = new Drawable[] { new OsuSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, + Margin = new MarginPadding { Top = 5 }, Text = @"your personal best".ToUpper(), Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold), }, @@ -58,7 +58,7 @@ namespace osu.Game.Screens.Select.Details } }; - TopScore.BindValueChanged((score) => onScoreChanged(score.NewValue)); + Score.BindValueChanged((score) => onScoreChanged(score.NewValue)); } private void onScoreChanged(APILegacyUserTopScoreInfo score) @@ -66,7 +66,10 @@ namespace osu.Game.Screens.Select.Details scoreContainer.Clear(); if (score != null) + { scoreContainer.Add(new LeaderboardScore(score.Score, score.Position)); + Show(); + } } protected override void PopIn() diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 0f6d4f3188..c41bf17685 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -18,6 +19,8 @@ namespace osu.Game.Screens.Select.Leaderboards { public class BeatmapLeaderboard : Leaderboard { + public Bindable TopScore = new Bindable(); + public Action ScoreSelected; private BeatmapInfo beatmap; @@ -133,7 +136,11 @@ namespace osu.Game.Screens.Select.Leaderboards var req = new GetScoresRequest(Beatmap, ruleset.Value ?? Beatmap.Ruleset, Scope, requestMods); - req.Success += r => scoresCallback?.Invoke(r.Scores); + req.Success += r => + { + scoresCallback?.Invoke(r.Scores); + TopScore.Value = r.UserScore; + }; return req; } From ecf0e624849901aedcb2eac6008853773ef6aee8 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 14 Jul 2019 16:16:21 +0300 Subject: [PATCH 0210/2815] CI fixes --- .../SongSelect/TestSceneUserTopScore.cs | 19 +++++++++---------- osu.Game/Screens/Select/BeatmapDetailArea.cs | 3 +-- .../Select/Details/UserTopScoreContainer.cs | 2 +- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs index b33dbddccb..1de8a5375e 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs @@ -16,11 +16,10 @@ namespace osu.Game.Tests.Visual.SongSelect { public class TestSceneUserTopScore : OsuTestScene { - private readonly UserTopScoreContainer topScoreContainer; - private readonly APILegacyUserTopScoreInfo[] scores; - public TestSceneUserTopScore() { + UserTopScoreContainer topScoreContainer; + Add(new Container { Origin = Anchor.BottomCentre, @@ -42,13 +41,7 @@ namespace osu.Game.Tests.Visual.SongSelect } }); - AddStep(@"Trigger visibility", topScoreContainer.ToggleVisibility); - AddStep(@"Add score(rank 999)", () => topScoreContainer.Score.Value = scores[0]); - AddStep(@"Add score(rank 110000)", () => topScoreContainer.Score.Value = scores[1]); - AddStep(@"Add score(rank 22333)", () => topScoreContainer.Score.Value = scores[2]); - AddStep(@"Add null score", () => topScoreContainer.Score.Value = null); - - scores = new APILegacyUserTopScoreInfo[] + APILegacyUserTopScoreInfo[] scores = new[] { new APILegacyUserTopScoreInfo { @@ -115,6 +108,12 @@ namespace osu.Game.Tests.Visual.SongSelect } } }; + + AddStep(@"Trigger visibility", topScoreContainer.ToggleVisibility); + AddStep(@"Add score(rank 999)", () => topScoreContainer.Score.Value = scores[0]); + AddStep(@"Add score(rank 110000)", () => topScoreContainer.Score.Value = scores[1]); + AddStep(@"Add score(rank 22333)", () => topScoreContainer.Score.Value = scores[2]); + AddStep(@"Add null score", () => topScoreContainer.Score.Value = null); } } } diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs index cac1281cc2..6cd6372152 100644 --- a/osu.Game/Screens/Select/BeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs @@ -4,7 +4,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Screens.Select.Details; using osu.Game.Screens.Select.Leaderboards; @@ -65,7 +64,7 @@ namespace osu.Game.Screens.Select Child = new GridContainer { RelativeSizeAxes = Axes.Both, - RowDimensions = new Dimension[] + RowDimensions = new[] { new Dimension(GridSizeMode.Distributed), new Dimension(GridSizeMode.AutoSize), diff --git a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs b/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs index 0bc30fae6b..0300d14ac1 100644 --- a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs +++ b/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs @@ -58,7 +58,7 @@ namespace osu.Game.Screens.Select.Details } }; - Score.BindValueChanged((score) => onScoreChanged(score.NewValue)); + Score.BindValueChanged(score => onScoreChanged(score.NewValue)); } private void onScoreChanged(APILegacyUserTopScoreInfo score) From 19680c8df8f7c883df97f600bde0e1f882687758 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 14 Jul 2019 16:37:05 +0300 Subject: [PATCH 0211/2815] Minor adjustments --- osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs | 2 +- osu.Game/Screens/Select/Details/UserTopScoreContainer.cs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs index 1de8a5375e..5ddccb4c8e 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.SongSelect } }); - APILegacyUserTopScoreInfo[] scores = new[] + var scores = new APILegacyUserTopScoreInfo[] { new APILegacyUserTopScoreInfo { diff --git a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs b/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs index 0300d14ac1..e5e33a47d8 100644 --- a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs +++ b/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs @@ -63,13 +63,14 @@ namespace osu.Game.Screens.Select.Details private void onScoreChanged(APILegacyUserTopScoreInfo score) { - scoreContainer.Clear(); - if (score != null) { + scoreContainer.Clear(); scoreContainer.Add(new LeaderboardScore(score.Score, score.Position)); Show(); } + else + Hide(); } protected override void PopIn() From a01e7260e06e7b23d6b40d7c54d56bee969eb2cc Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 14 Jul 2019 16:49:46 +0300 Subject: [PATCH 0212/2815] Testcase fix --- osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs index 5ddccb4c8e..5f3e7d09dd 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.SongSelect } }); - var scores = new APILegacyUserTopScoreInfo[] + var scores = new[] { new APILegacyUserTopScoreInfo { From b93b26a0366069000d99535a6f83f62e7e377726 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 14 Jul 2019 19:38:46 +0300 Subject: [PATCH 0213/2815] Fix link formatting --- .../Profile/Sections/UnderscoredBeatmapLink.cs | 14 +++++++------- .../Profile/Sections/UnderscoredLinkContainer.cs | 9 +++++++++ .../Profile/Sections/UnderscoredUserLink.cs | 9 ++++----- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/UnderscoredBeatmapLink.cs b/osu.Game/Overlays/Profile/Sections/UnderscoredBeatmapLink.cs index 370d6d84ef..ddad99355c 100644 --- a/osu.Game/Overlays/Profile/Sections/UnderscoredBeatmapLink.cs +++ b/osu.Game/Overlays/Profile/Sections/UnderscoredBeatmapLink.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 osu.Framework.Allocation; using osu.Game.Beatmaps; namespace osu.Game.Overlays.Profile.Sections @@ -15,15 +14,16 @@ namespace osu.Game.Overlays.Profile.Sections this.beatmap = beatmap; } - [BackgroundDependencyLoader(true)] - private void load(BeatmapSetOverlay beatmapSetOverlay) + protected override void LoadComplete() { + base.LoadComplete(); + ClickAction = () => { - if (beatmap.OnlineBeatmapID != null) - beatmapSetOverlay?.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value); - else if (beatmap.BeatmapSet?.OnlineBeatmapSetID != null) - beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmap.BeatmapSet.OnlineBeatmapSetID.Value); + var beatmapId = beatmap.OnlineBeatmapID; + + if (beatmapId.HasValue) + Game?.ShowBeatmap(beatmapId.Value); }; } } diff --git a/osu.Game/Overlays/Profile/Sections/UnderscoredLinkContainer.cs b/osu.Game/Overlays/Profile/Sections/UnderscoredLinkContainer.cs index 8daf0bd24d..09a23d2fe3 100644 --- a/osu.Game/Overlays/Profile/Sections/UnderscoredLinkContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/UnderscoredLinkContainer.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.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -17,6 +18,8 @@ namespace osu.Game.Overlays.Profile.Sections private readonly Container underscore; private readonly FillFlowContainer textContent; + protected OsuGame Game; + protected Action ClickAction; public IReadOnlyList Text @@ -59,6 +62,12 @@ namespace osu.Game.Overlays.Profile.Sections }; } + [BackgroundDependencyLoader(true)] + private void load(OsuGame game) + { + Game = game; + } + protected override bool OnHover(HoverEvent e) { underscore.FadeIn(duration, Easing.OutQuint); diff --git a/osu.Game/Overlays/Profile/Sections/UnderscoredUserLink.cs b/osu.Game/Overlays/Profile/Sections/UnderscoredUserLink.cs index f50bc7f7ba..9b3e8ccdf6 100644 --- a/osu.Game/Overlays/Profile/Sections/UnderscoredUserLink.cs +++ b/osu.Game/Overlays/Profile/Sections/UnderscoredUserLink.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; - namespace osu.Game.Overlays.Profile.Sections { public class UnderscoredUserLink : UnderscoredLinkContainer @@ -14,10 +12,11 @@ namespace osu.Game.Overlays.Profile.Sections this.userId = userId; } - [BackgroundDependencyLoader(true)] - private void load(UserProfileOverlay userProfileOverlay) + protected override void LoadComplete() { - ClickAction = () => userProfileOverlay?.ShowUser(userId); + base.LoadComplete(); + + ClickAction = () => Game?.ShowUser(userId); } } } From d093eb6660c7f04ff27c75dd587e08bb67bd48d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jul 2019 11:45:15 +0900 Subject: [PATCH 0214/2815] Mark sprite read-only --- osu.Game/Graphics/Backgrounds/Background.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/Background.cs b/osu.Game/Graphics/Backgrounds/Background.cs index 526b3da8a6..436fcd0476 100644 --- a/osu.Game/Graphics/Backgrounds/Background.cs +++ b/osu.Game/Graphics/Backgrounds/Background.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 osu.Framework.Allocation; @@ -16,7 +16,7 @@ namespace osu.Game.Graphics.Backgrounds /// public class Background : CompositeDrawable { - public Sprite Sprite; + public readonly Sprite Sprite; private readonly string textureName; From 2186ffda555e610f799a3d2da0865ced1b3b949f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jul 2019 11:46:41 +0900 Subject: [PATCH 0215/2815] Avoid unnecessarily creating buffered container for zero-blur --- osu.Game/Graphics/Backgrounds/Background.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/Background.cs b/osu.Game/Graphics/Backgrounds/Background.cs index 436fcd0476..0043aab7e2 100644 --- a/osu.Game/Graphics/Backgrounds/Background.cs +++ b/osu.Game/Graphics/Backgrounds/Background.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 osu.Framework.Allocation; @@ -51,7 +51,7 @@ namespace osu.Game.Graphics.Backgrounds /// A to which further transforms can be added. public void BlurTo(Vector2 newBlurSigma, double duration = 0, Easing easing = Easing.None) { - if (bufferedContainer == null) + if (bufferedContainer == null && newBlurSigma != Vector2.Zero) { RemoveInternal(Sprite); From 12e7668afc3245b5395c976ff8e71cd9d5e7a3b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jul 2019 11:48:33 +0900 Subject: [PATCH 0216/2815] Fix potential cross-thread talk from bindable updates --- osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 08f1881038..5225740d0b 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.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; @@ -168,6 +168,12 @@ namespace osu.Game.Screens.Backgrounds private void load(OsuConfigManager config) { userBlurLevel = config.GetBindable(OsuSetting.BlurLevel); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + userBlurLevel.ValueChanged += _ => UpdateVisuals(); BlurAmount.ValueChanged += _ => UpdateVisuals(); } From d92f6c762ba88e6d4335134a9ec131c8c9c457e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jul 2019 11:53:16 +0900 Subject: [PATCH 0217/2815] Fix potential nullref --- osu.Game/Graphics/Backgrounds/Background.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Backgrounds/Background.cs b/osu.Game/Graphics/Backgrounds/Background.cs index 0043aab7e2..d13475189d 100644 --- a/osu.Game/Graphics/Backgrounds/Background.cs +++ b/osu.Game/Graphics/Backgrounds/Background.cs @@ -63,7 +63,7 @@ namespace osu.Game.Graphics.Backgrounds }); } - bufferedContainer.BlurTo(newBlurSigma, duration, easing); + bufferedContainer?.BlurTo(newBlurSigma, duration, easing); } } } From 27258e3a9b6b16d2ed845b9e65ab63515366a718 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jul 2019 14:38:05 +0900 Subject: [PATCH 0218/2815] Rename test scene to match class --- ...SceneUserTopScore.cs => TestSceneUserTopScoreContainer.cs} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename osu.Game.Tests/Visual/SongSelect/{TestSceneUserTopScore.cs => TestSceneUserTopScoreContainer.cs} (97%) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs similarity index 97% rename from osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs rename to osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs index 5f3e7d09dd..38ebb58e76 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScore.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs @@ -14,9 +14,9 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.SongSelect { - public class TestSceneUserTopScore : OsuTestScene + public class TestSceneUserTopScoreContainer : OsuTestScene { - public TestSceneUserTopScore() + public TestSceneUserTopScoreContainer() { UserTopScoreContainer topScoreContainer; From 9c1badd2e3771a36c2eea89d090c0db873991f71 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jul 2019 15:42:54 +0900 Subject: [PATCH 0219/2815] Fix rulesets not matching in dictionary lookups due to missing GetHashCode implementation --- osu.Game/Rulesets/RulesetInfo.cs | 14 ++++++++++++++ osu.Game/Rulesets/RulesetStore.cs | 6 +++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index ca331ec339..8eb2abec79 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics.CodeAnalysis; using Newtonsoft.Json; namespace osu.Game.Rulesets @@ -23,6 +24,19 @@ namespace osu.Game.Rulesets public bool Equals(RulesetInfo other) => other != null && ID == other.ID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo; + [SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")] + public override int GetHashCode() + { + unchecked + { + var hashCode = ID.HasValue ? ID.GetHashCode() : 0; + hashCode = (hashCode * 397) ^ (InstantiationInfo != null ? InstantiationInfo.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Name != null ? Name.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ Available.GetHashCode(); + return hashCode; + } + } + public override string ToString() => $"{Name} ({ShortName}) ID: {ID}"; } } diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index fd42f96c92..2d8c9f5b49 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets public RulesetStore(IDatabaseContextFactory factory) : base(factory) { - AddMissingRulesets(); + addMissingRulesets(); } /// @@ -52,13 +52,13 @@ namespace osu.Game.Rulesets /// /// All available rulesets. /// - public IEnumerable AvailableRulesets; + public IEnumerable AvailableRulesets { get; private set; } private static Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args) => loaded_assemblies.Keys.FirstOrDefault(a => a.FullName == args.Name); private const string ruleset_library_prefix = "osu.Game.Rulesets"; - protected void AddMissingRulesets() + private void addMissingRulesets() { using (var usage = ContextFactory.GetForWrite()) { From d0c8aaba4e1f6442e188f7042deb4d8a0a5b5b3f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jul 2019 15:50:50 +0900 Subject: [PATCH 0220/2815] Override basic equality function too --- osu.Game/Rulesets/RulesetInfo.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 8eb2abec79..d9cff86265 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -24,6 +24,8 @@ namespace osu.Game.Rulesets public bool Equals(RulesetInfo other) => other != null && ID == other.ID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo; + public override bool Equals(object obj) => obj is RulesetInfo rulesetInfo && Equals(rulesetInfo); + [SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")] public override int GetHashCode() { From d4c1005c7e820bfa7d7cb1a5b1791e3ba1b0a5a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jul 2019 15:53:19 +0900 Subject: [PATCH 0221/2815] Fix incorrect comparison in line updating logic --- osu.Game/OsuGame.cs | 2 +- osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 361ff62155..2a484fc122 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -248,7 +248,7 @@ namespace osu.Game } // Use first beatmap available for current ruleset, else switch ruleset. - var first = databasedSet.Beatmaps.Find(b => b.Ruleset == Ruleset.Value) ?? databasedSet.Beatmaps.First(); + var first = databasedSet.Beatmaps.Find(b => b.Ruleset.Equals(Ruleset.Value)) ?? databasedSet.Beatmaps.First(); Ruleset.Value = first.Ruleset; Beatmap.Value = BeatmapManager.GetWorkingBeatmap(first); diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index f4272ab15c..bf0cd91321 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -73,7 +73,7 @@ namespace osu.Game.Overlays.Toolbar { foreach (var tabItem in TabContainer) { - if (tabItem.Value == Current.Value) + if (tabItem.Value.Equals(Current.Value)) { ModeButtonLine.MoveToX(tabItem.DrawPosition.X, !hasInitialPosition ? 0 : 200, Easing.OutQuint); break; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 0eeffda5eb..b3c3925a26 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -329,7 +329,7 @@ namespace osu.Game.Screens.Select if (this.IsCurrentScreen() && !Carousel.SelectBeatmap(e.NewValue?.BeatmapInfo, false)) // If selecting new beatmap without bypassing filters failed, there's possibly a ruleset mismatch - if (e.NewValue?.BeatmapInfo?.Ruleset != null && e.NewValue.BeatmapInfo.Ruleset != decoupledRuleset.Value) + if (e.NewValue?.BeatmapInfo?.Ruleset != null && !e.NewValue.BeatmapInfo.Ruleset.Equals(decoupledRuleset.Value)) { Ruleset.Value = e.NewValue.BeatmapInfo.Ruleset; Carousel.SelectBeatmap(e.NewValue.BeatmapInfo); From 7e367dc397e9ce17fe089b83df4b324689d5d756 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 15 Jul 2019 12:30:42 +0300 Subject: [PATCH 0222/2815] Push results screen when clicking on top score --- osu.Game/Screens/Select/Details/UserTopScoreContainer.cs | 9 ++++++++- osu.Game/Screens/Select/SongSelect.cs | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs b/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs index e5e33a47d8..ba6751475e 100644 --- a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs +++ b/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs @@ -8,6 +8,8 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; +using osu.Game.Scoring; +using System; namespace osu.Game.Screens.Select.Details { @@ -21,6 +23,8 @@ namespace osu.Game.Screens.Select.Details public Bindable Score = new Bindable(); + public Action ScoreSelected; + protected override bool StartHidden => true; public UserTopScoreContainer() @@ -66,7 +70,10 @@ namespace osu.Game.Screens.Select.Details if (score != null) { scoreContainer.Clear(); - scoreContainer.Add(new LeaderboardScore(score.Score, score.Position)); + scoreContainer.Add(new LeaderboardScore(score.Score, score.Position) + { + Action = () => ScoreSelected?.Invoke(score.Score) + }); Show(); } else diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index b3c3925a26..94b1fedeab 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -216,6 +216,7 @@ namespace osu.Game.Screens.Select } BeatmapDetails.Leaderboard.ScoreSelected += s => this.Push(new SoloResults(s)); + BeatmapDetails.TopScore.ScoreSelected += s => this.Push(new SoloResults(s)); } [BackgroundDependencyLoader(true)] From d62e42ba1459d5b32c3c721f9a322d9af7d7bb19 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 15 Jul 2019 13:09:21 +0300 Subject: [PATCH 0223/2815] Remove custom link container implementation --- .../Sections/BeatmapMetadataContainer.cs | 26 ++---- .../Historical/DrawableMostPlayedBeatmap.cs | 75 +++++++--------- .../Sections/Ranks/DrawableProfileScore.cs | 29 +++++- .../Sections/UnderscoredBeatmapLink.cs | 30 ------- .../Sections/UnderscoredLinkContainer.cs | 89 ------------------- .../Profile/Sections/UnderscoredUserLink.cs | 22 ----- 6 files changed, 67 insertions(+), 204 deletions(-) delete mode 100644 osu.Game/Overlays/Profile/Sections/UnderscoredBeatmapLink.cs delete mode 100644 osu.Game/Overlays/Profile/Sections/UnderscoredLinkContainer.cs delete mode 100644 osu.Game/Overlays/Profile/Sections/UnderscoredUserLink.cs diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs index 16326900f1..597171cc7f 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs @@ -4,22 +4,19 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Localisation; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays.Profile.Sections { /// /// Display artist/title/mapper information, commonly used as the left portion of a profile or score display row (see ). /// - public class BeatmapMetadataContainer : OsuHoverContainer + public abstract class BeatmapMetadataContainer : OsuHoverContainer { private readonly BeatmapInfo beatmap; - public BeatmapMetadataContainer(BeatmapInfo beatmap) + protected BeatmapMetadataContainer(BeatmapInfo beatmap) { this.beatmap = beatmap; AutoSizeAxes = Axes.Both; @@ -40,23 +37,10 @@ namespace osu.Game.Overlays.Profile.Sections Child = new FillFlowContainer { AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = new LocalisedString(( - $"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} [{beatmap.Version}] ", - $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} [{beatmap.Version}] ")), - Font = OsuFont.GetFont(size: 15, weight: FontWeight.SemiBold, italics: true) - }, - new OsuSpriteText - { - Text = new LocalisedString((beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist)), - Padding = new MarginPadding { Top = 3 }, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular, italics: true) - }, - }, + Children = CreateText(beatmap), }; } + + protected abstract Drawable[] CreateText(BeatmapInfo beatmap); } } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs index 98872d6141..fc457676a8 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs @@ -19,13 +19,12 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { public class DrawableMostPlayedBeatmap : OsuHoverContainer { - private readonly OsuSpriteText mapperText; private readonly Box background; private const int cover_width = 100; private const int corner_radius = 10; private readonly SpriteIcon icon; private readonly OsuSpriteText playCountText; - private readonly UnderscoredUserLink mapper; + private readonly LinkFlowContainer mapper; protected override IEnumerable EffectTargets => new[] { background }; @@ -71,54 +70,24 @@ namespace osu.Game.Overlays.Profile.Sections.Historical Padding = new MarginPadding { Left = 15, Right = 20 }, Children = new Drawable[] { - new UnderscoredBeatmapLink(beatmap) + new MostPlayedBeatmapMetadataContainer(beatmap) { Anchor = Anchor.CentreLeft, Origin = Anchor.BottomLeft, Margin = new MarginPadding { Bottom = 2 }, - Text = new[] - { - new OsuSpriteText - { - Text = new LocalisedString(( - $"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} [{beatmap.Version}] ", - $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} [{beatmap.Version}] ")), - Font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold) - }, - new OsuSpriteText - { - Text = "by " + new LocalisedString((beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist)), - Font = OsuFont.GetFont(size: 20, weight: FontWeight.Regular) - }, - } }, - new FillFlowContainer + mapper = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: 15, weight: FontWeight.Regular)) { - AutoSizeAxes = Axes.Both, Anchor = Anchor.CentreLeft, Origin = Anchor.TopLeft, + AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Margin = new MarginPadding { Top = 2 }, - Children = new Drawable[] - { - mapperText = new OsuSpriteText - { - Text = "mapped by ", - Font = OsuFont.GetFont(size: 15, weight: FontWeight.Regular), - }, - mapper = new UnderscoredUserLink(beatmap.Metadata.Author.Id) - { - Text = new[] - { - new OsuSpriteText - { - Text = beatmap.Metadata.Author.Username, - Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold), - } - } - }, - } - }, + }.With(d => + { + d.AddText("mapped by "); + d.AddUserLink(beatmap.Metadata.Author); + }), new FillFlowContainer { Anchor = Anchor.CentreRight, @@ -157,8 +126,32 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { IdleColour = colors.GreySeafoam; HoverColour = colors.GreySeafoamLight; - mapperText.Colour = mapper.Colour = colors.GreySeafoamLighter; + mapper.Colour = colors.GreySeafoamLighter; icon.Colour = playCountText.Colour = colors.Yellow; } + + private class MostPlayedBeatmapMetadataContainer : BeatmapMetadataContainer + { + public MostPlayedBeatmapMetadataContainer(BeatmapInfo beatmap) + : base(beatmap) + { + } + + protected override Drawable[] CreateText(BeatmapInfo beatmap) => new Drawable[] + { + new OsuSpriteText + { + Text = new LocalisedString(( + $"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} [{beatmap.Version}] ", + $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} [{beatmap.Version}] ")), + Font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold) + }, + new OsuSpriteText + { + Text = "by " + new LocalisedString((beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist)), + Font = OsuFont.GetFont(size: 20, weight: FontWeight.Regular) + }, + }; + } } } diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index b77357edd8..e54ce44ca2 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -10,6 +10,8 @@ using osu.Game.Online.Leaderboards; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using osu.Game.Scoring; +using osu.Game.Beatmaps; +using osu.Framework.Localisation; namespace osu.Game.Overlays.Profile.Sections.Ranks { @@ -51,7 +53,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks RightFlowContainer.Insert(1, text); - LeftFlowContainer.Add(new BeatmapMetadataContainer(Score.Beatmap)); + LeftFlowContainer.Add(new ProfileScoreBeatmapMetadataContainer(Score.Beatmap)); LeftFlowContainer.Add(new DrawableDate(Score.Date)); foreach (Mod mod in Score.Mods) @@ -64,5 +66,30 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Width = 60, FillMode = FillMode.Fit, }; + + private class ProfileScoreBeatmapMetadataContainer : BeatmapMetadataContainer + { + public ProfileScoreBeatmapMetadataContainer(BeatmapInfo beatmap) + : base(beatmap) + { + } + + protected override Drawable[] CreateText(BeatmapInfo beatmap) => new Drawable[] + { + new OsuSpriteText + { + Text = new LocalisedString(( + $"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} [{beatmap.Version}] ", + $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} [{beatmap.Version}] ")), + Font = OsuFont.GetFont(size: 15, weight: FontWeight.SemiBold, italics: true) + }, + new OsuSpriteText + { + Text = new LocalisedString((beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist)), + Padding = new MarginPadding { Top = 3 }, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular, italics: true) + }, + }; + } } } diff --git a/osu.Game/Overlays/Profile/Sections/UnderscoredBeatmapLink.cs b/osu.Game/Overlays/Profile/Sections/UnderscoredBeatmapLink.cs deleted file mode 100644 index ddad99355c..0000000000 --- a/osu.Game/Overlays/Profile/Sections/UnderscoredBeatmapLink.cs +++ /dev/null @@ -1,30 +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.Game.Beatmaps; - -namespace osu.Game.Overlays.Profile.Sections -{ - public class UnderscoredBeatmapLink : UnderscoredLinkContainer - { - private readonly BeatmapInfo beatmap; - - public UnderscoredBeatmapLink(BeatmapInfo beatmap) - { - this.beatmap = beatmap; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - ClickAction = () => - { - var beatmapId = beatmap.OnlineBeatmapID; - - if (beatmapId.HasValue) - Game?.ShowBeatmap(beatmapId.Value); - }; - } - } -} diff --git a/osu.Game/Overlays/Profile/Sections/UnderscoredLinkContainer.cs b/osu.Game/Overlays/Profile/Sections/UnderscoredLinkContainer.cs deleted file mode 100644 index 09a23d2fe3..0000000000 --- a/osu.Game/Overlays/Profile/Sections/UnderscoredLinkContainer.cs +++ /dev/null @@ -1,89 +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.Input.Events; -using osu.Game.Graphics.Sprites; -using System; -using System.Collections.Generic; - -namespace osu.Game.Overlays.Profile.Sections -{ - public abstract class UnderscoredLinkContainer : Container - { - private const int duration = 200; - private readonly Container underscore; - private readonly FillFlowContainer textContent; - - protected OsuGame Game; - - protected Action ClickAction; - - public IReadOnlyList Text - { - get => textContent.Children; - set - { - textContent.Clear(); - textContent.AddRange(value); - } - } - - protected UnderscoredLinkContainer() - { - AutoSizeAxes = Axes.Both; - Child = new Container - { - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - underscore = new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - Height = 1, - Alpha = 0, - AlwaysPresent = true, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - } - }, - textContent = new FillFlowContainer - { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - }, - }, - }; - } - - [BackgroundDependencyLoader(true)] - private void load(OsuGame game) - { - Game = game; - } - - protected override bool OnHover(HoverEvent e) - { - underscore.FadeIn(duration, Easing.OutQuint); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - underscore.FadeOut(duration, Easing.OutQuint); - base.OnHoverLost(e); - } - - protected override bool OnClick(ClickEvent e) - { - ClickAction?.Invoke(); - return base.OnClick(e); - } - } -} diff --git a/osu.Game/Overlays/Profile/Sections/UnderscoredUserLink.cs b/osu.Game/Overlays/Profile/Sections/UnderscoredUserLink.cs deleted file mode 100644 index 9b3e8ccdf6..0000000000 --- a/osu.Game/Overlays/Profile/Sections/UnderscoredUserLink.cs +++ /dev/null @@ -1,22 +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.Overlays.Profile.Sections -{ - public class UnderscoredUserLink : UnderscoredLinkContainer - { - private readonly long userId; - - public UnderscoredUserLink(long userId) - { - this.userId = userId; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - ClickAction = () => Game?.ShowUser(userId); - } - } -} From 111541fe7add521e40a6e9d90cd92f16f449d050 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 15 Jul 2019 13:31:57 +0300 Subject: [PATCH 0224/2815] Use limit in requests --- osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs | 6 ++++-- .../Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs | 6 ++++-- .../Online/API/Requests/GetUserRecentActivitiesRequest.cs | 6 ++++-- osu.Game/Online/API/Requests/GetUserScoresRequest.cs | 6 ++++-- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs b/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs index 45d751f00e..57005181ca 100644 --- a/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs @@ -11,17 +11,19 @@ namespace osu.Game.Online.API.Requests { private readonly long userId; private readonly int offset; + private readonly int limit; private readonly BeatmapSetType type; - public GetUserBeatmapsRequest(long userId, BeatmapSetType type, int offset = 0) + public GetUserBeatmapsRequest(long userId, BeatmapSetType type, int offset = 0, int limit = 6) { this.userId = userId; this.offset = offset; + this.limit = limit; this.type = type; } // ReSharper disable once ImpureMethodCallOnReadonlyValueField - protected override string Target => $@"users/{userId}/beatmapsets/{type.ToString().Underscore()}?offset={offset}"; + protected override string Target => $@"users/{userId}/beatmapsets/{type.ToString().Underscore()}?offset={offset}&limit={limit}"; } public enum BeatmapSetType diff --git a/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs b/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs index 40e52bdaf6..fccb27a86c 100644 --- a/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs @@ -10,13 +10,15 @@ namespace osu.Game.Online.API.Requests { private readonly long userId; private readonly int offset; + private readonly int limit; - public GetUserMostPlayedBeatmapsRequest(long userId, int offset = 0) + public GetUserMostPlayedBeatmapsRequest(long userId, int offset = 0, int limit = 5) { this.userId = userId; this.offset = offset; + this.limit = limit; } - protected override string Target => $@"users/{userId}/beatmapsets/most_played?offset={offset}"; + protected override string Target => $@"users/{userId}/beatmapsets/most_played?offset={offset}&limit={limit}"; } } diff --git a/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs b/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs index 9f80180e70..d066636911 100644 --- a/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs @@ -10,14 +10,16 @@ namespace osu.Game.Online.API.Requests { private readonly long userId; private readonly int offset; + private readonly int limit; - public GetUserRecentActivitiesRequest(long userId, int offset = 0) + public GetUserRecentActivitiesRequest(long userId, int offset = 0, int limit = 5) { this.userId = userId; this.offset = offset; + this.limit = limit; } - protected override string Target => $"users/{userId}/recent_activity?offset={offset}"; + protected override string Target => $"users/{userId}/recent_activity?offset={offset}&limit={limit}"; } public enum RecentActivityType diff --git a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs index 48a43bbbad..e81686b9fe 100644 --- a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs @@ -11,16 +11,18 @@ namespace osu.Game.Online.API.Requests private readonly long userId; private readonly ScoreType type; private readonly int offset; + private readonly int limit; - public GetUserScoresRequest(long userId, ScoreType type, int offset = 0) + public GetUserScoresRequest(long userId, ScoreType type, int offset = 0, int limit = 5) { this.userId = userId; this.type = type; this.offset = offset; + this.offset = limit; } // ReSharper disable once ImpureMethodCallOnReadonlyValueField - protected override string Target => $@"users/{userId}/scores/{type.ToString().ToLowerInvariant()}?offset={offset}"; + protected override string Target => $@"users/{userId}/scores/{type.ToString().ToLowerInvariant()}?offset={offset}&limit={limit}"; } public enum ScoreType From 9458bca58f954a6b80094b7940013b8dcdd5fc22 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 15 Jul 2019 13:37:25 +0300 Subject: [PATCH 0225/2815] Update usage of requests --- osu.Game/Online/API/Requests/GetUserScoresRequest.cs | 2 +- .../Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs | 2 +- .../Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs | 2 +- .../Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs | 2 +- .../Profile/Sections/Recent/PaginatedRecentActivityContainer.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs index e81686b9fe..0e2f7ec417 100644 --- a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs @@ -18,7 +18,7 @@ namespace osu.Game.Online.API.Requests this.userId = userId; this.type = type; this.offset = offset; - this.offset = limit; + this.limit = limit; } // ReSharper disable once ImpureMethodCallOnReadonlyValueField diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index b6b0e605d7..22f8e6c6b0 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -29,7 +29,7 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps protected override void ShowMore() { - request = new GetUserBeatmapsRequest(User.Value.Id, type, VisiblePages++ * ItemsPerPage); + request = new GetUserBeatmapsRequest(User.Value.Id, type, VisiblePages++ * ItemsPerPage, ItemsPerPage); request.Success += sets => Schedule(() => { MoreButton.FadeTo(sets.Count == ItemsPerPage ? 1 : 0); diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs index 6085b0bc05..92b0304a68 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical protected override void ShowMore() { - request = new GetUserMostPlayedBeatmapsRequest(User.Value.Id, VisiblePages++ * ItemsPerPage); + request = new GetUserMostPlayedBeatmapsRequest(User.Value.Id, VisiblePages++ * ItemsPerPage, ItemsPerPage); request.Success += beatmaps => Schedule(() => { MoreButton.FadeTo(beatmaps.Count == ItemsPerPage ? 1 : 0); diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index a149cfa12e..5bb362e21c 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks protected override void ShowMore() { - request = new GetUserScoresRequest(User.Value.Id, type, VisiblePages++ * ItemsPerPage); + request = new GetUserScoresRequest(User.Value.Id, type, VisiblePages++ * ItemsPerPage, ItemsPerPage); request.Success += scores => Schedule(() => { foreach (var s in scores) diff --git a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs index b72aec7a44..215c96a423 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Profile.Sections.Recent protected override void ShowMore() { - request = new GetUserRecentActivitiesRequest(User.Value.Id, VisiblePages++ * ItemsPerPage); + request = new GetUserRecentActivitiesRequest(User.Value.Id, VisiblePages++ * ItemsPerPage, ItemsPerPage); request.Success += activities => Schedule(() => { MoreButton.FadeTo(activities.Count == ItemsPerPage ? 1 : 0); From 83ffb1d5420e8f5f28316583a84e5f87a40d3cc7 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 15 Jul 2019 14:15:03 -0700 Subject: [PATCH 0226/2815] Remove unnecessary transforms on top score user section --- .../Overlays/BeatmapSet/Scores/TopScoreUserSection.cs | 4 ++-- osu.Game/Users/Drawables/UpdateableAvatar.cs | 8 +------- osu.Game/Users/Drawables/UpdateableFlag.cs | 8 +------- 3 files changed, 4 insertions(+), 16 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs index a15d3c5fd1..ffc39e5af2 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs @@ -62,7 +62,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }, } }, - avatar = new UpdateableAvatar(hideImmediately: true) + avatar = new UpdateableAvatar { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -99,7 +99,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Origin = Anchor.CentreLeft, Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold) }, - flag = new UpdateableFlag(hideImmediately: true) + flag = new UpdateableFlag { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game/Users/Drawables/UpdateableAvatar.cs b/osu.Game/Users/Drawables/UpdateableAvatar.cs index a49f2d079b..795b90ba11 100644 --- a/osu.Game/Users/Drawables/UpdateableAvatar.cs +++ b/osu.Game/Users/Drawables/UpdateableAvatar.cs @@ -5,7 +5,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; -using osu.Framework.Graphics.Transforms; namespace osu.Game.Users.Drawables { @@ -38,8 +37,6 @@ namespace osu.Game.Users.Drawables set => base.EdgeEffect = value; } - protected override bool TransformImmediately { get; } - /// /// Whether to show a default guest representation on null user (as opposed to nothing). /// @@ -50,14 +47,11 @@ namespace osu.Game.Users.Drawables /// public readonly BindableBool OpenOnClick = new BindableBool(true); - public UpdateableAvatar(User user = null, bool hideImmediately = false) + public UpdateableAvatar(User user = null) { - TransformImmediately = hideImmediately; User = user; } - protected override TransformSequence ApplyHideTransforms(Drawable drawable) => TransformImmediately ? drawable?.FadeOut() : base.ApplyHideTransforms(drawable); - protected override Drawable CreateDrawable(User user) { if (user == null && !ShowGuestOnNull) diff --git a/osu.Game/Users/Drawables/UpdateableFlag.cs b/osu.Game/Users/Drawables/UpdateableFlag.cs index 78d1a8de20..abc16b2390 100644 --- a/osu.Game/Users/Drawables/UpdateableFlag.cs +++ b/osu.Game/Users/Drawables/UpdateableFlag.cs @@ -3,7 +3,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Transforms; namespace osu.Game.Users.Drawables { @@ -15,21 +14,16 @@ namespace osu.Game.Users.Drawables set => Model = value; } - protected override bool TransformImmediately { get; } - /// /// Whether to show a place holder on null country. /// public bool ShowPlaceholderOnNull = true; - public UpdateableFlag(Country country = null, bool hideImmediately = false) + public UpdateableFlag(Country country = null) { - TransformImmediately = hideImmediately; Country = country; } - protected override TransformSequence ApplyHideTransforms(Drawable drawable) => TransformImmediately ? drawable?.FadeOut() : base.ApplyHideTransforms(drawable); - protected override Drawable CreateDrawable(Country country) { if (country == null && !ShowPlaceholderOnNull) From ed203cb0ffc3d22659e322976da02b2855864c68 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Jul 2019 13:45:59 +0900 Subject: [PATCH 0227/2815] Delay intial hitobject updates --- .../Objects/Drawables/DrawableOsuHitObject.cs | 3 ++ .../Objects/Drawables/DrawableHitObject.cs | 42 ++++++++++++++----- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index f372cb65ce..4533e08a2b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -29,6 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ShakeDuration = 30, RelativeSizeAxes = Axes.Both }); + Alpha = 0; } @@ -38,6 +39,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void ClearInternal(bool disposeChildren = true) => shakeContainer.Clear(disposeChildren); protected override bool RemoveInternal(Drawable drawable) => shakeContainer.Remove(drawable); + protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt; + protected sealed override void UpdateState(ArmedState state) { double transformTime = HitObject.StartTime - HitObject.TimePreempt; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 1f6ca4dd73..76233eabf2 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -179,6 +179,38 @@ namespace osu.Game.Rulesets.Objects.Drawables UpdateResult(false); } + private double? lifetimeStart; + + public override double LifetimeStart + { + get => lifetimeStart ?? (HitObject.StartTime - InitialLifetimeOffset); + set + { + base.LifetimeStart = value; + lifetimeStart = value; + } + } + + /// + /// A safe offset prior to the start time of at which this may begin displaying contents. + /// By default, s are assumed to display their contents within 10 seconds prior to the start time of . + /// + /// + /// This is only used as an optimisation to delay the initial update of this and may be tuned more aggressively if required. + /// A more accurate should be set inside for an state. + /// + protected virtual double InitialLifetimeOffset => 10000; + + /// + /// Will called at least once after the of this has been passed. + /// + internal void OnLifetimeEnd() + { + foreach (var nested in NestedHitObjects) + nested.OnLifetimeEnd(); + UpdateResult(false); + } + protected virtual void AddNested(DrawableHitObject h) { h.OnNewResult += (d, r) => OnNewResult?.Invoke(d, r); @@ -223,16 +255,6 @@ namespace osu.Game.Rulesets.Objects.Drawables OnNewResult?.Invoke(this, Result); } - /// - /// Will called at least once after the of this has been passed. - /// - internal void OnLifetimeEnd() - { - foreach (var nested in NestedHitObjects) - nested.OnLifetimeEnd(); - UpdateResult(false); - } - /// /// Processes this , checking if a scoring result has occurred. /// From e789bb37c8a57aaca7bc67e7b8b3b4dc1bbebb3d Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 16 Jul 2019 14:55:41 +0900 Subject: [PATCH 0228/2815] Ignore shift-delete in SearchTextBox --- .../Graphics/UserInterface/SearchTextBox.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/SearchTextBox.cs b/osu.Game/Graphics/UserInterface/SearchTextBox.cs index 7023711aaa..cd801a3fc9 100644 --- a/osu.Game/Graphics/UserInterface/SearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/SearchTextBox.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input; using osu.Framework.Input.Events; using osuTK; using osuTK.Input; @@ -15,6 +16,8 @@ namespace osu.Game.Graphics.UserInterface public override bool HandleLeftRightArrows => false; + private InputManager inputManager; + public SearchTextBox() { Height = 35; @@ -33,6 +36,21 @@ namespace osu.Game.Graphics.UserInterface PlaceholderText = "type to search"; } + protected override void LoadComplete() + { + inputManager = GetContainingInputManager(); + base.LoadComplete(); + } + + protected override bool HandleAction(PlatformAction action) + { + // Allow shift-delete to be handled locally + if (inputManager.CurrentState.Keyboard.ShiftPressed && action.ActionType == PlatformActionType.CharNext && action.ActionMethod == PlatformActionMethod.Delete) + return false; + + return base.HandleAction(action); + } + protected override bool OnKeyDown(KeyDownEvent e) { if (!e.ControlPressed && !e.ShiftPressed) From 7634e3cc813c43d317c518dda349878bd94b7013 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Jul 2019 14:57:11 +0900 Subject: [PATCH 0229/2815] Fix song select iterating over all beatmaps in database unnnecessarily --- osu.Game/Screens/Select/SongSelect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index b3c3925a26..260867b1ea 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -253,7 +253,7 @@ namespace osu.Game.Screens.Select Schedule(() => { // if we have no beatmaps but osu-stable is found, let's prompt the user to import. - if (!beatmaps.GetAllUsableBeatmapSets().Any() && beatmaps.StableInstallationAvailable) + if (!beatmaps.GetAllUsableBeatmapSetsEnumerable().Any() && beatmaps.StableInstallationAvailable) dialogOverlay.Push(new ImportFromStablePopup(() => { Task.Run(beatmaps.ImportFromStableAsync).ContinueWith(_ => scores.ImportFromStableAsync(), TaskContinuationOptions.OnlyOnRanToCompletion); From b95a5983385a349174404d3f71957ec22462abc7 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 16 Jul 2019 15:12:01 +0900 Subject: [PATCH 0230/2815] don't check for shift --- osu.Game/Graphics/UserInterface/SearchTextBox.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/SearchTextBox.cs b/osu.Game/Graphics/UserInterface/SearchTextBox.cs index cd801a3fc9..c1020e6340 100644 --- a/osu.Game/Graphics/UserInterface/SearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/SearchTextBox.cs @@ -16,8 +16,6 @@ namespace osu.Game.Graphics.UserInterface public override bool HandleLeftRightArrows => false; - private InputManager inputManager; - public SearchTextBox() { Height = 35; @@ -36,16 +34,10 @@ namespace osu.Game.Graphics.UserInterface PlaceholderText = "type to search"; } - protected override void LoadComplete() - { - inputManager = GetContainingInputManager(); - base.LoadComplete(); - } - protected override bool HandleAction(PlatformAction action) { - // Allow shift-delete to be handled locally - if (inputManager.CurrentState.Keyboard.ShiftPressed && action.ActionType == PlatformActionType.CharNext && action.ActionMethod == PlatformActionMethod.Delete) + // Allow delete to be handled locally + if (action.ActionType == PlatformActionType.CharNext && action.ActionMethod == PlatformActionMethod.Delete) return false; return base.HandleAction(action); From b0415dc30abb7aec345c535988097be14446271b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Jul 2019 17:33:14 +0900 Subject: [PATCH 0231/2815] Remove cursortrail drawnode allocs --- .../UI/Cursor/CursorTrail.cs | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index b986076593..6a70728c50 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -162,7 +162,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private readonly TrailPart[] parts = new TrailPart[max_sprites]; private Vector2 size; - private readonly VertexBatch vertexBatch = new QuadBatch(max_sprites, 1); + private readonly CustomBatch vertexBatch = new CustomBatch(max_sprites, 1); public TrailDrawNode(CursorTrail source) : base(source) @@ -197,20 +197,14 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor for (int i = 0; i < parts.Length; ++i) { Vector2 pos = parts[i].Position; - float localTime = parts[i].Time; + vertexBatch.DrawTime = parts[i].Time; DrawQuad( texture, new Quad(pos.X - size.X / 2, pos.Y - size.Y / 2, size.X, size.Y), DrawColourInfo.Colour, null, - v => vertexBatch.Add(new TexturedTrailVertex - { - Position = v.Position, - TexturePosition = v.TexturePosition, - Time = localTime + 1, - Colour = v.Colour, - })); + vertexBatch.AddAction); } shader.Unbind(); @@ -222,6 +216,27 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor vertexBatch.Dispose(); } + + private class CustomBatch : QuadBatch + { + public new readonly Action AddAction; + + public float DrawTime; + + public CustomBatch(int size, int maxBuffers) + : base(size, maxBuffers) + { + AddAction = add; + } + + private void add(TexturedVertex2D vertex) => Add(new TexturedTrailVertex + { + Position = vertex.Position, + TexturePosition = vertex.TexturePosition, + Time = DrawTime + 1, + Colour = vertex.Colour, + }); + } } [StructLayout(LayoutKind.Sequential)] From 62b867018d555d6c034ce00991768bb35bcc2338 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Jul 2019 17:50:03 +0900 Subject: [PATCH 0232/2815] Refactor a bit --- .../UI/Cursor/CursorTrail.cs | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 6a70728c50..05eb0ffdbf 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -162,7 +162,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private readonly TrailPart[] parts = new TrailPart[max_sprites]; private Vector2 size; - private readonly CustomBatch vertexBatch = new CustomBatch(max_sprites, 1); + private readonly TrailBatch vertexBatch = new TrailBatch(max_sprites, 1); public TrailDrawNode(CursorTrail source) : base(source) @@ -196,9 +196,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor for (int i = 0; i < parts.Length; ++i) { - Vector2 pos = parts[i].Position; vertexBatch.DrawTime = parts[i].Time; + Vector2 pos = parts[i].Position; + DrawQuad( texture, new Quad(pos.X - size.X / 2, pos.Y - size.Y / 2, size.X, size.Y), @@ -217,25 +218,23 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor vertexBatch.Dispose(); } - private class CustomBatch : QuadBatch + // Todo: This shouldn't exist, but is currently used to reduce allocations by caching variable-capturing closures. + private class TrailBatch : QuadBatch { public new readonly Action AddAction; - public float DrawTime; - public CustomBatch(int size, int maxBuffers) + public TrailBatch(int size, int maxBuffers) : base(size, maxBuffers) { - AddAction = add; + AddAction = v => Add(new TexturedTrailVertex + { + Position = v.Position, + TexturePosition = v.TexturePosition, + Time = DrawTime + 1, + Colour = v.Colour, + }); } - - private void add(TexturedVertex2D vertex) => Add(new TexturedTrailVertex - { - Position = vertex.Position, - TexturePosition = vertex.TexturePosition, - Time = DrawTime + 1, - Colour = vertex.Colour, - }); } } From 7c5a227fc53c4e750e19ff630719e15101d22e4b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Jul 2019 14:46:25 +0900 Subject: [PATCH 0233/2815] Fix crashes when presenting replays --- osu.Game/Screens/Select/FooterButtonMods.cs | 16 ++++++++++------ osu.Game/Screens/Select/SongSelect.cs | 11 ++++++++--- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index c96c5022c0..fce4d1b2e2 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -9,18 +9,25 @@ using osu.Game.Rulesets.Mods; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osuTK; using osuTK.Input; namespace osu.Game.Screens.Select { - public class FooterButtonMods : FooterButton + public class FooterButtonMods : FooterButton, IHasCurrentValue> { - public FooterButtonMods(Bindable> mods) + public Bindable> Current { - FooterModDisplay modDisplay; + get => modDisplay.Current; + set => modDisplay.Current = value; + } + private readonly FooterModDisplay modDisplay; + + public FooterButtonMods() + { Add(new Container { Anchor = Anchor.CentreLeft, @@ -33,9 +40,6 @@ namespace osu.Game.Screens.Select AutoSizeAxes = Axes.Both, Margin = new MarginPadding { Left = 70 } }); - - if (mods != null) - modDisplay.Current = mods; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 260867b1ea..20dbcf2693 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -221,11 +221,9 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader(true)] private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins, ScoreManager scores) { - mods.BindTo(Mods); - if (Footer != null) { - Footer.AddButton(new FooterButtonMods(mods), ModSelect); + Footer.AddButton(new FooterButtonMods { Current = mods }, ModSelect); Footer.AddButton(new FooterButtonRandom { Action = triggerRandom }); Footer.AddButton(new FooterButtonOptions(), BeatmapOptions); @@ -263,6 +261,13 @@ namespace osu.Game.Screens.Select } } + protected override void LoadComplete() + { + base.LoadComplete(); + + mods.BindTo(Mods); + } + private DependencyContainer dependencies; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) From a9286fee07fc2c814062586a49ee116fb719334b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Jul 2019 18:19:13 +0900 Subject: [PATCH 0234/2815] Recycle slider paths when the parenting slider dies --- .../Objects/Drawables/DrawableSlider.cs | 6 ++++++ .../Objects/Drawables/Pieces/SliderBody.cs | 19 ++++++++++++++++++- .../Objects/Drawables/DrawableHitObject.cs | 8 ++++---- osu.Game/Rulesets/UI/HitObjectContainer.cs | 10 ++++++++-- 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 05cb42d853..4d67c9ae34 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -156,6 +156,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } + public override void OnKilled() + { + base.OnKilled(); + Body.RecyclePath(); + } + protected override void SkinChanged(ISkinSource skin, bool allowFallback) { base.SkinChanged(skin, allowFallback); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs index 33b3667c4f..97c7c9cec5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs @@ -13,7 +13,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public const float DEFAULT_BORDER_SIZE = 1; - private readonly SliderPath path; + private SliderPath path; + protected Path Path => path; public float PathRadius @@ -77,6 +78,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces InternalChild = path = new SliderPath(); } + /// + /// Initialises a new , releasing all resources retained by the old one. + /// + public void RecyclePath() + { + InternalChild = path = new SliderPath + { + Position = path.Position, + PathRadius = path.PathRadius, + AccentColour = path.AccentColour, + BorderColour = path.BorderColour, + BorderSize = path.BorderSize, + Vertices = path.Vertices + }; + } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => path.ReceivePositionalInputAt(screenSpacePos); /// diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 76233eabf2..e61fac679e 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -7,7 +7,6 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; -using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Game.Audio; using osu.Game.Graphics; @@ -202,12 +201,13 @@ namespace osu.Game.Rulesets.Objects.Drawables protected virtual double InitialLifetimeOffset => 10000; /// - /// Will called at least once after the of this has been passed. + /// Will be called at least once after this has become not alive. /// - internal void OnLifetimeEnd() + public virtual void OnKilled() { foreach (var nested in NestedHitObjects) - nested.OnLifetimeEnd(); + nested.OnKilled(); + UpdateResult(false); } diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 2f3a384e95..aa942e70df 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -34,8 +34,14 @@ namespace osu.Game.Rulesets.UI protected override void OnChildLifetimeBoundaryCrossed(LifetimeBoundaryCrossedEvent e) { - if (e.Kind == LifetimeBoundaryKind.End && e.Direction == LifetimeBoundaryCrossingDirection.Forward && e.Child is DrawableHitObject hitObject) - hitObject.OnLifetimeEnd(); + if (!(e.Child is DrawableHitObject hitObject)) + return; + + if (e.Kind == LifetimeBoundaryKind.End && e.Direction == LifetimeBoundaryCrossingDirection.Forward + || e.Kind == LifetimeBoundaryKind.Start && e.Direction == LifetimeBoundaryCrossingDirection.Backward) + { + hitObject.OnKilled(); + } } } } From 66036508b607348d1f88daf0845bc5aeffbc2c71 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jul 2019 17:39:04 +0900 Subject: [PATCH 0235/2815] Fix potential crash when displaying leaderbaords --- osu.Game/Online/Leaderboards/Leaderboard.cs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 35f7ba1c1b..18c827707a 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -51,7 +51,6 @@ namespace osu.Game.Online.Leaderboards loading.Hide(); - // schedule because we may not be loaded yet (LoadComponentAsync complains). showScoresDelegate?.Cancel(); showScoresCancellationSource?.Cancel(); @@ -61,28 +60,22 @@ namespace osu.Game.Online.Leaderboards // ensure placeholder is hidden when displaying scores PlaceholderState = PlaceholderState.Successful; - scrollFlow = CreateScoreFlow(); - scrollFlow.ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)); + var sf = CreateScoreFlow(); + sf.ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)); - if (!IsLoaded) - showScoresDelegate = Schedule(showScores); - else - showScores(); - - void showScores() => LoadComponentAsync(scrollFlow, _ => + // schedule because we may not be loaded yet (LoadComponentAsync complains). + showScoresDelegate = Schedule(() => LoadComponentAsync(sf, _ => { - scrollContainer.Add(scrollFlow); + scrollContainer.Add(scrollFlow = sf); int i = 0; foreach (var s in scrollFlow.Children) - { using (s.BeginDelayedSequence(i++ * 50, true)) s.Show(); - } scrollContainer.ScrollTo(0f, false); - }, (showScoresCancellationSource = new CancellationTokenSource()).Token); + }, (showScoresCancellationSource = new CancellationTokenSource()).Token)); } } From cca472d412dd7d3f9983c9f06fe485eb49f22b99 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Jul 2019 19:19:45 +0900 Subject: [PATCH 0236/2815] Fix direct ruleset selector binding in ctor --- osu.Game/Overlays/Direct/DirectRulesetSelector.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Direct/DirectRulesetSelector.cs b/osu.Game/Overlays/Direct/DirectRulesetSelector.cs index fdab9f1b90..2c5ea85c5a 100644 --- a/osu.Game/Overlays/Direct/DirectRulesetSelector.cs +++ b/osu.Game/Overlays/Direct/DirectRulesetSelector.cs @@ -26,8 +26,13 @@ namespace osu.Game.Overlays.Direct TabContainer.Masking = false; TabContainer.Spacing = new Vector2(10, 0); AutoSizeAxes = Axes.Both; + } - Current.DisabledChanged += value => SelectedTab.FadeColour(value ? Color4.DarkGray : Color4.White, 200, Easing.OutQuint); + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindDisabledChanged(value => SelectedTab.FadeColour(value ? Color4.DarkGray : Color4.White, 200, Easing.OutQuint)); } protected override TabItem CreateTabItem(RulesetInfo value) => new DirectRulesetTabItem(value); From 9f6ff63634b19d99010b282a66a8df760d113da9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Jul 2019 19:25:41 +0900 Subject: [PATCH 0237/2815] Fix judgement disposals causing huge LOH pressure --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 9 ++++++++- osu.Game/Skinning/LocalSkinOverrideContainer.cs | 6 +++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 0cbe0cca85..9037faf606 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.UI; using System.Linq; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.UI.Cursor; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu.UI { @@ -39,7 +40,13 @@ namespace osu.Game.Rulesets.Osu.UI RelativeSizeAxes = Axes.Both, Depth = 1, }, - HitObjectContainer, + // Todo: This should not exist, but currently helps to reduce LOH allocations due to unbinding skin source events on judgement disposal + // Todo: Remove when hitobjects are properly pooled + new LocalSkinOverrideContainer(null) + { + RelativeSizeAxes = Axes.Both, + Child = HitObjectContainer, + }, approachCircles = new ApproachCircleProxyContainer { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Skinning/LocalSkinOverrideContainer.cs b/osu.Game/Skinning/LocalSkinOverrideContainer.cs index 37f4cc28a2..7882e0f31b 100644 --- a/osu.Game/Skinning/LocalSkinOverrideContainer.cs +++ b/osu.Game/Skinning/LocalSkinOverrideContainer.cs @@ -34,7 +34,7 @@ namespace osu.Game.Skinning public Drawable GetDrawableComponent(string componentName) { Drawable sourceDrawable; - if (beatmapSkins.Value && (sourceDrawable = skin.GetDrawableComponent(componentName)) != null) + if (beatmapSkins.Value && (sourceDrawable = skin?.GetDrawableComponent(componentName)) != null) return sourceDrawable; return fallbackSource?.GetDrawableComponent(componentName); @@ -43,7 +43,7 @@ namespace osu.Game.Skinning public Texture GetTexture(string componentName) { Texture sourceTexture; - if (beatmapSkins.Value && (sourceTexture = skin.GetTexture(componentName)) != null) + if (beatmapSkins.Value && (sourceTexture = skin?.GetTexture(componentName)) != null) return sourceTexture; return fallbackSource.GetTexture(componentName); @@ -52,7 +52,7 @@ namespace osu.Game.Skinning public SampleChannel GetSample(string sampleName) { SampleChannel sourceChannel; - if (beatmapHitsounds.Value && (sourceChannel = skin.GetSample(sampleName)) != null) + if (beatmapHitsounds.Value && (sourceChannel = skin?.GetSample(sampleName)) != null) return sourceChannel; return fallbackSource?.GetSample(sampleName); From 883c090248b49ec98dd14746304bc2374ad7a4cc Mon Sep 17 00:00:00 2001 From: Dan Balasescu <1329837+smoogipoo@users.noreply.github.com> Date: Wed, 17 Jul 2019 20:02:20 +0900 Subject: [PATCH 0238/2815] Fix disabled state potentially not being set --- osu.Game/Overlays/Direct/DirectRulesetSelector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Direct/DirectRulesetSelector.cs b/osu.Game/Overlays/Direct/DirectRulesetSelector.cs index 2c5ea85c5a..106aaa616b 100644 --- a/osu.Game/Overlays/Direct/DirectRulesetSelector.cs +++ b/osu.Game/Overlays/Direct/DirectRulesetSelector.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Direct { base.LoadComplete(); - Current.BindDisabledChanged(value => SelectedTab.FadeColour(value ? Color4.DarkGray : Color4.White, 200, Easing.OutQuint)); + Current.BindDisabledChanged(value => SelectedTab.FadeColour(value ? Color4.DarkGray : Color4.White, 200, Easing.OutQuint), true); } protected override TabItem CreateTabItem(RulesetInfo value) => new DirectRulesetTabItem(value); From 42b1e1772fe591452e3e0778771e42dfbc0ef105 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jul 2019 22:07:11 +0900 Subject: [PATCH 0239/2815] 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 98f9bf1a42..31c1c29865 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -63,6 +63,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 436ba90a88..f54a51d5db 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -15,7 +15,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index c24349bcb5..eb6d3ead06 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From cc3492f8f29e7e192b5af10dca9479efb288b299 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jul 2019 00:01:38 +0900 Subject: [PATCH 0240/2815] 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 31c1c29865..9aa5e631ad 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -63,6 +63,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f54a51d5db..86a68d2159 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -15,7 +15,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index eb6d3ead06..712effcc39 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From f1423b8cb5717361443594e4311f00a1a6b34c90 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jul 2019 22:38:17 +0900 Subject: [PATCH 0241/2815] Add more brackets --- osu.Game/Rulesets/UI/HitObjectContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index aa942e70df..9485189433 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -37,8 +37,8 @@ namespace osu.Game.Rulesets.UI if (!(e.Child is DrawableHitObject hitObject)) return; - if (e.Kind == LifetimeBoundaryKind.End && e.Direction == LifetimeBoundaryCrossingDirection.Forward - || e.Kind == LifetimeBoundaryKind.Start && e.Direction == LifetimeBoundaryCrossingDirection.Backward) + if ((e.Kind == LifetimeBoundaryKind.End && e.Direction == LifetimeBoundaryCrossingDirection.Forward) + || (e.Kind == LifetimeBoundaryKind.Start && e.Direction == LifetimeBoundaryCrossingDirection.Backward)) { hitObject.OnKilled(); } From a6ddcd78d46a8fc38fa9ae8d37bfe724d58e7610 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jul 2019 01:08:12 +0900 Subject: [PATCH 0242/2815] Fix results screen not showing first tab correctly --- osu.Game/Screens/Ranking/Results.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index f53fb75e1a..cac26b3dbf 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -256,9 +256,12 @@ namespace osu.Game.Screens.Ranking } }; - foreach (var t in CreateResultPages()) - modeChangeButtons.AddItem(t); - modeChangeButtons.Current.Value = modeChangeButtons.Items.FirstOrDefault(); + var pages = CreateResultPages(); + + foreach (var p in pages) + modeChangeButtons.AddItem(p); + + modeChangeButtons.Current.Value = pages.FirstOrDefault(); modeChangeButtons.Current.BindValueChanged(page => { From 1ce2b2eaceabb32ef5c3f1953e6a4cce30387b9e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Jul 2019 13:18:06 +0900 Subject: [PATCH 0243/2815] Set title as default grouping/sorting modes --- osu.Game/Screens/Select/FilterControl.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index bd0b0dd5cd..57fe15fd99 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -120,7 +120,8 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.X, Height = 24, Width = 0.5f, - AutoSort = true + AutoSort = true, + Current = { Value = GroupMode.Title } }, //spriteText = new OsuSpriteText //{ @@ -139,6 +140,7 @@ namespace osu.Game.Screens.Select Width = 0.5f, Height = 24, AutoSort = true, + Current = { Value = SortMode.Title } } } }, From f175f597e76bf717ac528ee25c98dd7cb12bc0ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jul 2019 14:05:57 +0900 Subject: [PATCH 0244/2815] Update follower count API source --- osu.Game/Overlays/Profile/Header/Components/AddFriendButton.cs | 2 +- osu.Game/Users/User.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/AddFriendButton.cs b/osu.Game/Overlays/Profile/Header/Components/AddFriendButton.cs index 2e4fd6fe3d..6e1b6e2c7d 100644 --- a/osu.Game/Overlays/Profile/Header/Components/AddFriendButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/AddFriendButton.cs @@ -53,6 +53,6 @@ namespace osu.Game.Overlays.Profile.Header.Components User.BindValueChanged(user => updateFollowers(user.NewValue), true); } - private void updateFollowers(User user) => followerText.Text = user?.FollowerCount?.Length > 0 ? user.FollowerCount[0].ToString("#,##0") : "0"; + private void updateFollowers(User user) => followerText.Text = user?.FollowerCount.ToString("#,##0"); } } diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index df41e194b0..34bd4bbaae 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -118,7 +118,7 @@ namespace osu.Game.Users public int PostCount; [JsonProperty(@"follower_count")] - public int[] FollowerCount; + public int FollowerCount; [JsonProperty] private string[] playstyle From ca72409dc269fe0ffd0a1045f17cdcec98e48ab2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Jul 2019 20:16:10 +0900 Subject: [PATCH 0245/2815] More closely match osu-web styling --- .../Historical/DrawableMostPlayedBeatmap.cs | 147 +++++++++++------- 1 file changed, 87 insertions(+), 60 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs index fc457676a8..a6c41cde72 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs @@ -14,34 +14,45 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osuTK; using System.Collections.Generic; +using osu.Framework.Graphics.Cursor; namespace osu.Game.Overlays.Profile.Sections.Historical { public class DrawableMostPlayedBeatmap : OsuHoverContainer { - private readonly Box background; private const int cover_width = 100; private const int corner_radius = 10; - private readonly SpriteIcon icon; - private readonly OsuSpriteText playCountText; - private readonly LinkFlowContainer mapper; + + private readonly BeatmapInfo beatmap; + private readonly int playCount; + + private Box background; protected override IEnumerable EffectTargets => new[] { background }; public DrawableMostPlayedBeatmap(BeatmapInfo beatmap, int playCount) { + this.beatmap = beatmap; + this.playCount = playCount; Enabled.Value = true; //manually enabled, because we have no action RelativeSizeAxes = Axes.X; - Height = 60; + AutoSizeAxes = Axes.Y; + Masking = true; CornerRadius = corner_radius; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + IdleColour = colours.GreySeafoam; + HoverColour = colours.GreySeafoamLight; + Children = new Drawable[] { new UpdateableBeatmapSetCover { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Y, Width = cover_width, BeatmapSet = beatmap.BeatmapSet, @@ -49,69 +60,53 @@ namespace osu.Game.Overlays.Profile.Sections.Historical }, new Container { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Left = cover_width - corner_radius }, Children = new Drawable[] { new Container { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Masking = true, CornerRadius = corner_radius, Children = new Drawable[] { - background = new Box - { - RelativeSizeAxes = Axes.Both, - }, + background = new Box { RelativeSizeAxes = Axes.Both }, new Container { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = 15, Right = 20 }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(10), Children = new Drawable[] { - new MostPlayedBeatmapMetadataContainer(beatmap) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.BottomLeft, - Margin = new MarginPadding { Bottom = 2 }, - }, - mapper = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: 15, weight: FontWeight.Regular)) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.TopLeft, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Margin = new MarginPadding { Top = 2 }, - }.With(d => - { - d.AddText("mapped by "); - d.AddUserLink(beatmap.Metadata.Author); - }), new FillFlowContainer { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, + Direction = FillDirection.Vertical, Children = new Drawable[] { - icon = new SpriteIcon + new MostPlayedBeatmapMetadataContainer(beatmap), + new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular)) { - Icon = FontAwesome.Solid.CaretRight, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Size = new Vector2(20), - }, - playCountText = new OsuSpriteText + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Colour = colours.GreySeafoamLighter + }.With(d => { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Text = playCount.ToString(), - Font = OsuFont.GetFont(size: 30, weight: FontWeight.Regular, fixedWidth: true), - }, + d.AddText("mapped by "); + d.AddUserLink(beatmap.Metadata.Author); + }), } - } + }, + new PlayCountText(playCount) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight + }, } }, } @@ -121,15 +116,6 @@ namespace osu.Game.Overlays.Profile.Sections.Historical }; } - [BackgroundDependencyLoader] - private void load(OsuColour colors) - { - IdleColour = colors.GreySeafoam; - HoverColour = colors.GreySeafoamLight; - mapper.Colour = colors.GreySeafoamLighter; - icon.Colour = playCountText.Colour = colors.Yellow; - } - private class MostPlayedBeatmapMetadataContainer : BeatmapMetadataContainer { public MostPlayedBeatmapMetadataContainer(BeatmapInfo beatmap) @@ -144,14 +130,55 @@ namespace osu.Game.Overlays.Profile.Sections.Historical Text = new LocalisedString(( $"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} [{beatmap.Version}] ", $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} [{beatmap.Version}] ")), - Font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold) + Font = OsuFont.GetFont(weight: FontWeight.Bold) }, new OsuSpriteText { Text = "by " + new LocalisedString((beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist)), - Font = OsuFont.GetFont(size: 20, weight: FontWeight.Regular) + Font = OsuFont.GetFont(weight: FontWeight.Regular) }, }; } + + private class PlayCountText : CompositeDrawable, IHasTooltip + { + public string TooltipText => "times played"; + + public PlayCountText(int playCount) + { + AutoSizeAxes = Axes.Both; + + InternalChild = new FillFlowContainer + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(5, 0), + Children = new Drawable[] + { + new SpriteIcon + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Size = new Vector2(12), + Icon = FontAwesome.Solid.Play, + }, + new OsuSpriteText + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Text = playCount.ToString(), + Font = OsuFont.GetFont(size: 20, weight: FontWeight.Regular), + }, + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.Yellow; + } + } } } From f87e9b801716136e0ad43d124bc1b312b5b6212f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Jul 2019 20:17:19 +0900 Subject: [PATCH 0246/2815] Remove unnecessary tooltip text --- osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs index 597171cc7f..13b547eed3 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs @@ -19,8 +19,8 @@ namespace osu.Game.Overlays.Profile.Sections protected BeatmapMetadataContainer(BeatmapInfo beatmap) { this.beatmap = beatmap; + AutoSizeAxes = Axes.Both; - TooltipText = $"{beatmap.Metadata.Artist} - {beatmap.Metadata.Title}"; } [BackgroundDependencyLoader(true)] From fa978a47b004085da22d0ebacb31e7df7c8d18d7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 18 Jul 2019 15:08:50 +0300 Subject: [PATCH 0247/2815] Fix loading animation is no longer present --- .../BeatmapSet/Scores/ScoresContainer.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 22d7ea9c97..0adaaa20b2 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -19,7 +19,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores public class ScoresContainer : CompositeDrawable { private const int spacing = 15; - private const int fade_duration = 200; private readonly Box background; private readonly ScoreTable scoreTable; @@ -53,8 +52,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { Schedule(() => { - loading = false; - topScoresContainer.Clear(); if (value?.Scores.Any() != true) @@ -128,13 +125,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores background.Colour = colours.Gray2; } - private bool loading - { - set => loadingAnimation.FadeTo(value ? 1 : 0, fade_duration); - } - private void getScores(BeatmapInfo beatmap) { + loadingAnimation.Show(); + getScoresRequest?.Cancel(); getScoresRequest = null; @@ -142,14 +136,17 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (beatmap?.OnlineBeatmapID.HasValue != true) { - loading = false; + loadingAnimation.Hide(); return; } getScoresRequest = new GetScoresRequest(beatmap, beatmap.Ruleset); - getScoresRequest.Success += scores => Scores = scores; + getScoresRequest.Success += scores => + { + loadingAnimation.Hide(); + Scores = scores; + }; api.Queue(getScoresRequest); - loading = true; } } } From 00f1d1b53cc47c9be1743bf1b69d28b0c89ee659 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Jul 2019 00:53:00 +0900 Subject: [PATCH 0248/2815] Reduce Triangle drawnode overhead by ~90% This was never batching, ever. Pointless memory overhead. --- osu.Game/Graphics/Backgrounds/Triangles.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 29113e0e2f..3502531d13 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -137,11 +137,13 @@ namespace osu.Game.Graphics.Backgrounds } } + protected int AimCount; + private void addTriangles(bool randomY) { - int aimTriangleCount = (int)(DrawWidth * DrawHeight * 0.002f / (triangleScale * triangleScale) * SpawnRatio); + AimCount = (int)(DrawWidth * DrawHeight * 0.002f / (triangleScale * triangleScale) * SpawnRatio); - for (int i = 0; i < aimTriangleCount - parts.Count; i++) + for (int i = 0; i < AimCount - parts.Count; i++) parts.Add(createTriangle(randomY)); } @@ -190,11 +192,12 @@ namespace osu.Game.Graphics.Backgrounds private readonly List parts = new List(); private Vector2 size; - private readonly LinearBatch vertexBatch = new LinearBatch(100 * 3, 10, PrimitiveType.Triangles); + private readonly LinearBatch vertexBatch; public TrianglesDrawNode(Triangles source) : base(source) { + vertexBatch = new LinearBatch(source.AimCount * 3, 2, PrimitiveType.Triangles); } public override void ApplyState() From a23bb3a6b32f48355dbb6f0b837d1835e4b0a14b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Jul 2019 01:14:55 +0900 Subject: [PATCH 0249/2815] Account for headless nulls (but how?) --- osu.Game/Graphics/Backgrounds/Triangles.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 3502531d13..20e92cd2c6 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -249,7 +249,7 @@ namespace osu.Game.Graphics.Backgrounds { base.Dispose(isDisposing); - vertexBatch.Dispose(); + vertexBatch?.Dispose(); } } From 95f36c36c4d00dc53c8b27bd2bb44c167d21b739 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Jul 2019 01:52:53 +0900 Subject: [PATCH 0250/2815] Late-initialize vertex batch for safety --- osu.Game/Graphics/Backgrounds/Triangles.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 20e92cd2c6..4f50abc985 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -192,12 +192,11 @@ namespace osu.Game.Graphics.Backgrounds private readonly List parts = new List(); private Vector2 size; - private readonly LinearBatch vertexBatch; + private LinearBatch vertexBatch; public TrianglesDrawNode(Triangles source) : base(source) { - vertexBatch = new LinearBatch(source.AimCount * 3, 2, PrimitiveType.Triangles); } public override void ApplyState() @@ -214,6 +213,9 @@ namespace osu.Game.Graphics.Backgrounds public override void Draw(Action vertexAction) { + if (vertexBatch == null && Source.AimCount > 0) + vertexBatch = new LinearBatch(Source.AimCount * 3, 2, PrimitiveType.Triangles); + base.Draw(vertexAction); shader.Bind(); From 024f136d820db7791324d988fc4e9d2a20e47676 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Jul 2019 14:24:11 +0900 Subject: [PATCH 0251/2815] Reduce buffer count by one --- osu.Game/Graphics/Backgrounds/Triangles.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 4f50abc985..62151d066b 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -213,11 +213,14 @@ namespace osu.Game.Graphics.Backgrounds public override void Draw(Action vertexAction) { - if (vertexBatch == null && Source.AimCount > 0) - vertexBatch = new LinearBatch(Source.AimCount * 3, 2, PrimitiveType.Triangles); - base.Draw(vertexAction); + if (vertexBatch == null || vertexBatch.Size != Source.AimCount * 6) + { + vertexBatch?.Dispose(); + vertexBatch = new LinearBatch(Source.AimCount * 6, 1, PrimitiveType.Triangles); + } + shader.Bind(); Vector2 localInflationAmount = edge_smoothness * DrawInfo.MatrixInverse.ExtractScale().Xy; From 99ab77b926a5fb3e569e2955eb577e1a2211adfd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jul 2019 15:33:09 +0900 Subject: [PATCH 0252/2815] Add PaginatedWebRequest to handle request pagination --- .../API/Requests/GetUserBeatmapsRequest.cs | 11 +++---- .../GetUserMostPlayedBeatmapsRequest.cs | 9 ++---- .../GetUserRecentActivitiesRequest.cs | 9 ++---- .../API/Requests/GetUserScoresRequest.cs | 10 ++----- .../API/Requests/PaginatedAPIRequest.cs | 30 +++++++++++++++++++ 5 files changed, 43 insertions(+), 26 deletions(-) create mode 100644 osu.Game/Online/API/Requests/PaginatedAPIRequest.cs diff --git a/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs b/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs index 57005181ca..978d0915fb 100644 --- a/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs @@ -7,23 +7,20 @@ using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { - public class GetUserBeatmapsRequest : APIRequest> + public class GetUserBeatmapsRequest : PaginatedAPIRequest> { private readonly long userId; - private readonly int offset; - private readonly int limit; + private readonly BeatmapSetType type; public GetUserBeatmapsRequest(long userId, BeatmapSetType type, int offset = 0, int limit = 6) + : base(offset, limit) { this.userId = userId; - this.offset = offset; - this.limit = limit; this.type = type; } - // ReSharper disable once ImpureMethodCallOnReadonlyValueField - protected override string Target => $@"users/{userId}/beatmapsets/{type.ToString().Underscore()}?offset={offset}&limit={limit}"; + protected override string Target => $@"users/{userId}/beatmapsets/{type.ToString().Underscore()}"; } public enum BeatmapSetType diff --git a/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs b/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs index fccb27a86c..a363d127d8 100644 --- a/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs @@ -6,19 +6,16 @@ using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { - public class GetUserMostPlayedBeatmapsRequest : APIRequest> + public class GetUserMostPlayedBeatmapsRequest : PaginatedAPIRequest> { private readonly long userId; - private readonly int offset; - private readonly int limit; public GetUserMostPlayedBeatmapsRequest(long userId, int offset = 0, int limit = 5) + : base(offset, limit) { this.userId = userId; - this.offset = offset; - this.limit = limit; } - protected override string Target => $@"users/{userId}/beatmapsets/most_played?offset={offset}&limit={limit}"; + protected override string Target => $@"users/{userId}/beatmapsets/most_played"; } } diff --git a/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs b/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs index d066636911..5675e877b5 100644 --- a/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs @@ -6,20 +6,17 @@ using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { - public class GetUserRecentActivitiesRequest : APIRequest> + public class GetUserRecentActivitiesRequest : PaginatedAPIRequest> { private readonly long userId; - private readonly int offset; - private readonly int limit; public GetUserRecentActivitiesRequest(long userId, int offset = 0, int limit = 5) + : base(offset, limit) { this.userId = userId; - this.offset = offset; - this.limit = limit; } - protected override string Target => $"users/{userId}/recent_activity?offset={offset}&limit={limit}"; + protected override string Target => $"users/{userId}/recent_activity"; } public enum RecentActivityType diff --git a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs index 0e2f7ec417..03a11b68ac 100644 --- a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs @@ -6,23 +6,19 @@ using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { - public class GetUserScoresRequest : APIRequest> + public class GetUserScoresRequest : PaginatedAPIRequest> { private readonly long userId; private readonly ScoreType type; - private readonly int offset; - private readonly int limit; public GetUserScoresRequest(long userId, ScoreType type, int offset = 0, int limit = 5) + : base(offset, limit) { this.userId = userId; this.type = type; - this.offset = offset; - this.limit = limit; } - // ReSharper disable once ImpureMethodCallOnReadonlyValueField - protected override string Target => $@"users/{userId}/scores/{type.ToString().ToLowerInvariant()}?offset={offset}&limit={limit}"; + protected override string Target => $@"users/{userId}/scores/{type.ToString().ToLowerInvariant()}"; } public enum ScoreType diff --git a/osu.Game/Online/API/Requests/PaginatedAPIRequest.cs b/osu.Game/Online/API/Requests/PaginatedAPIRequest.cs new file mode 100644 index 0000000000..45360612bd --- /dev/null +++ b/osu.Game/Online/API/Requests/PaginatedAPIRequest.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.Globalization; +using osu.Framework.IO.Network; + +namespace osu.Game.Online.API.Requests +{ + public abstract class PaginatedAPIRequest : APIRequest + { + private readonly int offset; + private readonly int limit; + + protected PaginatedAPIRequest(int offset, int limit) + { + this.offset = offset; + this.limit = limit; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + + req.AddParameter("offset", offset.ToString(CultureInfo.InvariantCulture)); + req.AddParameter("limit", limit.ToString(CultureInfo.InvariantCulture)); + + return req; + } + } +} From 066bee35350fa0f094a1b08983d426ff5b178f18 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jul 2019 16:02:33 +0900 Subject: [PATCH 0253/2815] Simplify offset calculation --- .../Online/API/Requests/GetUserBeatmapsRequest.cs | 4 ++-- .../Requests/GetUserMostPlayedBeatmapsRequest.cs | 4 ++-- .../API/Requests/GetUserRecentActivitiesRequest.cs | 4 ++-- .../Online/API/Requests/GetUserScoresRequest.cs | 4 ++-- .../Online/API/Requests/PaginatedAPIRequest.cs | 14 +++++++------- .../Sections/Beatmaps/PaginatedBeatmapContainer.cs | 2 +- .../PaginatedMostPlayedBeatmapContainer.cs | 2 +- .../Sections/Ranks/PaginatedScoreContainer.cs | 2 +- .../Recent/PaginatedRecentActivityContainer.cs | 2 +- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs b/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs index 978d0915fb..f3384163b8 100644 --- a/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs @@ -13,8 +13,8 @@ namespace osu.Game.Online.API.Requests private readonly BeatmapSetType type; - public GetUserBeatmapsRequest(long userId, BeatmapSetType type, int offset = 0, int limit = 6) - : base(offset, limit) + public GetUserBeatmapsRequest(long userId, BeatmapSetType type, int page = 0, int itemsPerPage = 6) + : base(page, itemsPerPage) { this.userId = userId; this.type = type; diff --git a/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs b/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs index a363d127d8..9f094e51c4 100644 --- a/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs @@ -10,8 +10,8 @@ namespace osu.Game.Online.API.Requests { private readonly long userId; - public GetUserMostPlayedBeatmapsRequest(long userId, int offset = 0, int limit = 5) - : base(offset, limit) + public GetUserMostPlayedBeatmapsRequest(long userId, int page = 0, int itemsPerPage = 5) + : base(page, itemsPerPage) { this.userId = userId; } diff --git a/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs b/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs index 5675e877b5..4908e5ecc2 100644 --- a/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs @@ -10,8 +10,8 @@ namespace osu.Game.Online.API.Requests { private readonly long userId; - public GetUserRecentActivitiesRequest(long userId, int offset = 0, int limit = 5) - : base(offset, limit) + public GetUserRecentActivitiesRequest(long userId, int page = 0, int itemsPerPage = 5) + : base(page, itemsPerPage) { this.userId = userId; } diff --git a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs index 03a11b68ac..d41966fe1b 100644 --- a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs @@ -11,8 +11,8 @@ namespace osu.Game.Online.API.Requests private readonly long userId; private readonly ScoreType type; - public GetUserScoresRequest(long userId, ScoreType type, int offset = 0, int limit = 5) - : base(offset, limit) + public GetUserScoresRequest(long userId, ScoreType type, int page = 0, int itemsPerPage = 5) + : base(page, itemsPerPage) { this.userId = userId; this.type = type; diff --git a/osu.Game/Online/API/Requests/PaginatedAPIRequest.cs b/osu.Game/Online/API/Requests/PaginatedAPIRequest.cs index 45360612bd..52e12f04ee 100644 --- a/osu.Game/Online/API/Requests/PaginatedAPIRequest.cs +++ b/osu.Game/Online/API/Requests/PaginatedAPIRequest.cs @@ -8,21 +8,21 @@ namespace osu.Game.Online.API.Requests { public abstract class PaginatedAPIRequest : APIRequest { - private readonly int offset; - private readonly int limit; + private readonly int page; + private readonly int itemsPerPage; - protected PaginatedAPIRequest(int offset, int limit) + protected PaginatedAPIRequest(int page, int itemsPerPage) { - this.offset = offset; - this.limit = limit; + this.page = page; + this.itemsPerPage = itemsPerPage; } protected override WebRequest CreateWebRequest() { var req = base.CreateWebRequest(); - req.AddParameter("offset", offset.ToString(CultureInfo.InvariantCulture)); - req.AddParameter("limit", limit.ToString(CultureInfo.InvariantCulture)); + req.AddParameter("offset", (page * itemsPerPage).ToString(CultureInfo.InvariantCulture)); + req.AddParameter("limit", itemsPerPage.ToString(CultureInfo.InvariantCulture)); return req; } diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index 22f8e6c6b0..1b6c1c99a6 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -29,7 +29,7 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps protected override void ShowMore() { - request = new GetUserBeatmapsRequest(User.Value.Id, type, VisiblePages++ * ItemsPerPage, ItemsPerPage); + request = new GetUserBeatmapsRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); request.Success += sets => Schedule(() => { MoreButton.FadeTo(sets.Count == ItemsPerPage ? 1 : 0); diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs index 92b0304a68..9409cd9aeb 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical protected override void ShowMore() { - request = new GetUserMostPlayedBeatmapsRequest(User.Value.Id, VisiblePages++ * ItemsPerPage, ItemsPerPage); + request = new GetUserMostPlayedBeatmapsRequest(User.Value.Id, VisiblePages++, ItemsPerPage); request.Success += beatmaps => Schedule(() => { MoreButton.FadeTo(beatmaps.Count == ItemsPerPage ? 1 : 0); diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 5bb362e21c..4a9ac6e5c7 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks protected override void ShowMore() { - request = new GetUserScoresRequest(User.Value.Id, type, VisiblePages++ * ItemsPerPage, ItemsPerPage); + request = new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); request.Success += scores => Schedule(() => { foreach (var s in scores) diff --git a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs index 215c96a423..f2a778a874 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Profile.Sections.Recent protected override void ShowMore() { - request = new GetUserRecentActivitiesRequest(User.Value.Id, VisiblePages++ * ItemsPerPage, ItemsPerPage); + request = new GetUserRecentActivitiesRequest(User.Value.Id, VisiblePages++, ItemsPerPage); request.Success += activities => Schedule(() => { MoreButton.FadeTo(activities.Count == ItemsPerPage ? 1 : 0); From eebfdd88650446da481fdfceebbfdb0b1f17c8ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Jul 2019 23:43:25 +0900 Subject: [PATCH 0254/2815] 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 9aa5e631ad..b9451fc744 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -63,6 +63,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 86a68d2159..d90b1d36e1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -15,7 +15,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 712effcc39..fa2521a19e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From 5696d794235850fc37578ef475c4b91e2ec74d98 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Jul 2019 23:47:48 +0900 Subject: [PATCH 0255/2815] Use TriangleBatch --- osu.Game/Graphics/Backgrounds/Triangles.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 62151d066b..a2d8f42fac 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -8,7 +8,6 @@ using osuTK.Graphics; using System; using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Textures; -using osuTK.Graphics.ES30; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Primitives; using osu.Framework.Allocation; @@ -192,7 +191,7 @@ namespace osu.Game.Graphics.Backgrounds private readonly List parts = new List(); private Vector2 size; - private LinearBatch vertexBatch; + private TriangleBatch vertexBatch; public TrianglesDrawNode(Triangles source) : base(source) @@ -215,10 +214,10 @@ namespace osu.Game.Graphics.Backgrounds { base.Draw(vertexAction); - if (vertexBatch == null || vertexBatch.Size != Source.AimCount * 6) + if (vertexBatch == null || vertexBatch.Size != Source.AimCount) { vertexBatch?.Dispose(); - vertexBatch = new LinearBatch(Source.AimCount * 6, 1, PrimitiveType.Triangles); + vertexBatch = new TriangleBatch(Source.AimCount, 1); } shader.Bind(); From 2a94b68ecb4cebe004240b7f88a680b6ccd4f54b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 20 Jul 2019 22:50:17 +0900 Subject: [PATCH 0256/2815] Simplify logic --- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 0adaaa20b2..a6cc2b0500 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -127,19 +127,15 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private void getScores(BeatmapInfo beatmap) { - loadingAnimation.Show(); - getScoresRequest?.Cancel(); getScoresRequest = null; Scores = null; if (beatmap?.OnlineBeatmapID.HasValue != true) - { - loadingAnimation.Hide(); return; - } + loadingAnimation.Show(); getScoresRequest = new GetScoresRequest(beatmap, beatmap.Ruleset); getScoresRequest.Success += scores => { From 4c592a5e65a11ad8a3b472f0ab6ba796e1196109 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 20 Jul 2019 22:50:35 +0900 Subject: [PATCH 0257/2815] Fix TriangleDrawNode crash when aimcount is zero --- osu.Game/Graphics/Backgrounds/Triangles.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index a2d8f42fac..79f227fafe 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -214,7 +214,7 @@ namespace osu.Game.Graphics.Backgrounds { base.Draw(vertexAction); - if (vertexBatch == null || vertexBatch.Size != Source.AimCount) + if (vertexBatch == null || vertexBatch.Size != Source.AimCount && Source.AimCount > 0) { vertexBatch?.Dispose(); vertexBatch = new TriangleBatch(Source.AimCount, 1); From 2926932a1acf5adc8da8125ba83b05a7960ebb60 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 20 Jul 2019 21:10:17 +0200 Subject: [PATCH 0258/2815] Add MusicControllerToast used to display current music playback status on OSD --- osu.Game/OsuGame.cs | 6 +-- osu.Game/Overlays/OSD/MusicControllerToast.cs | 13 ++++++ osu.Game/Overlays/OSD/OsdIconToast.cs | 45 ------------------- 3 files changed, 16 insertions(+), 48 deletions(-) create mode 100644 osu.Game/Overlays/OSD/MusicControllerToast.cs delete mode 100644 osu.Game/Overlays/OSD/OsdIconToast.cs diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index bae301a8a6..dde87cd2ed 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -726,21 +726,21 @@ namespace osu.Game if (!musicController.IsLoaded) return true; if (musicController.PlayTrack()) - osd.Display(new Overlays.OSD.OsdIconToast(musicController.IsPlaying ? "Play track" : "Pause track", musicController.IsPlaying ? FontAwesome.Solid.PlayCircle : FontAwesome.Solid.PauseCircle)); + osd.Display(new Overlays.OSD.MusicControllerToast(musicController.IsPlaying ? "Play track" : "Pause track")); return true; case GlobalAction.MusicNext: if (!musicController.IsLoaded) return true; if (musicController.NextTrack()) - osd.Display(new Overlays.OSD.OsdIconToast("Next track", FontAwesome.Solid.FastForward)); + osd.Display(new Overlays.OSD.MusicControllerToast("Next track")); return true; case GlobalAction.MusicPrev: if (!musicController.IsLoaded) return true; if (musicController.PreviousTrack()) - osd.Display(new Overlays.OSD.OsdIconToast("Previous track", FontAwesome.Solid.FastBackward)); + osd.Display(new Overlays.OSD.MusicControllerToast("Previous track")); return true; } diff --git a/osu.Game/Overlays/OSD/MusicControllerToast.cs b/osu.Game/Overlays/OSD/MusicControllerToast.cs new file mode 100644 index 0000000000..d9e0ad2c07 --- /dev/null +++ b/osu.Game/Overlays/OSD/MusicControllerToast.cs @@ -0,0 +1,13 @@ +// 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.Overlays.OSD +{ + public class MusicControllerToast : Toast + { + public MusicControllerToast(string value) + : base("Music Playback", value, "") + { + } + } +} diff --git a/osu.Game/Overlays/OSD/OsdIconToast.cs b/osu.Game/Overlays/OSD/OsdIconToast.cs deleted file mode 100644 index 0e2bcd377f..0000000000 --- a/osu.Game/Overlays/OSD/OsdIconToast.cs +++ /dev/null @@ -1,45 +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.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; - -namespace osu.Game.Overlays.OSD -{ - public class OsdIconToast : OsdToast - { - public OsdIconToast(string message, IconUsage icon) - { - Children = new Drawable[] - { - new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Direction = FillDirection.Vertical, - Spacing = new osuTK.Vector2(10), - Children = new Drawable[] - { - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.BottomCentre, - Font = OsuFont.GetFont(size: 24, weight: FontWeight.Light), - Text = message - }, - new SpriteIcon - { - Icon = icon, - Size = new osuTK.Vector2(45), - Anchor = Anchor.Centre, - Origin = Anchor.BottomCentre, - } - } - } - }; - } - } -} From 842417cf42480d7df01a50e23f6e016a720ca9a4 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 21 Jul 2019 03:07:27 +0300 Subject: [PATCH 0259/2815] Check if selected scope requires API --- osu.Game/Online/Leaderboards/Leaderboard.cs | 6 +++++- osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs | 2 ++ osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 18c827707a..5e3f57a19e 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -194,13 +194,17 @@ namespace osu.Game.Online.Leaderboards private APIRequest getScoresRequest; + protected abstract bool IsOnlineScope(); + public void APIStateChanged(IAPIProvider api, APIState state) { switch (state) { case APIState.Online: case APIState.Offline: - UpdateScores(); + if (IsOnlineScope()) + UpdateScores(); + break; } } diff --git a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs index fff713f026..873765c17c 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs @@ -33,6 +33,8 @@ namespace osu.Game.Screens.Multi.Match.Components }, true); } + protected override bool IsOnlineScope() => true; + protected override APIRequest FetchScores(Action> scoresCallback) { if (roomId.Value == null) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 0f6d4f3188..3c857cc44a 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -79,6 +79,8 @@ namespace osu.Game.Screens.Select.Leaderboards }; } + protected override bool IsOnlineScope() => Scope != BeatmapLeaderboardScope.Local; + protected override APIRequest FetchScores(Action> scoresCallback) { if (Scope == BeatmapLeaderboardScope.Local) From e76b3e2b406d2e744b1794edbd1fe6cca17352d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 21 Jul 2019 10:42:40 +0900 Subject: [PATCH 0260/2815] User property instead of method --- osu.Game/Online/Leaderboards/Leaderboard.cs | 4 ++-- osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs | 2 +- osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 5e3f57a19e..98f15599fc 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -194,7 +194,7 @@ namespace osu.Game.Online.Leaderboards private APIRequest getScoresRequest; - protected abstract bool IsOnlineScope(); + protected abstract bool IsOnlineScope { get; } public void APIStateChanged(IAPIProvider api, APIState state) { @@ -202,7 +202,7 @@ namespace osu.Game.Online.Leaderboards { case APIState.Online: case APIState.Offline: - if (IsOnlineScope()) + if (IsOnlineScope) UpdateScores(); break; diff --git a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs index 873765c17c..ae27e53813 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Multi.Match.Components }, true); } - protected override bool IsOnlineScope() => true; + protected override bool IsOnlineScope => true; protected override APIRequest FetchScores(Action> scoresCallback) { diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 3c857cc44a..cb45c00f66 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -79,7 +79,7 @@ namespace osu.Game.Screens.Select.Leaderboards }; } - protected override bool IsOnlineScope() => Scope != BeatmapLeaderboardScope.Local; + protected override bool IsOnlineScope => Scope != BeatmapLeaderboardScope.Local; protected override APIRequest FetchScores(Action> scoresCallback) { From ed0ef90613bf1038883c4368cb6702da211fe45f Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 21 Jul 2019 12:14:55 +0300 Subject: [PATCH 0261/2815] Separate glowing sprite text into it's own class --- .../Graphics/Sprites/GlowingSpriteText.cs | 110 ++++++++++++++++++ .../Online/Leaderboards/LeaderboardScore.cs | 57 ++------- 2 files changed, 122 insertions(+), 45 deletions(-) create mode 100644 osu.Game/Graphics/Sprites/GlowingSpriteText.cs diff --git a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs new file mode 100644 index 0000000000..77187b9ff2 --- /dev/null +++ b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs @@ -0,0 +1,110 @@ +// 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.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Graphics.Sprites +{ + public class GlowingSpriteText : Container + { + private readonly BufferedContainer blurContainer; + private readonly OsuSpriteText spriteText, glowingText; + + private string text = string.Empty; + + public string Text + { + get => text; + set + { + text = value; + + spriteText.Text = text; + glowingText.Text = text; + } + } + + private FontUsage font = OsuFont.Default.With(fixedWidth: true); + + public FontUsage Font + { + get => font; + set + { + font = value.With(fixedWidth: true); + + spriteText.Font = font; + glowingText.Font = font; + } + } + + private Vector2 textSize; + + public Vector2 TextSize + { + get => textSize; + set + { + textSize = value; + + spriteText.Size = textSize; + glowingText.Size = textSize; + } + } + + public ColourInfo TextColour + { + get => spriteText.Colour; + set => spriteText.Colour = value; + } + + public ColourInfo GlowColour + { + get => glowingText.Colour; + set => glowingText.Colour = value; + } + + public GlowingSpriteText() + { + AutoSizeAxes = Axes.Both; + + Children = new Drawable[] + { + blurContainer = new BufferedContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + BlurSigma = new Vector2(4), + CacheDrawnFrameBuffer = true, + RelativeSizeAxes = Axes.Both, + Blending = BlendingMode.Additive, + Size = new Vector2(3f), + Children = new[] + { + glowingText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = font, + Text = text, + Shadow = false, + }, + }, + }, + spriteText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = font, + Text = text, + Shadow = false, + }, + }; + } + } +} diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 9840b59805..008f8208eb 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -187,7 +187,13 @@ namespace osu.Game.Online.Leaderboards Spacing = new Vector2(5f, 0f), Children = new Drawable[] { - scoreLabel = new GlowingSpriteText(score.TotalScore.ToString(@"N0"), OsuFont.Numeric.With(size: 23), Color4.White, OsuColour.FromHex(@"83ccfa")), + scoreLabel = new GlowingSpriteText + { + TextColour = Color4.White, + GlowColour = OsuColour.FromHex(@"83ccfa"), + Text = score.TotalScore.ToString(@"N0"), + Font = OsuFont.Numeric.With(size: 23), + }, RankContainer = new Container { Size = new Vector2(40f, 20f), @@ -275,49 +281,6 @@ namespace osu.Game.Online.Leaderboards base.OnHoverLost(e); } - private class GlowingSpriteText : Container - { - public GlowingSpriteText(string text, FontUsage font, Color4 textColour, Color4 glowColour) - { - AutoSizeAxes = Axes.Both; - - Children = new Drawable[] - { - new BufferedContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - BlurSigma = new Vector2(4), - CacheDrawnFrameBuffer = true, - RelativeSizeAxes = Axes.Both, - Blending = BlendingMode.Additive, - Size = new Vector2(3f), - Children = new[] - { - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = font.With(fixedWidth: true), - Text = text, - Colour = glowColour, - Shadow = false, - }, - }, - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = font.With(fixedWidth: true), - Text = text, - Colour = textColour, - Shadow = false, - }, - }; - } - } - private class ScoreComponentLabel : Container, IHasTooltip { private const float icon_size = 20; @@ -367,10 +330,14 @@ namespace osu.Game.Online.Leaderboards }, }, }, - new GlowingSpriteText(statistic.Value, OsuFont.GetFont(size: 17, weight: FontWeight.Bold), Color4.White, OsuColour.FromHex(@"83ccfa")) + new GlowingSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, + TextColour = Color4.White, + GlowColour = OsuColour.FromHex(@"83ccfa"), + Text = statistic.Value, + Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold), }, }, }; From 9d7f6abbddb5354839d1afe45d706b806789abef Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 21 Jul 2019 12:18:31 +0300 Subject: [PATCH 0262/2815] Remove unnecessary field --- osu.Game/Graphics/Sprites/GlowingSpriteText.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs index 77187b9ff2..b075dc0476 100644 --- a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs +++ b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs @@ -12,7 +12,6 @@ namespace osu.Game.Graphics.Sprites { public class GlowingSpriteText : Container { - private readonly BufferedContainer blurContainer; private readonly OsuSpriteText spriteText, glowingText; private string text = string.Empty; @@ -75,7 +74,7 @@ namespace osu.Game.Graphics.Sprites Children = new Drawable[] { - blurContainer = new BufferedContainer + new BufferedContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, From 87fb22352c4fea9f2740021e8315f9331e263926 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 21 Jul 2019 12:28:55 +0300 Subject: [PATCH 0263/2815] glowingText -> blurredText --- osu.Game/Graphics/Sprites/GlowingSpriteText.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs index b075dc0476..7082e99600 100644 --- a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs +++ b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs @@ -12,7 +12,7 @@ namespace osu.Game.Graphics.Sprites { public class GlowingSpriteText : Container { - private readonly OsuSpriteText spriteText, glowingText; + private readonly OsuSpriteText spriteText, blurredText; private string text = string.Empty; @@ -85,7 +85,7 @@ namespace osu.Game.Graphics.Sprites Size = new Vector2(3f), Children = new[] { - glowingText = new OsuSpriteText + blurredText = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, From 57fc5cbda86e6c5ea6565227d66732d0cd8de506 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 21 Jul 2019 12:29:06 +0300 Subject: [PATCH 0264/2815] Fix CI issues --- osu.Game/Graphics/Sprites/GlowingSpriteText.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs index 7082e99600..abc81df7a7 100644 --- a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs +++ b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osuTK; -using osuTK.Graphics; namespace osu.Game.Graphics.Sprites { From affd0b28789b0339be71ee68e2265d5cae49f759 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 21 Jul 2019 12:34:52 +0300 Subject: [PATCH 0265/2815] Fix build issues --- osu.Game/Graphics/Sprites/GlowingSpriteText.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs index abc81df7a7..546b8cae58 100644 --- a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs +++ b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs @@ -23,7 +23,7 @@ namespace osu.Game.Graphics.Sprites text = value; spriteText.Text = text; - glowingText.Text = text; + blurredText.Text = text; } } @@ -37,7 +37,7 @@ namespace osu.Game.Graphics.Sprites font = value.With(fixedWidth: true); spriteText.Font = font; - glowingText.Font = font; + blurredText.Font = font; } } @@ -51,7 +51,7 @@ namespace osu.Game.Graphics.Sprites textSize = value; spriteText.Size = textSize; - glowingText.Size = textSize; + blurredText.Size = textSize; } } @@ -63,8 +63,8 @@ namespace osu.Game.Graphics.Sprites public ColourInfo GlowColour { - get => glowingText.Colour; - set => glowingText.Colour = value; + get => blurredText.Colour; + set => blurredText.Colour = value; } public GlowingSpriteText() From de8ac9a428b994691b2195ddf7cf3ab74b82e47a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 21 Jul 2019 21:41:07 +0300 Subject: [PATCH 0266/2815] Simple implementation --- osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs | 5 +++++ osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs | 4 ++++ osu.Game/Overlays/BeatmapSet/Header.cs | 2 ++ 3 files changed, 11 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs index ea3f0b61b9..df3a45d1cc 100644 --- a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs @@ -66,6 +66,11 @@ namespace osu.Game.Beatmaps /// public int FavouriteCount { get; set; } + /// + /// Whether this beatmap set has been favourited by the current user. + /// + public bool HasFavourited { get; set; } + /// /// The availability of this beatmap set. /// diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs index 200a705500..e5bfde8f8f 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs @@ -30,6 +30,9 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"preview_url")] private string preview { get; set; } + [JsonProperty(@"has_favourited")] + private bool hasFavourited { get; set; } + [JsonProperty(@"play_count")] private int playCount { get; set; } @@ -91,6 +94,7 @@ namespace osu.Game.Online.API.Requests.Responses Ranked = ranked, LastUpdated = lastUpdated, Availability = availability, + HasFavourited = hasFavourited, }, Beatmaps = beatmaps?.Select(b => b.ToBeatmap(rulesets)).ToList(), }; diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index b50eac2c1a..dd8a2f18c1 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -246,6 +246,8 @@ namespace osu.Game.Overlays.BeatmapSet onlineStatusPill.Status = setInfo.NewValue.OnlineInfo.Status; downloadButtonsContainer.FadeIn(transition_duration); + + favouriteButton.Favourited.Value = setInfo.NewValue.OnlineInfo.HasFavourited; favouriteButton.FadeIn(transition_duration); updateDownloadButtons(); From e50b70d6157266cb6448719b033aba2a15eb4401 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jul 2019 15:59:22 +0900 Subject: [PATCH 0267/2815] Centralise osu! circle radius specification --- .../Blueprints/HitCircles/Components/HitCirclePiece.cs | 2 +- .../Blueprints/Sliders/Components/SliderBodyPiece.cs | 3 +-- .../Objects/Drawables/DrawableSlider.cs | 2 +- .../Objects/Drawables/Pieces/CirclePiece.cs | 2 +- .../Objects/Drawables/Pieces/ExplodePiece.cs | 2 +- .../Objects/Drawables/Pieces/FlashPiece.cs | 4 ++-- .../Objects/Drawables/Pieces/RingPiece.cs | 4 ++-- .../Objects/Drawables/Pieces/SliderBall.cs | 10 ++++------ osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 2 +- 9 files changed, 14 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs index 7f6a60c400..fe11ead94d 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components this.hitCircle = hitCircle; Origin = Anchor.Centre; - Size = new Vector2((float)OsuHitObject.OBJECT_RADIUS * 2); + Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); Scale = new Vector2(hitCircle.Scale); CornerRadius = Size.X / 2; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs index 957550a051..f1f55731b6 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs @@ -24,7 +24,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components InternalChild = body = new ManualSliderBody { AccentColour = Color4.Transparent, - PathRadius = slider.Scale * 64 }; } @@ -34,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components body.BorderColour = colours.Yellow; PositionBindable.BindValueChanged(_ => updatePosition(), true); - ScaleBindable.BindValueChanged(scale => body.PathRadius = scale.NewValue * 64, true); + ScaleBindable.BindValueChanged(scale => body.PathRadius = scale.NewValue * OsuHitObject.OBJECT_RADIUS, true); } private void updatePosition() => Position = slider.StackedPosition; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 4d67c9ae34..56b5decd30 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { Body = new SnakingSliderBody(s) { - PathRadius = s.Scale * 64, + PathRadius = s.Scale * OsuHitObject.OBJECT_RADIUS, }, ticks = new Container { RelativeSizeAxes = Axes.Both }, repeatPoints = new Container { RelativeSizeAxes = Axes.Both }, diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs index 786cac7198..dc0b149140 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces public CirclePiece() { - Size = new Vector2((float)OsuHitObject.OBJECT_RADIUS * 2); + Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); Masking = true; CornerRadius = Size.X / 2; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs index b960f40578..8ff16f8b84 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public ExplodePiece() { - Size = new Vector2(128); + Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); Anchor = Anchor.Centre; Origin = Anchor.Centre; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs index 8e5eb886aa..c22073f56c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.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 osu.Framework.Graphics; @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public FlashPiece() { - Size = new Vector2(128); + Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); Anchor = Anchor.Centre; Origin = Anchor.Centre; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs index 28180a7f71..af733f16e1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public RingPiece() { - Size = new Vector2(128); + Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces RelativeSizeAxes = Axes.Both } } - }); + }, confineMode: ConfineMode.NoScaling); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index 7d1d77ae96..9ba8ad3474 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -17,8 +17,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class SliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition { - private const float width = 128; - private Color4 accentColour = Color4.Black; public Func GetInitialHitAction; @@ -57,8 +55,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Width = width, - Height = width, + Width = OsuHitObject.OBJECT_RADIUS * 2, + Height = OsuHitObject.OBJECT_RADIUS * 2, Alpha = 0, Child = new SkinnableDrawable("Play/osu/sliderfollowcircle", _ => new CircularContainer { @@ -84,8 +82,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Alpha = 1, Child = new Container { - Width = width, - Height = width, + Width = OsuHitObject.OBJECT_RADIUS * 2, + Height = OsuHitObject.OBJECT_RADIUS * 2, // TODO: support skin filename animation (sliderb0, sliderb1...) Child = new SkinnableDrawable("Play/osu/sliderb", _ => new CircularContainer { diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 364c182dd4..d1221fd2d3 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Objects { public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition { - public const double OBJECT_RADIUS = 64; + public const float OBJECT_RADIUS = 64; public double TimePreempt = 600; public double TimeFadeIn = 400; From 74c961bcffcf7030ccae4320e3fb951346a734d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jul 2019 12:10:28 +0900 Subject: [PATCH 0268/2815] Add more flexible skin element confine modes --- .../Drawables/Connections/FollowPoint.cs | 2 +- .../Objects/Drawables/DrawableRepeatPoint.cs | 2 +- .../Objects/Drawables/DrawableSliderTick.cs | 4 +- .../Objects/Drawables/Pieces/NumberPiece.cs | 2 +- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 2 +- .../Gameplay/TestSceneSkinReloadable.cs | 4 +- .../Rulesets/Judgements/DrawableJudgement.cs | 2 +- osu.Game/Skinning/SkinnableDrawable.cs | 44 +++++++++++++------ osu.Game/Skinning/SkinnableSprite.cs | 4 +- osu.Game/Skinning/SkinnableSpriteText.cs | 4 +- 10 files changed, 43 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index aacf3ee08d..a2a23e9ff7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections Anchor = Anchor.Centre, Alpha = 0.5f, } - }, restrictSize: false); + }, confineMode: ConfineMode.NoScaling); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index cce6dfe106..cf6f05a604 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { RelativeSizeAxes = Axes.Both, Icon = FontAwesome.Solid.ChevronRight - }, restrictSize: false) + }) }; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index 72b648bfd0..01ef1eb3e0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public DrawableSliderTick(SliderTick sliderTick) : base(sliderTick) { - Size = new Vector2(16) * sliderTick.Scale; + Size = new Vector2(16 * sliderTick.Scale); Origin = Anchor.Centre; InternalChildren = new Drawable[] @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Colour = AccentColour, Alpha = 0.3f, } - }, restrictSize: false) + }) }; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs index 84034d3ee9..e8dc63abca 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { Font = OsuFont.Numeric.With(size: 40), UseFullGlyphHeight = false, - }, restrictSize: false) + }, confineMode: ConfineMode.NoScaling) { Text = @"1" } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index 27546fa424..b3492a2b31 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor }, }, } - }, restrictSize: false) + }) { Origin = Anchor.Centre, Anchor = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinReloadable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinReloadable.cs index c7a0df6e9f..af03ddc65c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinReloadable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinReloadable.cs @@ -92,8 +92,8 @@ namespace osu.Game.Tests.Visual.Gameplay public new Drawable Drawable => base.Drawable; public int SkinChangedCount { get; private set; } - public SkinConsumer(string name, Func defaultImplementation, Func allowFallback = null, bool restrictSize = true) - : base(name, defaultImplementation, allowFallback, restrictSize) + public SkinConsumer(string name, Func defaultImplementation, Func allowFallback = null) + : base(name, defaultImplementation, allowFallback) { } diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 2150726a42..61c2644c6f 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Judgements Font = OsuFont.Numeric.With(size: 12), Colour = judgementColour(Result.Type), Scale = new Vector2(0.85f, 1), - }, restrictSize: false) + }) }; } diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs index 995cb15136..a30bdbd2dc 100644 --- a/osu.Game/Skinning/SkinnableDrawable.cs +++ b/osu.Game/Skinning/SkinnableDrawable.cs @@ -9,8 +9,8 @@ namespace osu.Game.Skinning { public class SkinnableDrawable : SkinnableDrawable { - public SkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null, bool restrictSize = true) - : base(name, defaultImplementation, allowFallback, restrictSize) + public SkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) + : base(name, defaultImplementation, allowFallback, confineMode) { } } @@ -29,7 +29,7 @@ namespace osu.Game.Skinning private readonly string componentName; - private readonly bool restrictSize; + private readonly ConfineMode confineMode; /// /// Create a new skinnable drawable. @@ -37,18 +37,18 @@ namespace osu.Game.Skinning /// The namespace-complete resource name for this skinnable element. /// A function to create the default skin implementation of this element. /// A conditional to decide whether to allow fallback to the default implementation if a skinned element is not present. - /// Whether a user-skin drawable should be limited to the size of our parent. - public SkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null, bool restrictSize = true) - : this(name, allowFallback, restrictSize) + /// How (if at all) the should be resize to fit within our own bounds. + public SkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) + : this(name, allowFallback, confineMode) { createDefault = defaultImplementation; } - protected SkinnableDrawable(string name, Func allowFallback = null, bool restrictSize = true) + protected SkinnableDrawable(string name, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) : base(allowFallback) { componentName = name; - this.restrictSize = restrictSize; + this.confineMode = confineMode; RelativeSizeAxes = Axes.Both; } @@ -58,7 +58,7 @@ namespace osu.Game.Skinning protected virtual T CreateDefault(string name) => createDefault(name); /// - /// Whether to apply size restrictions (specified via ) to the default implementation. + /// Whether to apply size restrictions (specified via ) to the default implementation. /// protected virtual bool ApplySizeRestrictionsToDefault => false; @@ -76,12 +76,18 @@ namespace osu.Game.Skinning if (Drawable != null) { - if (restrictSize && (!isDefault || ApplySizeRestrictionsToDefault)) + if (confineMode != ConfineMode.NoScaling && (!isDefault || ApplySizeRestrictionsToDefault)) { - Drawable.RelativeSizeAxes = Axes.Both; - Drawable.Size = Vector2.One; - Drawable.Scale = Vector2.One; - Drawable.FillMode = FillMode.Fit; + bool applyScaling = confineMode == ConfineMode.ScaleToFit || + (confineMode == ConfineMode.ScaleDownToFit && (Drawable.DrawSize.X > DrawSize.X || Drawable.DrawSize.Y > DrawSize.Y)); + + if (applyScaling) + { + Drawable.RelativeSizeAxes = Axes.Both; + Drawable.Size = Vector2.One; + Drawable.Scale = Vector2.One; + Drawable.FillMode = FillMode.Fit; + } } Drawable.Origin = Anchor.Centre; @@ -93,4 +99,14 @@ namespace osu.Game.Skinning ClearInternal(); } } + + public enum ConfineMode + { + /// + /// 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/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs index ceb1ed0f70..1716ec71de 100644 --- a/osu.Game/Skinning/SkinnableSprite.cs +++ b/osu.Game/Skinning/SkinnableSprite.cs @@ -18,8 +18,8 @@ namespace osu.Game.Skinning [Resolved] private TextureStore textures { get; set; } - public SkinnableSprite(string name, Func allowFallback = null, bool restrictSize = true) - : base(name, allowFallback, restrictSize) + public SkinnableSprite(string name, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) + : base(name, allowFallback, confineMode) { } diff --git a/osu.Game/Skinning/SkinnableSpriteText.cs b/osu.Game/Skinning/SkinnableSpriteText.cs index 36e646d743..d12a6f74f7 100644 --- a/osu.Game/Skinning/SkinnableSpriteText.cs +++ b/osu.Game/Skinning/SkinnableSpriteText.cs @@ -8,8 +8,8 @@ namespace osu.Game.Skinning { public class SkinnableSpriteText : SkinnableDrawable, IHasText { - public SkinnableSpriteText(string name, Func defaultImplementation, Func allowFallback = null, bool restrictSize = true) - : base(name, defaultImplementation, allowFallback, restrictSize) + public SkinnableSpriteText(string name, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) + : base(name, defaultImplementation, allowFallback, confineMode) { } From 9d091c96b81d2bf461890137cc32d87daa7b4972 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jul 2019 17:52:50 +0900 Subject: [PATCH 0269/2815] Use cache to ensure correct DrawSize when deciding scaling --- osu.Game/Skinning/SkinnableDrawable.cs | 32 +++++++++++++++++++------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs index a30bdbd2dc..5d1ca98bfd 100644 --- a/osu.Game/Skinning/SkinnableDrawable.cs +++ b/osu.Game/Skinning/SkinnableDrawable.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Caching; using osu.Framework.Graphics; using osuTK; @@ -55,6 +56,10 @@ namespace osu.Game.Skinning private readonly Func createDefault; + private readonly Cached scaling = new Cached(); + + private bool isDefault; + protected virtual T CreateDefault(string name) => createDefault(name); /// @@ -66,7 +71,7 @@ namespace osu.Game.Skinning { Drawable = skin.GetDrawableComponent(componentName); - bool isDefault = false; + isDefault = false; if (Drawable == null && allowFallback) { @@ -76,7 +81,23 @@ namespace osu.Game.Skinning if (Drawable != null) { - if (confineMode != ConfineMode.NoScaling && (!isDefault || ApplySizeRestrictionsToDefault)) + scaling.Invalidate(); + Drawable.Origin = Anchor.Centre; + Drawable.Anchor = Anchor.Centre; + + InternalChild = Drawable; + } + else + ClearInternal(); + } + + protected override void Update() + { + base.Update(); + + if (!scaling.IsValid) + { + if (Drawable != null && confineMode != ConfineMode.NoScaling && (!isDefault || ApplySizeRestrictionsToDefault)) { bool applyScaling = confineMode == ConfineMode.ScaleToFit || (confineMode == ConfineMode.ScaleDownToFit && (Drawable.DrawSize.X > DrawSize.X || Drawable.DrawSize.Y > DrawSize.Y)); @@ -90,13 +111,8 @@ namespace osu.Game.Skinning } } - Drawable.Origin = Anchor.Centre; - Drawable.Anchor = Anchor.Centre; - - InternalChild = Drawable; + scaling.Validate(); } - else - ClearInternal(); } } From eca63980d2ed7cda53a848af781eb495960a11c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jul 2019 17:53:01 +0900 Subject: [PATCH 0270/2815] Add comprehensive scaling mode tests --- .../Gameplay/TestSceneSkinReloadable.cs | 145 --------- .../Gameplay/TestSceneSkinnableDrawable.cs | 283 ++++++++++++++++++ 2 files changed, 283 insertions(+), 145 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneSkinReloadable.cs create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinReloadable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinReloadable.cs deleted file mode 100644 index af03ddc65c..0000000000 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinReloadable.cs +++ /dev/null @@ -1,145 +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 System; -using NUnit.Framework; -using osu.Framework.Audio.Sample; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Textures; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Skinning; -using osuTK.Graphics; - -namespace osu.Game.Tests.Visual.Gameplay -{ - public class TestSceneSkinReloadable : OsuTestScene - { - [Test] - public void TestInitialLoad() - { - var secondarySource = new SecondarySource(); - SkinConsumer consumer = null; - - AddStep("setup layout", () => - { - Child = new SkinSourceContainer - { - RelativeSizeAxes = Axes.Both, - Child = new LocalSkinOverrideContainer(secondarySource) - { - RelativeSizeAxes = Axes.Both, - Child = consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true) - } - }; - }); - - AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox); - AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1); - } - - [Test] - public void TestOverride() - { - var secondarySource = new SecondarySource(); - - SkinConsumer consumer = null; - Container target = null; - - AddStep("setup layout", () => - { - Child = new SkinSourceContainer - { - RelativeSizeAxes = Axes.Both, - Child = target = new LocalSkinOverrideContainer(secondarySource) - { - RelativeSizeAxes = Axes.Both, - } - }; - }); - - AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true))); - AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox); - AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1); - } - - private class NamedBox : Container - { - public NamedBox(string name) - { - Children = new Drawable[] - { - new Box - { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Font = OsuFont.Default.With(size: 40), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = name - } - }; - } - } - - private class SkinConsumer : SkinnableDrawable - { - public new Drawable Drawable => base.Drawable; - public int SkinChangedCount { get; private set; } - - public SkinConsumer(string name, Func defaultImplementation, Func allowFallback = null) - : base(name, defaultImplementation, allowFallback) - { - } - - protected override void SkinChanged(ISkinSource skin, bool allowFallback) - { - base.SkinChanged(skin, allowFallback); - SkinChangedCount++; - } - } - - private class BaseSourceBox : NamedBox - { - public BaseSourceBox() - : base("Base Source") - { - } - } - - private class SecondarySourceBox : NamedBox - { - public SecondarySourceBox() - : base("Secondary Source") - { - } - } - - private class SecondarySource : ISkin - { - public Drawable GetDrawableComponent(string componentName) => new SecondarySourceBox(); - - public Texture GetTexture(string componentName) => throw new NotImplementedException(); - - public SampleChannel GetSample(string sampleName) => throw new NotImplementedException(); - - public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException(); - } - - private class SkinSourceContainer : Container, ISkin - { - public Drawable GetDrawableComponent(string componentName) => new BaseSourceBox(); - - public Texture GetTexture(string componentName) => throw new NotImplementedException(); - - public SampleChannel GetSample(string sampleName) => throw new NotImplementedException(); - - public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException(); - } - } -} diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs new file mode 100644 index 0000000000..0b5978e3eb --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -0,0 +1,283 @@ +// 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.Globalization; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Audio.Sample; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneSkinnableDrawable : OsuTestScene + { + [Test] + public void TestConfineScaleDown() + { + FillFlowContainer fill = null; + + AddStep("setup layout larger source", () => + { + Child = new LocalSkinOverrideContainer(new SizedSource(50)) + { + RelativeSizeAxes = Axes.Both, + Child = fill = new FillFlowContainer + { + Size = new Vector2(30), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Spacing = new Vector2(10), + 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) + } + }, + }; + }); + + AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 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 })); + } + + [Test] + public void TestConfineScaleUp() + { + FillFlowContainer fill = null; + + AddStep("setup layout larger source", () => + { + Child = new LocalSkinOverrideContainer(new SizedSource(30)) + { + RelativeSizeAxes = Axes.Both, + Child = fill = new FillFlowContainer + { + Size = new Vector2(50), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Spacing = new Vector2(10), + 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) + } + }, + }; + }); + + AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 30, 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 })); + } + + [Test] + public void TestInitialLoad() + { + var secondarySource = new SecondarySource(); + SkinConsumer consumer = null; + + AddStep("setup layout", () => + { + Child = new SkinSourceContainer + { + RelativeSizeAxes = Axes.Both, + Child = new LocalSkinOverrideContainer(secondarySource) + { + RelativeSizeAxes = Axes.Both, + Child = consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true) + } + }; + }); + + AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox); + AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1); + } + + [Test] + public void TestOverride() + { + var secondarySource = new SecondarySource(); + + SkinConsumer consumer = null; + Container target = null; + + AddStep("setup layout", () => + { + Child = new SkinSourceContainer + { + RelativeSizeAxes = Axes.Both, + Child = target = new LocalSkinOverrideContainer(secondarySource) + { + RelativeSizeAxes = Axes.Both, + } + }; + }); + + AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true))); + AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox); + AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1); + } + + private class ExposedSkinnableDrawable : SkinnableDrawable + { + public new Drawable Drawable => base.Drawable; + + public ExposedSkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) + : base(name, defaultImplementation, allowFallback, confineMode) + { + } + } + + private class DefaultBox : DrawWidthBox + { + public DefaultBox() + { + RelativeSizeAxes = Axes.Both; + } + } + + private class DrawWidthBox : Container + { + private readonly OsuSpriteText text; + + public DrawWidthBox() + { + Children = new Drawable[] + { + new Box + { + Colour = Color4.Gray, + RelativeSizeAxes = Axes.Both, + }, + text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }; + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + text.Text = DrawWidth.ToString(CultureInfo.InvariantCulture); + } + } + + private class NamedBox : Container + { + public NamedBox(string name) + { + Children = new Drawable[] + { + new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Font = OsuFont.Default.With(size: 40), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = name + } + }; + } + } + + private class SkinConsumer : SkinnableDrawable + { + public new Drawable Drawable => base.Drawable; + public int SkinChangedCount { get; private set; } + + public SkinConsumer(string name, Func defaultImplementation, Func allowFallback = null) + : base(name, defaultImplementation, allowFallback) + { + } + + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + base.SkinChanged(skin, allowFallback); + SkinChangedCount++; + } + } + + private class BaseSourceBox : NamedBox + { + public BaseSourceBox() + : base("Base Source") + { + } + } + + private class SecondarySourceBox : NamedBox + { + public SecondarySourceBox() + : base("Secondary Source") + { + } + } + + private class SizedSource : ISkin + { + private readonly float size; + + public SizedSource(float size) + { + this.size = size; + } + + public Drawable GetDrawableComponent(string componentName) => + componentName == "available" + ? new DrawWidthBox + { + Colour = Color4.Yellow, + Size = new Vector2(size) + } + : null; + + public Texture GetTexture(string componentName) => throw new NotImplementedException(); + + public SampleChannel GetSample(string sampleName) => throw new NotImplementedException(); + + public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException(); + } + + private class SecondarySource : ISkin + { + public Drawable GetDrawableComponent(string componentName) => new SecondarySourceBox(); + + public Texture GetTexture(string componentName) => throw new NotImplementedException(); + + public SampleChannel GetSample(string sampleName) => throw new NotImplementedException(); + + public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException(); + } + + private class SkinSourceContainer : Container, ISkin + { + public Drawable GetDrawableComponent(string componentName) => new BaseSourceBox(); + + public Texture GetTexture(string componentName) => throw new NotImplementedException(); + + public SampleChannel GetSample(string sampleName) => throw new NotImplementedException(); + + public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException(); + } + } +} From 36c557c752be5bdecb37b9e761c6ad4551e47c12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jul 2019 18:39:33 +0900 Subject: [PATCH 0271/2815] Add null check and simplify scaling conditional logic via switch --- osu.Game/Skinning/SkinnableDrawable.cs | 33 ++++++++++++++++---------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs index 5d1ca98bfd..eb0508b568 100644 --- a/osu.Game/Skinning/SkinnableDrawable.cs +++ b/osu.Game/Skinning/SkinnableDrawable.cs @@ -84,7 +84,6 @@ namespace osu.Game.Skinning scaling.Invalidate(); Drawable.Origin = Anchor.Centre; Drawable.Anchor = Anchor.Centre; - InternalChild = Drawable; } else @@ -97,21 +96,31 @@ namespace osu.Game.Skinning if (!scaling.IsValid) { - if (Drawable != null && confineMode != ConfineMode.NoScaling && (!isDefault || ApplySizeRestrictionsToDefault)) + try { - bool applyScaling = confineMode == ConfineMode.ScaleToFit || - (confineMode == ConfineMode.ScaleDownToFit && (Drawable.DrawSize.X > DrawSize.X || Drawable.DrawSize.Y > DrawSize.Y)); + if (Drawable == null || (isDefault && !ApplySizeRestrictionsToDefault)) return; - if (applyScaling) + switch (confineMode) { - Drawable.RelativeSizeAxes = Axes.Both; - Drawable.Size = Vector2.One; - Drawable.Scale = Vector2.One; - Drawable.FillMode = FillMode.Fit; - } - } + case ConfineMode.NoScaling: + return; - scaling.Validate(); + case ConfineMode.ScaleDownToFit: + if (Drawable.DrawSize.X <= DrawSize.X && Drawable.DrawSize.Y <= DrawSize.Y) + return; + + break; + } + + Drawable.RelativeSizeAxes = Axes.Both; + Drawable.Size = Vector2.One; + Drawable.Scale = Vector2.One; + Drawable.FillMode = FillMode.Fit; + } + finally + { + scaling.Validate(); + } } } } From 8327452fe18b89b80fe374b6e873f97bacedc706 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jul 2019 14:45:25 +0900 Subject: [PATCH 0272/2815] Make AccentColour a bindable --- .../Drawable/DrawableCatchHitObject.cs | 2 +- .../Objects/Drawable/DrawableDroplet.cs | 11 +---- .../Objects/Drawable/DrawableFruit.cs | 46 +++++++++---------- .../TestSceneHoldNoteSelectionBlueprint.cs | 2 +- .../TestSceneNotes.cs | 4 +- .../Objects/Drawables/DrawableHoldNote.cs | 26 ++++------- .../Objects/Drawables/DrawableHoldNoteTick.cs | 17 ++----- .../Objects/Drawables/DrawableNote.cs | 30 +++++------- osu.Game.Rulesets.Mania/UI/Column.cs | 2 +- osu.Game.Rulesets.Mania/UI/HitExplosion.cs | 2 +- .../Objects/Drawables/DrawableHitCircle.cs | 18 +++----- .../Objects/Drawables/DrawableOsuHitObject.cs | 4 +- .../Objects/Drawables/DrawableSlider.cs | 19 +++----- .../Objects/Drawables/DrawableSliderTick.cs | 3 +- .../Objects/Drawables/DrawableHitObject.cs | 5 +- 15 files changed, 75 insertions(+), 116 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs index 5785d9a9ca..2ccb01a3e3 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable base.SkinChanged(skin, allowFallback); if (HitObject is IHasComboInformation combo) - AccentColour = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White; + AccentColour.Value = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White; } protected override void UpdateState(ArmedState state) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs index 9cabdc3dd9..059310d671 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Objects.Drawable { @@ -27,16 +26,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable private void load() { AddInternal(pulp = new Pulp { Size = Size }); - } - public override Color4 AccentColour - { - get => base.AccentColour; - set - { - base.AccentColour = value; - pulp.AccentColour = AccentColour; - } + AccentColour.BindValueChanged(colour => { pulp.AccentColour = colour.NewValue; }, true); } } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs index 77407def54..ce2daebbf1 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable private void load() { // todo: this should come from the skin. - AccentColour = colourForRepresentation(HitObject.VisualRepresentation); + AccentColour.Value = colourForRepresentation(HitObject.VisualRepresentation); AddRangeInternal(new[] { @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable Hollow = !HitObject.HyperDash, Type = EdgeEffectType.Glow, Radius = 4 * radius_adjust, - Colour = HitObject.HyperDash ? Color4.Red : AccentColour.Darken(1).Opacity(0.6f) + Colour = HitObject.HyperDash ? Color4.Red : AccentColour.Value.Darken(1).Opacity(0.6f) }, Size = new Vector2(Height), Anchor = Anchor.Centre, @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable new Box { AlwaysPresent = true, - Colour = AccentColour, + Colour = AccentColour.Value, Alpha = 0, RelativeSizeAxes = Axes.Both } @@ -115,32 +115,32 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { new Pulp { - AccentColour = AccentColour, + AccentColour = AccentColour.Value, Size = new Vector2(small_pulp), Y = -0.34f, }, new Pulp { - AccentColour = AccentColour, + AccentColour = AccentColour.Value, Size = new Vector2(large_pulp_4), Position = positionAt(0, distance_from_centre_4), }, new Pulp { - AccentColour = AccentColour, + AccentColour = AccentColour.Value, Size = new Vector2(large_pulp_4), Position = positionAt(90, distance_from_centre_4), }, new Pulp { - AccentColour = AccentColour, + AccentColour = AccentColour.Value, Size = new Vector2(large_pulp_4), Position = positionAt(180, distance_from_centre_4), }, new Pulp { Size = new Vector2(large_pulp_4), - AccentColour = AccentColour, + AccentColour = AccentColour.Value, Position = positionAt(270, distance_from_centre_4), }, } @@ -154,32 +154,32 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { new Pulp { - AccentColour = AccentColour, + AccentColour = AccentColour.Value, Size = new Vector2(small_pulp), Y = -0.3f, }, new Pulp { - AccentColour = AccentColour, + AccentColour = AccentColour.Value, Size = new Vector2(large_pulp_4), Position = positionAt(45, distance_from_centre_4), }, new Pulp { - AccentColour = AccentColour, + AccentColour = AccentColour.Value, Size = new Vector2(large_pulp_4), Position = positionAt(135, distance_from_centre_4), }, new Pulp { - AccentColour = AccentColour, + AccentColour = AccentColour.Value, Size = new Vector2(large_pulp_4), Position = positionAt(225, distance_from_centre_4), }, new Pulp { Size = new Vector2(large_pulp_4), - AccentColour = AccentColour, + AccentColour = AccentColour.Value, Position = positionAt(315, distance_from_centre_4), }, } @@ -193,26 +193,26 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { new Pulp { - AccentColour = AccentColour, + AccentColour = AccentColour.Value, Size = new Vector2(small_pulp), Y = -0.33f, }, new Pulp { - AccentColour = AccentColour, + AccentColour = AccentColour.Value, Size = new Vector2(large_pulp_3), Position = positionAt(60, distance_from_centre_3), }, new Pulp { - AccentColour = AccentColour, + AccentColour = AccentColour.Value, Size = new Vector2(large_pulp_3), Position = positionAt(180, distance_from_centre_3), }, new Pulp { Size = new Vector2(large_pulp_3), - AccentColour = AccentColour, + AccentColour = AccentColour.Value, Position = positionAt(300, distance_from_centre_3), }, } @@ -226,26 +226,26 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { new Pulp { - AccentColour = AccentColour, + AccentColour = AccentColour.Value, Size = new Vector2(small_pulp), Y = -0.25f, }, new Pulp { - AccentColour = AccentColour, + AccentColour = AccentColour.Value, Size = new Vector2(large_pulp_3), Position = positionAt(0, distance_from_centre_3), }, new Pulp { - AccentColour = AccentColour, + AccentColour = AccentColour.Value, Size = new Vector2(large_pulp_3), Position = positionAt(120, distance_from_centre_3), }, new Pulp { Size = new Vector2(large_pulp_3), - AccentColour = AccentColour, + AccentColour = AccentColour.Value, Position = positionAt(240, distance_from_centre_3), }, } @@ -259,13 +259,13 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { new Pulp { - AccentColour = AccentColour, + AccentColour = AccentColour.Value, Size = new Vector2(small_pulp), Y = -0.3f }, new Pulp { - AccentColour = AccentColour, + AccentColour = AccentColour.Value, Size = new Vector2(large_pulp_4 * 0.8f, large_pulp_4 * 2.5f), Y = 0.05f, }, diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs index 04c5724f93..622d840a0c 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Mania.Tests Child = drawableObject = new DrawableHoldNote(holdNote) { Height = 300, - AccentColour = OsuColour.Gray(0.3f) + AccentColour = { Value = OsuColour.Gray(0.3f) } } }; } diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs index b2613a59d5..031abb08e2 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Mania.Tests AutoSizeAxes = Axes.Both, Child = new NoteContainer(direction, $"note {identifier}, scrolling {direction.ToString().ToLowerInvariant()}") { - Child = hitObject = new DrawableNote(note) { AccentColour = Color4.OrangeRed } + Child = hitObject = new DrawableNote(note) { AccentColour = { Value = Color4.OrangeRed } } } }; } @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Mania.Tests Child = hitObject = new DrawableHoldNote(note) { RelativeSizeAxes = Axes.Both, - AccentColour = Color4.OrangeRed, + AccentColour = { Value = Color4.OrangeRed }, } } }; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 9368af987d..952c6e128e 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -6,7 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; -using osuTK.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Scoring; @@ -36,11 +35,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// private bool hasBroken; - private readonly Container tickContainer; - public DrawableHoldNote(HoldNote hitObject) : base(hitObject) { + Container tickContainer; RelativeSizeAxes = Axes.X; AddRangeInternal(new Drawable[] @@ -74,6 +72,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables AddNested(Head); AddNested(Tail); + + AccentColour.BindValueChanged(colour => + { + bodyPiece.AccentColour = colour.NewValue; + Head.AccentColour.Value = colour.NewValue; + Tail.AccentColour.Value = colour.NewValue; + tickContainer.ForEach(t => t.AccentColour.Value = colour.NewValue); + }, true); } protected override void OnDirectionChanged(ValueChangedEvent e) @@ -83,20 +89,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables bodyPiece.Anchor = bodyPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; } - public override Color4 AccentColour - { - get => base.AccentColour; - set - { - base.AccentColour = value; - - bodyPiece.AccentColour = value; - Head.AccentColour = value; - Tail.AccentColour = value; - tickContainer.ForEach(t => t.AccentColour = value); - } - } - protected override void CheckForResult(bool userTriggered, double timeOffset) { if (Tail.AllJudged) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs index 9a29273282..9b0322a6cd 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs @@ -3,7 +3,6 @@ using System; using osuTK; -using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -23,11 +22,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// public Func HoldStartTime; - private readonly Container glowContainer; - public DrawableHoldNoteTick(HoldNoteTick hitObject) : base(hitObject) { + Container glowContainer; + Anchor = Anchor.TopCentre; Origin = Anchor.TopCentre; @@ -53,23 +52,17 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables } } }); - } - public override Color4 AccentColour - { - get => base.AccentColour; - set + AccentColour.BindValueChanged(colour => { - base.AccentColour = value; - glowContainer.EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, Radius = 2f, Roundness = 15f, - Colour = value.Opacity(0.3f) + Colour = colour.NewValue.Opacity(0.3f) }; - } + }, true); } protected override void CheckForResult(bool userTriggered, double timeOffset) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index afd7777861..dccff7f6ac 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -3,7 +3,6 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; -using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Effects; using osu.Framework.Input.Bindings; @@ -30,6 +29,18 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Masking = true; AddInternal(headPiece = new NotePiece()); + + AccentColour.BindValueChanged(colour => + { + headPiece.AccentColour = colour.NewValue; + + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = colour.NewValue.Lighten(1f).Opacity(0.6f), + Radius = 10, + }; + }, true); } protected override void OnDirectionChanged(ValueChangedEvent e) @@ -39,23 +50,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables headPiece.Anchor = headPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; } - public override Color4 AccentColour - { - get => base.AccentColour; - set - { - base.AccentColour = value; - headPiece.AccentColour = AccentColour; - - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = AccentColour.Lighten(1f).Opacity(0.6f), - Radius = 10, - }; - } - } - protected override void CheckForResult(bool userTriggered, double timeOffset) { if (!userTriggered) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index c59bed4ea7..91dd236ab1 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Mania.UI /// The DrawableHitObject to add. public override void Add(DrawableHitObject hitObject) { - hitObject.AccentColour = AccentColour; + hitObject.AccentColour.Value = AccentColour; hitObject.OnNewResult += OnNewResult; HitObjectContainer.Add(hitObject); diff --git a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs index 0ec1fc38d2..48470add8b 100644 --- a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Mania.UI EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, - Colour = Interpolation.ValueAt(0.1f, judgedObject.AccentColour, Color4.White, 0, 1), + Colour = Interpolation.ValueAt(0.1f, judgedObject.AccentColour.Value, Color4.White, 0, 1), Radius = 100, }, Child = new Box diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index fef0bfdc2c..a83f1b5e56 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -10,7 +10,6 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osuTK; using osu.Game.Rulesets.Scoring; -using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -98,19 +97,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables positionBindable.BindTo(HitObject.PositionBindable); stackHeightBindable.BindTo(HitObject.StackHeightBindable); scaleBindable.BindTo(HitObject.ScaleBindable); - } - public override Color4 AccentColour - { - get => base.AccentColour; - set + AccentColour.BindValueChanged(colour => { - base.AccentColour = value; - explode.Colour = AccentColour; - glow.Colour = AccentColour; - circle.Colour = AccentColour; - ApproachCircle.Colour = AccentColour; - } + explode.Colour = colour.NewValue; + glow.Colour = colour.NewValue; + circle.Colour = colour.NewValue; + ApproachCircle.Colour = colour.NewValue; + }, true); } protected override void CheckForResult(bool userTriggered, double timeOffset) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 4533e08a2b..30cf09c9d7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.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; @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.SkinChanged(skin, allowFallback); if (HitObject is IHasComboInformation combo) - AccentColour = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White; + AccentColour.Value = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White; } protected virtual void UpdatePreemptState() => this.FadeIn(HitObject.TimeFadeIn); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 4d67c9ae34..e06287f527 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -114,20 +114,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables pathBindable.BindTo(slider.PathBindable); pathBindable.BindValueChanged(_ => Body.Refresh()); - } - public override Color4 AccentColour - { - get => base.AccentColour; - set + AccentColour.BindValueChanged(colour => { - base.AccentColour = value; - Body.AccentColour = AccentColour; - Ball.AccentColour = AccentColour; + Body.AccentColour = colour.NewValue; + Ball.AccentColour = colour.NewValue; foreach (var drawableHitObject in NestedHitObjects) - drawableHitObject.AccentColour = AccentColour; - } + drawableHitObject.AccentColour.Value = colour.NewValue; + }, true); } public readonly Bindable Tracking = new Bindable(); @@ -167,9 +162,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.SkinChanged(skin, allowFallback); Body.BorderSize = skin.GetValue(s => s.SliderBorderSize) ?? SliderBody.DEFAULT_BORDER_SIZE; - Body.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderTrackOverride") ? s.CustomColours["SliderTrackOverride"] : (Color4?)null) ?? AccentColour; + Body.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderTrackOverride") ? s.CustomColours["SliderTrackOverride"] : (Color4?)null) ?? AccentColour.Value; Body.BorderColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBorder") ? s.CustomColours["SliderBorder"] : (Color4?)null) ?? Color4.White; - Ball.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? AccentColour; + Ball.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? AccentColour.Value; } protected override void CheckForResult(bool userTriggered, double timeOffset) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index 72b648bfd0..ec294a1630 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -34,6 +34,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, CornerRadius = Size.X / 2, + Colour = AccentColour.Value, BorderThickness = 2, BorderColour = Color4.White, @@ -41,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Child = new Box { RelativeSizeAxes = Axes.Both, - Colour = AccentColour, + Colour = AccentColour.Value, Alpha = 0.3f, } }, restrictSize: false) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index e61fac679e..1fd6176f4f 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -9,7 +9,6 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics.Primitives; using osu.Game.Audio; -using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; @@ -18,14 +17,14 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables { - public abstract class DrawableHitObject : SkinReloadableDrawable, IHasAccentColour + public abstract class DrawableHitObject : SkinReloadableDrawable { public readonly HitObject HitObject; /// /// The colour used for various elements of this DrawableHitObject. /// - public virtual Color4 AccentColour { get; set; } = Color4.Gray; + public readonly Bindable AccentColour = new Bindable(Color4.Gray); // Todo: Rulesets should be overriding the resources instead, but we need to figure out where/when to apply overrides first protected virtual string SampleNamespace => null; From a631aac66435e3b6f0e2a3be851096a5c7ec74b4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jul 2019 15:01:01 +0900 Subject: [PATCH 0273/2815] Fix workingbeatmap's disposal potentially null-refing --- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 37aa0024da..949a2aab6f 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -247,7 +247,7 @@ namespace osu.Game.Beatmaps // cancelling the beatmap load is safe for now since the retrieval is a synchronous // operation. if we add an async retrieval method this may need to be reconsidered. - beatmapCancellation.Cancel(); + beatmapCancellation?.Cancel(); total_count.Value--; } From 91f86adb66250b20062ebbe3fbe1d7aede2b1b2d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jul 2019 15:05:56 +0900 Subject: [PATCH 0274/2815] Move DrawableHitObject state management to base class --- .../Drawable/DrawableCatchHitObject.cs | 10 --- .../Objects/Drawables/DrawableOsuHitObject.cs | 49 +---------- .../Objects/Drawables/DrawableHitObject.cs | 82 ++++++++++++++++--- 3 files changed, 75 insertions(+), 66 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs index 2ccb01a3e3..f5dd7cd255 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs @@ -3,12 +3,10 @@ using System; using osuTK; -using osuTK.Graphics; using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; -using osu.Game.Skinning; namespace osu.Game.Rulesets.Catch.Objects.Drawable { @@ -60,14 +58,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable ApplyResult(r => r.Type = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss); } - protected override void SkinChanged(ISkinSource skin, bool allowFallback) - { - base.SkinChanged(skin, allowFallback); - - if (HitObject is IHasComboInformation combo) - AccentColour.Value = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White; - } - protected override void UpdateState(ArmedState state) { using (BeginAbsoluteSequence(HitObject.StartTime - HitObject.TimePreempt)) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 30cf09c9d7..145be253bc 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -1,15 +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 osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Graphics; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Judgements; -using osu.Game.Rulesets.Scoring; -using osu.Game.Skinning; -using osuTK.Graphics; using osu.Game.Graphics.Containers; namespace osu.Game.Rulesets.Osu.Objects.Drawables @@ -39,49 +34,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void ClearInternal(bool disposeChildren = true) => shakeContainer.Clear(disposeChildren); protected override bool RemoveInternal(Drawable drawable) => shakeContainer.Remove(drawable); + protected override bool UseTransformStateManagement => true; + protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt; - protected sealed override void UpdateState(ArmedState state) - { - double transformTime = HitObject.StartTime - HitObject.TimePreempt; - - base.ApplyTransformsAt(transformTime, true); - base.ClearTransformsAfter(transformTime, true); - - using (BeginAbsoluteSequence(transformTime, true)) - { - UpdatePreemptState(); - - var judgementOffset = Math.Min(HitObject.HitWindows.HalfWindowFor(HitResult.Miss), Result?.TimeOffset ?? 0); - - using (BeginDelayedSequence(HitObject.TimePreempt + judgementOffset, true)) - UpdateCurrentState(state); - } - } - - protected override void SkinChanged(ISkinSource skin, bool allowFallback) - { - base.SkinChanged(skin, allowFallback); - - if (HitObject is IHasComboInformation combo) - AccentColour.Value = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White; - } - - protected virtual void UpdatePreemptState() => this.FadeIn(HitObject.TimeFadeIn); - - protected virtual void UpdateCurrentState(ArmedState state) - { - } - - // Todo: At some point we need to move these to DrawableHitObject after ensuring that all other Rulesets apply - // transforms in the same way and don't rely on them not being cleared - public override void ClearTransformsAfter(double time, bool propagateChildren = false, string targetMember = null) - { - } - - public override void ApplyTransformsAt(double time, bool propagateChildren = false) - { - } + protected override void UpdatePreemptState() => this.FadeIn(HitObject.TimeFadeIn); private OsuInputManager osuActionInputManager; internal OsuInputManager OsuActionInputManager => osuActionInputManager ?? (osuActionInputManager = GetContainingInputManager() as OsuInputManager); diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 1fd6176f4f..06ea82746f 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -79,10 +79,12 @@ namespace osu.Game.Rulesets.Objects.Drawables public override bool RemoveCompletedTransforms => false; protected override bool RequiresChildrenUpdate => true; - public override bool IsPresent => base.IsPresent || (State.Value == ArmedState.Idle && Clock?.CurrentTime >= LifetimeStart); + public override bool IsPresent => base.IsPresent || (state.Value == ArmedState.Idle && Clock?.CurrentTime >= LifetimeStart); public readonly Bindable State = new Bindable(); + private readonly Bindable state = new Bindable(); + protected DrawableHitObject(HitObject hitObject) { HitObject = hitObject; @@ -122,21 +124,81 @@ namespace osu.Game.Rulesets.Objects.Drawables { base.LoadComplete(); - State.ValueChanged += armed => + state.BindValueChanged(armed => { - UpdateState(armed.NewValue); + updateState(armed.NewValue); // apply any custom state overrides ApplyCustomUpdateState?.Invoke(this, armed.NewValue); if (armed.NewValue == ArmedState.Hit) PlaySamples(); - }; - - State.TriggerChange(); + }, true); } - protected abstract void UpdateState(ArmedState state); + protected virtual bool UseTransformStateManagement => false; + + private void updateState(ArmedState state) + { + if (UseTransformStateManagement) + { + double transformTime = HitObject.StartTime - InitialLifetimeOffset; + + base.ApplyTransformsAt(transformTime, true); + base.ClearTransformsAfter(transformTime, true); + + using (BeginAbsoluteSequence(transformTime, true)) + { + UpdatePreemptState(); + + var judgementOffset = Math.Min(HitObject.HitWindows?.HalfWindowFor(HitResult.Miss) ?? double.MaxValue, Result?.TimeOffset ?? 0); + + using (BeginDelayedSequence(InitialLifetimeOffset + judgementOffset, true)) + { + UpdateCurrentState(state); + State.Value = state; + } + } + } + else + { + State.Value = state; + } + + UpdateState(state); + } + + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + base.SkinChanged(skin, allowFallback); + + if (HitObject is IHasComboInformation combo) + AccentColour.Value = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White; + } + + protected virtual void UpdatePreemptState() + { + } + + protected virtual void UpdateCurrentState(ArmedState state) + { + } + + public override void ClearTransformsAfter(double time, bool propagateChildren = false, string targetMember = null) + { + if (!UseTransformStateManagement) + base.ClearTransformsAfter(time, propagateChildren, targetMember); + } + + public override void ApplyTransformsAt(double time, bool propagateChildren = false) + { + if (!UseTransformStateManagement) + base.ApplyTransformsAt(time, propagateChildren); + } + + protected virtual void UpdateState(ArmedState state) + { + } /// /// Bind to apply a custom state which can override the default implementation. @@ -163,7 +225,7 @@ namespace osu.Game.Rulesets.Objects.Drawables Result.TimeOffset = 0; Result.Type = HitResult.None; - State.Value = ArmedState.Idle; + state.Value = ArmedState.Idle; } } } @@ -243,11 +305,11 @@ namespace osu.Game.Rulesets.Objects.Drawables break; case HitResult.Miss: - State.Value = ArmedState.Miss; + state.Value = ArmedState.Miss; break; default: - State.Value = ArmedState.Hit; + state.Value = ArmedState.Hit; break; } From 6d889c8a37b3f83978fc5020c59857608ff5f5d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jul 2019 15:43:27 +0900 Subject: [PATCH 0275/2815] Revert unintended change --- osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs index af733f16e1..575f2c92c5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.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 osu.Framework.Graphics; @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces RelativeSizeAxes = Axes.Both } } - }, confineMode: ConfineMode.NoScaling); + }); } } } From be170b412496834986c5d214dd4660b4d26115a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jul 2019 15:33:12 +0900 Subject: [PATCH 0276/2815] Naming and documentation improvements --- .../Objects/Drawables/DrawableHitCircle.cs | 6 +-- .../Objects/Drawables/DrawableOsuHitObject.cs | 2 +- .../Objects/Drawables/DrawableRepeatPoint.cs | 4 +- .../Objects/Drawables/DrawableSlider.cs | 2 +- .../Objects/Drawables/DrawableSliderTick.cs | 4 +- .../Objects/Drawables/DrawableSpinner.cs | 6 +-- .../Objects/Drawables/DrawableHitObject.cs | 39 ++++++++++++------- 7 files changed, 37 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index a83f1b5e56..d3d763daf3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -128,16 +128,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ApplyResult(r => r.Type = result); } - protected override void UpdatePreemptState() + protected override void UpdateInitialTransforms() { - base.UpdatePreemptState(); + base.UpdateInitialTransforms(); ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadeIn * 2, HitObject.TimePreempt)); ApproachCircle.ScaleTo(1.1f, HitObject.TimePreempt); ApproachCircle.Expire(true); } - protected override void UpdateCurrentState(ArmedState state) + protected override void UpdateStateTransforms(ArmedState state) { glow.FadeOut(400); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 145be253bc..8107d366b3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt; - protected override void UpdatePreemptState() => this.FadeIn(HitObject.TimeFadeIn); + protected override void UpdateInitialTransforms() => this.FadeIn(HitObject.TimeFadeIn); private OsuInputManager osuActionInputManager; internal OsuInputManager OsuActionInputManager => osuActionInputManager ?? (osuActionInputManager = GetContainingInputManager() as OsuInputManager); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index cce6dfe106..1e2c0ae59f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ApplyResult(r => r.Type = drawableSlider.Tracking.Value ? HitResult.Great : HitResult.Miss); } - protected override void UpdatePreemptState() + protected override void UpdateInitialTransforms() { animDuration = Math.Min(150, repeatPoint.SpanDuration / 2); @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ); } - protected override void UpdateCurrentState(ArmedState state) + protected override void UpdateStateTransforms(ArmedState state) { switch (state) { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index e06287f527..d2089c05f3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }); } - protected override void UpdateCurrentState(ArmedState state) + protected override void UpdateStateTransforms(ArmedState state) { Ball.FadeIn(); Ball.ScaleTo(HitObject.Scale); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index ec294a1630..3e128e9f15 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -55,13 +55,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ApplyResult(r => r.Type = Tracking ? HitResult.Great : HitResult.Miss); } - protected override void UpdatePreemptState() + protected override void UpdateInitialTransforms() { this.FadeOut().FadeIn(ANIM_DURATION); this.ScaleTo(0.5f).ScaleTo(1f, ANIM_DURATION * 4, Easing.OutElasticHalf); } - protected override void UpdateCurrentState(ArmedState state) + protected override void UpdateStateTransforms(ArmedState state) { switch (state) { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 1794da54b7..a0bd301fdb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -196,9 +196,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables symbol.RotateTo(Disc.Rotation / 2, 500, Easing.OutQuint); } - protected override void UpdatePreemptState() + protected override void UpdateInitialTransforms() { - base.UpdatePreemptState(); + base.UpdateInitialTransforms(); circleContainer.ScaleTo(Spinner.Scale * 0.3f); circleContainer.ScaleTo(Spinner.Scale, HitObject.TimePreempt / 1.4f, Easing.OutQuint); @@ -213,7 +213,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables .ScaleTo(1, 500, Easing.OutQuint); } - protected override void UpdateCurrentState(ArmedState state) + protected override void UpdateStateTransforms(ArmedState state) { var sequence = this.Delay(Spinner.Duration).FadeOut(160); diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 06ea82746f..26d51e3809 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -118,8 +118,6 @@ namespace osu.Game.Rulesets.Objects.Drawables } } - protected override void ClearInternal(bool disposeChildren = true) => throw new InvalidOperationException($"Should never clear a {nameof(DrawableHitObject)}"); - protected override void LoadComplete() { base.LoadComplete(); @@ -136,8 +134,19 @@ namespace osu.Game.Rulesets.Objects.Drawables }, true); } + #region State / Transform Management + + /// + /// Enables automatic transform management of this hitobject. Implementation of transforms should be done in and only. Rewinding and removing previous states is done automatically. + /// + /// + /// Going forward, this is the preferred way of implementing s. Previous functionality + /// is offered as a compatibility layer until all rulesets have been migrated across. + /// protected virtual bool UseTransformStateManagement => false; + protected override void ClearInternal(bool disposeChildren = true) => throw new InvalidOperationException($"Should never clear a {nameof(DrawableHitObject)}"); + private void updateState(ArmedState state) { if (UseTransformStateManagement) @@ -149,13 +158,13 @@ namespace osu.Game.Rulesets.Objects.Drawables using (BeginAbsoluteSequence(transformTime, true)) { - UpdatePreemptState(); + UpdateInitialTransforms(); var judgementOffset = Math.Min(HitObject.HitWindows?.HalfWindowFor(HitResult.Miss) ?? double.MaxValue, Result?.TimeOffset ?? 0); using (BeginDelayedSequence(InitialLifetimeOffset + judgementOffset, true)) { - UpdateCurrentState(state); + UpdateStateTransforms(state); State.Value = state; } } @@ -168,19 +177,11 @@ namespace osu.Game.Rulesets.Objects.Drawables UpdateState(state); } - protected override void SkinChanged(ISkinSource skin, bool allowFallback) - { - base.SkinChanged(skin, allowFallback); - - if (HitObject is IHasComboInformation combo) - AccentColour.Value = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White; - } - - protected virtual void UpdatePreemptState() + protected virtual void UpdateInitialTransforms() { } - protected virtual void UpdateCurrentState(ArmedState state) + protected virtual void UpdateStateTransforms(ArmedState state) { } @@ -200,6 +201,16 @@ namespace osu.Game.Rulesets.Objects.Drawables { } + #endregion + + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + base.SkinChanged(skin, allowFallback); + + if (HitObject is IHasComboInformation combo) + AccentColour.Value = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White; + } + /// /// Bind to apply a custom state which can override the default implementation. /// From c3b81bef4ab8e9160e73f0013067082d4239706f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jul 2019 15:55:38 +0900 Subject: [PATCH 0277/2815] Flip default to the preferred method going forward --- .../Objects/Drawable/DrawableCatchHitObject.cs | 3 +++ .../Objects/Drawables/DrawableManiaHitObject.cs | 3 +++ .../Objects/Drawables/DrawableOsuHitObject.cs | 2 -- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs | 1 + .../Objects/Drawables/DrawableTaikoHitObject.cs | 2 ++ osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- 6 files changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs index f5dd7cd255..a1279e8443 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs @@ -58,8 +58,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable ApplyResult(r => r.Type = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss); } + protected override bool UseTransformStateManagement => false; + protected override void UpdateState(ArmedState state) { + // TODO: update to use new state management. using (BeginAbsoluteSequence(HitObject.StartTime - HitObject.TimePreempt)) this.FadeIn(200); diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index 0873f753be..db6b53e76d 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -58,8 +58,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables HitObject = hitObject; } + protected override bool UseTransformStateManagement => false; + protected override void UpdateState(ArmedState state) { + // TODO: update to use new state management. switch (state) { case ArmedState.Miss: diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 8107d366b3..579f16e0d4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -34,8 +34,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void ClearInternal(bool disposeChildren = true) => shakeContainer.Clear(disposeChildren); protected override bool RemoveInternal(Drawable drawable) => shakeContainer.Remove(drawable); - protected override bool UseTransformStateManagement => true; - protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt; protected override void UpdateInitialTransforms() => this.FadeIn(HitObject.TimeFadeIn); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 4c8d5d5204..34ae7db984 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -94,6 +94,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected override void UpdateState(ArmedState state) { + // TODO: update to use new state management. var circlePiece = MainPiece as CirclePiece; circlePiece?.FlashBox.FinishTransforms(); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index bd45b52d7b..b46738c69a 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -121,6 +121,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } } + protected override bool UseTransformStateManagement => false; + // Normal and clap samples are handled by the drum protected override IEnumerable GetSamples() => HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP); diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 26d51e3809..aef163cda7 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// Going forward, this is the preferred way of implementing s. Previous functionality /// is offered as a compatibility layer until all rulesets have been migrated across. /// - protected virtual bool UseTransformStateManagement => false; + protected virtual bool UseTransformStateManagement => true; protected override void ClearInternal(bool disposeChildren = true) => throw new InvalidOperationException($"Should never clear a {nameof(DrawableHitObject)}"); From d4d286c9880a4ed3a08c9245e5f5491d87f5c9f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jul 2019 16:08:38 +0900 Subject: [PATCH 0278/2815] Add full documentation --- .../Objects/Drawables/DrawableHitObject.cs | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index aef163cda7..d10f829dae 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -136,6 +136,11 @@ namespace osu.Game.Rulesets.Objects.Drawables #region State / Transform Management + /// + /// Bind to apply a custom state which can override the default implementation. + /// + public event Action ApplyCustomUpdateState; + /// /// Enables automatic transform management of this hitobject. Implementation of transforms should be done in and only. Rewinding and removing previous states is done automatically. /// @@ -177,26 +182,45 @@ namespace osu.Game.Rulesets.Objects.Drawables UpdateState(state); } + /// + /// Apply (generally fade-in) transforms. + /// The local drawable hierarchy is recursively delayed to for convenience. + /// + /// + /// This is called once before every . This is to ensure a good state in the case + /// the was negative and potentially altered the pre-hit transforms. + /// protected virtual void UpdateInitialTransforms() { } + /// + /// Apply transforms based on the current . Previous states are automatically cleared. + /// + /// The new armed state. protected virtual void UpdateStateTransforms(ArmedState state) { } public override void ClearTransformsAfter(double time, bool propagateChildren = false, string targetMember = null) { + // When we are using automatic state menement, parent calls to this should be blocked for safety. if (!UseTransformStateManagement) base.ClearTransformsAfter(time, propagateChildren, targetMember); } public override void ApplyTransformsAt(double time, bool propagateChildren = false) { + // When we are using automatic state menement, parent calls to this should be blocked for safety. if (!UseTransformStateManagement) base.ApplyTransformsAt(time, propagateChildren); } + /// + /// Legacy method to handle state changes. + /// Should generally not be used when is true; use instead. + /// + /// The new armed state. protected virtual void UpdateState(ArmedState state) { } @@ -211,11 +235,6 @@ namespace osu.Game.Rulesets.Objects.Drawables AccentColour.Value = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White; } - /// - /// Bind to apply a custom state which can override the default implementation. - /// - public event Action ApplyCustomUpdateState; - /// /// Plays all the hit sounds for this . /// This is invoked automatically when this is hit. @@ -268,6 +287,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// /// This is only used as an optimisation to delay the initial update of this and may be tuned more aggressively if required. + /// It is indirectly used to decide the automatic transform offset provided to . /// A more accurate should be set inside for an state. /// protected virtual double InitialLifetimeOffset => 10000; From 07a0df7c4f8e12f6acd09f068c4d77a369165549 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jul 2019 18:29:04 +0900 Subject: [PATCH 0279/2815] Fix bracket precedence --- osu.Game/Graphics/Backgrounds/Triangles.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 79f227fafe..2b68e8530d 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -214,7 +214,7 @@ namespace osu.Game.Graphics.Backgrounds { base.Draw(vertexAction); - if (vertexBatch == null || vertexBatch.Size != Source.AimCount && Source.AimCount > 0) + if (Source.AimCount > 0 && (vertexBatch == null || vertexBatch.Size != Source.AimCount)) { vertexBatch?.Dispose(); vertexBatch = new TriangleBatch(Source.AimCount, 1); From 3e95cb9145b935ce9eabcb2e91c092fa5053756a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 22 Jul 2019 14:35:18 +0300 Subject: [PATCH 0280/2815] Use bindable and disable button --- .../BeatmapSet/Buttons/FavouriteButton.cs | 16 ++++++++++++---- osu.Game/Overlays/BeatmapSet/Header.cs | 5 ++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index 7207739646..5266623494 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osuTK; @@ -15,7 +16,8 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons { public class FavouriteButton : HeaderButton { - public readonly Bindable Favourited = new Bindable(); + private readonly Bindable favourited = new Bindable(); + public readonly Bindable BeatmapSet = new Bindable(); [BackgroundDependencyLoader] private void load() @@ -54,7 +56,15 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons }, }); - Favourited.ValueChanged += favourited => + BeatmapSet.BindValueChanged(setInfo => + { + if (setInfo.NewValue?.OnlineInfo?.HasFavourited == null) + return; + + favourited.Value = setInfo.NewValue.OnlineInfo.HasFavourited; + }); + + favourited.ValueChanged += favourited => { if (favourited.NewValue) { @@ -67,8 +77,6 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons icon.Icon = FontAwesome.Regular.Heart; } }; - - Action = () => Favourited.Value = !Favourited.Value; } protected override void UpdateAfterChildren() diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index dd8a2f18c1..d5b6fbd8e6 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -161,7 +161,8 @@ namespace osu.Game.Overlays.BeatmapSet Margin = new MarginPadding { Top = 10 }, Children = new Drawable[] { - favouriteButton = new FavouriteButton(), + favouriteButton = new FavouriteButton + { BeatmapSet = { BindTarget = BeatmapSet } }, downloadButtonsContainer = new FillFlowContainer { RelativeSizeAxes = Axes.Both, @@ -246,8 +247,6 @@ namespace osu.Game.Overlays.BeatmapSet onlineStatusPill.Status = setInfo.NewValue.OnlineInfo.Status; downloadButtonsContainer.FadeIn(transition_duration); - - favouriteButton.Favourited.Value = setInfo.NewValue.OnlineInfo.HasFavourited; favouriteButton.FadeIn(transition_duration); updateDownloadButtons(); From 075ca3d8ea50df15918410798bed9aa2a9272df0 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 22 Jul 2019 14:47:35 +0300 Subject: [PATCH 0281/2815] CI fix --- osu.Game/Overlays/BeatmapSet/Header.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index d5b6fbd8e6..260a989628 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -162,7 +162,9 @@ namespace osu.Game.Overlays.BeatmapSet Children = new Drawable[] { favouriteButton = new FavouriteButton - { BeatmapSet = { BindTarget = BeatmapSet } }, + { + BeatmapSet = { BindTarget = BeatmapSet } + }, downloadButtonsContainer = new FillFlowContainer { RelativeSizeAxes = Axes.Both, From cdf75b409854cbf941c7827906c9a805f851aaf5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jul 2019 22:14:09 +0900 Subject: [PATCH 0282/2815] Public before private --- osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index 5266623494..11f56bc163 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs @@ -16,9 +16,10 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons { public class FavouriteButton : HeaderButton { - private readonly Bindable favourited = new Bindable(); public readonly Bindable BeatmapSet = new Bindable(); + private readonly Bindable favourited = new Bindable(); + [BackgroundDependencyLoader] private void load() { From 764513feea591b2d6e25e378e15913017d525b20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jul 2019 23:13:48 +0900 Subject: [PATCH 0283/2815] Fix code quality --- .../Select/Details/UserTopScoreContainer.cs | 46 ++++++++++--------- osu.Game/Screens/Select/SongSelect.cs | 6 ++- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs b/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs index ba6751475e..cc2d2a3dae 100644 --- a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs +++ b/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs @@ -31,30 +31,31 @@ namespace osu.Game.Screens.Select.Details { RelativeSizeAxes = Axes.X; Height = height; - Anchor = Anchor.BottomCentre; - Origin = Anchor.BottomCentre; + + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + Children = new Drawable[] { contentContainer = new Container { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Height = height, - RelativeSizeAxes = Axes.X, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Both, Children = new Drawable[] { new OsuSpriteText { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, Margin = new MarginPadding { Top = 5 }, Text = @"your personal best".ToUpper(), Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold), }, scoreContainer = new Container { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, } @@ -62,22 +63,25 @@ namespace osu.Game.Screens.Select.Details } }; - Score.BindValueChanged(score => onScoreChanged(score.NewValue)); + Score.BindValueChanged(onScoreChanged); } - private void onScoreChanged(APILegacyUserTopScoreInfo score) + private void onScoreChanged(ValueChangedEvent score) { - if (score != null) + var newScore = score.NewValue; + + if (newScore == null) { - scoreContainer.Clear(); - scoreContainer.Add(new LeaderboardScore(score.Score, score.Position) - { - Action = () => ScoreSelected?.Invoke(score.Score) - }); - Show(); - } - else Hide(); + return; + } + + scoreContainer.Child = new LeaderboardScore(newScore.Score, newScore.Position) + { + Action = () => ScoreSelected?.Invoke(newScore.Score) + }; + + Show(); } protected override void PopIn() diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 209707d8fe..a3b87b5068 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -215,8 +215,10 @@ namespace osu.Game.Screens.Select }); } - BeatmapDetails.Leaderboard.ScoreSelected += s => this.Push(new SoloResults(s)); - BeatmapDetails.TopScore.ScoreSelected += s => this.Push(new SoloResults(s)); + void displayScore(ScoreInfo score) => this.Push(new SoloResults(score)); + + BeatmapDetails.Leaderboard.ScoreSelected += displayScore; + BeatmapDetails.TopScore.ScoreSelected += displayScore; } [BackgroundDependencyLoader(true)] From d83d93ee66fa26b2aabe1678d5c4704f1f74c0d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jul 2019 23:21:07 +0900 Subject: [PATCH 0284/2815] Use asynchronous loading --- .../Select/Details/UserTopScoreContainer.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs b/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs index cc2d2a3dae..1535aa3df1 100644 --- a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs +++ b/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs @@ -10,6 +10,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; using osu.Game.Scoring; using System; +using System.Threading; namespace osu.Game.Screens.Select.Details { @@ -66,6 +67,8 @@ namespace osu.Game.Screens.Select.Details Score.BindValueChanged(onScoreChanged); } + private CancellationTokenSource loadScoreCancellation; + private void onScoreChanged(ValueChangedEvent score) { var newScore = score.NewValue; @@ -76,12 +79,17 @@ namespace osu.Game.Screens.Select.Details return; } - scoreContainer.Child = new LeaderboardScore(newScore.Score, newScore.Position) + scoreContainer.Clear(); + loadScoreCancellation?.Cancel(); + + LoadComponentAsync(new LeaderboardScore(newScore.Score, newScore.Position) { Action = () => ScoreSelected?.Invoke(newScore.Score) - }; - - Show(); + }, drawableScore => + { + scoreContainer.Child = drawableScore; + Show(); + }, (loadScoreCancellation = new CancellationTokenSource()).Token); } protected override void PopIn() From 95241165cce4724758f7267630b1755c748b1dbc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jul 2019 23:26:11 +0900 Subject: [PATCH 0285/2815] Fix text alignment --- osu.Game/Screens/Select/Details/UserTopScoreContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs b/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs index 1535aa3df1..5a224756b9 100644 --- a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs +++ b/osu.Game/Screens/Select/Details/UserTopScoreContainer.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 osu.Framework.Bindables; @@ -47,8 +47,8 @@ namespace osu.Game.Screens.Select.Details { new OsuSpriteText { - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, Margin = new MarginPadding { Top = 5 }, Text = @"your personal best".ToUpper(), Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold), From 5a6c8bfec9ff1563856ecd22936dc396e2360d90 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jul 2019 23:28:17 +0900 Subject: [PATCH 0286/2815] Adjust transition to now show janky resize --- .../Select/Details/UserTopScoreContainer.cs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs b/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs index 5a224756b9..8e9df8bbb1 100644 --- a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs +++ b/osu.Game/Screens/Select/Details/UserTopScoreContainer.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 osu.Framework.Bindables; @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Select.Details public class UserTopScoreContainer : VisibilityContainer { private const int height = 90; - private const int duration = 300; + private const int duration = 800; private readonly Container contentContainer; private readonly Container scoreContainer; @@ -92,16 +92,8 @@ namespace osu.Game.Screens.Select.Details }, (loadScoreCancellation = new CancellationTokenSource()).Token); } - protected override void PopIn() - { - this.ResizeHeightTo(height, duration, Easing.OutQuint); - contentContainer.FadeIn(duration, Easing.OutQuint); - } + protected override void PopIn() => this.ResizeHeightTo(height, duration / 4f, Easing.OutQuint).OnComplete(_ => contentContainer.FadeIn(duration, Easing.OutQuint)); - protected override void PopOut() - { - this.ResizeHeightTo(0, duration, Easing.OutQuint); - contentContainer.FadeOut(duration, Easing.OutQuint); - } + protected override void PopOut() => contentContainer.FadeOut(duration, Easing.OutQuint).OnComplete(_ => this.ResizeHeightTo(0)); } } From 2f111e6cf4ea5e56147a395c8bff06a3c7508b2c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jul 2019 23:57:26 +0900 Subject: [PATCH 0287/2815] Fix incorrect corner radius --- .../Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs index a6c41cde72..0e9b534ca2 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs @@ -21,7 +21,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical public class DrawableMostPlayedBeatmap : OsuHoverContainer { private const int cover_width = 100; - private const int corner_radius = 10; + private const int corner_radius = 6; private readonly BeatmapInfo beatmap; private readonly int playCount; From ee6fed5b33c219cada52a9628301d61767dabf57 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 22 Jul 2019 18:17:49 +0300 Subject: [PATCH 0288/2815] Use fixed height --- .../Sections/Historical/DrawableMostPlayedBeatmap.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs index 0e9b534ca2..0206c4e13b 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs @@ -22,6 +22,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { private const int cover_width = 100; private const int corner_radius = 6; + private const int height = 50; private readonly BeatmapInfo beatmap; private readonly int playCount; @@ -37,7 +38,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical Enabled.Value = true; //manually enabled, because we have no action RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; + Height = height; Masking = true; CornerRadius = corner_radius; @@ -60,15 +61,13 @@ namespace osu.Game.Overlays.Profile.Sections.Historical }, new Container { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Left = cover_width - corner_radius }, Children = new Drawable[] { new Container { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.Both, Masking = true, CornerRadius = corner_radius, Children = new Drawable[] @@ -76,8 +75,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical background = new Box { RelativeSizeAxes = Axes.Both }, new Container { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(10), Children = new Drawable[] { From 94ed03548d7a401bc43be3b9ce602412efb6e662 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 22 Jul 2019 18:34:31 +0300 Subject: [PATCH 0289/2815] Hide top score at every leaderboard change --- osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index ee16123e20..f4a18e3b58 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -86,6 +86,8 @@ namespace osu.Game.Screens.Select.Leaderboards protected override APIRequest FetchScores(Action> scoresCallback) { + TopScore.Value = null; + if (Scope == BeatmapLeaderboardScope.Local) { var scores = scoreManager From 081355e3d158f74cc74ff061455e013ecb09406f Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 22 Jul 2019 23:12:09 +0300 Subject: [PATCH 0290/2815] Use IHasText and simplify properties --- .../Graphics/Sprites/GlowingSpriteText.cs | 44 ++++--------------- 1 file changed, 8 insertions(+), 36 deletions(-) diff --git a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs index 546b8cae58..6c92d4cd06 100644 --- a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs +++ b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs @@ -9,50 +9,26 @@ using osuTK; namespace osu.Game.Graphics.Sprites { - public class GlowingSpriteText : Container + public class GlowingSpriteText : Container, IHasText { private readonly OsuSpriteText spriteText, blurredText; - private string text = string.Empty; - public string Text { - get => text; - set - { - text = value; - - spriteText.Text = text; - blurredText.Text = text; - } + get => spriteText.Text; + set => blurredText.Text = spriteText.Text = value; } - - private FontUsage font = OsuFont.Default.With(fixedWidth: true); - + public FontUsage Font { - get => font; - set - { - font = value.With(fixedWidth: true); - - spriteText.Font = font; - blurredText.Font = font; - } + get => spriteText.Font; + set => blurredText.Font = spriteText.Font = value.With(fixedWidth: true); } - private Vector2 textSize; - public Vector2 TextSize { - get => textSize; - set - { - textSize = value; - - spriteText.Size = textSize; - blurredText.Size = textSize; - } + get => spriteText.Size; + set => blurredText.Size = spriteText.Size = value; } public ColourInfo TextColour @@ -88,8 +64,6 @@ namespace osu.Game.Graphics.Sprites { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = font, - Text = text, Shadow = false, }, }, @@ -98,8 +72,6 @@ namespace osu.Game.Graphics.Sprites { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = font, - Text = text, Shadow = false, }, }; From 32e9547ce9d264b779f1d90b60bf765c1e41dd1c Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 22 Jul 2019 23:16:54 +0300 Subject: [PATCH 0291/2815] Trim whitespace --- osu.Game/Graphics/Sprites/GlowingSpriteText.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs index 6c92d4cd06..74e387d60e 100644 --- a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs +++ b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs @@ -18,7 +18,7 @@ namespace osu.Game.Graphics.Sprites get => spriteText.Text; set => blurredText.Text = spriteText.Text = value; } - + public FontUsage Font { get => spriteText.Font; From ffcc1c62af5fcfed1d9ec7b3b322b4145d724d56 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 22 Jul 2019 23:22:39 +0300 Subject: [PATCH 0292/2815] simplify moving condition --- osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index bf0cd91321..927986159b 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -71,15 +71,7 @@ namespace osu.Game.Overlays.Toolbar // Scheduled to allow the flow layout to be computed before the line position is updated private void moveLineToCurrent() => ScheduleAfterChildren(() => { - foreach (var tabItem in TabContainer) - { - if (tabItem.Value.Equals(Current.Value)) - { - ModeButtonLine.MoveToX(tabItem.DrawPosition.X, !hasInitialPosition ? 0 : 200, Easing.OutQuint); - break; - } - } - + ModeButtonLine.MoveToX(SelectedTab.DrawPosition.X, !hasInitialPosition ? 0 : 200, Easing.OutQuint); hasInitialPosition = true; }); From 76b79f355443cba1b1f60fea02f24fae764e771d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 23 Jul 2019 01:14:45 +0300 Subject: [PATCH 0293/2815] Transform adjustments --- osu.Game/Screens/Select/Details/UserTopScoreContainer.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs b/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs index 8e9df8bbb1..c10f4d4fd4 100644 --- a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs +++ b/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs @@ -42,7 +42,8 @@ namespace osu.Game.Screens.Select.Details { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + Height = height, Children = new Drawable[] { new OsuSpriteText @@ -94,6 +95,10 @@ namespace osu.Game.Screens.Select.Details protected override void PopIn() => this.ResizeHeightTo(height, duration / 4f, Easing.OutQuint).OnComplete(_ => contentContainer.FadeIn(duration, Easing.OutQuint)); - protected override void PopOut() => contentContainer.FadeOut(duration, Easing.OutQuint).OnComplete(_ => this.ResizeHeightTo(0)); + protected override void PopOut() + { + this.ResizeHeightTo(0); + contentContainer.FadeOut(duration / 4f, Easing.OutQuint); + } } } From d5ee4cbc9cc5d3bae002d6e6357296ebc9be5b1c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jul 2019 13:11:06 +0900 Subject: [PATCH 0294/2815] Move TouchDevice mod to new "system" category --- osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs | 2 ++ osu.Game/Rulesets/Mods/ModType.cs | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs index 571756d056..f0db548e74 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs @@ -11,6 +11,8 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Acronym => "TD"; public override double ScoreMultiplier => 1; + public override ModType Type => ModType.System; + public override bool Ranked => true; } } diff --git a/osu.Game/Rulesets/Mods/ModType.cs b/osu.Game/Rulesets/Mods/ModType.cs index cd649728cf..e3c82e42f5 100644 --- a/osu.Game/Rulesets/Mods/ModType.cs +++ b/osu.Game/Rulesets/Mods/ModType.cs @@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Mods DifficultyIncrease, Conversion, Automation, - Fun + Fun, + System } } From f8feac792c4cc1da31de38e2ebb4b48d74633439 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jul 2019 13:12:17 +0900 Subject: [PATCH 0295/2815] Return TouchDevice in GetAllMods response --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index baa4aff413..8df0f77629 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -138,6 +138,12 @@ namespace osu.Game.Rulesets.Osu new MultiMod(new ModWindUp(), new ModWindDown()), }; + case ModType.System: + return new Mod[] + { + new OsuModTouchDevice(), + }; + default: return new Mod[] { }; } From e628e44d8e7a45ea961481dafe8c02a92b965209 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 23 Jul 2019 13:25:03 +0900 Subject: [PATCH 0296/2815] update comment --- osu.Game/Graphics/UserInterface/SearchTextBox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/SearchTextBox.cs b/osu.Game/Graphics/UserInterface/SearchTextBox.cs index c1020e6340..a3fe9bb8f5 100644 --- a/osu.Game/Graphics/UserInterface/SearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/SearchTextBox.cs @@ -36,7 +36,7 @@ namespace osu.Game.Graphics.UserInterface protected override bool HandleAction(PlatformAction action) { - // Allow delete to be handled locally + // Shift-delete is unnecessary for search inputs, so its propagated up the input queue. if (action.ActionType == PlatformActionType.CharNext && action.ActionMethod == PlatformActionMethod.Delete) return false; From 292bd22f92656b972318ce766b4ff6d0440c92f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jul 2019 13:38:05 +0900 Subject: [PATCH 0297/2815] Allow multiple instances of osu! when running under debug --- osu.Desktop/Program.cs | 45 ++++++++++++++++++++++++------------------ osu.Game/OsuGame.cs | 3 ++- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index cb488fea52..141b2cdbbc 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -29,29 +29,36 @@ namespace osu.Desktop if (!host.IsPrimaryInstance) { - var importer = new ArchiveImportIPCChannel(host); - // Restore the cwd so relative paths given at the command line work correctly - Directory.SetCurrentDirectory(cwd); - - foreach (var file in args) + if (args.Length > 0 && args[0].Contains('.')) // easy way to check for a file import in args { - Console.WriteLine(@"Importing {0}", file); - if (!importer.ImportAsync(Path.GetFullPath(file)).Wait(3000)) - throw new TimeoutException(@"IPC took too long to send"); + var importer = new ArchiveImportIPCChannel(host); + // Restore the cwd so relative paths given at the command line work correctly + Directory.SetCurrentDirectory(cwd); + + foreach (var file in args) + { + Console.WriteLine(@"Importing {0}", file); + if (!importer.ImportAsync(Path.GetFullPath(file)).Wait(3000)) + throw new TimeoutException(@"IPC took too long to send"); + } + + return 0; } + + // we want to allow multiple instances to be started when in debug. + if (!DebugUtils.IsDebugBuild) + return 0; } - else - { - switch (args.FirstOrDefault() ?? string.Empty) - { - default: - host.Run(new OsuGameDesktop(args)); - break; - case "--tournament": - host.Run(new TournamentGame()); - break; - } + switch (args.FirstOrDefault() ?? string.Empty) + { + default: + host.Run(new OsuGameDesktop(args)); + break; + + case "--tournament": + host.Run(new TournamentGame()); + break; } return 0; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 2a484fc122..41b67f343a 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -20,6 +20,7 @@ using System.Threading; using System.Threading.Tasks; using osu.Framework.Audio; using osu.Framework.Bindables; +using osu.Framework.Development; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; @@ -153,7 +154,7 @@ namespace osu.Game { this.frameworkConfig = frameworkConfig; - if (!Host.IsPrimaryInstance) + if (!Host.IsPrimaryInstance && !DebugUtils.IsDebugBuild) { Logger.Log(@"osu! does not support multiple running instances.", LoggingTarget.Runtime, LogLevel.Error); Environment.Exit(0); From 776757545d15ae536f223afeb8e6ada4d8bab89b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 23 Jul 2019 15:17:02 +0900 Subject: [PATCH 0298/2815] Fix FTB causing flashlight to block vision correctly --- .../Vertices/PositionAndColourVertex.cs | 26 +++++++++++++++++++ osu.Game/Rulesets/Mods/ModFlashlight.cs | 12 ++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Graphics/OpenGL/Vertices/PositionAndColourVertex.cs diff --git a/osu.Game/Graphics/OpenGL/Vertices/PositionAndColourVertex.cs b/osu.Game/Graphics/OpenGL/Vertices/PositionAndColourVertex.cs new file mode 100644 index 0000000000..8714138322 --- /dev/null +++ b/osu.Game/Graphics/OpenGL/Vertices/PositionAndColourVertex.cs @@ -0,0 +1,26 @@ +// 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.Runtime.InteropServices; +using osu.Framework.Graphics.OpenGL.Vertices; +using osuTK; +using osuTK.Graphics; +using osuTK.Graphics.ES30; + +namespace osu.Game.Graphics.OpenGL.Vertices +{ + [StructLayout(LayoutKind.Sequential)] + public struct PositionAndColourVertex : IEquatable, IVertex + { + [VertexMember(2, VertexAttribPointerType.Float)] + public Vector2 Position; + + [VertexMember(4, VertexAttribPointerType.Float)] + public Color4 Colour; + + public bool Equals(PositionAndColourVertex other) + => Position.Equals(other.Position) + && Colour.Equals(other.Colour); + } +} diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 405d21c711..cb0c2fafe5 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Batches; using osu.Framework.Graphics.OpenGL.Vertices; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shaders; @@ -13,6 +14,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps.Timing; using osu.Game.Graphics; +using osu.Game.Graphics.OpenGL.Vertices; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; @@ -153,9 +155,17 @@ namespace osu.Game.Rulesets.Mods private Vector2 flashlightSize; private float flashlightDim; + private readonly VertexBatch quadBatch = new QuadBatch(1, 1); + private readonly Action addAction; + public FlashlightDrawNode(Flashlight source) : base(source) { + addAction = v => quadBatch.Add(new PositionAndColourVertex + { + Position = v.Position, + Colour = v.Colour + }); } public override void ApplyState() @@ -179,7 +189,7 @@ namespace osu.Game.Rulesets.Mods shader.GetUniform("flashlightSize").UpdateValue(ref flashlightSize); shader.GetUniform("flashlightDim").UpdateValue(ref flashlightDim); - DrawQuad(Texture.WhitePixel, screenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction: vertexAction); + DrawQuad(Texture.WhitePixel, screenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction: addAction); shader.Unbind(); } From 4d8e2a78d119c79907da53197d9bd0abc5736889 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 23 Jul 2019 15:31:09 +0900 Subject: [PATCH 0299/2815] update with new framework changes and update comment --- osu.Game/Graphics/UserInterface/SearchTextBox.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/SearchTextBox.cs b/osu.Game/Graphics/UserInterface/SearchTextBox.cs index a3fe9bb8f5..4b49174e65 100644 --- a/osu.Game/Graphics/UserInterface/SearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/SearchTextBox.cs @@ -34,13 +34,14 @@ namespace osu.Game.Graphics.UserInterface PlaceholderText = "type to search"; } - protected override bool HandleAction(PlatformAction action) + public override bool OnPressed(PlatformAction action) { - // Shift-delete is unnecessary for search inputs, so its propagated up the input queue. + // Shift-delete, used in MacOS for character deletion, is unnecessary here as arrow keys are blocked by HandleLeftRightArrows + // Avoid handling it here to allow other components to potentially consume the shortcut if (action.ActionType == PlatformActionType.CharNext && action.ActionMethod == PlatformActionMethod.Delete) return false; - return base.HandleAction(action); + return base.OnPressed(action); } protected override bool OnKeyDown(KeyDownEvent e) From 5e72ed0d1276dd149d2d69dc6da90f308a047e96 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 23 Jul 2019 15:35:12 +0900 Subject: [PATCH 0300/2815] Fix potential nullref --- osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index 927986159b..2c79f5bc0e 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -71,8 +71,11 @@ namespace osu.Game.Overlays.Toolbar // Scheduled to allow the flow layout to be computed before the line position is updated private void moveLineToCurrent() => ScheduleAfterChildren(() => { - ModeButtonLine.MoveToX(SelectedTab.DrawPosition.X, !hasInitialPosition ? 0 : 200, Easing.OutQuint); - hasInitialPosition = true; + if (SelectedTab != null) + { + ModeButtonLine.MoveToX(SelectedTab.DrawPosition.X, !hasInitialPosition ? 0 : 200, Easing.OutQuint); + hasInitialPosition = true; + } }); public override bool HandleNonPositionalInput => !Current.Disabled && base.HandleNonPositionalInput; From 704fe2d6554f5138d994d5528b10be3831cc4459 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jul 2019 16:01:05 +0900 Subject: [PATCH 0301/2815] Remove text shadow in chat --- osu.Game/Overlays/Chat/ChatLine.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index e29216dffd..2576b38ec8 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -85,6 +85,7 @@ namespace osu.Game.Overlays.Chat Drawable effectedUsername = username = new OsuSpriteText { + Shadow = false, Colour = hasBackground ? customUsernameColour : username_colours[message.Sender.Id % username_colours.Length], Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Bold, italics: true) }; @@ -133,6 +134,7 @@ namespace osu.Game.Overlays.Chat { timestamp = new OsuSpriteText { + Shadow = false, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Font = OsuFont.GetFont(size: TextSize * 0.75f, weight: FontWeight.SemiBold, fixedWidth: true) @@ -155,6 +157,8 @@ namespace osu.Game.Overlays.Chat { contentFlow = new LinkFlowContainer(t => { + t.Shadow = false; + if (Message.IsAction) { t.Font = OsuFont.GetFont(italics: true); From e81ef4bf339e59307f1cb47a12e9097e580afd47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jul 2019 16:44:19 +0900 Subject: [PATCH 0302/2815] Rewrite comment --- osu.Game/Graphics/UserInterface/SearchTextBox.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/SearchTextBox.cs b/osu.Game/Graphics/UserInterface/SearchTextBox.cs index 4b49174e65..c3efe2ed45 100644 --- a/osu.Game/Graphics/UserInterface/SearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/SearchTextBox.cs @@ -36,8 +36,9 @@ namespace osu.Game.Graphics.UserInterface public override bool OnPressed(PlatformAction action) { - // Shift-delete, used in MacOS for character deletion, is unnecessary here as arrow keys are blocked by HandleLeftRightArrows - // Avoid handling it here to allow other components to potentially consume the shortcut + // Shift+delete is handled via PlatformAction on macOS. this is not so useful in the context of a SearchTextBox + // as we do not allow arrow key navigation in the first place (ie. the care should always be at the end of text) + // Avoid handling it here to allow other components to potentially consume the shortcut. if (action.ActionType == PlatformActionType.CharNext && action.ActionMethod == PlatformActionMethod.Delete) return false; From 8220a513102a6128fc6e3c11383b365029b4aa4f Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 23 Jul 2019 17:59:50 +0900 Subject: [PATCH 0303/2815] Make backbutton handle global input last --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 669fd62e45..ff006efdd6 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Bindings; +using osu.Game.Graphics.UserInterface; namespace osu.Game.Input.Bindings { @@ -55,8 +56,11 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed), }; + /// + /// Make sure that the handles global input first, and that handles global input last. + /// protected override IEnumerable KeyBindingInputQueue => - handler == null ? base.KeyBindingInputQueue : base.KeyBindingInputQueue.Prepend(handler); + (handler == null ? base.KeyBindingInputQueue : base.KeyBindingInputQueue.Prepend(handler)).OrderBy(d => d is BackButton); } public enum GlobalAction From b1a9ce85e7fbaaac4ff9962cb71be382d4853a0f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 23 Jul 2019 20:30:47 +0900 Subject: [PATCH 0304/2815] Fix ticks being given an extra colour --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index ec294a1630..dafdcf3b82 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -34,11 +34,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, CornerRadius = Size.X / 2, - Colour = AccentColour.Value, - BorderThickness = 2, BorderColour = Color4.White, - Child = new Box { RelativeSizeAxes = Axes.Both, From 74b09c72fa044d9fc0f21f19f03da216df2bf291 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 23 Jul 2019 21:08:41 +0900 Subject: [PATCH 0305/2815] Refactor state updates to convert State into an IBindable --- .../Objects/Drawables/DrawableHitObject.cs | 46 +++++++++---------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index d10f829dae..3253302c71 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -79,12 +79,12 @@ namespace osu.Game.Rulesets.Objects.Drawables public override bool RemoveCompletedTransforms => false; protected override bool RequiresChildrenUpdate => true; - public override bool IsPresent => base.IsPresent || (state.Value == ArmedState.Idle && Clock?.CurrentTime >= LifetimeStart); - - public readonly Bindable State = new Bindable(); + public override bool IsPresent => base.IsPresent || (State.Value == ArmedState.Idle && Clock?.CurrentTime >= LifetimeStart); private readonly Bindable state = new Bindable(); + public IBindable State => state; + protected DrawableHitObject(HitObject hitObject) { HitObject = hitObject; @@ -121,17 +121,7 @@ namespace osu.Game.Rulesets.Objects.Drawables protected override void LoadComplete() { base.LoadComplete(); - - state.BindValueChanged(armed => - { - updateState(armed.NewValue); - - // apply any custom state overrides - ApplyCustomUpdateState?.Invoke(this, armed.NewValue); - - if (armed.NewValue == ArmedState.Hit) - PlaySamples(); - }, true); + updateState(ArmedState.Idle, true); } #region State / Transform Management @@ -152,8 +142,17 @@ namespace osu.Game.Rulesets.Objects.Drawables protected override void ClearInternal(bool disposeChildren = true) => throw new InvalidOperationException($"Should never clear a {nameof(DrawableHitObject)}"); - private void updateState(ArmedState state) + private void updateState(ArmedState newState, bool force = false) { + if (State.Value == newState && !force) + return; + + // apply any custom state overrides + ApplyCustomUpdateState?.Invoke(this, newState); + + if (newState == ArmedState.Hit) + PlaySamples(); + if (UseTransformStateManagement) { double transformTime = HitObject.StartTime - InitialLifetimeOffset; @@ -169,17 +168,15 @@ namespace osu.Game.Rulesets.Objects.Drawables using (BeginDelayedSequence(InitialLifetimeOffset + judgementOffset, true)) { - UpdateStateTransforms(state); - State.Value = state; + UpdateStateTransforms(newState); + state.Value = newState; } } } else - { - State.Value = state; - } + state.Value = newState; - UpdateState(state); + UpdateState(newState); } /// @@ -255,7 +252,8 @@ namespace osu.Game.Rulesets.Objects.Drawables Result.TimeOffset = 0; Result.Type = HitResult.None; - state.Value = ArmedState.Idle; + + updateState(ArmedState.Idle); } } } @@ -336,11 +334,11 @@ namespace osu.Game.Rulesets.Objects.Drawables break; case HitResult.Miss: - state.Value = ArmedState.Miss; + updateState(ArmedState.Miss); break; default: - state.Value = ArmedState.Hit; + updateState(ArmedState.Hit); break; } From 4e7e2d1d5230b76bb25edfbceffb8b51c060f475 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 23 Jul 2019 21:15:55 +0900 Subject: [PATCH 0306/2815] Adjust comments --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 3253302c71..1d9d885527 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -180,7 +180,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } /// - /// Apply (generally fade-in) transforms. + /// Apply (generally fade-in) transforms leading into the start time. /// The local drawable hierarchy is recursively delayed to for convenience. /// /// @@ -201,14 +201,14 @@ namespace osu.Game.Rulesets.Objects.Drawables public override void ClearTransformsAfter(double time, bool propagateChildren = false, string targetMember = null) { - // When we are using automatic state menement, parent calls to this should be blocked for safety. + // When we are using automatic state management, parent calls to this should be blocked for safety. if (!UseTransformStateManagement) base.ClearTransformsAfter(time, propagateChildren, targetMember); } public override void ApplyTransformsAt(double time, bool propagateChildren = false) { - // When we are using automatic state menement, parent calls to this should be blocked for safety. + // When we are using automatic state management, parent calls to this should be blocked for safety. if (!UseTransformStateManagement) base.ApplyTransformsAt(time, propagateChildren); } From 2610ee2abb4a63603e4afb1b7c9468a59d43fb1b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jul 2019 21:39:10 +0900 Subject: [PATCH 0307/2815] 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 b9451fc744..b24493665e 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -63,6 +63,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d90b1d36e1..c05cc6f9dd 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -15,7 +15,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index fa2521a19e..3b18039600 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From 38559685a96ec763291aff3bed423696462b69cf Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 24 Jul 2019 12:47:41 +0900 Subject: [PATCH 0308/2815] proxy backbutton instead --- osu.Game/OsuGame.cs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 2a484fc122..4c7fc688ab 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -394,6 +394,16 @@ namespace osu.Game AddRange(new Drawable[] { + backButton = new BackButton + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Action = () => + { + if ((screenStack.CurrentScreen as IOsuScreen)?.AllowBackButton == true) + screenStack.Exit(); + } + }, new VolumeControlReceptor { RelativeSizeAxes = Axes.Both, @@ -403,19 +413,10 @@ namespace osu.Game screenContainer = new ScalingContainer(ScalingMode.ExcludeOverlays) { RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + Children = new[] { screenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }, - backButton = new BackButton - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Action = () => - { - if ((screenStack.CurrentScreen as IOsuScreen)?.AllowBackButton == true) - screenStack.Exit(); - } - }, + backButton.CreateProxy(), logoContainer = new Container { RelativeSizeAxes = Axes.Both }, } }, From da3dc610baf632279149814ac500f7d47883c4d3 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 24 Jul 2019 12:52:18 +0900 Subject: [PATCH 0309/2815] revert globalaction changes --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index ff006efdd6..669fd62e45 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -7,7 +7,6 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Bindings; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Input.Bindings { @@ -56,11 +55,8 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed), }; - /// - /// Make sure that the handles global input first, and that handles global input last. - /// protected override IEnumerable KeyBindingInputQueue => - (handler == null ? base.KeyBindingInputQueue : base.KeyBindingInputQueue.Prepend(handler)).OrderBy(d => d is BackButton); + handler == null ? base.KeyBindingInputQueue : base.KeyBindingInputQueue.Prepend(handler); } public enum GlobalAction From 136f3b8b6b1430605ebe10391d9f40503d505a52 Mon Sep 17 00:00:00 2001 From: DTSDAO Date: Wed, 24 Jul 2019 20:49:35 +0800 Subject: [PATCH 0310/2815] Add iOS import osu! files --- osu.iOS/AppDelegate.cs | 19 ++++++++++++++++++- osu.iOS/Info.plist | 13 +++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/osu.iOS/AppDelegate.cs b/osu.iOS/AppDelegate.cs index 058e246ed8..93eca8caa1 100644 --- a/osu.iOS/AppDelegate.cs +++ b/osu.iOS/AppDelegate.cs @@ -1,15 +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.Threading.Tasks; using Foundation; using osu.Framework.iOS; using osu.Game; +using UIKit; namespace osu.iOS { [Register("AppDelegate")] public class AppDelegate : GameAppDelegate { - protected override Framework.Game CreateGame() => new OsuGameIOS(); + private OsuGameIOS IOSGame; + + protected override Framework.Game CreateGame() + { + //Save OsuGameIOS for Import + IOSGame = new OsuGameIOS(); + return IOSGame; + } + + public override bool OpenUrl(UIApplication application, NSUrl url, string sourceApplication, NSObject annotation) + { + //Open in Application + Task.Run(() => IOSGame.Import(url.Path)); + + return true; + } } } diff --git a/osu.iOS/Info.plist b/osu.iOS/Info.plist index d7992353cf..f6fc768632 100644 --- a/osu.iOS/Info.plist +++ b/osu.iOS/Info.plist @@ -40,5 +40,18 @@ XSAppIconAssets Assets.xcassets/AppIcon.appiconset + CFBundleDocumentTypes + + + LSHandlerRank + Owner + CFBundleTypeName + public.item + LSItemContentTypes + + public.item + + + From 433f192214da43c206a47296531cadb2c76910f1 Mon Sep 17 00:00:00 2001 From: DTSDAO Date: Wed, 24 Jul 2019 21:51:01 +0800 Subject: [PATCH 0311/2815] Add iOS custom UTIs --- osu.iOS/Info.plist | 56 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/osu.iOS/Info.plist b/osu.iOS/Info.plist index f6fc768632..0775d1522d 100644 --- a/osu.iOS/Info.plist +++ b/osu.iOS/Info.plist @@ -40,16 +40,68 @@ XSAppIconAssets Assets.xcassets/AppIcon.appiconset + UTExportedTypeDeclarations + + + UTTypeConformsTo + + + + UTTypeIdentifier + sh.ppy.osu.items + UTTypeTagSpecification + + + + UTTypeConformsTo + + sh.ppy.osu.items + + UTTypeIdentifier + sh.ppy.osu.osr + UTTypeTagSpecification + + public.filename-extension + osr + + + + UTTypeConformsTo + + sh.ppy.osu.items + + UTTypeIdentifier + sh.ppy.osu.osk + UTTypeTagSpecification + + public.filename-extension + osk + + + + UTTypeConformsTo + + sh.ppy.osu.items + + UTTypeIdentifier + sh.ppy.osu.osz + UTTypeTagSpecification + + public.filename-extension + osz + + + CFBundleDocumentTypes LSHandlerRank Owner CFBundleTypeName - public.item + Supported osu! files LSItemContentTypes - public.item + sh.ppy.osu.items From 78a8a6490e50a15eb8aad5f83675f1c741e6ea30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Wed, 24 Jul 2019 17:17:29 +0200 Subject: [PATCH 0312/2815] close chat tabs with middle mouse button --- .../Overlays/Chat/Tabs/ChannelSelectorTabItem.cs | 3 +++ osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs index 7386bffb1a..f6533be551 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Online.Chat; @@ -39,5 +40,7 @@ namespace osu.Game.Overlays.Chat.Tabs Name = "+"; } } + + protected override bool OnMouseUp(MouseUpEvent e) => false; } } diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs index 7f820e4ff7..859ca30d7f 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs @@ -16,6 +16,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; using osuTK; using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Overlays.Chat.Tabs { @@ -138,6 +139,17 @@ namespace osu.Game.Overlays.Chat.Tabs updateState(); } + protected override bool OnMouseUp(MouseUpEvent e) + { + if (e.Button == MouseButton.Middle) + { + CloseButton.Action(); + return true; + } + + return false; + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { From bbcc8f072116e52e5105bc241166a4396d3b9af5 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 25 Jul 2019 11:11:20 +0900 Subject: [PATCH 0313/2815] Add new container level and unblock footer --- osu.Game/OsuGame.cs | 33 +++++++++++++++++++------------ osu.Game/Screens/Select/Footer.cs | 4 ++-- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 6fb76c640f..a248da4304 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -395,16 +395,6 @@ namespace osu.Game AddRange(new Drawable[] { - backButton = new BackButton - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Action = () => - { - if ((screenStack.CurrentScreen as IOsuScreen)?.AllowBackButton == true) - screenStack.Exit(); - } - }, new VolumeControlReceptor { RelativeSizeAxes = Axes.Both, @@ -416,9 +406,26 @@ namespace osu.Game RelativeSizeAxes = Axes.Both, Children = new[] { - screenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }, - backButton.CreateProxy(), - logoContainer = new Container { RelativeSizeAxes = Axes.Both }, + backButton = new BackButton + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Action = () => + { + if ((screenStack.CurrentScreen as IOsuScreen)?.AllowBackButton == true) + screenStack.Exit(); + } + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Children = new[] + { + screenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }, + backButton.CreateProxy(), + logoContainer = new Container { RelativeSizeAxes = Axes.Both }, + } + } } }, overlayContent = new Container { RelativeSizeAxes = Axes.Both }, diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs index 0680711f1c..0043acb818 100644 --- a/osu.Game/Screens/Select/Footer.cs +++ b/osu.Game/Screens/Select/Footer.cs @@ -104,8 +104,8 @@ namespace osu.Game.Screens.Select updateModeLight(); } - protected override bool OnMouseDown(MouseDownEvent e) => true; + protected override bool OnMouseDown(MouseDownEvent e) => false; - protected override bool OnClick(ClickEvent e) => true; + protected override bool OnClick(ClickEvent e) => false; } } From c16af882993b86a0ef91d7813ba5c551dbced753 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Jul 2019 11:47:27 +0900 Subject: [PATCH 0314/2815] Start exploding animation earlier (don't wait for flash) --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index d3d763daf3..001003155d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -169,6 +169,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables .FadeOut(100); explode.FadeIn(flash_in); + explodeContainer.ScaleTo(1.5f, 400, Easing.OutQuad); using (BeginDelayedSequence(flash_in, true)) { @@ -178,7 +179,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables number.FadeOut(); this.FadeOut(800); - explodeContainer.ScaleTo(1.5f, 400, Easing.OutQuad); } Expire(); From e63c97b306b712db8b2159d8bfd3acef76006e81 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 25 Jul 2019 11:20:24 +0900 Subject: [PATCH 0315/2815] remove unnecessary overrides --- osu.Game/Screens/Select/Footer.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs index 0043acb818..a1384f19b4 100644 --- a/osu.Game/Screens/Select/Footer.cs +++ b/osu.Game/Screens/Select/Footer.cs @@ -103,9 +103,5 @@ namespace osu.Game.Screens.Select updateModeLight(); } - - protected override bool OnMouseDown(MouseDownEvent e) => false; - - protected override bool OnClick(ClickEvent e) => false; } } From 7275beaf1aedbcb55fce70deaab3f075762ea153 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Jul 2019 18:38:24 +0900 Subject: [PATCH 0316/2815] Remove unnecessary type specification from SkinnableDrawable --- osu.Game/Skinning/SkinnableDrawable.cs | 22 ++++++---------------- osu.Game/Skinning/SkinnableSprite.cs | 5 +++-- osu.Game/Skinning/SkinnableSpriteText.cs | 2 +- 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs index eb0508b568..0c635a3d2f 100644 --- a/osu.Game/Skinning/SkinnableDrawable.cs +++ b/osu.Game/Skinning/SkinnableDrawable.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; @@ -8,23 +8,13 @@ using osuTK; namespace osu.Game.Skinning { - public class SkinnableDrawable : SkinnableDrawable - { - public SkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) - : base(name, defaultImplementation, allowFallback, confineMode) - { - } - } - /// /// A drawable which can be skinned via an . /// - /// The type of drawable. - public class SkinnableDrawable : SkinReloadableDrawable - where T : Drawable + public class SkinnableDrawable : SkinReloadableDrawable { /// - /// The displayed component. May or may not be a type- member. + /// The displayed component. /// protected Drawable Drawable { get; private set; } @@ -39,7 +29,7 @@ namespace osu.Game.Skinning /// A function to create the default skin implementation of this element. /// A conditional to decide whether to allow fallback to the default implementation if a skinned element is not present. /// How (if at all) the should be resize to fit within our own bounds. - public SkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) + public SkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) : this(name, allowFallback, confineMode) { createDefault = defaultImplementation; @@ -54,13 +44,13 @@ namespace osu.Game.Skinning RelativeSizeAxes = Axes.Both; } - private readonly Func createDefault; + private readonly Func createDefault; private readonly Cached scaling = new Cached(); private bool isDefault; - protected virtual T CreateDefault(string name) => createDefault(name); + protected virtual Drawable CreateDefault(string name) => createDefault(name); /// /// Whether to apply size restrictions (specified via ) to the default implementation. diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs index 1716ec71de..07ba48d6ae 100644 --- a/osu.Game/Skinning/SkinnableSprite.cs +++ b/osu.Game/Skinning/SkinnableSprite.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; @@ -11,7 +12,7 @@ namespace osu.Game.Skinning /// /// A skinnable element which uses a stable sprite and can therefore share implementation logic. /// - public class SkinnableSprite : SkinnableDrawable + public class SkinnableSprite : SkinnableDrawable { protected override bool ApplySizeRestrictionsToDefault => true; @@ -23,6 +24,6 @@ namespace osu.Game.Skinning { } - protected override Sprite CreateDefault(string name) => new Sprite { Texture = textures.Get(name) }; + protected override Drawable CreateDefault(string name) => new Sprite { Texture = textures.Get(name) }; } } diff --git a/osu.Game/Skinning/SkinnableSpriteText.cs b/osu.Game/Skinning/SkinnableSpriteText.cs index d12a6f74f7..5af6df15e1 100644 --- a/osu.Game/Skinning/SkinnableSpriteText.cs +++ b/osu.Game/Skinning/SkinnableSpriteText.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics.Sprites; namespace osu.Game.Skinning { - public class SkinnableSpriteText : SkinnableDrawable, IHasText + public class SkinnableSpriteText : SkinnableDrawable, IHasText { public SkinnableSpriteText(string name, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) : base(name, defaultImplementation, allowFallback, confineMode) From c989185774497a13ef6cf977fb58052b56722ec3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Jul 2019 19:32:24 +0900 Subject: [PATCH 0317/2815] Fix incorrect approach circle scaling --- .../Objects/Drawables/DrawableHitCircle.cs | 2 +- .../Drawables/Pieces/ApproachCircle.cs | 22 ++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index d3d763daf3..9b8627d8f1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.UpdateInitialTransforms(); ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadeIn * 2, HitObject.TimePreempt)); - ApproachCircle.ScaleTo(1.1f, HitObject.TimePreempt); + ApproachCircle.ScaleTo(1f, HitObject.TimePreempt); ApproachCircle.Expire(true); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs index 9981585f9e..5813197336 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { @@ -24,7 +25,26 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces [BackgroundDependencyLoader] private void load(TextureStore textures) { - Child = new SkinnableSprite("Play/osu/approachcircle"); + Child = new SkinnableApproachCircle(); + } + + private class SkinnableApproachCircle : SkinnableSprite + { + public SkinnableApproachCircle() + : base("Play/osu/approachcircle") + { + } + + protected override Drawable CreateDefault(string name) + { + var drawable = base.CreateDefault(name); + + // account for the sprite being used for the default approach circle being taken from stable, + // when hitcircles have 5px padding on each size. this should be removed if we update the sprite. + drawable.Scale = new Vector2(128 / 118f); + + return drawable; + } } } } From 6ef3c71e22c430039cdfab297a0095bfc48a20a6 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 25 Jul 2019 11:31:46 +0900 Subject: [PATCH 0318/2815] remove unused using --- osu.Game/Screens/Select/Footer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs index a1384f19b4..71641cab5d 100644 --- a/osu.Game/Screens/Select/Footer.cs +++ b/osu.Game/Screens/Select/Footer.cs @@ -9,7 +9,6 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; namespace osu.Game.Screens.Select From 69844e6c24e973a74024be87ba654f16e9958221 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jul 2019 12:18:18 +0900 Subject: [PATCH 0319/2815] Fix beatmap present failing directly after an import --- osu.Game/Screens/Select/BeatmapCarousel.cs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 16354534f4..5069096a44 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -81,7 +81,8 @@ namespace osu.Game.Screens.Select itemsCache.Invalidate(); scrollPositionCache.Invalidate(); - Schedule(() => + // Run on late scheduler want to ensure this runs after all pending UpdateBeatmapSet / RemoveBeatmapSet operations are run. + SchedulerAfterChildren.Add(() => { BeatmapSetsChanged?.Invoke(); BeatmapSetsLoaded = true; @@ -129,19 +130,16 @@ namespace osu.Game.Screens.Select loadBeatmapSets(beatmaps.GetAllUsableBeatmapSetsEnumerable()); } - public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) + public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => { - Schedule(() => - { - var existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.ID == beatmapSet.ID); + var existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.ID == beatmapSet.ID); - if (existingSet == null) - return; + if (existingSet == null) + return; - root.RemoveChild(existingSet); - itemsCache.Invalidate(); - }); - } + root.RemoveChild(existingSet); + itemsCache.Invalidate(); + }); public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => { From 2b184658d1d81b7ae6f86cf5b38e963d2b53f2cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jul 2019 20:28:31 +0900 Subject: [PATCH 0320/2815] Adjust follow points by circle size --- .../Objects/Drawables/Connections/FollowPointRenderer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 7569626230..a269b87c75 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -97,13 +97,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections Position = pointStartPosition, Rotation = rotation, Alpha = 0, - Scale = new Vector2(1.5f), + Scale = new Vector2(1.5f * currHitObject.Scale), }); using (fp.BeginAbsoluteSequence(fadeInTime)) { fp.FadeIn(currHitObject.TimeFadeIn); - fp.ScaleTo(1, currHitObject.TimeFadeIn, Easing.Out); + fp.ScaleTo(currHitObject.Scale, currHitObject.TimeFadeIn, Easing.Out); fp.MoveTo(pointEndPosition, currHitObject.TimeFadeIn, Easing.Out); From 28653e871cad59e40d978a088717ee21f3e0614f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jul 2019 15:59:29 +0900 Subject: [PATCH 0321/2815] Give repeat points a size specification --- .../Objects/Drawables/DrawableRepeatPoint.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index 2e6e7e03ac..543e5d20d5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables this.repeatPoint = repeatPoint; this.drawableSlider = drawableSlider; - Size = new Vector2(45 * repeatPoint.Scale); + Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2 * repeatPoint.Scale); Blending = BlendingMode.Additive; Origin = Anchor.Centre; @@ -36,7 +36,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables new SkinnableDrawable("Play/osu/reversearrow", _ => new SpriteIcon { RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.Solid.ChevronRight + Icon = FontAwesome.Solid.ChevronRight, + Size = new Vector2(0.35f) }) }; } From 5a9d18380cc6844ecc462b727970deba05a2bfac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jul 2019 16:40:40 +0900 Subject: [PATCH 0322/2815] Use scale correctly in DrawableRepeatPoint --- .../Objects/Drawables/DrawableRepeatPoint.cs | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index 543e5d20d5..d07b37488c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.MathUtils; @@ -20,28 +22,40 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private double animDuration; + private readonly SkinnableDrawable scaleContainer; + public DrawableRepeatPoint(RepeatPoint repeatPoint, DrawableSlider drawableSlider) : base(repeatPoint) { this.repeatPoint = repeatPoint; this.drawableSlider = drawableSlider; - Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2 * repeatPoint.Scale); + Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); Blending = BlendingMode.Additive; Origin = Anchor.Centre; - InternalChildren = new Drawable[] + InternalChild = scaleContainer = new SkinnableDrawable("Play/osu/reversearrow", _ => new SpriteIcon { - new SkinnableDrawable("Play/osu/reversearrow", _ => new SpriteIcon - { - RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.Solid.ChevronRight, - Size = new Vector2(0.35f) - }) + RelativeSizeAxes = Axes.Both, + Icon = FontAwesome.Solid.ChevronRight, + Size = new Vector2(0.35f) + }) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, }; } + private readonly IBindable scaleBindable = new Bindable(); + + [BackgroundDependencyLoader] + private void load() + { + scaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue), true); + scaleBindable.BindTo(HitObject.ScaleBindable); + } + protected override void CheckForResult(bool userTriggered, double timeOffset) { if (repeatPoint.StartTime <= Time.Current) From 2cb3619b548ead0a1422017d246840843dc8eafc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jul 2019 13:27:41 +0900 Subject: [PATCH 0323/2815] Allow scaling outside of defined area Caters to skins which show borders outside of the circle for repeats. --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index d07b37488c..f75b62eecf 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables RelativeSizeAxes = Axes.Both, Icon = FontAwesome.Solid.ChevronRight, Size = new Vector2(0.35f) - }) + }, confineMode: ConfineMode.NoScaling) { Anchor = Anchor.Centre, Origin = Anchor.Centre, From 5e153a3dd39364ff742505f5322ef83900015de3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jul 2019 13:42:29 +0900 Subject: [PATCH 0324/2815] Use scale correctly in DrawableSliderTick --- .../Objects/Drawables/DrawableSliderTick.cs | 45 ++++++++++++------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index c8062b67b5..653e73ac3f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -1,6 +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 osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osuTK; @@ -16,36 +18,49 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public const double ANIM_DURATION = 150; + private const float default_tick_size = 16; + public bool Tracking { get; set; } public override bool DisplayResult => false; + private readonly SkinnableDrawable scaleContainer; + public DrawableSliderTick(SliderTick sliderTick) : base(sliderTick) { - Size = new Vector2(16 * sliderTick.Scale); + Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); Origin = Anchor.Centre; - InternalChildren = new Drawable[] + InternalChild = scaleContainer = new SkinnableDrawable("Play/osu/sliderscorepoint", _ => new CircularContainer { - new SkinnableDrawable("Play/osu/sliderscorepoint", _ => new Container + Masking = true, + Origin = Anchor.Centre, + Size = new Vector2(default_tick_size), + BorderThickness = default_tick_size / 4, + BorderColour = Color4.White, + Child = new Box { - Masking = true, RelativeSizeAxes = Axes.Both, - Origin = Anchor.Centre, - CornerRadius = Size.X / 2, - BorderThickness = 2, - BorderColour = Color4.White, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = AccentColour.Value, - Alpha = 0.3f, - } - }) + Colour = AccentColour.Value, + Alpha = 0.3f, + } + }) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, }; } + private readonly IBindable scaleBindable = new Bindable(); + + [BackgroundDependencyLoader] + private void load() + { + scaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue), true); + scaleBindable.BindTo(HitObject.ScaleBindable); + } + protected override void CheckForResult(bool userTriggered, double timeOffset) { if (timeOffset >= 0) From a290437286e1c55e34efea34bc5b0327271c71a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jul 2019 12:34:40 +0900 Subject: [PATCH 0325/2815] Fix skin changed events triggering after disposal --- osu.Game/Skinning/SkinReloadableDrawable.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/SkinReloadableDrawable.cs b/osu.Game/Skinning/SkinReloadableDrawable.cs index c09d5b1f92..440645bee7 100644 --- a/osu.Game/Skinning/SkinReloadableDrawable.cs +++ b/osu.Game/Skinning/SkinReloadableDrawable.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; @@ -36,12 +36,14 @@ namespace osu.Game.Skinning skin.SourceChanged += onChange; } - private void onChange() => SkinChanged(skin, allowDefaultFallback); + private void onChange() => + // schedule required to avoid calls after disposed. + Schedule(() => SkinChanged(skin, allowDefaultFallback)); protected override void LoadAsyncComplete() { base.LoadAsyncComplete(); - onChange(); + SkinChanged(skin, allowDefaultFallback); } /// From 9473f6d3e3b83970e4b785629f46080fa55cb440 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Jul 2019 11:45:33 +0900 Subject: [PATCH 0326/2815] Fix incorrect ratios being applied to playfield / skin elements This now matches osu-stable 1:1. --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 3 +-- .../UI/OsuPlayfieldAdjustmentContainer.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 15 +++++++++++++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index b3492a2b31..ea81527fa9 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -101,11 +101,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor }, }, } - }) + }, confineMode: ConfineMode.NoScaling) { Origin = Anchor.Centre, Anchor = Anchor.Centre, - RelativeSizeAxes = Axes.Both, } }; diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs index e28ff5f460..8b6b483618 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.UI Anchor = Anchor.Centre; Origin = Anchor.Centre; - Size = new Vector2(0.75f); + Size = new Vector2(0.8f); InternalChild = new Container { diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 513a024a36..7b6cd613ec 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -95,7 +95,6 @@ namespace osu.Game.Skinning public override Texture GetTexture(string componentName) { float ratio = 2; - var texture = Textures.Get($"{componentName}@2x"); if (texture == null) @@ -105,7 +104,19 @@ namespace osu.Game.Skinning } if (texture != null) - texture.ScaleAdjust = ratio / 0.72f; // brings sizing roughly in-line with stable + { + texture.ScaleAdjust = ratio; + + switch (componentName) + { + case "cursormiddle": + case "cursortrail": + case "cursor": + // apply inverse of adjustment in OsuPlayfieldAdjustmentContainer for non-gameplay-scale textures. + texture.ScaleAdjust *= 1.6f; + break; + } + } return texture; } From 46e17646acee682392c3b576b13aa97ca47b2978 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Jul 2019 18:49:52 +0900 Subject: [PATCH 0327/2815] Align slider path size with legacy skins --- .../Objects/Drawables/DrawableSlider.cs | 7 +++---- osu.Game/Skinning/LegacySkin.cs | 17 +++++++++++++++++ osu.Game/Skinning/SkinConfiguration.cs | 2 ++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 4a6bd45007..dc05f3de58 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -48,10 +48,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables InternalChildren = new Drawable[] { - Body = new SnakingSliderBody(s) - { - PathRadius = s.Scale * OsuHitObject.OBJECT_RADIUS, - }, + Body = new SnakingSliderBody(s), ticks = new Container { RelativeSizeAxes = Axes.Both }, repeatPoints = new Container { RelativeSizeAxes = Axes.Both }, Ball = new SliderBall(s, this) @@ -162,6 +159,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.SkinChanged(skin, allowFallback); Body.BorderSize = skin.GetValue(s => s.SliderBorderSize) ?? SliderBody.DEFAULT_BORDER_SIZE; + Body.PathRadius = slider.Scale * (skin.GetValue(s => s.SliderPathRadius) ?? OsuHitObject.OBJECT_RADIUS); + Body.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderTrackOverride") ? s.CustomColours["SliderTrackOverride"] : (Color4?)null) ?? AccentColour.Value; Body.BorderColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBorder") ? s.CustomColours["SliderBorder"] : (Color4?)null) ?? Color4.White; Ball.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? AccentColour.Value; diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 7b6cd613ec..af9a24df42 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -24,6 +24,13 @@ namespace osu.Game.Skinning protected IResourceStore Samples; + /// + /// On osu-stable, hitcircles have 5 pixels of transparent padding on each side to allow for shadows etc. + /// Their hittable area is 128px, but the actual circle portion is 118px. + /// We must account for some gameplay elements such as slider bodies, where this padding is not present. + /// + private const float legacy_circle_radius = 64 - 5; + public LegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager) : this(skin, new LegacySkinResourceStore(skin, storage), audioManager, "skin.ini") { @@ -41,6 +48,16 @@ namespace osu.Game.Skinning Samples = audioManager.GetSampleStore(storage); Textures = new TextureStore(new TextureLoaderStore(storage)); + + bool hasHitCircle = false; + + using (var testStream = storage.GetStream("hitcircle")) + hasHitCircle |= testStream != null; + + if (hasHitCircle) + { + Configuration.SliderPathRadius = legacy_circle_radius; + } } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs index 043622f8ce..93b599f9f6 100644 --- a/osu.Game/Skinning/SkinConfiguration.cs +++ b/osu.Game/Skinning/SkinConfiguration.cs @@ -27,6 +27,8 @@ namespace osu.Game.Skinning public float? SliderBorderSize { get; set; } + public float? SliderPathRadius { get; set; } + public bool? CursorExpand { get; set; } = true; } } From 5e8867066c3e395c9b6b646fdd4d539cd09de635 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jul 2019 14:43:44 +0900 Subject: [PATCH 0328/2815] Correctly handling bindable scale changes --- .../Objects/Drawables/DrawableSlider.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index dc05f3de58..020a64a26b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); scaleBindable.BindValueChanged(scale => { - Body.PathRadius = scale.NewValue * 64; + updatePathRadius(); Ball.Scale = new Vector2(scale.NewValue); }); @@ -154,18 +154,23 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Body.RecyclePath(); } + private float sliderPathRadius; + protected override void SkinChanged(ISkinSource skin, bool allowFallback) { base.SkinChanged(skin, allowFallback); Body.BorderSize = skin.GetValue(s => s.SliderBorderSize) ?? SliderBody.DEFAULT_BORDER_SIZE; - Body.PathRadius = slider.Scale * (skin.GetValue(s => s.SliderPathRadius) ?? OsuHitObject.OBJECT_RADIUS); + sliderPathRadius = skin.GetValue(s => s.SliderPathRadius) ?? OsuHitObject.OBJECT_RADIUS; + updatePathRadius(); Body.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderTrackOverride") ? s.CustomColours["SliderTrackOverride"] : (Color4?)null) ?? AccentColour.Value; Body.BorderColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBorder") ? s.CustomColours["SliderBorder"] : (Color4?)null) ?? Color4.White; Ball.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? AccentColour.Value; } + private void updatePathRadius() => Body.PathRadius = slider.Scale * sliderPathRadius; + protected override void CheckForResult(bool userTriggered, double timeOffset) { if (userTriggered || Time.Current < slider.EndTime) From c4bed0e6d29944cb6045267a3a4fdd17cf40bf34 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 25 Jul 2019 15:31:21 +0900 Subject: [PATCH 0329/2815] Resize BeatmapCarousel, update carouselitem logic --- osu.Game/Screens/Select/BeatmapCarousel.cs | 21 +++++++++------- osu.Game/Screens/Select/FilterControl.cs | 6 ----- osu.Game/Screens/Select/SongSelect.cs | 28 +++++++++++++++------- 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 16354534f4..938ae1c028 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -108,10 +108,12 @@ namespace osu.Game.Screens.Select root = new CarouselRoot(this); Child = new OsuContextMenuContainer { + Masking = false, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Child = scrollableContent = new Container { + Masking = false, RelativeSizeAxes = Axes.X, } }; @@ -424,7 +426,9 @@ namespace osu.Game.Screens.Select if (!scrollPositionCache.IsValid) updateScrollPosition(); - float drawHeight = DrawHeight; + // The draw positions of individual sets extend beyond the size of the carousel and into its parent + // As a result, this should use the Parent's draw height instead. + float drawHeight = Parent.DrawHeight; // Remove all items that should no longer be on-screen scrollableContent.RemoveAll(p => p.Y < Current - p.DrawHeight || p.Y > Current + drawHeight || !p.IsPresent); @@ -432,9 +436,9 @@ namespace osu.Game.Screens.Select // Find index range of all items that should be on-screen Trace.Assert(Items.Count == yPositions.Count); - int firstIndex = yPositions.BinarySearch(Current - DrawableCarouselItem.MAX_HEIGHT); + int firstIndex = yPositions.BinarySearch(Current - DrawableCarouselItem.MAX_HEIGHT - Parent.Padding.Top); if (firstIndex < 0) firstIndex = ~firstIndex; - int lastIndex = yPositions.BinarySearch(Current + drawHeight); + int lastIndex = yPositions.BinarySearch(Current + drawHeight + Parent.Padding.Bottom); if (lastIndex < 0) lastIndex = ~lastIndex; int notVisibleCount = 0; @@ -637,18 +641,19 @@ namespace osu.Game.Screens.Select /// the current scroll position. /// /// The item to be updated. - /// Half the draw height of the carousel container. - private void updateItem(DrawableCarouselItem p, float halfHeight) + /// Half the draw height of the carousel container's parent. + private void updateItem(DrawableCarouselItem p, float parentHalfHeight) { var height = p.IsPresent ? p.DrawHeight : 0; - float itemDrawY = p.Position.Y - Current + height / 2; - float dist = Math.Abs(1f - itemDrawY / halfHeight); + // The actual Y position of the item needs to be offset by any potential padding set by the container's parent. + float itemDrawY = p.Position.Y - Current + Parent.Padding.Top + height / 2; + float dist = Math.Abs(1f - itemDrawY / parentHalfHeight); // Setting the origin position serves as an additive position on top of potential // local transformation we may want to apply (e.g. when a item gets selected, we // may want to smoothly transform it leftwards.) - p.OriginPosition = new Vector2(-offsetX(dist, halfHeight), 0); + p.OriginPosition = new Vector2(-offsetX(dist, parentHalfHeight), 0); // We are applying a multiplicative alpha (which is internally done by nesting an // additional container and setting that container's alpha) such that we can diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 57fe15fd99..97ba1cce6b 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -187,11 +187,5 @@ namespace osu.Game.Screens.Select } private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria()); - - protected override bool OnMouseDown(MouseDownEvent e) => true; - - protected override bool OnMouseMove(MouseMoveEvent e) => true; - - protected override bool OnClick(ClickEvent e) => true; } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 20dbcf2693..011c62f0eb 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -45,6 +45,7 @@ namespace osu.Game.Screens.Select protected const float BACKGROUND_BLUR = 20; private const float left_area_padding = 20; + private const float filter_control_height = 100; public readonly FilterControl FilterControl; @@ -121,7 +122,7 @@ namespace osu.Game.Screens.Select Size = new Vector2(wedged_container_size.X, 1), Padding = new MarginPadding { - Bottom = 50, + Bottom = Footer.HEIGHT, Top = wedged_container_size.Y + left_area_padding, Left = left_area_padding, Right = left_area_padding * 2, @@ -147,20 +148,29 @@ namespace osu.Game.Screens.Select Width = 0.5f, Children = new Drawable[] { - Carousel = new BeatmapCarousel + new Container { - Masking = false, RelativeSizeAxes = Axes.Both, - Size = new Vector2(1 - wedged_container_size.X, 1), - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - SelectionChanged = updateSelectedBeatmap, - BeatmapSetsChanged = carouselBeatmapsLoaded, + Padding = new MarginPadding + { + Top = filter_control_height, + Bottom = Footer.HEIGHT + }, + Child = Carousel = new BeatmapCarousel + { + Masking = false, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(1 - wedged_container_size.X, 1), + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + SelectionChanged = updateSelectedBeatmap, + BeatmapSetsChanged = carouselBeatmapsLoaded, + }, }, FilterControl = new FilterControl { RelativeSizeAxes = Axes.X, - Height = 100, + Height = filter_control_height, FilterChanged = c => Carousel.Filter(c), Background = { Width = 2 }, Exit = () => From 58feba72a30824e7d5624dbf20bf6a0e2acc0666 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jul 2019 15:59:44 +0900 Subject: [PATCH 0330/2815] Fix scheduled events not running on previous drawables --- osu.Game/Skinning/SkinReloadableDrawable.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinReloadableDrawable.cs b/osu.Game/Skinning/SkinReloadableDrawable.cs index 440645bee7..397c6656a5 100644 --- a/osu.Game/Skinning/SkinReloadableDrawable.cs +++ b/osu.Game/Skinning/SkinReloadableDrawable.cs @@ -38,7 +38,8 @@ namespace osu.Game.Skinning private void onChange() => // schedule required to avoid calls after disposed. - Schedule(() => SkinChanged(skin, allowDefaultFallback)); + // note that this has the side-effect of components only performance a skin change when they are alive. + Scheduler.AddOnce(() => SkinChanged(skin, allowDefaultFallback)); protected override void LoadAsyncComplete() { From eb6bda3f0853cf2fa3c11f3f0787e1c1ffeb06d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Jul 2019 19:12:41 +0900 Subject: [PATCH 0331/2815] Cache DrawableHitObject for skinnables to access --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 1d9d885527..c1cb1a7a33 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -17,6 +17,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables { + [Cached(typeof(DrawableHitObject))] public abstract class DrawableHitObject : SkinReloadableDrawable { public readonly HitObject HitObject; From 859233526d4ad1f3173e0b6d4a5c33425cbfc45b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Jul 2019 18:50:57 +0900 Subject: [PATCH 0332/2815] Move circle visual implementation to new class Allows for more precise skin control over state animations. --- .../Objects/Drawables/DrawableHitCircle.cs | 127 +++++++++--------- .../Objects/Drawables/DrawableOsuHitObject.cs | 2 +- .../Objects/Drawables/Pieces/CirclePiece.cs | 31 +---- .../Drawables/Pieces/MainCirclePiece.cs | 94 +++++++++++++ 4 files changed, 157 insertions(+), 97 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 001003155d..897ab0a17b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -6,33 +6,29 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osuTK; using osu.Game.Rulesets.Scoring; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableHitCircle : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach { public ApproachCircle ApproachCircle; - private readonly CirclePiece circle; - private readonly RingPiece ring; - private readonly FlashPiece flash; - private readonly ExplodePiece explode; - private readonly NumberPiece number; - private readonly GlowPiece glow; private readonly IBindable positionBindable = new Bindable(); private readonly IBindable stackHeightBindable = new Bindable(); private readonly IBindable scaleBindable = new Bindable(); - public OsuAction? HitAction => circle.HitAction; - - private readonly Container explodeContainer; + public OsuAction? HitAction => hitArea.HitAction; private readonly Container scaleContainer; + private readonly HitArea hitArea; + public DrawableHitCircle(HitCircle h) : base(h) { @@ -47,44 +43,30 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, Anchor = Anchor.Centre, - Child = explodeContainer = new Container + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Children = new Drawable[] + hitArea = new HitArea { - glow = new GlowPiece(), - circle = new CirclePiece + Hit = () => { - Hit = () => - { - if (AllJudged) - return false; + if (AllJudged) + return false; - UpdateResult(true); - return true; - }, + UpdateResult(true); + return true; }, - number = new NumberPiece - { - Text = (HitObject.IndexInCurrentCombo + 1).ToString(), - }, - ring = new RingPiece(), - flash = new FlashPiece(), - explode = new ExplodePiece(), - ApproachCircle = new ApproachCircle - { - Alpha = 0, - Scale = new Vector2(4), - } + }, + new SkinnableDrawable("Play/Osu/Objects/Drawables/MainCircle", _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)), + ApproachCircle = new ApproachCircle + { + Alpha = 0, + Scale = new Vector2(4), } } }, }; - //may not be so correct - Size = circle.DrawSize; + Size = hitArea.DrawSize; } [BackgroundDependencyLoader] @@ -98,13 +80,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables stackHeightBindable.BindTo(HitObject.StackHeightBindable); scaleBindable.BindTo(HitObject.ScaleBindable); - AccentColour.BindValueChanged(colour => - { - explode.Colour = colour.NewValue; - glow.Colour = colour.NewValue; - circle.Colour = colour.NewValue; - ApproachCircle.Colour = colour.NewValue; - }, true); + AccentColour.BindValueChanged(accent => ApproachCircle.Colour = accent.NewValue, true); } protected override void CheckForResult(bool userTriggered, double timeOffset) @@ -139,8 +115,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void UpdateStateTransforms(ArmedState state) { - glow.FadeOut(400); - switch (state) { case ArmedState.Idle: @@ -148,7 +122,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Expire(true); - circle.HitAction = null; + hitArea.HitAction = null; // override lifetime end as FadeIn may have been changed externally, causing out expiration to be too early. LifetimeEnd = HitObject.StartTime + HitObject.HitWindows.HalfWindowFor(HitResult.Miss); @@ -163,29 +137,50 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables case ArmedState.Hit: ApproachCircle.FadeOut(50); - const double flash_in = 40; - flash.FadeTo(0.8f, flash_in) - .Then() - .FadeOut(100); - - explode.FadeIn(flash_in); - explodeContainer.ScaleTo(1.5f, 400, Easing.OutQuad); - - using (BeginDelayedSequence(flash_in, true)) - { - //after the flash, we can hide some elements that were behind it - ring.FadeOut(); - circle.FadeOut(); - number.FadeOut(); - - this.FadeOut(800); - } - - Expire(); + // todo: temporary / arbitrary + this.Delay(800).Expire(); break; } } public Drawable ProxiedLayer => ApproachCircle; + + private class HitArea : Drawable, IKeyBindingHandler + { + // IsHovered is used + public override bool HandlePositionalInput => true; + + public Func Hit; + + public OsuAction? HitAction; + + public HitArea() + { + Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + + public bool OnPressed(OsuAction action) + { + switch (action) + { + case OsuAction.LeftButton: + case OsuAction.RightButton: + if (IsHovered && (Hit?.Invoke() ?? false)) + { + HitAction = action; + return true; + } + + break; + } + + return false; + } + + public bool OnReleased(OsuAction action) => false; + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 579f16e0d4..a89fb8b682 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.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 osu.Game.Rulesets.Objects.Drawables; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs index dc0b149140..acbf5ba9ae 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -1,24 +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 osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input.Bindings; using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { - public class CirclePiece : Container, IKeyBindingHandler + public class CirclePiece : CompositeDrawable { - // IsHovered is used - public override bool HandlePositionalInput => true; - - public Func Hit; - - public OsuAction? HitAction; - public CirclePiece() { Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); @@ -30,25 +21,5 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces InternalChild = new SkinnableDrawable("Play/osu/hitcircle", _ => new DefaultCirclePiece()); } - - public bool OnPressed(OsuAction action) - { - switch (action) - { - case OsuAction.LeftButton: - case OsuAction.RightButton: - if (IsHovered && (Hit?.Invoke() ?? false)) - { - HitAction = action; - return true; - } - - break; - } - - return false; - } - - public bool OnReleased(OsuAction action) => false; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs new file mode 100644 index 0000000000..944c93bb6d --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs @@ -0,0 +1,94 @@ +// 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.Objects.Drawables; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + public class MainCirclePiece : CompositeDrawable + { + private readonly CirclePiece circle; + private readonly RingPiece ring; + private readonly FlashPiece flash; + private readonly ExplodePiece explode; + private readonly NumberPiece number; + private readonly GlowPiece glow; + + public MainCirclePiece(int index) + { + Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + InternalChildren = new Drawable[] + { + glow = new GlowPiece(), + circle = new CirclePiece(), + number = new NumberPiece + { + Text = (index + 1).ToString(), + }, + ring = new RingPiece(), + flash = new FlashPiece(), + explode = new ExplodePiece(), + }; + } + + private readonly IBindable state = new Bindable(); + + private readonly Bindable accentColour = new Bindable(); + + [BackgroundDependencyLoader] + private void load(DrawableHitObject drawableObject) + { + state.BindTo(drawableObject.State); + state.BindValueChanged(updateState, true); + + accentColour.BindTo(drawableObject.AccentColour); + accentColour.BindValueChanged(colour => + { + explode.Colour = colour.NewValue; + glow.Colour = colour.NewValue; + circle.Colour = colour.NewValue; + }, true); + } + + private void updateState(ValueChangedEvent state) + { + glow.FadeOut(400); + + switch (state.NewValue) + { + case ArmedState.Hit: + const double flash_in = 40; + const double flash_out = 100; + + flash.FadeTo(0.8f, flash_in) + .Then() + .FadeOut(flash_out); + + explode.FadeIn(flash_in); + this.ScaleTo(1.5f, 400, Easing.OutQuad); + + using (BeginDelayedSequence(flash_in, true)) + { + //after the flash, we can hide some elements that were behind it + ring.FadeOut(); + circle.FadeOut(); + number.FadeOut(); + + this.FadeOut(800); + } + + break; + } + } + } +} From 38e21caa5a50296697d200138c492628b3c3ed15 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jul 2019 12:34:54 +0900 Subject: [PATCH 0333/2815] Add legacy hitcircle --- osu.Game/Skinning/LegacySkin.cs | 75 ++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index af9a24df42..0a811a9514 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -6,15 +6,22 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +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.Framework.IO.Stores; using osu.Game.Database; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; using osuTK; +using osuTK.Graphics; namespace osu.Game.Skinning { @@ -36,6 +43,8 @@ namespace osu.Game.Skinning { } + private readonly bool hasHitCircle; + protected LegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager, string filename) : base(skin) { @@ -49,8 +58,6 @@ namespace osu.Game.Skinning Samples = audioManager.GetSampleStore(storage); Textures = new TextureStore(new TextureLoaderStore(storage)); - bool hasHitCircle = false; - using (var testStream = storage.GetStream("hitcircle")) hasHitCircle |= testStream != null; @@ -71,6 +78,12 @@ namespace osu.Game.Skinning { switch (componentName) { + case "Play/Osu/Objects/Drawables/MainCircle": + if (!hasHitCircle) + return null; + + return new LegacyMainCirclePiece(); + case "Play/Miss": componentName = "hit0"; break; @@ -243,5 +256,63 @@ namespace osu.Game.Skinning return texture; } } + + public class LegacyMainCirclePiece : CompositeDrawable + { + public LegacyMainCirclePiece() + { + Size = new Vector2(128); + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + + private readonly IBindable state = new Bindable(); + + private readonly Bindable accentColour = new Bindable(); + + [BackgroundDependencyLoader] + private void load(DrawableHitObject drawableObject, ISkinSource skin) + { + Sprite mainCircle; + + InternalChildren = new Drawable[] + { + mainCircle = new Sprite + { + Texture = skin.GetTexture("hitcircle"), + Colour = drawableObject.AccentColour.Value + }, + new SkinnableSpriteText("Play/osu/number-text", _ => new OsuSpriteText + { + Font = OsuFont.Numeric.With(size: 40), + UseFullGlyphHeight = false, + }, confineMode: ConfineMode.NoScaling) + { + Text = (((IHasComboInformation)drawableObject.HitObject).IndexInCurrentCombo + 1).ToString() + }, + new Sprite { Texture = skin.GetTexture("hitcircleoverlay") } + }; + + state.BindTo(drawableObject.State); + state.BindValueChanged(updateState, true); + + accentColour.BindTo(drawableObject.AccentColour); + accentColour.BindValueChanged(colour => mainCircle.Colour = colour.NewValue, true); + } + + private void updateState(ValueChangedEvent state) + { + const double legacy_fade_duration = 240; + + switch (state.NewValue) + { + case ArmedState.Hit: + this.FadeOut(legacy_fade_duration, Easing.Out); + this.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); + break; + } + } + } } } From c1b01308579e057a9748df8f66583f5fe89b3cc4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jul 2019 16:17:02 +0900 Subject: [PATCH 0334/2815] Add legacy cursormiddle support --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 133 +++++++++++-------- osu.Game/Skinning/LegacySkin.cs | 39 ++++++ 2 files changed, 115 insertions(+), 57 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index ea81527fa9..37ac972f41 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { public class OsuCursor : SkinReloadableDrawable { + private const float size = 28; + private bool cursorExpand; private Bindable cursorScale; @@ -30,7 +32,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor public OsuCursor() { Origin = Anchor.Centre; - Size = new Vector2(28); + + Size = new Vector2(size); } protected override void SkinChanged(ISkinSource skin, bool allowFallback) @@ -46,62 +49,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, Anchor = Anchor.Centre, - Child = scaleTarget = new SkinnableDrawable("cursor", _ => new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderThickness = Size.X / 6, - BorderColour = Color4.White, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Pink.Opacity(0.5f), - Radius = 5, - }, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true, - }, - new CircularContainer - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderThickness = Size.X / 3, - BorderColour = Color4.White.Opacity(0.5f), - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true, - }, - }, - }, - new CircularContainer - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Scale = new Vector2(0.1f), - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - }, - }, - }, - } - }, confineMode: ConfineMode.NoScaling) + Child = scaleTarget = new SkinnableDrawable("osu/Game/Rulesets/Osu/UI/Cursor/OsuCursor", _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling) { Origin = Anchor.Centre, Anchor = Anchor.Centre, @@ -144,5 +92,76 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor } public void Contract() => expandTarget.ScaleTo(released_scale, 100, Easing.OutQuad); + + private class DefaultCursor : CompositeDrawable + { + public DefaultCursor() + { + RelativeSizeAxes = Axes.Both; + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + InternalChildren = new Drawable[] + { + new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = size / 6, + BorderColour = Color4.White, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Pink.Opacity(0.5f), + Radius = 5, + }, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }, + new CircularContainer + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = size / 3, + BorderColour = Color4.White.Opacity(0.5f), + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }, + }, + }, + new CircularContainer + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(0.1f), + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }, + }, + }, + } + } + }; + } + } } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index af9a24df42..905541a592 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -6,9 +6,11 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; @@ -71,6 +73,12 @@ namespace osu.Game.Skinning { switch (componentName) { + case "osu/Game/Rulesets/Osu/UI/Cursor/OsuCursor": + if (GetTexture("cursor") != null) + return new LegacyCursor(); + + break; + case "Play/Miss": componentName = "hit0"; break; @@ -243,5 +251,36 @@ namespace osu.Game.Skinning return texture; } } + + public class LegacyCursor : CompositeDrawable + { + public LegacyCursor() + { + Size = new Vector2(50); + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + InternalChildren = new Drawable[] + { + new Sprite + { + Texture = skin.GetTexture("cursormiddle"), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new Sprite + { + Texture = skin.GetTexture("cursor"), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }; + } + } } } From bfaf9d5f419aef2cd84031e0e0c00aeffc3acf5e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jul 2019 16:24:10 +0900 Subject: [PATCH 0335/2815] Remove unnecessary special cases --- osu.Game/Skinning/LegacySkin.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 905541a592..48888191e9 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -129,20 +129,8 @@ namespace osu.Game.Skinning } if (texture != null) - { texture.ScaleAdjust = ratio; - switch (componentName) - { - case "cursormiddle": - case "cursortrail": - case "cursor": - // apply inverse of adjustment in OsuPlayfieldAdjustmentContainer for non-gameplay-scale textures. - texture.ScaleAdjust *= 1.6f; - break; - } - } - return texture; } From 6cc6aff66e94de5d3a387639f0aeaac5230a02c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jul 2019 15:35:51 +0900 Subject: [PATCH 0336/2815] Fix slider ball sizing when legacy skin falls back to default --- .../Objects/Drawables/DrawableSlider.cs | 2 - .../Objects/Drawables/Pieces/SliderBall.cs | 130 ++++++++++-------- 2 files changed, 76 insertions(+), 56 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 020a64a26b..a0626707af 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -115,7 +115,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables AccentColour.BindValueChanged(colour => { Body.AccentColour = colour.NewValue; - Ball.AccentColour = colour.NewValue; foreach (var drawableHitObject in NestedHitObjects) drawableHitObject.AccentColour.Value = colour.NewValue; @@ -166,7 +165,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Body.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderTrackOverride") ? s.CustomColours["SliderTrackOverride"] : (Color4?)null) ?? AccentColour.Value; Body.BorderColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBorder") ? s.CustomColours["SliderBorder"] : (Color4?)null) ?? Color4.White; - Ball.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? AccentColour.Value; } private void updatePathRadius() => Body.PathRadius = slider.Scale * sliderPathRadius; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index 9ba8ad3474..119005eb40 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -3,11 +3,14 @@ using System; using System.Linq; +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.Input; using osu.Framework.Input.Events; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osuTK.Graphics; using osu.Game.Skinning; @@ -17,88 +20,44 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class SliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition { - private Color4 accentColour = Color4.Black; - public Func GetInitialHitAction; - /// - /// The colour that is used for the slider ball. - /// - public Color4 AccentColour - { - get => accentColour; - set - { - accentColour = value; - if (drawableBall != null) - drawableBall.Colour = value; - } - } - private readonly Slider slider; public readonly Drawable FollowCircle; - private Drawable drawableBall; private readonly DrawableSlider drawableSlider; public SliderBall(Slider slider, DrawableSlider drawableSlider = null) { this.drawableSlider = drawableSlider; this.slider = slider; - Masking = true; - AutoSizeAxes = Axes.Both; + Blending = BlendingMode.Additive; Origin = Anchor.Centre; + Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); + Children = new[] { - FollowCircle = new Container + FollowCircle = new FollowCircleContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Width = OsuHitObject.OBJECT_RADIUS * 2, - Height = OsuHitObject.OBJECT_RADIUS * 2, + RelativeSizeAxes = Axes.Both, Alpha = 0, - Child = new SkinnableDrawable("Play/osu/sliderfollowcircle", _ => new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderThickness = 5, - BorderColour = Color4.Orange, - Blending = BlendingMode.Additive, - Child = new Box - { - Colour = Color4.Orange, - RelativeSizeAxes = Axes.Both, - Alpha = 0.2f, - } - }), + Child = new SkinnableDrawable("Play/osu/sliderfollowcircle", _ => new DefaultFollowCircle()), }, new CircularContainer { Masking = true, - AutoSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, Anchor = Anchor.Centre, Alpha = 1, Child = new Container { - Width = OsuHitObject.OBJECT_RADIUS * 2, - Height = OsuHitObject.OBJECT_RADIUS * 2, + RelativeSizeAxes = Axes.Both, // TODO: support skin filename animation (sliderb0, sliderb1...) - Child = new SkinnableDrawable("Play/osu/sliderb", _ => new CircularContainer - { - Masking = true, - RelativeSizeAxes = Axes.Both, - BorderThickness = 10, - BorderColour = Color4.White, - Alpha = 1, - Child = drawableBall = new Box - { - Colour = AccentColour, - RelativeSizeAxes = Axes.Both, - Alpha = 0.4f, - } - }), + Child = new SkinnableDrawable("Play/osu/sliderb", _ => new DefaultSliderBall()), } } }; @@ -191,7 +150,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces // in valid time range Time.Current >= slider.StartTime && Time.Current < slider.EndTime && // in valid position range - lastScreenSpaceMousePosition.HasValue && base.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) && + lastScreenSpaceMousePosition.HasValue && FollowCircle.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) && // valid action (actions?.Any(isValidTrackingAction) ?? false); } @@ -214,5 +173,68 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { Position = slider.CurvePositionAt(completionProgress); } + + private class FollowCircleContainer : Container + { + public override bool HandlePositionalInput => true; + } + + public class DefaultFollowCircle : CompositeDrawable + { + public DefaultFollowCircle() + { + RelativeSizeAxes = Axes.Both; + + InternalChild = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = 5, + BorderColour = Color4.Orange, + Blending = BlendingMode.Additive, + Child = new Box + { + Colour = Color4.Orange, + RelativeSizeAxes = Axes.Both, + Alpha = 0.2f, + } + }; + } + } + + public class DefaultSliderBall : CompositeDrawable + { + private readonly Bindable accentColour = new Bindable(); + + private Drawable drawableBall; + + [BackgroundDependencyLoader] + private void load(DrawableHitObject drawableObject, ISkinSource skin) + { + RelativeSizeAxes = Axes.Both; + + float radius = skin.GetValue(s => s.SliderPathRadius) ?? OsuHitObject.OBJECT_RADIUS; + + InternalChild = new CircularContainer + { + Masking = true, + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(radius / OsuHitObject.OBJECT_RADIUS), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + BorderThickness = 10, + BorderColour = Color4.White, + Alpha = 1, + Child = drawableBall = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.4f, + } + }; + + accentColour.BindTo(drawableObject.AccentColour); + accentColour.BindValueChanged(colour => drawableBall.Colour = colour.NewValue, true); + } + } } } From 7b82d184bd48dd3130552596ff1115a58986c651 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 25 Jul 2019 11:07:53 +0300 Subject: [PATCH 0337/2815] Add bindable boolean for break times --- osu.Game/Screens/Play/BreakOverlay.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index d390787090..0941b8a452 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -35,6 +37,11 @@ namespace osu.Game.Screens.Play public override bool RemoveCompletedTransforms => false; + /// + /// Whether we are currently in the break time range. + /// + public readonly BindableBool IsBreakTime = new BindableBool(); + private readonly Container remainingTimeAdjustmentBox; private readonly Container remainingTimeBox; private readonly RemainingTimeCounter remainingTimeCounter; @@ -109,6 +116,13 @@ namespace osu.Game.Screens.Play initializeBreaks(); } + protected override void Update() + { + base.Update(); + + IsBreakTime.Value = breaks.Where(b => b.HasEffect).Any(b => Clock.CurrentTime >= b.StartTime && Clock.CurrentTime <= b.EndTime); + } + private void initializeBreaks() { if (!IsLoaded) return; // we need a clock. From 0ea0a10ca45ee73e08efad09e55e98f17b1ee2da Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 25 Jul 2019 11:26:38 +0300 Subject: [PATCH 0338/2815] Expose as IBindable --- osu.Game/Screens/Play/BreakOverlay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 0941b8a452..593b54afb8 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -41,6 +41,9 @@ namespace osu.Game.Screens.Play /// Whether we are currently in the break time range. /// public readonly BindableBool IsBreakTime = new BindableBool(); + public IBindable IsBreakTime => isBreakTime; + + private readonly BindableBool isBreakTime = new BindableBool(); private readonly Container remainingTimeAdjustmentBox; private readonly Container remainingTimeBox; From 69d2f57f4fadc108076365780f13b96b6e8e08cc Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 25 Jul 2019 11:27:01 +0300 Subject: [PATCH 0339/2815] Avoid using LINQ --- osu.Game/Screens/Play/BreakOverlay.cs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 593b54afb8..bebbbc5b68 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -123,7 +122,24 @@ namespace osu.Game.Screens.Play { base.Update(); - IsBreakTime.Value = breaks.Where(b => b.HasEffect).Any(b => Clock.CurrentTime >= b.StartTime && Clock.CurrentTime <= b.EndTime); + updateBreakTimeBindable(); + } + + private void updateBreakTimeBindable() + { + foreach (var b in breaks) + { + if (!b.HasEffect) + continue; + + if (Clock.CurrentTime >= b.StartTime && Clock.CurrentTime <= b.EndTime) + { + isBreakTime.Value = true; + return; + } + } + + isBreakTime.Value = false; } private void initializeBreaks() From 9bd66b6e7a777f9273efd5b6c7734db909c78b2f Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 25 Jul 2019 11:27:32 +0300 Subject: [PATCH 0340/2815] Better xmldoc --- osu.Game/Screens/Play/BreakOverlay.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index bebbbc5b68..5958ca77d8 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -37,9 +37,8 @@ namespace osu.Game.Screens.Play public override bool RemoveCompletedTransforms => false; /// - /// Whether we are currently in the break time range. + /// Whether the gameplay is currently in a break. /// - public readonly BindableBool IsBreakTime = new BindableBool(); public IBindable IsBreakTime => isBreakTime; private readonly BindableBool isBreakTime = new BindableBool(); From 97eb5293a87102dbf918bd4b69ca0486937abd03 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 25 Jul 2019 17:32:21 +0900 Subject: [PATCH 0341/2815] Don't depend on parent sizing --- osu.Game/Screens/Select/BeatmapCarousel.cs | 28 +++++++++++----------- osu.Game/Screens/Select/FilterControl.cs | 1 - osu.Game/Screens/Select/Footer.cs | 4 ---- osu.Game/Screens/Select/SongSelect.cs | 21 ++++++++++------ 4 files changed, 28 insertions(+), 26 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 938ae1c028..e4b75a3540 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -426,19 +426,18 @@ namespace osu.Game.Screens.Select if (!scrollPositionCache.IsValid) updateScrollPosition(); - // The draw positions of individual sets extend beyond the size of the carousel and into its parent - // As a result, this should use the Parent's draw height instead. - float drawHeight = Parent.DrawHeight; + // The draw positions of individual sets extend beyond the size of the carousel and into the footer and header. + float visibleHeight = DrawHeight + SongSelect.FOOTER_HEIGHT + SongSelect.FILTER_CONTROL_HEIGHT; // Remove all items that should no longer be on-screen - scrollableContent.RemoveAll(p => p.Y < Current - p.DrawHeight || p.Y > Current + drawHeight || !p.IsPresent); + scrollableContent.RemoveAll(p => p.Y < Current - p.DrawHeight || p.Y > Current + visibleHeight || !p.IsPresent); // Find index range of all items that should be on-screen Trace.Assert(Items.Count == yPositions.Count); - int firstIndex = yPositions.BinarySearch(Current - DrawableCarouselItem.MAX_HEIGHT - Parent.Padding.Top); + int firstIndex = yPositions.BinarySearch(Current - DrawableCarouselItem.MAX_HEIGHT - SongSelect.FILTER_CONTROL_HEIGHT); if (firstIndex < 0) firstIndex = ~firstIndex; - int lastIndex = yPositions.BinarySearch(Current + drawHeight + Parent.Padding.Bottom); + int lastIndex = yPositions.BinarySearch(Current + visibleHeight + SongSelect.FOOTER_HEIGHT); if (lastIndex < 0) lastIndex = ~lastIndex; int notVisibleCount = 0; @@ -490,7 +489,7 @@ namespace osu.Game.Screens.Select // Update externally controlled state of currently visible items // (e.g. x-offset and opacity). - float halfHeight = drawHeight / 2; + float halfHeight = visibleHeight / 2; foreach (DrawableCarouselItem p in scrollableContent.Children) updateItem(p, halfHeight); } @@ -546,7 +545,8 @@ namespace osu.Game.Screens.Select yPositions.Clear(); - float currentY = DrawHeight / 2; + float visibleHeight = DrawHeight + SongSelect.FOOTER_HEIGHT + SongSelect.FILTER_CONTROL_HEIGHT; + float currentY = visibleHeight / 2; DrawableCarouselBeatmapSet lastSet = null; scrollTarget = null; @@ -600,7 +600,7 @@ namespace osu.Game.Screens.Select currentY += d.DrawHeight + 5; } - currentY += DrawHeight / 2; + currentY += visibleHeight / 2; scrollableContent.Height = currentY; if (BeatmapSetsLoaded && (selectedBeatmapSet == null || selectedBeatmap == null || selectedBeatmapSet.State.Value != CarouselItemState.Selected)) @@ -641,19 +641,19 @@ namespace osu.Game.Screens.Select /// the current scroll position. /// /// The item to be updated. - /// Half the draw height of the carousel container's parent. - private void updateItem(DrawableCarouselItem p, float parentHalfHeight) + /// Half the draw height of the carousel container's parent. + private void updateItem(DrawableCarouselItem p, float halfHeight) { var height = p.IsPresent ? p.DrawHeight : 0; // The actual Y position of the item needs to be offset by any potential padding set by the container's parent. - float itemDrawY = p.Position.Y - Current + Parent.Padding.Top + height / 2; - float dist = Math.Abs(1f - itemDrawY / parentHalfHeight); + float itemDrawY = p.Position.Y + SongSelect.FILTER_CONTROL_HEIGHT - Current + height / 2; + float dist = Math.Abs(1f - itemDrawY / halfHeight); // Setting the origin position serves as an additive position on top of potential // local transformation we may want to apply (e.g. when a item gets selected, we // may want to smoothly transform it leftwards.) - p.OriginPosition = new Vector2(-offsetX(dist, parentHalfHeight), 0); + p.OriginPosition = new Vector2(-offsetX(dist, halfHeight), 0); // We are applying a multiplicative alpha (which is internally done by nesting an // additional container and setting that container's alpha) such that we can diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 97ba1cce6b..31b71a21c8 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -14,7 +14,6 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Select.Filter; using Container = osu.Framework.Graphics.Containers.Container; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; using osu.Game.Configuration; using osu.Game.Rulesets; diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs index 0680711f1c..7db3d65a0b 100644 --- a/osu.Game/Screens/Select/Footer.cs +++ b/osu.Game/Screens/Select/Footer.cs @@ -62,10 +62,6 @@ namespace osu.Game.Screens.Select public Footer() { - RelativeSizeAxes = Axes.X; - Height = HEIGHT; - Anchor = Anchor.BottomCentre; - Origin = Anchor.BottomCentre; Children = new Drawable[] { new Box diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 011c62f0eb..e5576f78cf 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -43,9 +43,10 @@ namespace osu.Game.Screens.Select { private static readonly Vector2 wedged_container_size = new Vector2(0.5f, 245); + public const float FILTER_CONTROL_HEIGHT = 100; + public const float FOOTER_HEIGHT = 50; protected const float BACKGROUND_BLUR = 20; private const float left_area_padding = 20; - private const float filter_control_height = 100; public readonly FilterControl FilterControl; @@ -122,7 +123,7 @@ namespace osu.Game.Screens.Select Size = new Vector2(wedged_container_size.X, 1), Padding = new MarginPadding { - Bottom = Footer.HEIGHT, + Bottom = FOOTER_HEIGHT, Top = wedged_container_size.Y + left_area_padding, Left = left_area_padding, Right = left_area_padding * 2, @@ -153,8 +154,8 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { - Top = filter_control_height, - Bottom = Footer.HEIGHT + Top = FILTER_CONTROL_HEIGHT, + Bottom = FOOTER_HEIGHT }, Child = Carousel = new BeatmapCarousel { @@ -170,7 +171,7 @@ namespace osu.Game.Screens.Select FilterControl = new FilterControl { RelativeSizeAxes = Axes.X, - Height = filter_control_height, + Height = FILTER_CONTROL_HEIGHT, FilterChanged = c => Carousel.Filter(c), Background = { Width = 2 }, Exit = () => @@ -209,7 +210,7 @@ namespace osu.Game.Screens.Select Origin = Anchor.BottomLeft, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Bottom = Footer.HEIGHT }, + Margin = new MarginPadding { Bottom = FOOTER_HEIGHT }, Children = new Drawable[] { BeatmapOptions = new BeatmapOptionsOverlay(), @@ -221,7 +222,13 @@ namespace osu.Game.Screens.Select } } }, - Footer = new Footer() + Footer = new Footer + { + RelativeSizeAxes = Axes.X, + Height = FOOTER_HEIGHT, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + } }); } From a9f0dda9d7ec20b4a0492f670c02e03be26e4a1b Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 25 Jul 2019 17:36:13 +0900 Subject: [PATCH 0342/2815] Confine positional input --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index e4b75a3540..b917fc4bb4 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -53,6 +53,8 @@ namespace osu.Game.Screens.Select public override bool HandleNonPositionalInput => AllowSelection; public override bool HandlePositionalInput => AllowSelection; + protected override bool ConfinePositionalInput => true; + /// /// Whether carousel items have completed asynchronously loaded. /// From 172a9ce33a29a5286d23f46fc0b752e795362e12 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 25 Jul 2019 11:40:45 +0300 Subject: [PATCH 0343/2815] Use a for loop instead of foreach avoid allocating an iterator --- osu.Game/Screens/Play/BreakOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 5958ca77d8..918bb1e5c9 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -126,12 +126,12 @@ namespace osu.Game.Screens.Play private void updateBreakTimeBindable() { - foreach (var b in breaks) + for (int i = 0; i < breaks.Count; i++) { - if (!b.HasEffect) + if (!breaks[i].HasEffect) continue; - if (Clock.CurrentTime >= b.StartTime && Clock.CurrentTime <= b.EndTime) + if (Clock.CurrentTime >= breaks[i].StartTime && Clock.CurrentTime <= breaks[i].EndTime) { isBreakTime.Value = true; return; From 5a55433d6c7e2b76e8b7ceabd732374e0573efc1 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 25 Jul 2019 11:53:32 +0300 Subject: [PATCH 0344/2815] Return if breaks are null Fixes a test --- osu.Game/Screens/Play/BreakOverlay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 918bb1e5c9..8b9431a87c 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -126,6 +126,9 @@ namespace osu.Game.Screens.Play private void updateBreakTimeBindable() { + if (breaks == null) + return; + for (int i = 0; i < breaks.Count; i++) { if (!breaks[i].HasEffect) From d4f85af19c9745d49dadfbd77fe529c5248b6f83 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Jul 2019 18:22:56 +0900 Subject: [PATCH 0345/2815] Force snaking slider paths to retain a fixed size --- .../Objects/Drawables/Pieces/SliderBody.cs | 17 +++++++++-------- .../Drawables/Pieces/SnakingSliderBody.cs | 19 ++++++++++++++++++- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs index 97c7c9cec5..6bc19ee3b5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.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 osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; @@ -75,22 +76,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces protected SliderBody() { - InternalChild = path = new SliderPath(); + RecyclePath(); } /// /// Initialises a new , releasing all resources retained by the old one. /// - public void RecyclePath() + public virtual void RecyclePath() { InternalChild = path = new SliderPath { - Position = path.Position, - PathRadius = path.PathRadius, - AccentColour = path.AccentColour, - BorderColour = path.BorderColour, - BorderSize = path.BorderSize, - Vertices = path.Vertices + Position = path?.Position ?? Vector2.Zero, + PathRadius = path?.PathRadius ?? 10, + AccentColour = path?.AccentColour ?? Color4.White, + BorderColour = path?.BorderColour ?? Color4.White, + BorderSize = path?.BorderSize ?? DEFAULT_BORDER_SIZE, + Vertices = path?.Vertices ?? Array.Empty() }; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs index 73b184bffe..a3d3893c8b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Types; using osuTK; @@ -78,9 +79,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces slider.Path.GetPathToProgress(CurrentCurve, 0, 1); SetVertices(CurrentCurve); - // The body is sized to the full path size to avoid excessive autosize computations + // Force the body to be the final path size to avoid excessive autosize computations + Path.AutoSizeAxes = Axes.Both; Size = Path.Size; + updatePathSize(); + snakedPosition = Path.PositionInBoundingBox(Vector2.Zero); snakedPathOffset = Path.PositionInBoundingBox(Path.Vertices[0]); @@ -93,6 +97,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces setRange(lastSnakedStart, lastSnakedEnd); } + public override void RecyclePath() + { + base.RecyclePath(); + updatePathSize(); + } + + private void updatePathSize() + { + // Force the path to its final size to avoid excessive framebuffer resizes + Path.AutoSizeAxes = Axes.None; + Path.Size = Size; + } + private void setRange(double p0, double p1) { if (p0 > p1) From cdda264c491c4cef4b122ece777e04413e57cbc5 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 25 Jul 2019 12:28:21 +0300 Subject: [PATCH 0346/2815] Use global index and move it to find if break time Avoid using iterations --- osu.Game/Screens/Play/BreakOverlay.cs | 38 ++++++++++++++++++--------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 8b9431a87c..0470cdb0d5 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.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.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -30,6 +31,8 @@ namespace osu.Game.Screens.Play set { breaks = value; + currentBreakIndex = 0; + initializeBreaks(); } } @@ -41,6 +44,7 @@ namespace osu.Game.Screens.Play /// public IBindable IsBreakTime => isBreakTime; + private int currentBreakIndex; private readonly BindableBool isBreakTime = new BindableBool(); private readonly Container remainingTimeAdjustmentBox; @@ -126,22 +130,30 @@ namespace osu.Game.Screens.Play private void updateBreakTimeBindable() { - if (breaks == null) - return; - - for (int i = 0; i < breaks.Count; i++) + if (breaks == null || !breaks.Any()) { - if (!breaks[i].HasEffect) - continue; - - if (Clock.CurrentTime >= breaks[i].StartTime && Clock.CurrentTime <= breaks[i].EndTime) - { - isBreakTime.Value = true; - return; - } + isBreakTime.Value = false; + return; } - isBreakTime.Value = false; + int indexDirection = Clock.CurrentTime < breaks[currentBreakIndex].StartTime ? -1 : (Clock.CurrentTime > breaks[currentBreakIndex].EndTime ? 1 : 0); + + while (Clock.CurrentTime < breaks[currentBreakIndex].StartTime || Clock.CurrentTime > breaks[currentBreakIndex].EndTime) + { + currentBreakIndex += indexDirection; + + if (currentBreakIndex < 0 || currentBreakIndex >= breaks.Count) + break; + } + + if (currentBreakIndex < 0 || currentBreakIndex >= breaks.Count) + { + isBreakTime.Value = false; + currentBreakIndex = 0; + return; + } + + isBreakTime.Value = true; } private void initializeBreaks() From 5e5101280054709321e3f528312605be91843b28 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Thu, 25 Jul 2019 22:54:05 +0930 Subject: [PATCH 0347/2815] Rewrite updateBreakTimeBindable --- .../Visual/Gameplay/TestSceneBreakOverlay.cs | 26 ++++++++++++++++- osu.Game/Screens/Play/BreakOverlay.cs | 29 +++++++------------ 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs index 3cd1b8307a..ffa822d175 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs @@ -1,8 +1,13 @@ // 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.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps.Timing; using osu.Game.Screens.Play; @@ -11,11 +16,30 @@ namespace osu.Game.Tests.Visual.Gameplay [TestFixture] public class TestSceneBreakOverlay : OsuTestScene { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(BreakOverlay), + }; + private readonly BreakOverlay breakOverlay; + private readonly IBindable isBreakTimeBindable = new BindableBool(); public TestSceneBreakOverlay() { - Child = breakOverlay = new BreakOverlay(true); + SpriteText breakTimeText; + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + breakTimeText = new SpriteText(), + breakOverlay = new BreakOverlay(true) + } + }; + + isBreakTimeBindable.BindTo(breakOverlay.IsBreakTime); + isBreakTimeBindable.BindValueChanged(e => breakTimeText.Text = $"IsBreakTime: {e.NewValue}", true); AddStep("2s break", () => startBreak(2000)); AddStep("5s break", () => startBreak(5000)); diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 0470cdb0d5..aceacdbab1 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.Play set { breaks = value; - currentBreakIndex = 0; + nearestBreakIndex = 0; initializeBreaks(); } @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Play /// public IBindable IsBreakTime => isBreakTime; - private int currentBreakIndex; + private int nearestBreakIndex; private readonly BindableBool isBreakTime = new BindableBool(); private readonly Container remainingTimeAdjustmentBox; @@ -136,24 +136,17 @@ namespace osu.Game.Screens.Play return; } - int indexDirection = Clock.CurrentTime < breaks[currentBreakIndex].StartTime ? -1 : (Clock.CurrentTime > breaks[currentBreakIndex].EndTime ? 1 : 0); + while (nearestBreakIndex < breaks.Count - 1 && Clock.CurrentTime > breaks[nearestBreakIndex].EndTime) + nearestBreakIndex++; - while (Clock.CurrentTime < breaks[currentBreakIndex].StartTime || Clock.CurrentTime > breaks[currentBreakIndex].EndTime) - { - currentBreakIndex += indexDirection; + while (nearestBreakIndex > 0 && Clock.CurrentTime < breaks[nearestBreakIndex].StartTime) + nearestBreakIndex--; - if (currentBreakIndex < 0 || currentBreakIndex >= breaks.Count) - break; - } - - if (currentBreakIndex < 0 || currentBreakIndex >= breaks.Count) - { - isBreakTime.Value = false; - currentBreakIndex = 0; - return; - } - - isBreakTime.Value = true; + // This ensures that IsBreakTime is generally consistent with the overlay's transforms during a break. + // If the overlay never shows (break.HasEffect is false), IsBreakTime should be false. + // We also assume that the overlay's fade out transform is "not break time". + var nearestBreak = breaks[nearestBreakIndex]; + isBreakTime.Value = nearestBreak.HasEffect && Clock.CurrentTime >= nearestBreak.StartTime && Clock.CurrentTime <= nearestBreak.EndTime - fade_duration; } private void initializeBreaks() From 1d6c321e14ff3c820ae2f3578f62e239cacbdd18 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Fri, 26 Jul 2019 08:34:18 +0930 Subject: [PATCH 0348/2815] Ensure we don't ping-pong nearestBreakIndex between breaks --- osu.Game/Screens/Play/BreakOverlay.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index aceacdbab1..50b2c97f59 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -136,11 +136,16 @@ namespace osu.Game.Screens.Play return; } - while (nearestBreakIndex < breaks.Count - 1 && Clock.CurrentTime > breaks[nearestBreakIndex].EndTime) - nearestBreakIndex++; - - while (nearestBreakIndex > 0 && Clock.CurrentTime < breaks[nearestBreakIndex].StartTime) - nearestBreakIndex--; + if (Clock.CurrentTime > breaks[nearestBreakIndex].EndTime) + { + while (nearestBreakIndex < breaks.Count - 1 && Clock.CurrentTime > breaks[nearestBreakIndex].EndTime) + nearestBreakIndex++; + } + else + { + while (nearestBreakIndex > 0 && Clock.CurrentTime < breaks[nearestBreakIndex].StartTime) + nearestBreakIndex--; + } // This ensures that IsBreakTime is generally consistent with the overlay's transforms during a break. // If the overlay never shows (break.HasEffect is false), IsBreakTime should be false. From a08d54eb06b2b2686152b9b04729ba3aabe65fdb Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 26 Jul 2019 03:11:59 +0300 Subject: [PATCH 0349/2815] Remove unnecessary checks --- osu.Game/Screens/Play/BreakOverlay.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 50b2c97f59..aceacdbab1 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -136,16 +136,11 @@ namespace osu.Game.Screens.Play return; } - if (Clock.CurrentTime > breaks[nearestBreakIndex].EndTime) - { - while (nearestBreakIndex < breaks.Count - 1 && Clock.CurrentTime > breaks[nearestBreakIndex].EndTime) - nearestBreakIndex++; - } - else - { - while (nearestBreakIndex > 0 && Clock.CurrentTime < breaks[nearestBreakIndex].StartTime) - nearestBreakIndex--; - } + while (nearestBreakIndex < breaks.Count - 1 && Clock.CurrentTime > breaks[nearestBreakIndex].EndTime) + nearestBreakIndex++; + + while (nearestBreakIndex > 0 && Clock.CurrentTime < breaks[nearestBreakIndex].StartTime) + nearestBreakIndex--; // This ensures that IsBreakTime is generally consistent with the overlay's transforms during a break. // If the overlay never shows (break.HasEffect is false), IsBreakTime should be false. From b4c93b1777646c59507bed626c0c79d2e5f4f82b Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 26 Jul 2019 05:11:01 +0300 Subject: [PATCH 0350/2815] Use lookup direction than 2 while loops --- osu.Game/Screens/Play/BreakOverlay.cs | 31 +++++++++++++++++++-------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index aceacdbab1..9091e54159 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -31,7 +31,9 @@ namespace osu.Game.Screens.Play set { breaks = value; - nearestBreakIndex = 0; + + // reset index in case the new breaks list is smaller than last one + currentBreakIndex = 0; initializeBreaks(); } @@ -44,7 +46,7 @@ namespace osu.Game.Screens.Play /// public IBindable IsBreakTime => isBreakTime; - private int nearestBreakIndex; + private int currentBreakIndex; private readonly BindableBool isBreakTime = new BindableBool(); private readonly Container remainingTimeAdjustmentBox; @@ -133,20 +135,31 @@ namespace osu.Game.Screens.Play if (breaks == null || !breaks.Any()) { isBreakTime.Value = false; + currentBreakIndex = 0; return; } - while (nearestBreakIndex < breaks.Count - 1 && Clock.CurrentTime > breaks[nearestBreakIndex].EndTime) - nearestBreakIndex++; + var lastIndex = currentBreakIndex; + var lookupDirection = Clock.CurrentTime > breaks[lastIndex].EndTime ? 1 : (Clock.CurrentTime < breaks[lastIndex].StartTime ? -1 : 0); - while (nearestBreakIndex > 0 && Clock.CurrentTime < breaks[nearestBreakIndex].StartTime) - nearestBreakIndex--; + while (Clock.CurrentTime < breaks[currentBreakIndex].StartTime || Clock.CurrentTime > breaks[currentBreakIndex].EndTime) + { + currentBreakIndex += lookupDirection; + + // restore index if out of bounds + if (currentBreakIndex < 0 || currentBreakIndex >= breaks.Count) + { + isBreakTime.Value = false; + currentBreakIndex = lastIndex; + return; + } + } // This ensures that IsBreakTime is generally consistent with the overlay's transforms during a break. - // If the overlay never shows (break.HasEffect is false), IsBreakTime should be false. + // If the current break doesn't have effects, IsBreakTime should be false. // We also assume that the overlay's fade out transform is "not break time". - var nearestBreak = breaks[nearestBreakIndex]; - isBreakTime.Value = nearestBreak.HasEffect && Clock.CurrentTime >= nearestBreak.StartTime && Clock.CurrentTime <= nearestBreak.EndTime - fade_duration; + var currentBreak = breaks[currentBreakIndex]; + isBreakTime.Value = currentBreak.HasEffect && Clock.CurrentTime <= currentBreak.EndTime - fade_duration; } private void initializeBreaks() From 44895c4b69743042b1cb0e70e6a8231ce7bc44c7 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 26 Jul 2019 05:41:10 +0300 Subject: [PATCH 0351/2815] Use IReadOnlyList for break periods list --- osu.Game/Screens/Play/BreakOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index d390787090..2b401778a6 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -19,11 +19,11 @@ namespace osu.Game.Screens.Play private const float remaining_time_container_max_size = 0.3f; private const int vertical_margin = 25; - private List breaks; - private readonly Container fadeContainer; - public List Breaks + private IReadOnlyList breaks; + + public IReadOnlyList Breaks { get => breaks; set From c89830f3d89cd1bd2baa0e1731d8f67bf1147b94 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 26 Jul 2019 13:07:28 +0900 Subject: [PATCH 0352/2815] move constants, combine local vars into properties --- osu.Game/Screens/Select/BeatmapCarousel.cs | 22 ++++++++++++---------- osu.Game/Screens/Select/FilterControl.cs | 2 ++ osu.Game/Screens/Select/Footer.cs | 4 ++++ osu.Game/Screens/Select/SongSelect.cs | 22 +++++++--------------- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index b917fc4bb4..44aebf73c9 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -26,6 +26,9 @@ namespace osu.Game.Screens.Select { public class BeatmapCarousel : OsuScrollContainer { + private const float bleed_top = FilterControl.HEIGHT; + private const float bleed_bottom = Footer.HEIGHT; + /// /// Triggered when the loaded change and are completely loaded. /// @@ -110,12 +113,10 @@ namespace osu.Game.Screens.Select root = new CarouselRoot(this); Child = new OsuContextMenuContainer { - Masking = false, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Child = scrollableContent = new Container { - Masking = false, RelativeSizeAxes = Axes.X, } }; @@ -342,6 +343,12 @@ namespace osu.Game.Screens.Select public bool AllowSelection = true; + /// + /// The total bounds of what is displayable in the beatmap carousel. + /// + /// + private float visibleHeight => DrawHeight + bleed_bottom + bleed_top; + public void FlushPendingFilterOperations() { if (PendingFilter?.Completed == false) @@ -428,18 +435,15 @@ namespace osu.Game.Screens.Select if (!scrollPositionCache.IsValid) updateScrollPosition(); - // The draw positions of individual sets extend beyond the size of the carousel and into the footer and header. - float visibleHeight = DrawHeight + SongSelect.FOOTER_HEIGHT + SongSelect.FILTER_CONTROL_HEIGHT; - // Remove all items that should no longer be on-screen scrollableContent.RemoveAll(p => p.Y < Current - p.DrawHeight || p.Y > Current + visibleHeight || !p.IsPresent); // Find index range of all items that should be on-screen Trace.Assert(Items.Count == yPositions.Count); - int firstIndex = yPositions.BinarySearch(Current - DrawableCarouselItem.MAX_HEIGHT - SongSelect.FILTER_CONTROL_HEIGHT); + int firstIndex = yPositions.BinarySearch(Current - DrawableCarouselItem.MAX_HEIGHT - bleed_top); if (firstIndex < 0) firstIndex = ~firstIndex; - int lastIndex = yPositions.BinarySearch(Current + visibleHeight + SongSelect.FOOTER_HEIGHT); + int lastIndex = yPositions.BinarySearch(Current + visibleHeight + bleed_bottom); if (lastIndex < 0) lastIndex = ~lastIndex; int notVisibleCount = 0; @@ -547,7 +551,6 @@ namespace osu.Game.Screens.Select yPositions.Clear(); - float visibleHeight = DrawHeight + SongSelect.FOOTER_HEIGHT + SongSelect.FILTER_CONTROL_HEIGHT; float currentY = visibleHeight / 2; DrawableCarouselBeatmapSet lastSet = null; @@ -648,8 +651,7 @@ namespace osu.Game.Screens.Select { var height = p.IsPresent ? p.DrawHeight : 0; - // The actual Y position of the item needs to be offset by any potential padding set by the container's parent. - float itemDrawY = p.Position.Y + SongSelect.FILTER_CONTROL_HEIGHT - Current + height / 2; + float itemDrawY = p.Position.Y + bleed_top - Current + height / 2; float dist = Math.Abs(1f - itemDrawY / halfHeight); // Setting the origin position serves as an additive position on top of potential diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 31b71a21c8..84e8e90f54 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -21,6 +21,8 @@ namespace osu.Game.Screens.Select { public class FilterControl : Container { + public const float HEIGHT = 100; + public Action FilterChanged; private readonly OsuTabControl sortTabs; diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs index 7db3d65a0b..0680711f1c 100644 --- a/osu.Game/Screens/Select/Footer.cs +++ b/osu.Game/Screens/Select/Footer.cs @@ -62,6 +62,10 @@ namespace osu.Game.Screens.Select public Footer() { + RelativeSizeAxes = Axes.X; + Height = HEIGHT; + Anchor = Anchor.BottomCentre; + Origin = Anchor.BottomCentre; Children = new Drawable[] { new Box diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index e5576f78cf..73848cc740 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -42,9 +42,7 @@ namespace osu.Game.Screens.Select public abstract class SongSelect : OsuScreen, IKeyBindingHandler { private static readonly Vector2 wedged_container_size = new Vector2(0.5f, 245); - - public const float FILTER_CONTROL_HEIGHT = 100; - public const float FOOTER_HEIGHT = 50; + protected const float BACKGROUND_BLUR = 20; private const float left_area_padding = 20; @@ -123,7 +121,7 @@ namespace osu.Game.Screens.Select Size = new Vector2(wedged_container_size.X, 1), Padding = new MarginPadding { - Bottom = FOOTER_HEIGHT, + Bottom = Footer.HEIGHT, Top = wedged_container_size.Y + left_area_padding, Left = left_area_padding, Right = left_area_padding * 2, @@ -154,8 +152,8 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { - Top = FILTER_CONTROL_HEIGHT, - Bottom = FOOTER_HEIGHT + Top = FilterControl.HEIGHT, + Bottom = Footer.HEIGHT }, Child = Carousel = new BeatmapCarousel { @@ -171,7 +169,7 @@ namespace osu.Game.Screens.Select FilterControl = new FilterControl { RelativeSizeAxes = Axes.X, - Height = FILTER_CONTROL_HEIGHT, + Height = FilterControl.HEIGHT, FilterChanged = c => Carousel.Filter(c), Background = { Width = 2 }, Exit = () => @@ -210,7 +208,7 @@ namespace osu.Game.Screens.Select Origin = Anchor.BottomLeft, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Bottom = FOOTER_HEIGHT }, + Margin = new MarginPadding { Bottom = Footer.HEIGHT }, Children = new Drawable[] { BeatmapOptions = new BeatmapOptionsOverlay(), @@ -222,13 +220,7 @@ namespace osu.Game.Screens.Select } } }, - Footer = new Footer - { - RelativeSizeAxes = Axes.X, - Height = FOOTER_HEIGHT, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - } + Footer = new Footer() }); } From 7ec6ac7b0eb15e88ed18e13889a57ca917e63738 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jul 2019 13:15:36 +0900 Subject: [PATCH 0353/2815] Remove unnecessary override --- osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs index f6533be551..7386bffb1a 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Online.Chat; @@ -40,7 +39,5 @@ namespace osu.Game.Overlays.Chat.Tabs Name = "+"; } } - - protected override bool OnMouseUp(MouseUpEvent e) => false; } } From 56b27db7a412a30547f9f9530fa4ccfee5864e25 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jul 2019 13:17:21 +0900 Subject: [PATCH 0354/2815] Use "Click" instead of "Action" --- osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs index 859ca30d7f..2a3dd55c71 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs @@ -141,13 +141,15 @@ namespace osu.Game.Overlays.Chat.Tabs protected override bool OnMouseUp(MouseUpEvent e) { - if (e.Button == MouseButton.Middle) + switch (e.Button) { - CloseButton.Action(); - return true; - } + case MouseButton.Middle: + CloseButton.Click(); + return true; - return false; + default: + return false; + } } [BackgroundDependencyLoader] From 8a54dab5d0e28d4aaa372c1f62a8a655629c5a4e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jul 2019 13:27:37 +0900 Subject: [PATCH 0355/2815] Tidy up code --- osu.iOS/AppDelegate.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/osu.iOS/AppDelegate.cs b/osu.iOS/AppDelegate.cs index 93eca8caa1..9ef21e014c 100644 --- a/osu.iOS/AppDelegate.cs +++ b/osu.iOS/AppDelegate.cs @@ -12,20 +12,13 @@ namespace osu.iOS [Register("AppDelegate")] public class AppDelegate : GameAppDelegate { - private OsuGameIOS IOSGame; + private OsuGameIOS game; - protected override Framework.Game CreateGame() - { - //Save OsuGameIOS for Import - IOSGame = new OsuGameIOS(); - return IOSGame; - } + protected override Framework.Game CreateGame() => game = new OsuGameIOS(); public override bool OpenUrl(UIApplication application, NSUrl url, string sourceApplication, NSObject annotation) { - //Open in Application - Task.Run(() => IOSGame.Import(url.Path)); - + Task.Run(() => game.Import(url.Path)); return true; } } From 53ecb2ae82acf42a4e36ff5283de641aa6442ed6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jul 2019 13:48:29 +0900 Subject: [PATCH 0356/2815] Reduce notification span during beatmap imports --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- osu.Game/OsuGame.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 65efcaa949..166ba5111c 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -386,7 +386,7 @@ namespace osu.Game.Beatmaps beatmap.OnlineBeatmapID = res.OnlineBeatmapID; }; - req.Failure += e => { LogForModel(set, $"Online retrieval failed for {beatmap}", e); }; + req.Failure += e => { LogForModel(set, $"Online retrieval failed for {beatmap} ({e.Message})"); }; // intentionally blocking to limit web request concurrency req.Perform(api); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 41b67f343a..ceaf0c3d5e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -589,7 +589,7 @@ namespace osu.Game { int recentLogCount = 0; - const double debounce = 5000; + const double debounce = 60000; Logger.NewEntry += entry => { From 91fa8a6552c4591ad78b891be086159aa556baf6 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 26 Jul 2019 08:09:18 +0300 Subject: [PATCH 0357/2815] Simplify null and any check --- osu.Game/Screens/Play/BreakOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 9091e54159..86a991c02e 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -132,7 +132,7 @@ namespace osu.Game.Screens.Play private void updateBreakTimeBindable() { - if (breaks == null || !breaks.Any()) + if (breaks?.Any() != true) { isBreakTime.Value = false; currentBreakIndex = 0; From 806d41daf499df87917abaf0c3dd8772ee557e75 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 26 Jul 2019 08:11:13 +0300 Subject: [PATCH 0358/2815] Add function to reset break index --- osu.Game/Screens/Play/BreakOverlay.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 86a991c02e..99361a5bd7 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play breaks = value; // reset index in case the new breaks list is smaller than last one - currentBreakIndex = 0; + resetBreakIndex(); initializeBreaks(); } @@ -130,12 +130,17 @@ namespace osu.Game.Screens.Play updateBreakTimeBindable(); } + private void resetBreakIndex() + { + isBreakTime.Value = false; + currentBreakIndex = 0; + } + private void updateBreakTimeBindable() { if (breaks?.Any() != true) { - isBreakTime.Value = false; - currentBreakIndex = 0; + resetBreakIndex(); return; } From 3fa6804501a0d417feb4a876160fc83731101140 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 26 Jul 2019 08:12:32 +0300 Subject: [PATCH 0359/2815] Use better loops for moving index Easy to read, suggested by peppy --- osu.Game/Screens/Play/BreakOverlay.cs | 31 +++++++++++++++++---------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 99361a5bd7..f3decb38fb 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -144,19 +144,28 @@ namespace osu.Game.Screens.Play return; } - var lastIndex = currentBreakIndex; - var lookupDirection = Clock.CurrentTime > breaks[lastIndex].EndTime ? 1 : (Clock.CurrentTime < breaks[lastIndex].StartTime ? -1 : 0); + var time = Clock.CurrentTime; - while (Clock.CurrentTime < breaks[currentBreakIndex].StartTime || Clock.CurrentTime > breaks[currentBreakIndex].EndTime) + if (time > breaks[currentBreakIndex].EndTime) { - currentBreakIndex += lookupDirection; - - // restore index if out of bounds - if (currentBreakIndex < 0 || currentBreakIndex >= breaks.Count) + for (int i = currentBreakIndex; i < breaks.Count; i++) { - isBreakTime.Value = false; - currentBreakIndex = lastIndex; - return; + if (time <= breaks[i].EndTime) + { + currentBreakIndex = i; + break; + } + } + } + else if (time < breaks[currentBreakIndex].StartTime) + { + for (int i = currentBreakIndex; i >= 0; i--) + { + if (time >= breaks[i].StartTime) + { + currentBreakIndex = i; + break; + } } } @@ -164,7 +173,7 @@ namespace osu.Game.Screens.Play // If the current break doesn't have effects, IsBreakTime should be false. // We also assume that the overlay's fade out transform is "not break time". var currentBreak = breaks[currentBreakIndex]; - isBreakTime.Value = currentBreak.HasEffect && Clock.CurrentTime <= currentBreak.EndTime - fade_duration; + isBreakTime.Value = currentBreak.HasEffect && time >= currentBreak.StartTime && time <= currentBreak.EndTime - fade_duration; } private void initializeBreaks() From 6fac716ec704f1c07436d57a2979e95f0b504966 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 26 Jul 2019 08:15:46 +0300 Subject: [PATCH 0360/2815] Use container instead of fill flow --- .../Visual/Gameplay/TestSceneBreakOverlay.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs index ffa822d175..85f69bf058 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs @@ -27,13 +27,17 @@ namespace osu.Game.Tests.Visual.Gameplay public TestSceneBreakOverlay() { SpriteText breakTimeText; - Child = new FillFlowContainer + Child = new Container { RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, Children = new Drawable[] { - breakTimeText = new SpriteText(), + breakTimeText = new SpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Bottom = 35 }, + }, breakOverlay = new BreakOverlay(true) } }; From 3b0a48274380ef9616dcc029c0e217f62b477f98 Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 25 Jul 2019 23:10:00 -0700 Subject: [PATCH 0361/2815] Fix font weight of leaderboard mod filter --- osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs index 8134cfb42d..d158186899 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs @@ -64,7 +64,7 @@ namespace osu.Game.Graphics.UserInterface Direction = FillDirection.Horizontal, Children = new Drawable[] { - text = new OsuSpriteText { Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold) }, + text = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) }, icon = new SpriteIcon { Size = new Vector2(14), @@ -84,7 +84,11 @@ namespace osu.Game.Graphics.UserInterface } }; - Current.ValueChanged += selected => { icon.Icon = selected.NewValue ? FontAwesome.Regular.CheckCircle : FontAwesome.Regular.Circle; }; + Current.ValueChanged += selected => + { + icon.Icon = selected.NewValue ? FontAwesome.Regular.CheckCircle : FontAwesome.Regular.Circle; + text.Font = text.Font.With(weight: selected.NewValue ? FontWeight.Bold : FontWeight.Medium); + }; } [BackgroundDependencyLoader] From 6765e9f7fa6a6d0dbfb75aa2e012ac18eef3e99c Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 26 Jul 2019 15:13:10 +0900 Subject: [PATCH 0362/2815] Combine into properties and update for framework changes --- osu.Game/Screens/Select/BeatmapCarousel.cs | 32 ++++++++++++++-------- osu.Game/Screens/Select/SongSelect.cs | 2 +- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 44aebf73c9..c8746579b5 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -56,8 +56,6 @@ namespace osu.Game.Screens.Select public override bool HandleNonPositionalInput => AllowSelection; public override bool HandlePositionalInput => AllowSelection; - protected override bool ConfinePositionalInput => true; - /// /// Whether carousel items have completed asynchronously loaded. /// @@ -344,11 +342,24 @@ namespace osu.Game.Screens.Select public bool AllowSelection = true; /// - /// The total bounds of what is displayable in the beatmap carousel. - /// + /// The total height of the displayable portion of the Beatmap Carousel. + /// + /// This is different from , since + /// the beatmap carousel bleeds into the and the + /// /// private float visibleHeight => DrawHeight + bleed_bottom + bleed_top; + /// + /// The position of the lower visible bound with respect to the current scroll position. + /// + private float visibleBottomBound => Current + DrawHeight + bleed_bottom; + + /// + /// The position of the upper visible bound with respect to the current scroll position. + /// + private float visibleUpperBound => Current - bleed_top; + public void FlushPendingFilterOperations() { if (PendingFilter?.Completed == false) @@ -425,6 +436,8 @@ namespace osu.Game.Screens.Select return true; } + protected override bool ReceiveSubTreePositionalInputAt(Vector2 screenSpacePos) => ReceivePositionalInputAt(screenSpacePos); + protected override void Update() { base.Update(); @@ -436,14 +449,14 @@ namespace osu.Game.Screens.Select updateScrollPosition(); // Remove all items that should no longer be on-screen - scrollableContent.RemoveAll(p => p.Y < Current - p.DrawHeight || p.Y > Current + visibleHeight || !p.IsPresent); + scrollableContent.RemoveAll(p => p.Y < visibleUpperBound - p.DrawHeight || p.Y > visibleBottomBound || !p.IsPresent); // Find index range of all items that should be on-screen Trace.Assert(Items.Count == yPositions.Count); - int firstIndex = yPositions.BinarySearch(Current - DrawableCarouselItem.MAX_HEIGHT - bleed_top); + int firstIndex = yPositions.BinarySearch(visibleUpperBound - DrawableCarouselItem.MAX_HEIGHT); if (firstIndex < 0) firstIndex = ~firstIndex; - int lastIndex = yPositions.BinarySearch(Current + visibleHeight + bleed_bottom); + int lastIndex = yPositions.BinarySearch(visibleBottomBound); if (lastIndex < 0) lastIndex = ~lastIndex; int notVisibleCount = 0; @@ -584,7 +597,6 @@ namespace osu.Game.Screens.Select float? setY = null; if (!d.IsLoaded || beatmap.Alpha == 0) // can't use IsPresent due to DrawableCarouselItem override. - // ReSharper disable once PossibleNullReferenceException (resharper broken?) setY = lastSet.Y + lastSet.DrawHeight + 5; if (d.IsLoaded) @@ -649,9 +661,7 @@ namespace osu.Game.Screens.Select /// Half the draw height of the carousel container's parent. private void updateItem(DrawableCarouselItem p, float halfHeight) { - var height = p.IsPresent ? p.DrawHeight : 0; - - float itemDrawY = p.Position.Y + bleed_top - Current + height / 2; + float itemDrawY = p.Position.Y - visibleUpperBound + p.DrawHeight / 2; float dist = Math.Abs(1f - itemDrawY / halfHeight); // Setting the origin position serves as an additive position on top of potential diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 73848cc740..7dd934f91a 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Select public abstract class SongSelect : OsuScreen, IKeyBindingHandler { private static readonly Vector2 wedged_container_size = new Vector2(0.5f, 245); - + protected const float BACKGROUND_BLUR = 20; private const float left_area_padding = 20; From 5a94a223147b4e774452154b41ed2c4794d30615 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 26 Jul 2019 09:17:39 +0300 Subject: [PATCH 0363/2815] Add a quick check if we're not in a break with current index --- osu.Game/Screens/Play/BreakOverlay.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index f3decb38fb..8f07b90b30 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -146,6 +146,16 @@ namespace osu.Game.Screens.Play var time = Clock.CurrentTime; + if (currentBreakIndex < breaks.Count - 1) + { + // Quick check if we're not in a break with our current index. + if (time > breaks[currentBreakIndex].EndTime && time < breaks[currentBreakIndex + 1].StartTime) + { + isBreakTime.Value = false; + return; + } + } + if (time > breaks[currentBreakIndex].EndTime) { for (int i = currentBreakIndex; i < breaks.Count; i++) From 0b6cfec21cd0463617158abddc1c65f67c73cc75 Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 25 Jul 2019 23:20:56 -0700 Subject: [PATCH 0364/2815] Hide leaderboard mod filter when on details tab --- osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs index f66cd2b29a..f632a7f979 100644 --- a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs +++ b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs @@ -28,6 +28,8 @@ namespace osu.Game.Screens.Select private void invokeOnFilter() { OnFilter?.Invoke(tabs.Current.Value, modsCheckbox.Current.Value); + + modsCheckbox.FadeTo(tabs.Current.Value == BeatmapDetailTab.Details ? 0 : 1, 200, Easing.OutQuint); } [BackgroundDependencyLoader] From 0f6c6c7de0b981ab517e35b3f1a1f2c9aae31315 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 26 Jul 2019 15:22:29 +0900 Subject: [PATCH 0365/2815] consolidate halfheight as well --- osu.Game/Screens/Select/BeatmapCarousel.cs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c8746579b5..cbe54cce1a 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -342,13 +342,13 @@ namespace osu.Game.Screens.Select public bool AllowSelection = true; /// - /// The total height of the displayable portion of the Beatmap Carousel. + /// Half the height of the visible content. /// - /// This is different from , since + /// This is different from the height of , since /// the beatmap carousel bleeds into the and the /// /// - private float visibleHeight => DrawHeight + bleed_bottom + bleed_top; + private float visibleHalfHeight => (DrawHeight + bleed_bottom + bleed_top) / 2; /// /// The position of the lower visible bound with respect to the current scroll position. @@ -508,9 +508,8 @@ namespace osu.Game.Screens.Select // Update externally controlled state of currently visible items // (e.g. x-offset and opacity). - float halfHeight = visibleHeight / 2; foreach (DrawableCarouselItem p in scrollableContent.Children) - updateItem(p, halfHeight); + updateItem(p); } protected override void Dispose(bool isDisposing) @@ -564,7 +563,7 @@ namespace osu.Game.Screens.Select yPositions.Clear(); - float currentY = visibleHeight / 2; + float currentY = visibleHalfHeight; DrawableCarouselBeatmapSet lastSet = null; scrollTarget = null; @@ -617,7 +616,7 @@ namespace osu.Game.Screens.Select currentY += d.DrawHeight + 5; } - currentY += visibleHeight / 2; + currentY += visibleHalfHeight; scrollableContent.Height = currentY; if (BeatmapSetsLoaded && (selectedBeatmapSet == null || selectedBeatmap == null || selectedBeatmapSet.State.Value != CarouselItemState.Selected)) @@ -658,16 +657,15 @@ namespace osu.Game.Screens.Select /// the current scroll position. /// /// The item to be updated. - /// Half the draw height of the carousel container's parent. - private void updateItem(DrawableCarouselItem p, float halfHeight) + private void updateItem(DrawableCarouselItem p) { float itemDrawY = p.Position.Y - visibleUpperBound + p.DrawHeight / 2; - float dist = Math.Abs(1f - itemDrawY / halfHeight); + float dist = Math.Abs(1f - itemDrawY / visibleHalfHeight); // Setting the origin position serves as an additive position on top of potential // local transformation we may want to apply (e.g. when a item gets selected, we // may want to smoothly transform it leftwards.) - p.OriginPosition = new Vector2(-offsetX(dist, halfHeight), 0); + p.OriginPosition = new Vector2(-offsetX(dist, visibleHalfHeight), 0); // We are applying a multiplicative alpha (which is internally done by nesting an // additional container and setting that container's alpha) such that we can From 4c9e8527d86b57c65774b89c7db6ae552c9ae64b Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 26 Jul 2019 09:24:53 +0300 Subject: [PATCH 0366/2815] Modify global index directly in the for loop Moves the global index to a near break if not in a break yet --- osu.Game/Screens/Play/BreakOverlay.cs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 8f07b90b30..005cdd36d7 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -158,25 +158,15 @@ namespace osu.Game.Screens.Play if (time > breaks[currentBreakIndex].EndTime) { - for (int i = currentBreakIndex; i < breaks.Count; i++) - { - if (time <= breaks[i].EndTime) - { - currentBreakIndex = i; + for (; currentBreakIndex < breaks.Count; currentBreakIndex++) + if (time <= breaks[currentBreakIndex].EndTime) break; - } - } } else if (time < breaks[currentBreakIndex].StartTime) { - for (int i = currentBreakIndex; i >= 0; i--) - { - if (time >= breaks[i].StartTime) - { - currentBreakIndex = i; + for (; currentBreakIndex >= 0; currentBreakIndex--) + if (time >= breaks[currentBreakIndex].StartTime) break; - } - } } // This ensures that IsBreakTime is generally consistent with the overlay's transforms during a break. From 7fa419a38b0e47e9fb85b946e1a32042cb07f033 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jul 2019 15:49:21 +0900 Subject: [PATCH 0367/2815] Fix file layout order --- .../Select/BeatmapDetailAreaTabControl.cs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs index f632a7f979..ea466c2370 100644 --- a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs +++ b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs @@ -25,24 +25,6 @@ namespace osu.Game.Screens.Select private Bindable selectedTab; - private void invokeOnFilter() - { - OnFilter?.Invoke(tabs.Current.Value, modsCheckbox.Current.Value); - - modsCheckbox.FadeTo(tabs.Current.Value == BeatmapDetailTab.Details ? 0 : 1, 200, Easing.OutQuint); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colour, OsuConfigManager config) - { - modsCheckbox.AccentColour = tabs.AccentColour = colour.YellowLight; - - selectedTab = config.GetBindable(OsuSetting.BeatmapDetailTab); - - tabs.Current.BindTo(selectedTab); - tabs.Current.TriggerChange(); - } - public BeatmapDetailAreaTabControl() { Height = HEIGHT; @@ -74,6 +56,24 @@ namespace osu.Game.Screens.Select tabs.Current.ValueChanged += _ => invokeOnFilter(); modsCheckbox.Current.ValueChanged += _ => invokeOnFilter(); } + + [BackgroundDependencyLoader] + private void load(OsuColour colour, OsuConfigManager config) + { + modsCheckbox.AccentColour = tabs.AccentColour = colour.YellowLight; + + selectedTab = config.GetBindable(OsuSetting.BeatmapDetailTab); + + tabs.Current.BindTo(selectedTab); + tabs.Current.TriggerChange(); + } + + private void invokeOnFilter() + { + OnFilter?.Invoke(tabs.Current.Value, modsCheckbox.Current.Value); + + modsCheckbox.FadeTo(tabs.Current.Value == BeatmapDetailTab.Details ? 0 : 1, 200, Easing.OutQuint); + } } public enum BeatmapDetailTab From 4d49aad1538c5de6b245d1a1eb72a39b9f847059 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jul 2019 15:51:51 +0900 Subject: [PATCH 0368/2815] Start not visible --- osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs index ea466c2370..7f82d3cc12 100644 --- a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs +++ b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs @@ -50,6 +50,7 @@ namespace osu.Game.Screens.Select Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, Text = @"Mods", + Alpha = 0, }, }; From 9d080ec249bbb2237cd649ca168ad25f16ede974 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jul 2019 17:18:56 +0900 Subject: [PATCH 0369/2815] 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 b24493665e..385d630823 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -63,6 +63,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c05cc6f9dd..71de5ac17f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -15,7 +15,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 3b18039600..95ae4021e8 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From 9ef858806b9d8cde669446abb7695efa042b9993 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jul 2019 17:48:19 +0900 Subject: [PATCH 0370/2815] Fix existing usage of Path --- osu.Game/Graphics/UserInterface/LineGraph.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/LineGraph.cs b/osu.Game/Graphics/UserInterface/LineGraph.cs index 757a9a349c..714e953816 100644 --- a/osu.Game/Graphics/UserInterface/LineGraph.cs +++ b/osu.Game/Graphics/UserInterface/LineGraph.cs @@ -76,7 +76,12 @@ namespace osu.Game.Graphics.UserInterface { Masking = true, RelativeSizeAxes = Axes.Both, - Child = path = new SmoothPath { RelativeSizeAxes = Axes.Both, PathRadius = 1 } + Child = path = new SmoothPath + { + AutoSizeAxes = Axes.None, + RelativeSizeAxes = Axes.Both, + PathRadius = 1 + } }); } From 5317a8fa0f3cf0e537aacf3be0cde9c0c79bed08 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jul 2019 17:50:49 +0900 Subject: [PATCH 0371/2815] Update framework again --- 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 385d630823..0dd3c98116 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -63,6 +63,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 71de5ac17f..9f405d1099 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -15,7 +15,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 95ae4021e8..4e8ce18c6f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From 6e09d857fd3d2ed0b18737e6565836fb6ea5bdf7 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 26 Jul 2019 19:00:07 +0900 Subject: [PATCH 0372/2815] Fix ValueChanged events being called out of order --- osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 5606328575..5c98c72b75 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -53,7 +53,13 @@ namespace osu.Game.Graphics.Containers samplePopIn = audio.Samples.Get(@"UI/overlay-pop-in"); samplePopOut = audio.Samples.Get(@"UI/overlay-pop-out"); + } + protected override void LoadComplete() + { + base.LoadComplete(); + + // This must be added after the base LoadComplete. The overlay may need to be hidden immediately if its disabled. State.ValueChanged += onStateChanged; } From 1b0f7b0459c28ff4684b580e2104b69978ddea58 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 26 Jul 2019 19:05:55 +0900 Subject: [PATCH 0373/2815] more detailed explanation --- osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 5c98c72b75..158f96b46b 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -59,7 +59,8 @@ namespace osu.Game.Graphics.Containers { base.LoadComplete(); - // This must be added after the base LoadComplete. The overlay may need to be hidden immediately if its disabled. + // This must be added after the base LoadComplete. The overlay may need to be hidden immediately if its disabled, + // but the overlay doesn't get shown until after the stateChanged function from VisibilityContainer gets called. State.ValueChanged += onStateChanged; } From 699e366306c20050c91c5f639439a1a1afbeaaea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jul 2019 20:29:57 +0900 Subject: [PATCH 0374/2815] 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 0dd3c98116..6744590f0d 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -63,6 +63,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 9f405d1099..0b2baa982b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -15,7 +15,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 4e8ce18c6f..55d1afa645 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From ba4045a761e790097d5015d6dd174bf4fdc8ba3a Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sat, 27 Jul 2019 00:22:40 +0300 Subject: [PATCH 0375/2815] Fix transforming mods not working properly Hidden, Grow, Deflate, etc.. --- .../Rulesets/Objects/Drawables/DrawableHitObject.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 1d9d885527..181ae37a8b 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -147,12 +147,6 @@ namespace osu.Game.Rulesets.Objects.Drawables if (State.Value == newState && !force) return; - // apply any custom state overrides - ApplyCustomUpdateState?.Invoke(this, newState); - - if (newState == ArmedState.Hit) - PlaySamples(); - if (UseTransformStateManagement) { double transformTime = HitObject.StartTime - InitialLifetimeOffset; @@ -177,6 +171,12 @@ namespace osu.Game.Rulesets.Objects.Drawables state.Value = newState; UpdateState(newState); + + // apply any custom state overrides + ApplyCustomUpdateState?.Invoke(this, newState); + + if (newState == ArmedState.Hit) + PlaySamples(); } ///
From 3571cb96b0b923405beeccfd4f306f55f3be7b6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 27 Jul 2019 12:56:55 +0900 Subject: [PATCH 0376/2815] Fix broken merge --- 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 40079f01e4..cf88b697d9 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -434,7 +434,7 @@ namespace osu.Game.Screens.Select return true; } - protected override bool ReceiveSubTreePositionalInputAt(Vector2 screenSpacePos) => ReceivePositionalInputAt(screenSpacePos); + protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => ReceivePositionalInputAt(screenSpacePos); protected override void Update() { From 56dbd94d167d76ca82ba64afa5ae1859880ce4d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 27 Jul 2019 19:46:46 +0900 Subject: [PATCH 0377/2815] Adjust tournament map pool layout to allow for larger pools --- osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index 46d4cfa98c..d32c0d6156 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -47,8 +47,8 @@ namespace osu.Game.Tournament.Screens.MapPool mapFlows = new FillFlowContainer> { Y = 100, - Spacing = new Vector2(10, 20), - Padding = new MarginPadding(50), + Spacing = new Vector2(10, 10), + Padding = new MarginPadding(25), Direction = FillDirection.Vertical, RelativeSizeAxes = Axes.Both, }, @@ -218,7 +218,7 @@ namespace osu.Game.Tournament.Screens.MapPool { mapFlows.Add(currentFlow = new FillFlowContainer { - Spacing = new Vector2(10, 20), + Spacing = new Vector2(10, 5), Direction = FillDirection.Full, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y From 46f17885c6378aaaeb19e7cd0043629cefa494ff Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sat, 27 Jul 2019 15:51:14 +0300 Subject: [PATCH 0378/2815] Rewrite break overlay test --- .../Visual/Gameplay/TestSceneBreakOverlay.cs | 180 +++++++++++------- 1 file changed, 107 insertions(+), 73 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs index 85f69bf058..bbebd62b26 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs @@ -3,11 +3,10 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; -using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; +using osu.Framework.Timing; using osu.Game.Beatmaps.Timing; using osu.Game.Screens.Play; @@ -21,96 +20,131 @@ namespace osu.Game.Tests.Visual.Gameplay typeof(BreakOverlay), }; - private readonly BreakOverlay breakOverlay; - private readonly IBindable isBreakTimeBindable = new BindableBool(); + private readonly BreakOverlay breakOverlay, manualBreakOverlay; + private readonly ManualClock manualClock = new ManualClock(); + + private readonly IReadOnlyList testBreaks = new List + { + new BreakPeriod + { + StartTime = 1000, + EndTime = 5000, + }, + new BreakPeriod + { + StartTime = 6000, + EndTime = 13500, + }, + }; public TestSceneBreakOverlay() { - SpriteText breakTimeText; - Child = new Container + Add(breakOverlay = new BreakOverlay(true)); + Add(manualBreakOverlay = new BreakOverlay(true) { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - breakTimeText = new SpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Margin = new MarginPadding { Bottom = 35 }, - }, - breakOverlay = new BreakOverlay(true) - } - }; - - isBreakTimeBindable.BindTo(breakOverlay.IsBreakTime); - isBreakTimeBindable.BindValueChanged(e => breakTimeText.Text = $"IsBreakTime: {e.NewValue}", true); - - AddStep("2s break", () => startBreak(2000)); - AddStep("5s break", () => startBreak(5000)); - AddStep("10s break", () => startBreak(10000)); - AddStep("15s break", () => startBreak(15000)); - AddStep("2s, 2s", startMultipleBreaks); - AddStep("0.5s, 0.7s, 1s, 2s", startAnotherMultipleBreaks); + Alpha = 0, + Clock = new FramedClock(manualClock), + }); } - private void startBreak(double duration) + [Test] + public void TestShowBreaks() { - breakOverlay.Breaks = new List + loadClockStep(false); + + addShowBreakStep(2); + addShowBreakStep(5); + addShowBreakStep(15); + } + + [Test] + public void TestNoEffectsBreak() + { + var shortBreak = new BreakPeriod { EndTime = 500 }; + + loadClockStep(true); + AddStep("start short break", () => manualBreakOverlay.Breaks = new[] { shortBreak }); + + seekBreakStep("seek back to 0", 0, false); + addBreakSeeks(shortBreak, false); + } + + [Test] + public void TestMultipleBreaks() + { + loadClockStep(true); + AddStep("start multiple breaks", () => manualBreakOverlay.Breaks = testBreaks); + + seekBreakStep("seek back to 0", 0, false); + foreach (var b in testBreaks) + addBreakSeeks(b, false); + } + + [Test] + public void TestRewindBreaks() + { + loadClockStep(true); + AddStep("start multiple breaks in rewind", () => manualBreakOverlay.Breaks = testBreaks); + + seekBreakStep("seek back to 0", 0, false); + foreach (var b in testBreaks.Reverse()) + addBreakSeeks(b, true); + } + + [Test] + public void TestSkipBreaks() + { + loadClockStep(true); + AddStep("start multiple breaks with skipping", () => manualBreakOverlay.Breaks = testBreaks); + + var b = testBreaks.Last(); + seekBreakStep("seek back to 0", 0, false); + addBreakSeeks(b, false); + } + + private void addShowBreakStep(double seconds) + { + AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = new List { new BreakPeriod { StartTime = Clock.CurrentTime, - EndTime = Clock.CurrentTime + duration, + EndTime = Clock.CurrentTime + seconds * 1000, } - }; + }); } - private void startMultipleBreaks() + private void loadClockStep(bool loadManual) { - double currentTime = Clock.CurrentTime; - - breakOverlay.Breaks = new List + AddStep($"load {(loadManual ? "manual" : "normal")} clock", () => { - new BreakPeriod - { - StartTime = currentTime, - EndTime = currentTime + 2000, - }, - new BreakPeriod - { - StartTime = currentTime + 4000, - EndTime = currentTime + 6000, - } - }; + breakOverlay.FadeTo(loadManual ? 0 : 1); + manualBreakOverlay.FadeTo(loadManual ? 1 : 0); + }); } - private void startAnotherMultipleBreaks() + private void addBreakSeeks(BreakPeriod b, bool isReversed) { - double currentTime = Clock.CurrentTime; - - breakOverlay.Breaks = new List + if (isReversed) { - new BreakPeriod // Duration is less than 650 - too short to appear - { - StartTime = currentTime, - EndTime = currentTime + 500, - }, - new BreakPeriod - { - StartTime = currentTime + 1500, - EndTime = currentTime + 2200, - }, - new BreakPeriod - { - StartTime = currentTime + 3200, - EndTime = currentTime + 4200, - }, - new BreakPeriod - { - StartTime = currentTime + 5200, - EndTime = currentTime + 7200, - } - }; + seekBreakStep("seek to break after end", b.EndTime + 500, false); + seekBreakStep("seek to break end", b.EndTime, false); + seekBreakStep("seek to break middle", b.StartTime + b.Duration / 2, b.HasEffect); + seekBreakStep("seek to break start", b.StartTime, b.HasEffect); + } + else + { + seekBreakStep("seek to break start", b.StartTime, b.HasEffect); + seekBreakStep("seek to break middle", b.StartTime + b.Duration / 2, b.HasEffect); + seekBreakStep("seek to break end", b.EndTime, false); + seekBreakStep("seek to break after end", b.EndTime + 500, false); + } + } + + private void seekBreakStep(string seekStepDescription, double time, bool onBreak) + { + AddStep(seekStepDescription, () => manualClock.CurrentTime = time); + AddAssert($"is{(!onBreak ? " not " : " ")}break time", () => manualBreakOverlay.IsBreakTime.Value == onBreak); } } } From 6c580ac9d5c4456bc91a1f8a51f4d92457167669 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sat, 27 Jul 2019 15:52:01 +0300 Subject: [PATCH 0379/2815] Use while loops instead --- osu.Game/Screens/Play/BreakOverlay.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index f96c176104..532109d14b 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -158,15 +158,13 @@ namespace osu.Game.Screens.Play if (time > breaks[currentBreakIndex].EndTime) { - for (; currentBreakIndex < breaks.Count; currentBreakIndex++) - if (time <= breaks[currentBreakIndex].EndTime) - break; + while (time > breaks[currentBreakIndex].EndTime && currentBreakIndex < breaks.Count - 1) + currentBreakIndex++; } else if (time < breaks[currentBreakIndex].StartTime) { - for (; currentBreakIndex >= 0; currentBreakIndex--) - if (time >= breaks[currentBreakIndex].StartTime) - break; + while (time < breaks[currentBreakIndex].StartTime && currentBreakIndex > 0) + currentBreakIndex--; } // This ensures that IsBreakTime is generally consistent with the overlay's transforms during a break. From 95b568eb4646c45b2169899d599114f86c350865 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sat, 27 Jul 2019 15:52:30 +0300 Subject: [PATCH 0380/2815] Remove unnecessary condition --- osu.Game/Screens/Play/BreakOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 532109d14b..7e87c44259 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -161,7 +161,7 @@ namespace osu.Game.Screens.Play while (time > breaks[currentBreakIndex].EndTime && currentBreakIndex < breaks.Count - 1) currentBreakIndex++; } - else if (time < breaks[currentBreakIndex].StartTime) + else { while (time < breaks[currentBreakIndex].StartTime && currentBreakIndex > 0) currentBreakIndex--; From edf6453e04dfe28e092ceebe3f058e573f374ec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Sat, 27 Jul 2019 18:56:37 +0200 Subject: [PATCH 0381/2815] truncate long usernames in private chat --- osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs index 9e87bae864..4f93beee17 100644 --- a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs @@ -65,6 +65,12 @@ namespace osu.Game.Overlays.Chat.Tabs Text.X = ChatOverlay.TAB_AREA_HEIGHT; TextBold.X = ChatOverlay.TAB_AREA_HEIGHT; + + Text.Width = 100f; + TextBold.Width = 100f; + + Text.Truncate = true; + TextBold.Truncate = true; } protected override bool ShowCloseOnHover => false; From 4d49b38277ec35df3723eac519e6cba80f681e1a Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 28 Jul 2019 05:21:27 +0300 Subject: [PATCH 0382/2815] Add a function for loading a list of breaks --- .../Visual/Gameplay/TestSceneBreakOverlay.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs index bbebd62b26..c238f42b60 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs @@ -63,9 +63,8 @@ namespace osu.Game.Tests.Visual.Gameplay var shortBreak = new BreakPeriod { EndTime = 500 }; loadClockStep(true); - AddStep("start short break", () => manualBreakOverlay.Breaks = new[] { shortBreak }); + loadBreaksStep("short break", new[] { shortBreak }); - seekBreakStep("seek back to 0", 0, false); addBreakSeeks(shortBreak, false); } @@ -73,9 +72,8 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestMultipleBreaks() { loadClockStep(true); - AddStep("start multiple breaks", () => manualBreakOverlay.Breaks = testBreaks); + loadBreaksStep("multiple breaks", testBreaks); - seekBreakStep("seek back to 0", 0, false); foreach (var b in testBreaks) addBreakSeeks(b, false); } @@ -84,9 +82,8 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestRewindBreaks() { loadClockStep(true); - AddStep("start multiple breaks in rewind", () => manualBreakOverlay.Breaks = testBreaks); + loadBreaksStep("multiple breaks", testBreaks); - seekBreakStep("seek back to 0", 0, false); foreach (var b in testBreaks.Reverse()) addBreakSeeks(b, true); } @@ -95,10 +92,9 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestSkipBreaks() { loadClockStep(true); - AddStep("start multiple breaks with skipping", () => manualBreakOverlay.Breaks = testBreaks); + loadBreaksStep("multiple breaks", testBreaks); var b = testBreaks.Last(); - seekBreakStep("seek back to 0", 0, false); addBreakSeeks(b, false); } @@ -123,6 +119,12 @@ namespace osu.Game.Tests.Visual.Gameplay }); } + private void loadBreaksStep(string breakDescription, IReadOnlyList breaks) + { + AddStep($"load {breakDescription}", () => manualBreakOverlay.Breaks = breaks); + seekBreakStep("seek back to 0", 0, false); + } + private void addBreakSeeks(BreakPeriod b, bool isReversed) { if (isReversed) From 4143bafd67ac14e61b98855797a96bec2fac4647 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 28 Jul 2019 05:22:09 +0300 Subject: [PATCH 0383/2815] More simplifies --- osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs index c238f42b60..d2db92c855 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Gameplay }; private readonly BreakOverlay breakOverlay, manualBreakOverlay; - private readonly ManualClock manualClock = new ManualClock(); + private readonly ManualClock manualClock; private readonly IReadOnlyList testBreaks = new List { @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Gameplay Add(manualBreakOverlay = new BreakOverlay(true) { Alpha = 0, - Clock = new FramedClock(manualClock), + Clock = new FramedClock(manualClock = new ManualClock()), }); } @@ -94,8 +94,7 @@ namespace osu.Game.Tests.Visual.Gameplay loadClockStep(true); loadBreaksStep("multiple breaks", testBreaks); - var b = testBreaks.Last(); - addBreakSeeks(b, false); + addBreakSeeks(testBreaks.Last(), false); } private void addShowBreakStep(double seconds) From bd2fce4bb7997dde45486bd0ff2cbecbcdce6826 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Sun, 28 Jul 2019 13:45:54 +0900 Subject: [PATCH 0384/2815] don't use extra container --- osu.Game/OsuGame.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e71344738b..e5099f3c2f 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -416,16 +416,9 @@ namespace osu.Game screenStack.Exit(); } }, - new Container - { - RelativeSizeAxes = Axes.Both, - Children = new[] - { - screenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }, - backButton.CreateProxy(), - logoContainer = new Container { RelativeSizeAxes = Axes.Both }, - } - } + screenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }, + backButton.CreateProxy(), + logoContainer = new Container { RelativeSizeAxes = Axes.Both }, } }, overlayContent = new Container { RelativeSizeAxes = Axes.Both }, From 1dd3a6630082b0e0b88b43be089f471b54d19aec Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 28 Jul 2019 09:16:19 +0300 Subject: [PATCH 0385/2815] Remove unnecessary index resets --- osu.Game/Screens/Play/BreakOverlay.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 7e87c44259..726c825c84 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -33,7 +33,8 @@ namespace osu.Game.Screens.Play breaks = value; // reset index in case the new breaks list is smaller than last one - resetBreakIndex(); + isBreakTime.Value = false; + currentBreakIndex = 0; initializeBreaks(); } @@ -126,23 +127,13 @@ namespace osu.Game.Screens.Play protected override void Update() { base.Update(); - updateBreakTimeBindable(); } - private void resetBreakIndex() - { - isBreakTime.Value = false; - currentBreakIndex = 0; - } - private void updateBreakTimeBindable() { if (breaks?.Any() != true) - { - resetBreakIndex(); return; - } var time = Clock.CurrentTime; From 5bf0277fd401b5d668bd8939ba7a41264a1ca6c9 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 28 Jul 2019 09:17:13 +0300 Subject: [PATCH 0386/2815] Remove unnecessary quick check Not saving for anything --- osu.Game/Screens/Play/BreakOverlay.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 726c825c84..e3e4014eb3 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -137,16 +137,6 @@ namespace osu.Game.Screens.Play var time = Clock.CurrentTime; - if (currentBreakIndex < breaks.Count - 1) - { - // Quick check if we're not in a break with our current index. - if (time > breaks[currentBreakIndex].EndTime && time < breaks[currentBreakIndex + 1].StartTime) - { - isBreakTime.Value = false; - return; - } - } - if (time > breaks[currentBreakIndex].EndTime) { while (time > breaks[currentBreakIndex].EndTime && currentBreakIndex < breaks.Count - 1) From 37c32659429787f33f3a78005142d5803feb1ac8 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 28 Jul 2019 09:21:54 +0300 Subject: [PATCH 0387/2815] Manually call the update function on retrieving IsBreakTime value --- .../Visual/Gameplay/TestSceneBreakOverlay.cs | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs index d2db92c855..890c575eb6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Graphics; +using osu.Framework.Bindables; using osu.Framework.Timing; using osu.Game.Beatmaps.Timing; using osu.Game.Screens.Play; @@ -20,8 +20,9 @@ namespace osu.Game.Tests.Visual.Gameplay typeof(BreakOverlay), }; - private readonly BreakOverlay breakOverlay, manualBreakOverlay; private readonly ManualClock manualClock; + private readonly BreakOverlay breakOverlay; + private readonly TestBreakOverlay manualBreakOverlay; private readonly IReadOnlyList testBreaks = new List { @@ -40,7 +41,7 @@ namespace osu.Game.Tests.Visual.Gameplay public TestSceneBreakOverlay() { Add(breakOverlay = new BreakOverlay(true)); - Add(manualBreakOverlay = new BreakOverlay(true) + Add(manualBreakOverlay = new TestBreakOverlay(true) { Alpha = 0, Clock = new FramedClock(manualClock = new ManualClock()), @@ -147,5 +148,24 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep(seekStepDescription, () => manualClock.CurrentTime = time); AddAssert($"is{(!onBreak ? " not " : " ")}break time", () => manualBreakOverlay.IsBreakTime.Value == onBreak); } + + private class TestBreakOverlay : BreakOverlay + { + public new IBindable IsBreakTime + { + get + { + // Manually call the update function as it might take up to 2 frames for an automatic update to happen + Update(); + + return base.IsBreakTime; + } + } + + public TestBreakOverlay(bool letterboxing) + : base(letterboxing) + { + } + } } } From c9e45f8cdc2e5b26e6d099e21bf2e7d731fa2dab Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 28 Jul 2019 09:27:02 +0300 Subject: [PATCH 0388/2815] Assign clocks instead of creating 2 separate overlays --- .../Visual/Gameplay/TestSceneBreakOverlay.cs | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs index 890c575eb6..eaae647dbc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs @@ -20,9 +20,7 @@ namespace osu.Game.Tests.Visual.Gameplay typeof(BreakOverlay), }; - private readonly ManualClock manualClock; - private readonly BreakOverlay breakOverlay; - private readonly TestBreakOverlay manualBreakOverlay; + private readonly TestBreakOverlay breakOverlay; private readonly IReadOnlyList testBreaks = new List { @@ -40,12 +38,7 @@ namespace osu.Game.Tests.Visual.Gameplay public TestSceneBreakOverlay() { - Add(breakOverlay = new BreakOverlay(true)); - Add(manualBreakOverlay = new TestBreakOverlay(true) - { - Alpha = 0, - Clock = new FramedClock(manualClock = new ManualClock()), - }); + Add(breakOverlay = new TestBreakOverlay(true)); } [Test] @@ -112,16 +105,12 @@ namespace osu.Game.Tests.Visual.Gameplay private void loadClockStep(bool loadManual) { - AddStep($"load {(loadManual ? "manual" : "normal")} clock", () => - { - breakOverlay.FadeTo(loadManual ? 0 : 1); - manualBreakOverlay.FadeTo(loadManual ? 1 : 0); - }); + AddStep($"load {(loadManual ? "manual" : "normal")} clock", () => breakOverlay.SwitchClock(loadManual)); } private void loadBreaksStep(string breakDescription, IReadOnlyList breaks) { - AddStep($"load {breakDescription}", () => manualBreakOverlay.Breaks = breaks); + AddStep($"load {breakDescription}", () => breakOverlay.Breaks = breaks); seekBreakStep("seek back to 0", 0, false); } @@ -145,12 +134,22 @@ namespace osu.Game.Tests.Visual.Gameplay private void seekBreakStep(string seekStepDescription, double time, bool onBreak) { - AddStep(seekStepDescription, () => manualClock.CurrentTime = time); - AddAssert($"is{(!onBreak ? " not " : " ")}break time", () => manualBreakOverlay.IsBreakTime.Value == onBreak); + AddStep(seekStepDescription, () => breakOverlay.ManualClockTime = time); + AddAssert($"is{(!onBreak ? " not " : " ")}break time", () => breakOverlay.IsBreakTime.Value == onBreak); } private class TestBreakOverlay : BreakOverlay { + private readonly FramedClock framedManualClock; + private readonly ManualClock manualClock; + private IFrameBasedClock normalClock; + + public double ManualClockTime + { + get => manualClock.CurrentTime; + set => manualClock.CurrentTime = value; + } + public new IBindable IsBreakTime { get @@ -165,6 +164,15 @@ namespace osu.Game.Tests.Visual.Gameplay public TestBreakOverlay(bool letterboxing) : base(letterboxing) { + framedManualClock = new FramedClock(manualClock = new ManualClock()); + } + + public void SwitchClock(bool setManual) => Clock = setManual ? framedManualClock : normalClock; + + protected override void LoadComplete() + { + base.LoadComplete(); + normalClock = Clock; } } } From c6d4ce0f8a8bb0787744bc2d337231f8bcdb3f9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Sun, 28 Jul 2019 12:14:06 +0200 Subject: [PATCH 0389/2815] revert truncation in derived class --- osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs index 4f93beee17..9e87bae864 100644 --- a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs @@ -65,12 +65,6 @@ namespace osu.Game.Overlays.Chat.Tabs Text.X = ChatOverlay.TAB_AREA_HEIGHT; TextBold.X = ChatOverlay.TAB_AREA_HEIGHT; - - Text.Width = 100f; - TextBold.Width = 100f; - - Text.Truncate = true; - TextBold.Truncate = true; } protected override bool ShowCloseOnHover => false; From df8d4d896639867418b5ad5199170a1931e6cdfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Sun, 28 Jul 2019 12:16:32 +0200 Subject: [PATCH 0390/2815] add truncation to base class --- osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs | 2 ++ osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs index 2a3dd55c71..7007b0183d 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs @@ -102,6 +102,8 @@ namespace osu.Game.Overlays.Chat.Tabs Anchor = Anchor.CentreLeft, Text = value.ToString(), Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold) + Width = 115f, + Truncate = true, }, CloseButton = new TabCloseButton { diff --git a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs index 9e87bae864..194e62e52a 100644 --- a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs @@ -65,6 +65,8 @@ namespace osu.Game.Overlays.Chat.Tabs Text.X = ChatOverlay.TAB_AREA_HEIGHT; TextBold.X = ChatOverlay.TAB_AREA_HEIGHT; + + Text.Width = 100f; } protected override bool ShowCloseOnHover => false; From f7b9ddb48ce4dffa56129344a1591e623e502597 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Sun, 28 Jul 2019 12:40:21 +0200 Subject: [PATCH 0391/2815] combine Text and TextBold --- .../Chat/Tabs/ChannelSelectorTabItem.cs | 3 ++- osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs | 23 +++++++------------ .../Chat/Tabs/PrivateChannelTabItem.cs | 1 - 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs index 7386bffb1a..ba4f046b66 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs @@ -13,6 +13,8 @@ namespace osu.Game.Overlays.Chat.Tabs public override bool IsSwitchable => false; + protected override bool IsBoldWhenActive => false; + public ChannelSelectorTabItem() : base(new ChannelSelectorTabChannel()) { @@ -22,7 +24,6 @@ namespace osu.Game.Overlays.Chat.Tabs Icon.Alpha = 0; Text.Font = Text.Font.With(size: 45); - TextBold.Font = Text.Font.With(size: 45); } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs index 7007b0183d..ea7875ac63 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs @@ -29,7 +29,6 @@ namespace osu.Game.Overlays.Chat.Tabs public override bool IsRemovable => !Pinned; protected readonly SpriteText Text; - protected readonly SpriteText TextBold; protected readonly ClickableContainer CloseButton; private readonly Box box; private readonly Box highlightBox; @@ -92,16 +91,7 @@ namespace osu.Game.Overlays.Chat.Tabs Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, Text = value.ToString(), - Font = OsuFont.GetFont(size: 18) - }, - TextBold = new OsuSpriteText - { - Alpha = 0, - Margin = new MarginPadding(5), - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Text = value.ToString(), - Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold) + Font = OsuFont.GetFont(size: 18), Width = 115f, Truncate = true, }, @@ -125,6 +115,8 @@ namespace osu.Game.Overlays.Chat.Tabs protected virtual bool ShowCloseOnHover => true; + protected virtual bool IsBoldWhenActive => true; + protected override bool OnHover(HoverEvent e) { if (IsRemovable && ShowCloseOnHover) @@ -205,8 +197,10 @@ namespace osu.Game.Overlays.Chat.Tabs box.FadeColour(BackgroundActive, TRANSITION_LENGTH, Easing.OutQuint); highlightBox.FadeIn(TRANSITION_LENGTH, Easing.OutQuint); - Text.FadeOut(TRANSITION_LENGTH, Easing.OutQuint); - TextBold.FadeIn(TRANSITION_LENGTH, Easing.OutQuint); + if (IsBoldWhenActive) + { + Text.Font = Text.Font.With(weight: FontWeight.Bold); + } } protected virtual void FadeInactive() @@ -218,8 +212,7 @@ namespace osu.Game.Overlays.Chat.Tabs box.FadeColour(BackgroundInactive, TRANSITION_LENGTH, Easing.OutQuint); highlightBox.FadeOut(TRANSITION_LENGTH, Easing.OutQuint); - Text.FadeIn(TRANSITION_LENGTH, Easing.OutQuint); - TextBold.FadeOut(TRANSITION_LENGTH, Easing.OutQuint); + Text.Font = Text.Font.With(weight: FontWeight.Medium); } protected override void OnActivated() => updateState(); diff --git a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs index 194e62e52a..97f695c73a 100644 --- a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs @@ -64,7 +64,6 @@ namespace osu.Game.Overlays.Chat.Tabs avatar.OnLoadComplete += d => d.FadeInFromZero(300, Easing.OutQuint); Text.X = ChatOverlay.TAB_AREA_HEIGHT; - TextBold.X = ChatOverlay.TAB_AREA_HEIGHT; Text.Width = 100f; } From 088c04a20fa32b8c42ab7bf4ecac1acb363c0d81 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 28 Jul 2019 15:32:29 +0900 Subject: [PATCH 0392/2815] Revert "Fix BackButton handling escape before all other elements (#5440)" This reverts commit 17a6563f4c5d0c7e3a4dfac5469c65866de311aa. --- osu.Game/OsuGame.cs | 5 ++--- osu.Game/Screens/Select/Footer.cs | 5 +++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e5099f3c2f..ceaf0c3d5e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -404,8 +404,9 @@ namespace osu.Game screenContainer = new ScalingContainer(ScalingMode.ExcludeOverlays) { RelativeSizeAxes = Axes.Both, - Children = new[] + Children = new Drawable[] { + screenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }, backButton = new BackButton { Anchor = Anchor.BottomLeft, @@ -416,8 +417,6 @@ namespace osu.Game screenStack.Exit(); } }, - screenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }, - backButton.CreateProxy(), logoContainer = new Container { RelativeSizeAxes = Axes.Both }, } }, diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs index 71641cab5d..0680711f1c 100644 --- a/osu.Game/Screens/Select/Footer.cs +++ b/osu.Game/Screens/Select/Footer.cs @@ -9,6 +9,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; namespace osu.Game.Screens.Select @@ -102,5 +103,9 @@ namespace osu.Game.Screens.Select updateModeLight(); } + + protected override bool OnMouseDown(MouseDownEvent e) => true; + + protected override bool OnClick(ClickEvent e) => true; } } From 07f905d21c7944d1969b3435fedc703746c8d98a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2019 01:00:41 +0900 Subject: [PATCH 0393/2815] Tidy up code and fix explode animations not playing correctly --- osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs | 89 ++++++++++------------ 1 file changed, 40 insertions(+), 49 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs index 102275043c..18267a2e0e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs @@ -1,95 +1,86 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/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; using System.Collections.Generic; using System.Linq; -using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModSpinIn : Mod, IApplicableToDrawableHitObjects + public class OsuModSpinIn : Mod, IApplicableToDrawableHitObjects, IReadFromConfig { public override string Name => "Spin In"; - public override string ShortenedName => "SI"; - public override FontAwesome Icon => FontAwesome.fa_rotate_right; + public override string Acronym => "SI"; + public override IconUsage Icon => FontAwesome.Solid.Undo; public override ModType Type => ModType.Fun; public override string Description => "Circles spin in. No approach circles."; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModHidden) }; private const int rotate_offset = 360; - private const float rotate_starting_width = 2.0f; + private const float rotate_starting_width = 2; + private Bindable increaseFirstObjectVisibility = new Bindable(); + + public void ReadFromConfig(OsuConfigManager config) + { + increaseFirstObjectVisibility = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility); + } public void ApplyToDrawableHitObjects(IEnumerable drawables) { - foreach (var drawable in drawables) + foreach (var drawable in drawables.Skip(increaseFirstObjectVisibility.Value ? 1 : 0)) { - // Need to add custom update in order to disable fade - drawable.ApplyCustomUpdateState += ApplyZoomState; + switch (drawable) + { + case DrawableSpinner _: + continue; + + default: + drawable.ApplyCustomUpdateState += applyZoomState; + break; + } } } - protected void ApplyZoomState(DrawableHitObject drawable, ArmedState state) + private void applyZoomState(DrawableHitObject drawable, ArmedState state) { - if (!(drawable is DrawableOsuHitObject)) return; - if (state != ArmedState.Idle) return; - var h = (OsuHitObject)drawable.HitObject; - var appearTime = h.StartTime - h.TimePreempt + 1; - var moveDuration = h.TimePreempt - 1; - switch (drawable) { case DrawableHitCircle circle: - // Disable Fade - circle.Transforms - .Where(t => t.TargetMember == "Alpha") - .ForEach(t => circle.RemoveTransform(t)); - - using (circle.BeginAbsoluteSequence(appearTime, true)) - { - var origScale = drawable.Scale; - var origRotate = circle.Rotation; - - circle - .RotateTo(origRotate+rotate_offset) - .RotateTo(origRotate, moveDuration, Easing.InOutSine) - .ScaleTo(origScale * new Vector2(rotate_starting_width, 0)) - .ScaleTo(origScale, moveDuration, Easing.InOutSine) - .FadeTo(1); - } - - using (circle.ApproachCircle.BeginAbsoluteSequence(appearTime, true)) + using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) { circle.ApproachCircle.Hide(); + + circle.RotateTo(rotate_offset).Then().RotateTo(0, h.TimePreempt, Easing.InOutSine); + circle.ScaleTo(new Vector2(rotate_starting_width, 0)).Then().ScaleTo(1, h.TimePreempt, Easing.InOutSine); + + // bypass fade in. + if (state == ArmedState.Idle) + circle.FadeIn(); } break; case DrawableSlider slider: - // Disable fade - slider.Transforms - .Where(t => t.TargetMember == "Alpha") - .ForEach(t => slider.RemoveTransform(t)); - - using (slider.BeginAbsoluteSequence(appearTime, true)) + using (slider.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) { - var origScale = slider.Scale; + slider.ScaleTo(0).Then().ScaleTo(1, h.TimePreempt, Easing.InOutSine); - slider - .ScaleTo(0) - .ScaleTo(origScale, moveDuration, Easing.InOutSine) - .FadeTo(1); + // bypass fade in. + if (state == ArmedState.Idle) + slider.FadeIn(); } break; From 3e74079d0278a3876b41497acb4206b2d312fa06 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2019 01:15:54 +0900 Subject: [PATCH 0394/2815] Add incompatibility with scale tween mods --- osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs | 4 +++- osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs index 18267a2e0e..62b5ecfd58 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs @@ -24,7 +24,9 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override string Description => "Circles spin in. No approach circles."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModHidden) }; + + // todo: this mod should be able to be compatible with hidden with a bit of further implementation. + public override Type[] IncompatibleMods => new[] { typeof(OsuModeObjectScaleTween), typeof(OsuModHidden) }; private const int rotate_offset = 360; private const float rotate_starting_width = 2; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs index ad6a15718a..e926ade41b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.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 osu.Framework.Bindables; @@ -28,6 +29,8 @@ namespace osu.Game.Rulesets.Osu.Mods private Bindable increaseFirstObjectVisibility = new Bindable(); + public override Type[] IncompatibleMods => new[] { typeof(OsuModSpinIn) }; + public void ReadFromConfig(OsuConfigManager config) { increaseFirstObjectVisibility = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility); @@ -64,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Mods case DrawableSlider _: case DrawableHitCircle _: { - using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) + using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) drawable.ScaleTo(StartScale).Then().ScaleTo(EndScale, h.TimePreempt, Easing.OutSine); break; } @@ -75,7 +78,7 @@ namespace osu.Game.Rulesets.Osu.Mods { case DrawableHitCircle circle: // we don't want to see the approach circle - using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) + using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) circle.ApproachCircle.Hide(); break; } From de8f5028714007f6b0051793f60436555c29cb40 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2019 02:46:33 +0900 Subject: [PATCH 0395/2815] Add test --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 4d3992ce13..9196513a55 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Online private TestChatOverlay chatOverlay; private ChannelManager channelManager; - private readonly Channel channel1 = new Channel(new User()) { Name = "test1" }; + private readonly Channel channel1 = new Channel(new User()) { Name = "test really long username" }; private readonly Channel channel2 = new Channel(new User()) { Name = "test2" }; [SetUp] From 663f34d3d8643221feee276eb6923cc95a5b90a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2019 02:47:26 +0900 Subject: [PATCH 0396/2815] Remove width specifications --- .../Overlays/Chat/Tabs/ChannelSelectorTabItem.cs | 1 + osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs | 12 ++++++++++-- osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs | 6 ++---- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs index ba4f046b66..d5d9a6c2ce 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs @@ -24,6 +24,7 @@ namespace osu.Game.Overlays.Chat.Tabs Icon.Alpha = 0; Text.Font = Text.Font.With(size: 45); + Text.Truncate = false; } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs index ea7875ac63..3de321e127 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs @@ -87,12 +87,16 @@ namespace osu.Game.Overlays.Chat.Tabs }, Text = new OsuSpriteText { - Margin = new MarginPadding(5), Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, Text = value.ToString(), Font = OsuFont.GetFont(size: 18), - Width = 115f, + Padding = new MarginPadding(5) + { + Left = LeftTextPadding, + Right = RightTextPadding, + }, + RelativeSizeAxes = Axes.X, Truncate = true, }, CloseButton = new TabCloseButton @@ -111,6 +115,10 @@ namespace osu.Game.Overlays.Chat.Tabs }; } + protected virtual float LeftTextPadding => 5; + + protected virtual float RightTextPadding => IsRemovable ? 40 : 5; + protected virtual IconUsage DisplayIcon => FontAwesome.Solid.Hashtag; protected virtual bool ShowCloseOnHover => true; diff --git a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs index 97f695c73a..1413b8fe78 100644 --- a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs @@ -62,12 +62,10 @@ namespace osu.Game.Overlays.Chat.Tabs }); avatar.OnLoadComplete += d => d.FadeInFromZero(300, Easing.OutQuint); - - Text.X = ChatOverlay.TAB_AREA_HEIGHT; - - Text.Width = 100f; } + protected override float LeftTextPadding => base.LeftTextPadding + ChatOverlay.TAB_AREA_HEIGHT; + protected override bool ShowCloseOnHover => false; protected override void FadeActive() From 316b11d08b0647041c08aa5e839cb5adf1ccc11b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Sun, 28 Jul 2019 20:36:21 +0200 Subject: [PATCH 0397/2815] use single line if-statement --- osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs index 3de321e127..266e68f17e 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs @@ -205,10 +205,7 @@ namespace osu.Game.Overlays.Chat.Tabs box.FadeColour(BackgroundActive, TRANSITION_LENGTH, Easing.OutQuint); highlightBox.FadeIn(TRANSITION_LENGTH, Easing.OutQuint); - if (IsBoldWhenActive) - { - Text.Font = Text.Font.With(weight: FontWeight.Bold); - } + if (IsBoldWhenActive) Text.Font = Text.Font.With(weight: FontWeight.Bold); } protected virtual void FadeInactive() From f4effd12c301ecece374df443cc08bd0e2bf2958 Mon Sep 17 00:00:00 2001 From: jorolf Date: Sun, 28 Jul 2019 21:04:55 +0200 Subject: [PATCH 0398/2815] make legacy skins animatable --- osu.Game/Skinning/LegacySkin.cs | 38 +++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 513a024a36..24c6c9e4ec 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Graphics; +using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; @@ -52,22 +53,29 @@ namespace osu.Game.Skinning public override Drawable GetDrawableComponent(string componentName) { + bool animatable = false; + (bool looping, double frametime) animationData = (false, 1000 / 60d); + switch (componentName) { case "Play/Miss": componentName = "hit0"; + animatable = true; break; case "Play/Meh": componentName = "hit50"; + animatable = true; break; case "Play/Good": componentName = "hit100"; + animatable = true; break; case "Play/Great": componentName = "hit300"; + animatable = true; break; case "Play/osu/number-text": @@ -81,15 +89,31 @@ namespace osu.Game.Skinning }; } - // temporary allowance is given for skins the fact that stable handles non-animatable items such as hitcircles (incorrectly) - // by (incorrectly) displaying the first frame of animation rather than the non-animated version. - // users have used this to "hide" certain elements like hit300. - var texture = GetTexture($"{componentName}-0") ?? GetTexture(componentName); + var texture = GetTexture($"{componentName}-0"); - if (texture == null) - return null; + if (texture != null && animatable) + { + var animation = new TextureAnimation { DefaultFrameLength = animationData.frametime }; - return new Sprite { Texture = texture }; + for (int i = 1; texture != null; i++) + { + animation.AddFrame(texture); + texture = GetTexture($"{componentName}-{i}"); + } + + animation.Repeat = animationData.looping; + + return animation; + } + else + { + texture = GetTexture(componentName); + + if (texture == null) + return null; + + return new Sprite { Texture = texture }; + } } public override Texture GetTexture(string componentName) From 26fc782de9cb40dc36960b6c7764fa0c024a83ce Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 29 Jul 2019 10:33:34 +0900 Subject: [PATCH 0399/2815] Don't exit if screenstack is null --- osu.Game/Screens/Multi/Multiplayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 9ffd620e55..90806bab6e 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -212,7 +212,7 @@ namespace osu.Game.Screens.Multi public override bool OnExiting(IScreen next) { - if (!(screenStack.CurrentScreen is LoungeSubScreen)) + if (screenStack.CurrentScreen != null && !(screenStack.CurrentScreen is LoungeSubScreen)) { screenStack.Exit(); return true; From 4b5fb84888dd7afed64bdafc497031675182489d Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 29 Jul 2019 11:02:44 +0900 Subject: [PATCH 0400/2815] Rewrite comment --- osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 158f96b46b..c33e980a9a 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -59,8 +59,8 @@ namespace osu.Game.Graphics.Containers { base.LoadComplete(); - // This must be added after the base LoadComplete. The overlay may need to be hidden immediately if its disabled, - // but the overlay doesn't get shown until after the stateChanged function from VisibilityContainer gets called. + // This must be added after the base LoadComplete, since onStateChanged contains logic that + // must be run after other state change logic has been completed. State.ValueChanged += onStateChanged; } From 2e242075b486692326ad6efe0fc7e7ed97750e60 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 29 Jul 2019 14:30:46 +0900 Subject: [PATCH 0401/2815] Remove and re-add backbutton instead and add tests --- .../Visual/Menus/TestSceneExitingScreens.cs | 152 ++++++++++++++++++ .../Input/Bindings/GlobalActionContainer.cs | 29 +++- osu.Game/OsuGame.cs | 33 ++-- 3 files changed, 196 insertions(+), 18 deletions(-) create mode 100644 osu.Game.Tests/Visual/Menus/TestSceneExitingScreens.cs diff --git a/osu.Game.Tests/Visual/Menus/TestSceneExitingScreens.cs b/osu.Game.Tests/Visual/Menus/TestSceneExitingScreens.cs new file mode 100644 index 0000000000..fafaa3a397 --- /dev/null +++ b/osu.Game.Tests/Visual/Menus/TestSceneExitingScreens.cs @@ -0,0 +1,152 @@ +// 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Platform; +using osu.Framework.Screens; +using osu.Framework.Testing; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Mods; +using osu.Game.Screens.Menu; +using osu.Game.Screens.Select; +using osuTK.Graphics; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Menus +{ + public class TestSceneExitingScreens : ManualInputManagerTestScene + { + private readonly TestOsuGame osuGame = new TestOsuGame(); + + [BackgroundDependencyLoader] + private void load(GameHost gameHost) + { + osuGame.SetHost(gameHost); + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + osuGame + }; + } + + [SetUpSteps] + public void SetUpSteps() + { + AddUntilStep("wait for load", () => osuGame.IsLoaded); + AddUntilStep("wait for main menu", () => + { + var current = osuGame.ScreenStack?.CurrentScreen; + + switch (current) + { + case null: + case Intro _: + case Disclaimer _: + return false; + + case MainMenu _: + return true; + + default: + current.Exit(); + return false; + } + }); + } + + [Test] + public void TestExitingSongSelectWithEscape() + { + TestSongSelect songSelect = null; + + AddStep("Push songselect", () => osuGame.ScreenStack.Push(songSelect = new TestSongSelect())); + AddUntilStep("Wait for song select", () => songSelect.IsCurrentScreen()); + AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show()); + AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible); + AddStep("Press escape", () => pressAndRelease(Key.Escape)); + AddAssert("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden); + exitViaEscapeAndConfirm(); + } + + [Test] + public void TestExitingSongSelectWithClick() + { + TestSongSelect songSelect = null; + + AddStep("Push songselect", () => osuGame.ScreenStack.Push(songSelect = new TestSongSelect())); + AddUntilStep("Wait for song select", () => songSelect.IsCurrentScreen()); + AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show()); + AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible); + AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(osuGame.BackButton)); + + // BackButton handles hover using its child button, so this checks whether or not any of BackButton's children are hovered. + AddUntilStep("Back button is hovered", () => InputManager.HoveredDrawables.Any(d => d.Parent == osuGame.BackButton)); + + AddStep("Click back button", () => InputManager.Click(MouseButton.Left)); + AddAssert("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden); + exitViaBackButtonAndConfirm(); + } + + [Test] + public void TestExitMultiWithEscape() + { + Screens.Multi.Multiplayer multiplayer = null; + + AddStep("Push songselect", () => osuGame.ScreenStack.Push(multiplayer = new Screens.Multi.Multiplayer())); + AddUntilStep("Wait for song select", () => multiplayer.IsCurrentScreen()); + exitViaEscapeAndConfirm(); + } + + [Test] + public void TestExitMultiWithBackButton() + { + Screens.Multi.Multiplayer multiplayer = null; + + AddStep("Push songselect", () => osuGame.ScreenStack.Push(multiplayer = new Screens.Multi.Multiplayer())); + AddUntilStep("Wait for song select", () => multiplayer.IsCurrentScreen()); + + exitViaBackButtonAndConfirm(); + } + + private void exitViaEscapeAndConfirm() + { + AddStep("Press escape", () => pressAndRelease(Key.Escape)); + AddUntilStep("Wait for main menu", () => osuGame.ScreenStack.CurrentScreen is MainMenu); + } + + private void exitViaBackButtonAndConfirm() + { + AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(osuGame.BackButton)); + AddStep("Click back button", () => InputManager.Click(MouseButton.Left)); + AddUntilStep("Wait for main menu", () => osuGame.ScreenStack.CurrentScreen is MainMenu); + } + + private void pressAndRelease(Key key) + { + InputManager.PressKey(key); + InputManager.ReleaseKey(key); + } + + private class TestOsuGame : OsuGame + { + public new ScreenStack ScreenStack => base.ScreenStack; + + public new BackButton BackButton => base.BackButton; + } + + private class TestSongSelect : PlaySongSelect + { + public ModSelectOverlay ModSelectOverlay => ModSelect; + } + } +} diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 669fd62e45..373333696a 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Bindings; +using osu.Game.Graphics.UserInterface; namespace osu.Game.Input.Bindings { @@ -55,8 +56,32 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed), }; - protected override IEnumerable KeyBindingInputQueue => - handler == null ? base.KeyBindingInputQueue : base.KeyBindingInputQueue.Prepend(handler); + protected override IEnumerable KeyBindingInputQueue + { + get + { + var queue = base.KeyBindingInputQueue.ToList(); + + if (handler != null) + yield return handler; + + BackButton backButton = null; + + foreach (var drawable in queue) + { + if (drawable is BackButton button) + { + backButton = button; + continue; + } + + yield return drawable; + } + + if (backButton != null) + yield return backButton; + } + } } public enum GlobalAction diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ceaf0c3d5e..64958c9a09 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -81,10 +81,11 @@ namespace osu.Game public readonly Bindable OverlayActivationMode = new Bindable(); - private OsuScreenStack screenStack; + protected OsuScreenStack ScreenStack; + protected BackButton BackButton; + private VolumeOverlay volume; private OsuLogo osuLogo; - private BackButton backButton; private MainMenu menuScreen; private Intro introScreen; @@ -325,7 +326,7 @@ namespace osu.Game performFromMainMenuTask?.Cancel(); // if the current screen does not allow screen changing, give the user an option to try again later. - if (!bypassScreenAllowChecks && (screenStack.CurrentScreen as IOsuScreen)?.AllowExternalScreenChange == false) + if (!bypassScreenAllowChecks && (ScreenStack.CurrentScreen as IOsuScreen)?.AllowExternalScreenChange == false) { notifications.Post(new SimpleNotification { @@ -343,7 +344,7 @@ namespace osu.Game CloseAllOverlays(false); // we may already be at the target screen type. - if (targetScreen != null && screenStack.CurrentScreen?.GetType() == targetScreen) + if (targetScreen != null && ScreenStack.CurrentScreen?.GetType() == targetScreen) { action(); return; @@ -406,15 +407,15 @@ namespace osu.Game RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - screenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }, - backButton = new BackButton + ScreenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }, + BackButton = new BackButton { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Action = () => { - if ((screenStack.CurrentScreen as IOsuScreen)?.AllowBackButton == true) - screenStack.Exit(); + if ((ScreenStack.CurrentScreen as IOsuScreen)?.AllowBackButton == true) + ScreenStack.Exit(); } }, logoContainer = new Container { RelativeSizeAxes = Axes.Both }, @@ -427,15 +428,15 @@ namespace osu.Game idleTracker }); - screenStack.ScreenPushed += screenPushed; - screenStack.ScreenExited += screenExited; + ScreenStack.ScreenPushed += screenPushed; + ScreenStack.ScreenExited += screenExited; loadComponentSingleFile(osuLogo, logo => { logoContainer.Add(logo); // Loader has to be created after the logo has finished loading as Loader performs logo transformations on entering. - screenStack.Push(new Loader + ScreenStack.Push(new Loader { RelativeSizeAxes = Axes.Both }); @@ -755,13 +756,13 @@ namespace osu.Game protected override bool OnExiting() { - if (screenStack.CurrentScreen is Loader) + if (ScreenStack.CurrentScreen is Loader) return false; if (introScreen == null) return true; - if (!introScreen.DidLoadMenu || !(screenStack.CurrentScreen is Intro)) + if (!introScreen.DidLoadMenu || !(ScreenStack.CurrentScreen is Intro)) { Scheduler.Add(introScreen.MakeCurrent); return true; @@ -789,7 +790,7 @@ namespace osu.Game screenContainer.Padding = new MarginPadding { Top = ToolbarOffset }; overlayContent.Padding = new MarginPadding { Top = ToolbarOffset }; - MenuCursorContainer.CanShowCursor = (screenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false; + MenuCursorContainer.CanShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false; } protected virtual void ScreenChanged(IScreen current, IScreen newScreen) @@ -815,9 +816,9 @@ namespace osu.Game Toolbar.Show(); if (newOsuScreen.AllowBackButton) - backButton.Show(); + BackButton.Show(); else - backButton.Hide(); + BackButton.Hide(); } } From c14c3ba8ec28f4e927b7865de73cff615f9b942e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2019 16:55:19 +0900 Subject: [PATCH 0402/2815] Move database isolation logic to OsuTestScene for easier reuse --- .../Background/TestSceneUserDimContainer.cs | 17 ++------------ .../SongSelect/TestScenePlaySongSelect.cs | 23 ++----------------- osu.Game/Tests/Visual/OsuTestScene.cs | 15 ++++++++++++ 3 files changed, 19 insertions(+), 36 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index dc4ceed59e..f114559114 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -17,7 +17,6 @@ using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Configuration; -using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -51,26 +50,14 @@ namespace osu.Game.Tests.Visual.Background private DummySongSelect songSelect; private TestPlayerLoader playerLoader; private TestPlayer player; - private DatabaseContextFactory factory; private BeatmapManager manager; private RulesetStore rulesets; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - factory = new DatabaseContextFactory(LocalStorage); - factory.ResetDatabase(); - - using (var usage = factory.Get()) - usage.Migrate(); - - factory.ResetDatabase(); - - using (var usage = factory.Get()) - usage.Migrate(); - - Dependencies.Cache(rulesets = new RulesetStore(factory)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, audio, host, Beatmap.Default)); + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default)); Dependencies.Cache(new OsuConfigManager(LocalStorage)); manager.Import(TestResources.GetTestBeatmapForImport()).Wait(); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index f3255814f2..680250a226 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -15,7 +15,6 @@ using osu.Framework.MathUtils; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Beatmaps; -using osu.Game.Database; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; @@ -35,7 +34,6 @@ namespace osu.Game.Tests.Visual.SongSelect private RulesetStore rulesets; private WorkingBeatmap defaultBeatmap; - private DatabaseContextFactory factory; public override IReadOnlyList RequiredTypes => new[] { @@ -74,28 +72,11 @@ namespace osu.Game.Tests.Visual.SongSelect private TestSongSelect songSelect; - protected override void Dispose(bool isDisposing) - { - factory.ResetDatabase(); - base.Dispose(isDisposing); - } - [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - factory = new DatabaseContextFactory(LocalStorage); - factory.ResetDatabase(); - - using (var usage = factory.Get()) - usage.Migrate(); - - factory.ResetDatabase(); - - using (var usage = factory.Get()) - usage.Migrate(); - - Dependencies.Cache(rulesets = new RulesetStore(factory)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, audio, host, defaultBeatmap = Beatmap.Default)); + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, defaultBeatmap = Beatmap.Default)); Beatmap.SetDefault(); } diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 9b3c15aa91..27d72f3950 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -15,6 +15,7 @@ using osu.Framework.Platform; using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Tests.Beatmaps; @@ -43,6 +44,9 @@ namespace osu.Game.Tests.Visual private readonly Lazy localStorage; protected Storage LocalStorage => localStorage.Value; + private readonly Lazy contextFactory; + protected DatabaseContextFactory ContextFactory => contextFactory.Value; + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { // This is the earliest we can get OsuGameBase, which is used by the dummy working beatmap to find textures @@ -59,6 +63,14 @@ namespace osu.Game.Tests.Visual protected OsuTestScene() { localStorage = new Lazy(() => new NativeStorage($"{GetType().Name}-{Guid.NewGuid()}")); + contextFactory = new Lazy(() => + { + var factory = new DatabaseContextFactory(LocalStorage); + factory.ResetDatabase(); + using (var usage = factory.Get()) + usage.Migrate(); + return factory; + }); } [Resolved] @@ -85,6 +97,9 @@ namespace osu.Game.Tests.Visual if (beatmap?.Value.TrackLoaded == true) beatmap.Value.Track.Stop(); + if (contextFactory.IsValueCreated) + contextFactory.Value.ResetDatabase(); + if (localStorage.IsValueCreated) { try From cb17007fa740c67e665dbc0cd447412e5e81b78b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2019 16:55:39 +0900 Subject: [PATCH 0403/2815] Fix zero-length hash models incorrectly creating a unique hash --- osu.Game/Database/ArchiveModelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index ed65bdc069..efb76deff8 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -253,7 +253,7 @@ namespace osu.Game.Database using (Stream s = reader.GetStream(file)) s.CopyTo(hashable); - return hashable.ComputeSHA2Hash(); + return hashable.Length > 0 ? hashable.ComputeSHA2Hash() : null; } /// From 8742ed8a9cc5768c89f9b3d2230c0f3ca74abd1c Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 29 Jul 2019 17:23:45 +0900 Subject: [PATCH 0404/2815] Fix step names --- .../Visual/Menus/TestSceneExitingScreens.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneExitingScreens.cs b/osu.Game.Tests/Visual/Menus/TestSceneExitingScreens.cs index fafaa3a397..377d7b60fb 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneExitingScreens.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneExitingScreens.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Menus AddUntilStep("wait for load", () => osuGame.IsLoaded); AddUntilStep("wait for main menu", () => { - var current = osuGame.ScreenStack?.CurrentScreen; + var current = osuGame.ScreenStack.CurrentScreen; switch (current) { @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Menus { TestSongSelect songSelect = null; - AddStep("Push songselect", () => osuGame.ScreenStack.Push(songSelect = new TestSongSelect())); + AddStep("Push song select", () => osuGame.ScreenStack.Push(songSelect = new TestSongSelect())); AddUntilStep("Wait for song select", () => songSelect.IsCurrentScreen()); AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show()); AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible); @@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.Menus { TestSongSelect songSelect = null; - AddStep("Push songselect", () => osuGame.ScreenStack.Push(songSelect = new TestSongSelect())); + AddStep("Push song select", () => osuGame.ScreenStack.Push(songSelect = new TestSongSelect())); AddUntilStep("Wait for song select", () => songSelect.IsCurrentScreen()); AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show()); AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible); @@ -102,8 +102,8 @@ namespace osu.Game.Tests.Visual.Menus { Screens.Multi.Multiplayer multiplayer = null; - AddStep("Push songselect", () => osuGame.ScreenStack.Push(multiplayer = new Screens.Multi.Multiplayer())); - AddUntilStep("Wait for song select", () => multiplayer.IsCurrentScreen()); + AddStep("Push multiplayer", () => osuGame.ScreenStack.Push(multiplayer = new Screens.Multi.Multiplayer())); + AddUntilStep("Wait for multiplayer", () => multiplayer.IsCurrentScreen()); exitViaEscapeAndConfirm(); } @@ -112,8 +112,8 @@ namespace osu.Game.Tests.Visual.Menus { Screens.Multi.Multiplayer multiplayer = null; - AddStep("Push songselect", () => osuGame.ScreenStack.Push(multiplayer = new Screens.Multi.Multiplayer())); - AddUntilStep("Wait for song select", () => multiplayer.IsCurrentScreen()); + AddStep("Push multiplayer", () => osuGame.ScreenStack.Push(multiplayer = new Screens.Multi.Multiplayer())); + AddUntilStep("Wait for multiplayer", () => multiplayer.IsCurrentScreen()); exitViaBackButtonAndConfirm(); } From c514cbe2b78c9c4846b125d8b9f48924b1e7f176 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jul 2019 19:29:06 +0900 Subject: [PATCH 0405/2815] Add basic skinning test --- .../default-skin/approachcircle@2x.png | Bin 0 -> 18164 bytes .../Resources/default-skin/hit300k@2x.png | Bin 0 -> 29098 bytes .../Resources/default-skin/hitcircle@2x.png | Bin 0 -> 7768 bytes .../default-skin/hitcircleoverlay@2x.png | Bin 0 -> 45901 bytes .../Resources/metrics-skin/hitcircle@2x.png | Bin 0 -> 13140 bytes .../metrics-skin/hitcircleoverlay@2x.png | Bin 0 -> 38217 bytes .../special-skin/approachcircle@2x.png | Bin 0 -> 31796 bytes .../Resources/special-skin/hitcircle@2x.png | Bin 0 -> 263521 bytes .../special-skin/hitcircleoverlay@2x.png | Bin 0 -> 262013 bytes .../SkinnableTestScene.cs | 66 ++++++++++++++++++ .../TestSceneHitCircle.cs | 44 ++++++------ osu.Game/Skinning/SkinManager.cs | 6 +- .../Tests/Visual}/OsuGridTestScene.cs | 2 +- osu.iOS.props | 2 +- 14 files changed, 92 insertions(+), 28 deletions(-) create mode 100755 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/approachcircle@2x.png create mode 100755 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300k@2x.png create mode 100755 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hitcircle@2x.png create mode 100755 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hitcircleoverlay@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hitcircle@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hitcircleoverlay@2x.png create mode 100755 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/approachcircle@2x.png create mode 100755 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircle@2x.png create mode 100755 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs rename {osu.Game.Tests/Visual/UserInterface => osu.Game/Tests/Visual}/OsuGridTestScene.cs (97%) diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/approachcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/approachcircle@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..db2f4a5730b80c3488c618fe825e322742e4007c GIT binary patch literal 18164 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
xUBq)wpYuSo%L4;iD|W&d3&2irtb$J#-rmG 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 tq+89yAYBs>kLVeUC5rTls%z@vF}1DU#4;$aN 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 R|M{Pbx-w?>O~E?q{{RqUO*sGn literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hitcircleoverlay@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hitcircleoverlay@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4233d9bb6e6aa88c7da5c1998439789cc5f9c7b2 GIT binary patch literal 38217 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/approachcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/approachcircle@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..5d28b3f7cb5b7d682686c9a93a994ae506f68f2f GIT binary patch literal 31796 zcmdqIby!>Nwl9inai@5pXar9v?ou3z1r4rA@Zw&iNQ+b`6iQp1;#!J(v9>^gB0-Bw zaSA7V-?zTK_P%SMweNH8`RC>#B$@A+;~nxFGT-^mcs(68G7@GIEG#TC4RvJ$EG%sM zn>P^w<_N=W1rE#yF+$zU8w-n+=H`u!^&*=Q3yb8~#mLmhRQtXZ+{*)KYwu;}01WUz zV5qUMq~!t-ws1EG9}YVQCl^l{uHEKOTpTX;GF&F&+F)&jl7q90`a_h1;X@rG_(L~1 z)SgRDmP0x~3d6v|!N-;(z{B0sTPi??>kqzCnC~}-L0lYvkodUCa4FnO$YH9j$D!nf za^MgLN&w)(Vj>)pP@u2`7%Czr#32F}mIQ$%LBgT{u&@+ZLP`|M@z;wBGaJg@QOZDB z^{=@wzhtSEf`lkvWh`-r-djDl6jKM$wwg`|g5PYN3AB6Vszv&RZ zDEB`mw}*oq+#Ng|Jbk<|w8DSWBAmT^yu6*g{u|bR5C5kLFs9Yk{(FvpDT{~4-&1(| zsQ6*j_{$*wlG@uS5a9qaaPao>MZq0Z{4iqP`G+>%J_Zi|VbA};=@|0APxf(f{BO>^ z8Tn62IRv=;H>5Wsf0H7llu!<~K3*szFE96hU`OvCR^(7p;@~rJ@wE5y_vXLR;4g9q zWm_Ky87>TrAOK-ufC$(KEGZ=l!Tbq=MWw*tKS{N{>|GoK|0ab3z!Csa3@JoPOjJr- z7l$FUEDDP-Ue!l92zQ062edk34jPt_z!WlwWTyXy?tyw;SL(gGF%v4fi5ogQjU(| z4sa1$2Y{p_914&S6N3OCq9Tp}u&t=Ly`zJJq?o?I8m<(a-{?A-*q_W3w zgi3-PA(9fp06P&!VE`N=CIPU63c~@m5)gF)dIK}iuX<^>YI`Ty6r*}ob8k6~j^7mP13Cb)sspXN8jWH^kOF?0QKOwT32 z!QE8Z1!EcSKZFn!m-y!)v;Q)5lkRRJQcB&$8w1S1zY>z61M;tL?k*gE1fi5I{7(ho zHxPEP=lbig%fE2r|IA>2vh{a%z?kZP1MWY`yuBQK{B2PV3QidN{+rMN{kNifdHOk^ z{z-U*Ey@;?(H&6UGF*-*FAt8tVb0;>#bJv;xVymra3;vl)Bdll`8U3}{@Z!HZT#@CP)x=Fi;Fn|Aa=Im00|7x;ldL3 zj-p}?|4NmAnVtTxssjJF-v2iazrn=c0Qv7tX77M<`6vJXGsgZFf#VOf{eLsxzeU^s zEUW%M(e{7UMWCB5<>_+EMi;F8?UxQvWK~{7>=@ns2yy{hyYMoBZ*I8UCqx`^Oib-U0o3FYooeB_!eY1 z+eb>{{g*F0JN}UC>rUuy^T`hXPaUbl~APDM(21LKuwgT+RmRRLc{ zLVmbK;)pp`Ol@ZV%`4aXU`;ms<{Q_0oaLL(v_h=L!ooslZUK3qfPesVJV|O|LPCOS z{{L@>H`ZjaK%8A8Aw(*2d8M_zzdJ6j=GZnr$3Q0L89XveS$ijCm^m0cvIubMcg1@n z{3hp*k7Q^3FY%{Tbd~V?RC>p;_TJ^BoR9^6ZW=jJtcT~W2WjtkGw3m}s$quRig4RP zQt5E(E^;;>UYlCl)I8rVUpu#Xh3AXyGbV+x^FT50IV;Z8SHG5>nXI7fnOxij%00jB z#N5PoA%n17sW|*`6(}7C1Hi=wPa3bL*V5m^e>=+a9#?$`^(N(jhJ5D%6o>$l7>5TB z_;z5?vtAg^SYDhb->Khi0N=2@QV$2xtLYcwEE^VzbGDG%c&A)gJq^14dOgDGRS4wk z)nma~_`1|OSx^V(t6=ep&(Wp8%3Y@Z#->nXpLkFZ{1Q5s`?6rl+_0LD25#o-@|SSU)`fd;c$&r5;&1uLw7uxM;+)yU zk(vYA$i}L6wvVx>V;@X2W1(dk{oBO|Jm<}aO`I7jyxUn-Z-V?B3*rh6^L&) zare$hfwA!OnuTnY)SN~S))rX;)cixDW$)hlU(Z`eo=r|VLm)43`Q_P4$tl;|<6G4m z+vIv}2UomF#iO#u?ZSeFW%G_9@ksJ>`*ZIw`1z^5n84d#cX{vhssXgrP6*#DGlU|6_?#oF;*ZN#zZmX$VhpIVG){5E2n zSrhv(;U8d7K1WT>sC_2-E%LM$wS>aO(!c^dl zo&9oyEs}P#g(S)yUqx_jrf6F~q30QLvP*SGnA3#3Ca3vK3(ZF#jUID`{b z72-h#RZa_|9k)KiG|3}6n^q~GnXR-H3B8N(Au?dsxSfgZVTxb#{5~vt>(1KqC?6K_ z3fy&^RhrSi@FlDHO<90sm}d@!WhKu@h-U1ls$`-cLt4I(4tGm6-n$l4x7FiI2WMjs zlXNq!EIgcG1PXLJ@HgVYu!c$7p>EK`sEU*%L3y+0Q|l{J!v{m-F7|Xr%q)d5h#839 zJwv6(%1%<&PW?M7vj?!~UevrUz9 z^au|wr%u%JR?04STXv)T1q^s_LgT92)>ii`^ht_VH zLcYFn%-0w3l-YC%mUwHl*@Wdp?Z(_D1q;-fiEU8WuY0wb{KN0|^u0(v?4Lo3QDi?l zH6;-_r0u2g?DHkr}QNQ5(E`hL;mwbf& z;r600a9R-x>z(1nUd36Ff3vvYOun3s6G#@6FDNh^0f|Mdla=r>KZsz>GmY6pd)3Ml zYxCea#VoWKY~f@@FEHehbXT!%3;QH# zk$mrC?JPE%GvQkAM3om^ZB>c<gOoc_-X;#JmBQ4_WQ1zo9PcUn zsFLC$10L~I;-Jl{Dl3n!gGnemrv^ph>bQc6cva|~lU zMeq;q{rClf;5{R7Y?IyVGH1h;!>LbN%$%5|io9rh)3i#Q@VrJ#&oUem?M>v!lD$bZ zT={Cu!Q+<#lyvnA(aIm06O|Aij2Fb^p#7`ev~r88q$hKaxUzebnMhECl0MQhub~ic zM7{q|aWB7imNIfs5y1v=0^HNL+ZE%hAtHBKa0i^kEM(TsipB2MtdxI~uF3pJ4%s9a zPVBu3yr;nXI=f1|X`w~ufC6G^!}h1TcztR~32{@@mK?`al_{Zu)6N0={>(-zY`W1l zlZ-&YZc)X($n+LGX$t8t@~1+>kpawDJFe-Q5>w^yAm(0=2DU1UgyDz++2H z36i&*B874nKiclthH21>)Iwob}OWD~a zc%(#su`U2HX^ci|1j{ByZM0HiR1RvZ@7c?kn5B)pH?1;!VIX21tfS(w8S`pUP}$tM zBC^qusfS4^^*Z3v&9En%*gkR$vi)@Z;@KBwbOO*8U=gaZ5P2G z4i{{9s~f?;=$Jg?F1Q9lR@dqw2-$WXtT3$BnoLl*h3{t>x4;70>btJTa_Xu@$G!Xn zqC#6qMqR;1DbKqemgM~<8f_mdn2nn5B%*=jM7&>$aD|{N0+8qIPRv;ty;!OTqt4va z+{ajopk<1CZCEmAPoI-T)kc)XR0ehuq1M*v_?yV&qIt5#QOEGk5UPL$_bz@~_BWcG zday`2V)O^LAM))(h8HV#)yE5SE}8s;^WUb|-W93k5VzlPYb3>>2~@KxDA?d3PEl$< zZ|+K`+(!27V@758u6-3MxuYbJ(}s-&;3b%@c2m0hGO+{0RVsCivT-X(O5-WJ$XlC! zR9HTrF5owA&Zm`(ULfN?!!OrFQwy0bm_)si&4rNtVw5l1--jgfegL*T})VvPmFH3@e@n25#DSYAR_#93;R&Y0ksq5$@J_`Iq9(jwG-`FDM2>b z*hm$UE;<@p$UOJDmAm|96Qy(@{luBq#{ZK)B}CRt-_NE&!Yy){Qo;jiZN0aNW-47-hZM zg1?xD);y815FH6`+`;l5NVH}c;-0X;|15YkcC@q5>8Z$8#vx<0CXCsJ z*t#-oOo+DmIg!A4F>@`jiYcGx*rXOyxPQ+EWlPL#A-^jr&5ve5V)|FDN2ZVl(%U6#hqfa|fr2f~|V<`@T`@I&Z}n zX-#W8Z^vukp*3sXx=p5|={Qg6_EZgpE6E>)^uAom|8W5!Mb9`kM&Ta9Sqj}U+i&}? zX3LD>I}v#L^ZV95nP#JAy;xf+d5N%GYztx|M7y<-enyco$mGI5o_p+y%2j3VM(8Sm zC3(C>nHLBodS+)BpAsK_M~SNz8}K+d{4!&-!2Qd^dGc1Bpq;~nC7$F2!N@|$Uc4aZ zGiS&hzzARWpys~d0)Vq2B5`^GyRyfOi~_mvkvN!aw&yz)XY`WJ?Q+ifyynsboq3T% z$a_wG)JAgx`}4!zwd#_9vAsA19C!5x?WA^itvC6F=#G3@q(gu)$LA0 z$*fO)tD*{Frs}4szuKUsgz82RcjcTZUrKD$8)zyS9xBCpmX7KD-QQ-|NLsp}AiFAB zI#NG@c9pyU{kDqe1y=q5D#8fjPN`0Qs{*f@xKFXc|cqov=c7;;`@&VA7q~IBI>zGphhj_RLR5r8sK1A8D*-+TMAbM8!Fds(% z{sMYe!21pLfp*bpErPhYq$boMw?%R*WU(RwgdlFZfLKFH6V{pczfB@9Hehk*y}G!4 zjq8xvVWRA^R*l_k!@(B&}^e?+$lZ3R#;D$~|6OYFf>#-4d5j)wY30 zQ~k29`T*#7vCH|5(SrCdZz{-J;r1Zow(yb8`rL2qJ}VoM2BCf5o|5hPEz#t@EsNiN zQo#@!9<|HRmi74pv|IryXh{7%V1 z5se%MB@a#+=p)zZbc2mb#h(`2ap5tPu5=fpSvgN1Q?|GRvY2g7UMWXKZHK^6JEAO) z!rD~&^CYtmsXAl24lUF6DR)n~-@q$c`2tx%P&)qJdCAhy6HA*M^j4A+(&h>uNEN_D zxZV2bJNormn|W(p=80P;;k#7}{y@{_^A)H^jWor_mHRa;S^cbU9RuP2$QW3Cz2V66 z^v{#@z510EuZe7f9}txjx8K&>w}6fBFng4_Wd(2Vg|Ix5Ygg&6M{00|@!wXI1V#)* zG!9C#8CW7&t|5~FOFceD?9f_y|FR0pMP!E~hvD~mc2V>y)IH{)%O#B^I_p9K zE9%I3&HiS^J!)XkJOiBt>}wD^AzBxyzwa^qXez4S?X`TaN7}}-O(T=83l0i22IV9@ zi+(0z+0$8x=@%`_t=(Z+2@-a#F-&>55(p)J(g+6Tj+RaKmxJ0*sc37n$V&TP?(gTM;k^N zS#L{q(*g7-oY3zH0|l;Hzx($IJcl8wJb{!sGiOe zH6@nv+aDa&?femh)C;ANTwmt;@ivKWC)3<115J%6D?XjMe^?}Z>C8h&;#x+f*?2G3 zB9YR2nWf+r$GehTsBezqj3z;f$DR~vImKhv0k|GEDkYhCDd0I}+p}+7mN73`Lb+^B z)U65y=IA2lN!v>r2EVr!nl2UJ@m%7#6fO_mmIXW_3#5GCHtS&7g0C0=g zeeUQ=sx6|u4W$VQ$E0Z%k{Rbd8maZP@~_t z?99UQurWhAm7WtXznsc=C0of&VF(GR`#p5n71X;{C4crzKz?`~&!**>cVw`=^w_jY z*XQ&5wVmQZyL|oZF$-A@&656>IS+%-RKUpd)QOyc$W3+II_aeFi`Om5+G&TB-V@3u z$}C~6K(hE$kCDU0tJXd!;ra)eXI-+NoTnx1Og-=2og%;Ecs&($hP`v~F&_7D&?luY zT@gVLph4u9fo2jsC5@!mP}(saj4!6(ATJ=`8v5sCV+7u{|KVuY-*I8!->+tMK z*(TNVaW@-xlTwEYvLsoz^X#1GqeHSsPKEcd$ptgC!Mr>R65k+ zBjfxQW;c~r&k&zD$aY)%zDJ3ElQq!yX5lPZs3i1ub~ruU%`I^mWcWll*KWo%f_5D< z^iW-xMvHSo3^jn}gDS#{ADdP!IL%4#M58)dpQ-IWuL6^#r(o92)Sr(_e3GHw-)*v|0aPM`aAE1;suwqoTaA1_iM`tlq!B-1i@D(ikN-7 z=OfS0x3PL?X7|=!6{D*tTiyy>3E_R}G1m7nqn#7HVmp;2@3CGIB9$A0;?FaV^UrV{ zr*^Inc`32=DbI|Ot(fXId;6)36tO>I>?sPdL>yNn&O9cpi$9L>Qz03rD@i5)y>bu2 z_v!KN{j9^x7|Jay5Pvt*LS#*LH68mm%&I_ikIB2@?Dx{S%}cpL7Xu*)dG~|KNz=28 z!)Mp=Y^9;mnnD(|H0b45i%ID)x=8*3b*T~A52mOOTrx;wYs-%|${6*kzN-8&cwyuE}COuGOI z8LxI7(D(TAf5#qn`OL(B*2Wl`{sfn^jrRg;ES%|{f+^)aOqiDA*z3^u2z3Tv+psga zJtOM;UW{%^zf{${dQaJ+P;c#y)E`ZORGfyucvRNudiPlEEPQx&4aPpq$A%JJ!}Rww z_*U-p=8EHaT>1-Vdwc*Ty-?cZN=lV!L;1xxG7Vid2Snb+s!e!1PXXtuqrG#O2WJcZ!-us?$byl<^IZ6?9~ zO}G8oDJ-tUVy6l`jQvS3p@=e@D)Nv1TCPG|jE+(1b@s)T_OF2bCTgBs6o=EmjKKpH zpkjIW2H3+;$fgwE&}9lEi3Fu6rPbGcX0*OFKO8KUrTo|* z#hFsSFA_ca&U}T$te25Gh83}^b>zKg2^w5HoL|acQERwO*%JSfR?b1{)P8y5G;vvG@D=lV5a(G)_|BR}&%!^7m#00_OLQ zLL0S$o=*2dNSCBstj)0QDekq@UYZrHCXDXIq)OK@@ks{gT55N%-Khh1u$LK9tQe9m z%Qx}`?!8XAmGs_RG~>ykardU;-lyn=lNwe|p2S2)Pqa|N3G(Re;*ad5LJ0l}3-kULsa_wV$9p)^W<9x&P@l zqnwtkoCkt+MR1{_LEm3)RELNb7vig1ldVFb_W4L?L z6l&Ay%mlk=icicuiMVe2A-loy(|(>dKu7axN*c8BVnjr*?htx^yzCwWbS>W@$o$Q+ zA>lH%MhoLm<9fpjHQ$~*Czvs&GJ~Sg*Df}}k3AXXI?lSny2(!WvYrbk8B(Ecp&{{; zgm9?|do~8LFZ%6Uca{*_^-7};o4Jl~UpC{MvpI#aN(_P!{jId=yq_OaUUASWGJ|7v zA1@;p_$}D^N7knLi$l^}wQS|IUR4;nSY``wP~1n8B_ugL_TM^*iIOc{Y$*-+q1H2T z;C=|xWX{6BCORbr#%F2Hbz==1KUmI3(ab!~tcD4u4;pv`xrAh(?&zoNvrlNDD#SHE~yfQlPe^1b-_Uo}B_(7hr{^b2y8-w|$Wx*>WHJ`NuKVZf_{e(QQOdnIpZXdvU6hq@9Uei_*O!3UgAh-SCkZp&98 ziZiXDkE2itWHhjsS5XFEoyF*^%@eE+@m4JYJIS);!30R_?3qnEOxCe!qt zr=%=N7aZ9*sBBC-O6ya)w4I-8g{fbcO`{gc*Y5=FlG%GlWO`~jshDP<$Y>CL~SjU1@GGbN+G}!2$E3QS^); z_?V9FDz<>Frw2?IIjFgpj49e;{%2GDEXh5_x~F(^3_hKZ*n`x((N^TeU@JA9SI$Iy z=5L->GPcf#^WK*&aaFsdyQ#VKeriNkX$F@m@iiCIBBHU)20(@H=83N&G)x)x(J68( zyv>gRaR(1@p`&STk8w*B$L3h4K@7zSl0Q4^?gxVOFpXGxNShcNo9^kgoA=gtW}I&36n!bv+nt@5RzN@ZXc84br*5tg|#i5pXP40u^Z2S+R9&{{;jV+bc1M8^!+>&|*&wK6-u6P)K z$YJa>gD%Dl7#p6hbNXsHAv=xb-js>58B9yi_H3SZTYL}KjYm5-k`5HTZYyCsy}k^R z?x{7Ci*X0;={3miB^yYW0(6J6E8hBK;*({EtaK+IK3$3s+NVA?toU=fi1X5fuPnX% z^8wVSJ@*VyWz-lDg-+fQ)PALw__A+BYnm!x@pTG9rmOgo0*h+z)%BTgC10Ix2z~7Q zy!gkr32g6aJCCW#w{>2wFck1|7e_C=NWUx>kuK%A=-K-%FZX?9Ff9tTQSJV=j=V2^ zeX3-z*mOVbqj5V*rCZquW)NBbc3CcZ0so-3M|4&h7}iOa7~1whYRO1NWdKADTGKQN zmP)SzE5@{Rr6L{K`rS@11gE+=SI z0eY*y2X;@yTwqx+@_4bP;+D~{f=Uy*7E&~cPO>9@XzoAcIiuOxA#E%oU27nQT}5au z|9}VPzh)eO62*aaK2H(g_q^9v5=z<|=vV8NdYB8tW8PtCs0*TyEdC|bN**ZANY%!~ z8KBL7g(qF=MjnI6w6HWZ&cO8%b?)LlYr0%c_g$2moNy@#&(>Hbi`kD};hwYGBm8Wc zpMh3c30^Aa&S9ti4Vn$^!DKIfqBVjrT1JKyG1`H=n6yY#A?@kqlG5yU=UlJ$WMkCJ zcJT14XpL5kP1W66Q^(2Z^H{~GRR?Hd&-$;bfwD_=D*YDt%jzL%`y|iaFwF{-G4!{% z7$c1}LzVn1Tizbc&UI=7!{i@c#kZyf;PlfGZzwv_CAivB~AV$Y0lc2@GrYYZ-U%MwlR5k(#oYmF4M!I$5ko0>gth=&xu&5D+cB zvSzvxj|C)Zx6nk+Fz%2p3M49S+Q~Xm^PHkxunexlepY*FrMJ+@5AzYC->ySgm_LfW zeRrEV#ENJx^@+a*FZeZU{0S8>%|9+!MWwv;v=BUZN?_`fpMWjRZ9vZBd}pQAA=Yj4 zy5qAiGWpk@D?ZFH!9Ql<-a=aK*9c!*k%<_&iKz~vH_K&TdT;~feX5~6^A`jL^iKPuZ+ zdv=1*8$RndTzLBq^jpt@oO}HpOS!9ShD`8BU#bpB9jH!1b1r~`_v_m2IAgkftW=W1 zBwGQ&9Q`N$15ds6H_*wA>DmxxQThegbkJ*46 zfdmYkpOl+q&u}6oW0oyS8oh3L^JkR+Bl)1w)w|V|^(`6S=x>uP-}l*K_ivx#*0JcIgrCBqA~hFyezaN2165pT9j(rZUx8J3nPl8+6V&XVSMT3$T$DG@T6lRMJkfLo_BPhFP0y|Z zc3`=?ovU36)(JB+vO49lB=?nU`dJVGG0iM8bV_A>tONeV23&@q231LId zhI4S+3RJY*762 z!GYvJ8H- zDOeOJO1qboeyPcCMW0a3eQs|@3`qQ1wLcBva9SCx z6_V9=c*1-Uy;7VoRs2Y%0dcRq@X^zo)T?V~si$LnSx~;!;agn##o}S^()s;jR^14O zm<3kb#);X_KX@lQrAIW`b%ZqTy60!LAmx+OtAHrI2ci@!3k;^s0|}OI0~(8Uwv&zDIp|QH%Mzl% z(l9nI;EIQRL>7qy&!OqA-iuF4nus`GX_>oCW8cYLI3FI6Pk*=jLodOasl7TQ@d<-T z`K`n!OU1=F%ek8I_Errab$oX#XXZ=x*ZS@V<9RB??)FHk-N>2k+ROH^G zsJwyZ!?hQTqyET{RSOt<0uLf&hYY`hxf64>I(f9f2gP4{42YQF}S>LW+J%pj&X1iIp9FSMTiBWFhJ)-dkpS6CYUqa=vh( zpwzHRzikzWfhS0CSGL^h`ffR>o4PHKGeBmMXAoQe;DND~`>j-(EH)*=2z6nKCjqij z68C9g#tHSWy(#-bvMG*bO5$5&2klr(@-Y#$W>8MH`dYf+M;vkME;3}D?WafsNdpki zi>XuGHnYXv8ky&KrkSY29{AKf@i8`vo65t9F#7&Wkp80;EjCWbWO~C#pt7HYWLWLVpThjf7@HCsPXGJylr1zu(cTcg_fe(+wTs46@m~6l}dpG32pvOE+^}P;skW4Ft=t7QTx$5FRStw*f8yR?PIs` zTT__Zj%_ zCCqkN3riDJWeipQ8EsN$ND3lK#WSsPx-#>U== z#B%J+jp-_X!!bd7A#g!u{IJG+L3p_MG$+^rv)-noLb-qObjsRDOoB{fZ{`{sNdBWv=gcCC4S0Ehn9(Rw8Q!|zf=dds$o9PyMlApqCG;PQ*AyIL^m=eY*H@`14 z3p&M(#fBtPqQ;aX5M%F(OSIqTFU!iybsbeU7Z4`!aW>?`tUsn}?NA*Bji>Fal2J2> zr{3`m?7cz`l0;P>#^>SgGvQ^}s4SD&$?37?f@1C&w;y5#q)yfdPJ`U$?w)Kg$M_Lr<6}05(Q#J2_4J+vIC-L5$~d#^kYck8 ztPFG>iHeN9Db42z^K+d?wf7311}PE&xdK%9fC&yW9V7apH3N2V_rx(A=yWWr&czm)G`WSyQpSh7r@%`}a*ej7HQc!6oU zGy)We%L&B$wSIqscjP^;0Z3r?HMVn@ZRVH1^tG@=S`k@w{m5)_n@gy0iPJd21WiOk z;ksBM=}<^WPgYxCBG|#gs7+K6GNTw(Jkzuc+*_5Ge#n=?+UFM7`^9nI9$hRbaFSS@ zj?PbSxqrf{u(Lo-W-H=VXsO?Vk!g|=6lgiq!F#eZid3WG57ZE^Q~743qkVTUBxk1& z;%1$s4JSkux);qXqI_dGT587b$1E^mmT|LrJWRG0@5%(q2N9;ilm@=jg<9*6xfqnQ z|2m?}oec`#3@UnI&v~;4FHo>S)54>2{knA;ovJbqGN$J(>+=~==}ENiVWs>+-&L8? zG6Oq+-L($7Os&(8PV;JcRn3b^$&+t#4*JT#6E|{%FCIEWs5B>#WQ;w+*MF#A8?zS@ zLSp9xH5gV1Y|p5?d#p;lEWhudT0VjJnYefLgpx0>VKlvY#Wm&k>G{0|t-@C8Bbd^m z02WZ{@c>VQ;RBVaUUEdp$Vf4x7S`I^1(m#p1zuuOg7~iAa&!%8rYQz+0@Q}`U72Mw z@1Kgd=`PwiGN@r4Kib5cOr+b%2sC&OQCU_>TzGs4;I!<@bl|Yo4k_Z2Zo_}M1Tc&< z!r#vGy0Wb6+s)X0O5gYN6mh+48L(UB&r~a6Ug3{jsWv zN&sri+w^Jh?MYt4x^5`(2qy&eLRJTB@#ly222*;quQrU5E1r4Ui#cB~wG^lPN$#U7 zV$Tx+OKonlQg{Ql-DL=2;h zbo(3-X&*S_-np69duw@4c}{GK;k?&K9k;pdXvl^V!6V_1WSV>7c@2s7vId}TP@3go zoB!J~@ENomG{<9jHSud@Q?ar{R2C(M@>-$}#`vi;JFMjwz7a>XDi?&8XRkb@YeV+1JN~r&5m0(k`pr%O0dJX^Ns`se&QQ6+nMFR&vzCEH^ z!sarS2Tk7qa*bu=|q*5Y+ilJVeu+U*%L+jPdKwf zI|iZe_D+iB)n?7uL^oqZwv=ti1il!t3}$tiC+nqV;@9{S>rWziY zW=a>D3^n5AUgzQbnOMU_?R1!qX*~vc>c4-HL_YKe8$5IW9)>{9@QKL%I zom>mMD;L&%OOk6nbuqrh=m`!wMA_DL{Lq zj^)D)E}K5!z&U)D&)+UH^yr))#dcbn9H?{IWHG6pmI&)H9)og+(g#W-CF7?l;09p<>=*g?;dz0AVhy5xOPW3!qV+2 z9lh;1$NhDMus6i>xFqR`38(>{kA^9R7fZ5Dm#gJoy8_Q145r3>(@fM0aXP!v1bIam=zj&?^L_5Q_E7>W>@#nuvy~SC*ko_#1>i*Y*t2~VseZ|01dGc?!S-+&Wh}Zi;wa&@EWYYJEml$Go zY=C?tJoU%=OGfC$gm9tXC47COqSyGFA$ z4Cd~c)Ptr0;XnbYoY>>M2GDuyiz=bEqkY!A%=3qk&D-ua>CzG${tgPA`Mcd$O{!nZ zXSK01=*q@&Q+omp8$pe2^@Zy{-PGkaH%kUchHVPF1Xm z){Nw@x$l>4|nyK*k@fia^#O5V7L_UgdRP>AVm&n^3k@~8EX z@8U#@KPG=)QK=MNB#!ya}J!k0LF_q%qaaW64gc2o_uFGO{r@s$tsLEZK|< z_@#ePck{@f^M%hwWQhlM6dt{kV3 zkHc{eIJ7T%Iuf^730~u)CS@8h4B$}xIL`3KMv!VuR3|os(aw1HtX!z`4tX4zX&~eJ!?sDnYHDS z$Vp?~8|oc_K^pk&`S7UhB!jez&AWD^!RjuuNe0L1)UVbAJ zx8G@5^h!P4@p&f z@k$1PZ}O-yWCW&bz@VBNDDI@{0fy9k^y7b`(b2K6IuxT2g7 zBevdeCuW&K2lFvGqR?XV*DE9I(sisV%JPaYQ%`*>g>n)M{27UItChchDMnohGn0Pp zK*^7kb@tEe7f_610Np#88agp)Qu~30qi8G{+xQ#Gw&~XTvpYpcd}0zDO4spK)s7z9 z2mN*Q9v&xUoe|Xy*4FE~U&-S0Z?ZD;bR4>M{layPEV0aLW0Ul5fU5nKUphFJ{yzM{ zrB)V;SUzo!=4zYl4n4Z^(jk=)jnE(aVr;@r8h2mU`NFlBeS-d2e<(x!?!MtH zBWpPPv9Mw+wmz!nHMYz1+k-T)pB5WlGO4_`bVa(XO!>nE1vVa(fBJRSyVy$ES1AOR zjO85{Nx4hEiM#9k2F?gd<+MD=lvylpDvP8lF`Q8{HLsaGuySO&n;|~}cY$MyK@-bxfyE;| z`sy~TZZh!uk9$uvG!J@4|T%eSR_s(h}8AH4ewLB z`MwF8TEUp_G?9VmkH51*avV66!jx_*5~jAIHWDJ2dZEo+TW=H;;`IG_!qTqRRr#z72Gy}GWNB_fja|x^#`J-8 zM>)mN;zPm)b>==XXnveJc@YC{r_`Bz81XEh7&8YcXKa8Ry$!Q+_k>-^fHoN8Nmufi z1zAs>#Eh_|tL(%EkW9wZ?ePH30+>2eniqAUMRhV%~uHA2Ls!6 zOx~;@g5t%Ak1ySs$x?BB1LYPE1=}XbM+tinpO`qFNR7?qO7(7(dRcy&Mt0uRS@Vg} z)^U8-n9Y}SO34mLjfY>d5Ys=r<-+C|oVh&S4X%z~7Ar{r_+`l@^<|ZCzs=XD)`R>N|YzFk! zFaG1Z4m8`|GaozM#aKtHyx%?W12r|@w2*}Xg zA)$aGAR^r`bax}p5F;_9AT3?{y6@+G-o5{ZJwI_A!!g%&_Bz*E-_P0)^U6yGD}moE zNc5fU?piu|26|<|ln=x_CoM>6=q-yYM#s<+vcP~44n2Kh%Wu!9cozGFWlu_}Vzs6L z^-xi1UC^_^yjZx#q5e~?L?)@C1>o&y^>R~i()fC1dT~|PC9$Bu0C}~lKss`_f2>0J z&Ve<#6qi9Acb%0d_%uwL8st8!`+hFqC9$vOBJCaWg0XVbp!Rdt^H=})0Dh(@%xth| zgKfqz=M%-MQPY_ZP3?9z*vG~mCD`^+K+6G>?1$omE39&#%^;j8My_A8u%?Rft05!s z@d{_z=@7?!x#QdKD_k){zY@y%$wsTf@wQJdrgW}sVZ%R-Pa z{P#N;E|N|pu_SYJ+D`p_TwQroQkkwKoY_2;1-^ z1WM|cBpEA8-DefsQBd&XTRuTsr9xx;Zv2+!d6CMB1__zIdnTZha=%&QFo3qPW(*KI zya0{Vol*8Ps-!(^JqBgMkNHmH9OJ?NV+jm2%)&|>L0`ZN9bM)@3wY7YUFT+k!dzJs@ zCZjfjX@HwQNsWM)*~b!r36_PR583)d7|CL&+QSbC!Ox?JV*6SC4Dk!vMkq~um+Gc_ za(33=(Qq@B-dVJ-Dg^BuoBXjr3w@IddEVd1%fFKpXA~IS*I@-_Yg5X8*3f|RxH|5z zI8BaL@_X~I1;hH%oZhn`TIWe^Xn)l=1AWLSDAz4jAXqLMLQMC&QRqqu>>6M2;|o~s z0Oajz6!ErS4b1lZa*!73+lkD?D=kqx3(S2GExw2MIQDc~{1v;(mFlPy1h|DT6^>`<&Mc19ST~XL!Ugye&zZhn3xO4OI?O{ zk!4Ef($d)yj^ur1>P-h%TWUN^L{qwlz3&)EkN-jhi`UKZy0wk5GFZJr5jp&cdqDR0 zQ}xXp3dTv?^2Y&vbvF@(WD7`S84Bd_&^9+=e!Yx$)BZ}(*)bJChZ0cUnYlY3>+pRnmrv=ytNHLyq~-A`9uJ-9Pw?{mXMV+YmL7E8$oj@CHd z_6t{5U1IQNHe^O4jx(AB4g*^-J*SY-nXSzrmecoKeFLv)VjiAte&y@%=Ul*0r zMfP<|GgHt;*#vHLacH)rG1$!8>^H2o_>E%;DlD5+=+J33Z4p8D%-b3F#x|i&K=X7@ zJfZcAyajmxV0gB-u($a!n^IB1&C{Pgfj&A=uVWcSUu~ONoD{m03DBNJR8@v#yjwSPJgTq%=2jEpB|V?g z*;gX8V8QAT-q|!Vz(}8p5wS|=I#D>kync9j83Pn@0VDBb$ zp51Zv#kx@dt*~Ua(40^-1VdYg(E@N@|7cjF zbVhXyS2TT3e+%yd^~QorY2rDiH0+jCST+b}i>vsmN1-w~=pm0Db=eQjzk*D!&AK)* z=F6Ja6P;6rU`Ylew>=i4ZI#Us;=Hx}aYxE~`!J?~Oa48Fht1kGs3P;&l=#MHDg|rc z<(TV99PskH%$qUy^Tbte$mO)fCcI8ZJShzbieeUmb&mHQXa#9rY3HB?iP(6GDgB^@yYXpu zG(9*YYW7B18hMY<@<-UJ5Cubmj~>0$LH9GdP+Pd59ijPfBlH z0UQ7I=EGa9ER6%}1TlZO7>To)5%Y*_xY0+#TqMrFI_OS0neUH^C3wfSCD#7?1pN`N z5evF{kZRBR5*6eU_X4#r5y~mDQXX%dF8>u@p;6?^3p%wmlabQxT0Sq^q{1~MG>}h@ zypO&^^dG4=!D!%5jF!PtP5(h4l!@u}Dn+x(2a}$O`#ik>w?1P`;^w;q8)g#_?Cot? zH>Tvm*-p;_m&X^>luVY!r>LogSMx7Z(N1go(;3?7Obh+@Yw7~a+frTO^dmb zr+`j>asWwT?}kH6D=GfG9yjYMUTOJhg9xH8Y4vvuxe;9hVfZ6qU{2Sg;n(y-^x&2a=C0;kq6crMjx|;X>SzwU@hf$Qp zzVmn|R7!cT6z)fb{60~OA{RKGRwkSXM5u*&P3slGe-)L-QAc?1wZ~a+QLTBPuk;_u z;<{UQcUElZw#$?Eo&GR6fO{wn4Y(AB$g?+R)e6(5J}TiguPv6av0P-}(SyDxZN~u@ zeG+Ltm=V%FB&iVw?$9nI4g7{hZp~$V1eH~7k-KuuOdk}m6#5T~(&f*lp^wSGipWlw z(=cU?oh$%>cX8~2@InVj^H1WR2|Uo)SR3@fN}m1cj9`|V?&WZEX*~KN-L0`!Lov3e)Ae`Wj z4m#KbKJy6>-^KCy?-soDQP=?26Z5II%sSga%gf}5;)i_PC^?{sc8%@(2a#H zmSXh{iYlq`$`C-Ls$SA(eNpt=@5rFmMtk`Efc9ON#eQ0z$R*>U0O}X%L|Rf;0nR)6u@YWsI=|fRUpdES@Qwn?!TiwHfx~vbQUN16ezc2N$u$)?xFK3tKi&PA*n~QQ6zw2azsZ3lEJR)yQS3T zK#A0n&uZ1!AYlU7_i1Tckt@Vy-i=WN9hPvxBdg5did%n@!oU2`gjMKZM+2S@FnggH zVNl^%x~%5@XS>2>Xe{^RFHPIL-jG+8H^pLC-z1oMi(K-Cv{eU79yL``ykn{W(sw9% zJOMfCM^xcyaec$A$xnAF-(t3ZA!(oI>_G^ z$oCTLv3a@2<#3zi=lDGl1%1sgh^^We)H*MI?$FcHr1>5wEqbg@{`MKJMzxjsd zBP_;JyTE*35t@7-8AgA7Bi_d;f`>rfh!MB@95Jo13TFqY%Fb82AGHJremJC>Zu!nz ztU$kYgEqc=#b2^@zJ~@7No74)$rT$^_7`SWRous@kQ9Q`5E+5SSLB*nOl0fN`*F%2 zoXe;9)LH$IRL@%}t573&TCvha@2)khSiaFhp6e(9`BM>Z22~1*fPk;N?@iZ<=8c|p z=mRIE#&nCQ?oafPN3%=f((?)?2hM%Q`T>m()RFMey}u(I^h zVl=DNWC=RkF=uuYJczkup69;!iol1YB97-Q6eOayGSGzk*0xZqqCUs0<(|ajfUunJ zyhu+&?NZYpk_W%=96PJNJ3C{EIO=_kQuQ^bF+zc94ptWKa9YXa96AvzYeffBGrp2rO>Fi+H%O8p#!08QKhNb$ueu@l+E>s3p= z*N_wjRvHhJH^i1$>k-Pf{RUoe>kxljL-^=J4(t(&H$R-u5?7fjE^`b4b(|E`eEo$H zm;iO)iRdpkzg`<=DYhA0RB#d27vA|nP9ObDp2iJGA~psDhknccC>Eul0R{6t50!pMp@PpLNOA+NZwjVJvtLj(iR7V z9WM9gsCvxK!G=@sX)(X?ij4sKi&g}TlZPyjv#N$4t!lko?rzTOp$#+|dp!beGb7!d zRoX&+^5rNB5h+Hri)H|GwT+aX3wT;Do^KdF#q z^uh(sj3@%meXdxs5on)19A2=@iXi3R3#{^pt*hJXZRG;AJD9Y?Khl>uV*p4e5y8&&X<80~6i~ zt6IGtz(}eT%Hki69MIFvxO(m>@v*a4ZP0r~gE_ZVFdh{|=E(L_CG_|;LZ5yxJQ+h0Dl%96b)Idam1@^XL&J7H*iR2f5DsvQra1gxO8nVjico7C;-x9O}BY^ zOMO-!vZRI7kAl?O@&xNQL(^f-{6OMpC3)L(*^<@wu;4Bes~aM?o~)s+MD@kvubZ>@ z!C>_5YJjU7NWn3#bBn$wrSj`G;|)bXpIG|CdK#MhP{@yF?j@8(heT8-V{f$2IOz92 z`(7FUCU)x4n}*VY-AeW{fL2prKc~#Du@k3cWN^%BaTerd-Es84^|f`MY)EN zwN%98r03vLJ-FO7iTn=1b6Np*4W2_Atr`LB&Ig~?Pq;3Zz6$MKrqhn7(5MgDX%Ihg zyuWPuLX0z(Q;@EfCKS{bV%A4;TL`d!k83^-}B1?Wh_D+R@O~g3JwyaQH$%<>Sn$ftSW zj5NlkRp4;2m$#9tFO@(}tWBWm{13uhHzK2t4cjrP@cq~|xlPGDTcSZS0A7<%gz&ErOhqqMI^<=OYhh)Fo|FbpIB~LUy9LtfMK8Wv9I^;M>1TiTaLYaiM^^Zwcz| zu;g!%16U+a*<~aFyj%d}au+rcOw_IOdcKN@9?!@H@qS}S8b}!KfJ$m9kW7o^QnIVe zvzYavep%kigO)s;dpTcb=sNYg<&M?sK|3_JJRWOD z|G>p-@xIUaY{mZwF99K}$tEpXbcZUS+Zx}5QVxuxgni354lGWBH={%Zx>-kb7^%lr zB%5|M2F#Z)>VE^;%35JV8$Z%Mfx`9dk$4>H46KTrU@pMZ`b|RF1Rr(yyKJ3nV-%in zMlBUP+6kpXO%D0a$ohftt^>o#^8ef;s)@JA`)UY3=$Y;=Q1Mks>!KnJpsogQp8ChN z$_6k=F<;TG?)nC?UiaT6Y-WNn;z$g|Hhw z_mJW1E+JF`bQKx_!a|SEd!~IAwK$vBylB1_W>TRMttu`IlXlcwe}J!LVjO{yRJd6O zzf4%}*Aczvylm!kW)E1TzXJ$M#R!=2DinLoJf$xO+BQVrsgNidWkk5H2{jy*ikSW5 zVy#Ug{r9~JuO*S;;de>cgKo{(0X3e<)Jh{-IO*{t;WQb%8_)%=S!ilpRPO}S0xkA+ z&wTxj{XLAowVm`sZ(y+%Uv&i$ntr5YNIEIbJA-D`dn+`{^ZjV2_9E_k(Sdo#Wl-?X z?0d)x5kWGO<|HS5bO+S0s?GQf%ETMCvp8LX<29hSAD8Lc6?kLMx`3CJ_)@ANJz&%TJ(3^8`kZ+>eeEaj;&`;*A z0<_UCQ?zV@Qj}o2N|8(Y8tH&;-IGxz^JaRK%Q&53L6L(n_CNCc?2l+e7W=SPm0nWP z5xS6MPb222j#|75y}ywJiVRQKCRo^I7(cABRUD!|ef!=_)Dt9k3NTu6elxBCGbLzZ zd1FmyAz;Od-3IZhVnZJvpvQF5_Xb18k%qO!e`eYS>XJg)0@;P1wUK^uVechOx1-^f z+2u6xPmO$3S@SsW#Keg64LeWpKw{E}4&=KhRcl-9NtOuuLCZa`YkwhhTsZc2_Yy?~-oh?{V<5iTI;C%NH$1%|58bb9r+otmmIkobG02pAoO^ zYUN#5`hjWeDbGeed0S|Fn9(OSv@B9UniqhJTzA;c_r$zck{DGo6||&d?X$8yyGC#0 zW=>8|eV4gw)zP1@A2+8Al0&h9=w~RPNZVwvquNA)ABpdMs}z(c%POd*`?5C=F>v!x zy~7NN%JdQ|K58q?+$`P>Kbn$BvO8JmH8R0`^lXmAKQ@Q#D*oxR=lQUIBqcdpp0qr4O$6ACZsaw49Y_28Rd(Ou?*807C*26ET z>*CL&n9|n1LeQVu#G7-E_v4bh8~YgmrvWbMKouIW)01W4^sD3-q@#-En<74dOZ`}1 zGK0EX^=W+0d)-X!MYL8N)*2N6Bl=35ek(~FeO!>Q^FS1|E0h2bNlhW;=`Sk$0oNy% zJVjcxp|HCC2Fx*38zmp!`n)|(;QciQozUQt*GOedpkpV?!_${~GJhOY)U8f28E3X+ z?%D?T;Jd2bM{D$5t7qio(OU{;Y7`oMfsK*paW*}n7g^GxzC8P1j^abB5>~4ahsgl4 z#@;WDKCtP-q+f^)3eUa@YmJl!aM^uP2UNlW^Y&f3mPEah9knKeWv4IQ5>^Eu^HnRM z)HeqBLC@r9UJ6?%M8{`}tjMlM!wKR#oSG@QoXjF@&+_;pyy-r8dy-D72+L|cUC7(I zfPFiwx@N3W5cREC<3Z(xd6M?YOu|PS4=1k)?`VMC=67ryl)c?aD35;B#$`OZ z=Qv!_ z1^TTx`~X;z`00q%^f3)s1L!yCm6SXBnk^KRxoR6WSkmIaCN}s<2-lN%3q1RLaINTs z%I*9K-RE!sPZZX>2j*s0)WwuOi!+i`W9HcU!d%(@1WMzi-ImDo2b$qtMc*ZU+TT>Z zk!Nwz)Urt=&@3Vp&HrKag9&}W#W&Y~h8K9f=p@VyYGc~?#1VA1L*98tMasf4G;^4Wn6FfwwSUv>wvbXJaLhKR#j3)U3wrD02ChlgisAwE9arswIUQEuWc#ZAv3-)tgo=G1)(A8o|2>Q&ySRQp->4~ z0x7)Se7r8>t|J)m-fUMF)2J@TYsugb^t84(>bvkrShC6wb2v70OPO0LDv6@@KL76rN_a-hcAIQ@KS09hg%6mU&A%rlYL-h&Mao`|gZnmR)jm zoACa&Xt>tcueD7D*gr9~msYO zr8lr>YVwkbDk1-4N+SgbmO1?Bx*mOs48XwoYXY3%~CzsTD%~vUlek2rQli!emxZU5k7^OoT>)BtxE1MmK z0us%@Kh*nW>9HyT_6gf7)oP|iqp;R#+!#~rgm?#8q@)V7qHD9CN|1ErCS zm6p7F`Y+}3cUn-E=jZhlDu^@X)2=6|zn^U|m(e>P*lL+2e4k1;>C}_Wd}0iG$9}y> z{eh=!d2fo4sB^DyHNM^lT{X2psqj<|yN`D3Qc*NB=Y@jk|3kNQVb6()J9X%R;mXD( zcyG9ttp7rE(-Wqc)yFDD>GEFtn0?I+Kc@W99{Chw^MQr6>J;OI=ICRsqguloE&Qb( z|Nf4*{zC+fa9zKU@I#iB2kz2mZn>Y1J~&p*ze0$%arvrE{B`x^JN*^0xa&7~bdEo| z{JQOI>ChjQyygQYKx@Mt{-`m3ZEd^PXYB)WWCB*WDlYXQ9g<^J!m&UKC|v>r5K2=3x{c$mCb*-lhcn_( zjoHt}ITIo+dN5<%OnfVRvyud`R2w#d*BY?^Fm)XnVl*Z?&im~ z79H3b{pZyj*i~ppf7JZ{eHEV3eh z!z3-&bKTQgzjP?O^$SV0`~a&a{m;+L=HuzAx`#WLFoT(In7^oK2Fa7OS@6uMtLOhS z&ty~D-@zO^pE#^2$(KCGF@8+8W+Ga~BMRX+5Tp2ql&iyMZ4f5~{dfb&sj!3%nb4NP z3amO60>WNF5%HgcySnGICZBY7UxqZEkR>L0uRM9p9(^6HK74mA9Ugutv*6XhG=BAn zbZpBR5{-O~!lnDA(DP{$05x-w*vx0kAzC;lLwWL_gn$B+7h@Mc3qRcNdDI9{ZGC-I zs+atLOtOEOuR@rQ$~OeajxxQcy@)sZy+b?8l|}z$eix9paR92y8snD|e<|M`iM@Uw zdR;$#VwGD2EuKG1J;5YhxBD3toL6`;R5EBSLm$0pMINcWU>?Uc z5)64d5ZUv7pz9hB@yA^Aw8Y;MBkHCpf=`EB(sd^%5Sayp(7)G!D{@z{rSJ(Xu_T;YnBUdS&~c^)FnJ5{#}*$sH4TjXE*CzDsK%L z4~gn!l=|;XF#s@}=Q1t?X?7C2eyYkG{=zrJZ)nUF5WzLh6vX_eG(scskTpms!N-lb zlVxe!aGZ+5+e#zFU69mHT7Wt$Vgpb(bMjPSiV$``MtD7q>CYw5xO~~8y~IG;5e?^^ zw`3v9IpqR-9X>nB_ROpy=Z7+w5;8Wkwam(#WC&0(!zNd6P64iPD&n2=kG5CQjv^OB zTLTualn;k2_OSpL8WFH02;D8mK?3tlnHCLOUE=JLsuV{K0E0U!3|^%QEh|Lp|I5*L z&<-VrSkebhd%d-Nwl1QbSwTFnI!1}NSxU)U@sY3_;F%K&8wQaKmr+(qsnkwRtB-5k zru8Jbc}|_CLg9O%fl5qjY^qhGmw65o@AJx?W&oy+3fl#-n}d98uE_41Fkv!>RV(?6<94w z?}krlV#d}KzMe-(lZG+%P3U8*GY&GH34C0d{ts)O8&gGl4Q}(|4@tmiVHM|>-P#rb zhAgItwMp}vkGklS$jm;jl}a%n-J(QDDsm$UNSVqE zi!$CpU5E=-pH<7wF_@E( zZ;qxPS^x8eqS&05ayE2q$$db^b>I}4RQD|fWP zXO%46bZ6)lT1je@Pj`Rw>vNPpZqI|5C?9$f(5pxJpF^K@!37vuEo7iO|DA*D)L0Df z02LT3)`qYgSLIa7xTmX3UeiB-Til6!Um=Vl94+{xmd9E(ix;C=*{LDOqIE{$cS|@E zlGAm1Y5m?(348I$?QV+1{s`6XYea2yeAod-Q^f`z`1X*i0?2&>m3=qay}mjZLo|9@ z;a$@%z9*0ktFAgFwUg`1e6jdB z-+o;L8PKfRxxeC!Xulk8;Z41{8w801L(f zqK|)80q7VXT&0T1Pi0>l9+S@@GgZoR@XftK)GcF4S0E5k6Q-jp%{h>vlyA6s*Q`cw zJ-hBaWngAs3wspS8qI%B2dwo-M73DYo~Q*YrC3#$KLVrKoXB)l5Y5|MnRWJXkGdk& z7(d@i$J3kV@o;(4~FAMV>71k@Nt z$^M{*rP8XGkKNk&U&uQ(d)0P~t*$x4E{1GERPT^Ewx@NQ2Zr-gjO|UCP-UQ0P2zZs zv?-uujPP3dOxUf;#}A^3BAizp!YWeA-j3D?6CIaHvDKmG&=O@NUYe9}=5C0vfb6__ zar`{I#@2*LzpT5I{4HU)`$53aqY$AojK=!ZYDXE$Gji*=Y%1+6DXx9`=tP-$&RA3f*g9tz)Ec$|1A!bDuFQ; z)b746@6bxLBUYVtHz_Wic`47t=j%phKnm9OgedKCw9e^a*hF>Lk-=5UV>lXB+pLse z#Q*J4djZT>+f9#q_7tSqHlPQOw3!#F53h@XvN-bYN*yW5A-8-4d5{9jZ=+LI<;Az2 z&xKSlpHTO3HyZ7pIZ%_cS^j(Me#N-#n+0up%HgbE9Fc2Q5v^C=e|+cu`@(7Tx-R>z z#$e!ErI`**L8}leDXjf456mN@+5f2?6vQu!o7=nh-Wq+bd%KK+Zn6%>F}t!Wz%&t7ft<+rRn$bYbQiRZODUw~(U5UsOWmsE3a36V9t(ZG9b; zlXQw%AhbSv)in%xKgJpmdidJ_^O!H~V9sE+jWqZ6innY3ozDL4@41P+-7u)jwe++X z64J8Nx%+{ZpIsr4Ur8ZyM0I?M%xP$mP=#}eV={WTa?fWLXWj1!_EgCh62}7|NyGsq z%9>wdIJhQ?8d~E`4VwG9{ispItM~Z=qXhmvk}xD*`9D?D{>o!dHSY-@vObJO7SqE3 z6we+L)`B&LN~ouSHBh3>yr@e|G%U(dv-fbw_q$q+dxqM6GslA(^L{Dv8L3X8T31_^IXxOjQi(j9#?qNuRz-7}oF z|5S+6Qw!{J2?=BtWtCFiH^y|Ip3I+4m=yr(>&fn=-HPx5fAC-N8Ha$4jg7@AfnTGc vp-EH)UJUqf8DM%D0Af7N|8Kwkdk>OB(VSOjdBgsx>-|hkSG7{vHthcZI;KI^ literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircle@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..b4eb5221ffd0c2791d079ebfc6bafcf0ac7a31ba GIT binary patch literal 263521 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

ga#N){uvTxT7 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. Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.IO; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.IO.Stores; +using osu.Game.Skinning; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public abstract class SkinnableTestScene : OsuGridTestScene + { + private static Skin getSkinFromResources(SkinManager skins, string name) + { + using (var storage = new DllResourceStore("osu.Game.Rulesets.Osu.Tests.dll")) + { + var tempName = Path.GetTempFileName(); + + File.Delete(tempName); + Directory.CreateDirectory(tempName); + + var files = storage.GetAvailableResources().Where(f => f.StartsWith($"Resources/{name}")); + + foreach (var file in files) + using (var stream = storage.GetStream(file)) + using (var newFile = File.Create(Path.Combine(tempName, Path.GetFileName(file)))) + stream.CopyTo(newFile); + + return skins.GetSkin(skins.Import(tempName).Result); + } + } + + private Skin metricsSkin; + private Skin defaultSkin; + private Skin specialSkin; + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + var skins = new SkinManager(LocalStorage, ContextFactory, null, audio); + + metricsSkin = getSkinFromResources(skins, "metrics_skin"); + defaultSkin = getSkinFromResources(skins, "default_skin"); + specialSkin = getSkinFromResources(skins, "special_skin"); + } + + public void SetContents(Func creationFunction) + { + Cell(0).Child = new LocalSkinOverrideContainer(null) { RelativeSizeAxes = Axes.Both }.WithChild(creationFunction()); + Cell(1).Child = new LocalSkinOverrideContainer(metricsSkin) { RelativeSizeAxes = Axes.Both }.WithChild(creationFunction()); + Cell(2).Child = new LocalSkinOverrideContainer(defaultSkin) { RelativeSizeAxes = Axes.Both }.WithChild(creationFunction()); + Cell(3).Child = new LocalSkinOverrideContainer(specialSkin) { RelativeSizeAxes = Axes.Both }.WithChild(creationFunction()); + } + + protected SkinnableTestScene() + : base(2, 2) + { + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index d44a0cd841..8692744c0d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -7,7 +7,6 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Tests.Visual; using osuTK; using System.Collections.Generic; using System; @@ -19,37 +18,32 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestSceneHitCircle : OsuTestScene + public class TestSceneHitCircle : SkinnableTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableHitCircle) }; - private readonly Container content; - protected override Container Content => content; - private int depthIndex; public TestSceneHitCircle() { - base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); - - AddStep("Miss Big Single", () => testSingle(2)); - AddStep("Miss Medium Single", () => testSingle(5)); - AddStep("Miss Small Single", () => testSingle(7)); - AddStep("Hit Big Single", () => testSingle(2, true)); - AddStep("Hit Medium Single", () => testSingle(5, true)); - AddStep("Hit Small Single", () => testSingle(7, true)); - AddStep("Miss Big Stream", () => testStream(2)); - AddStep("Miss Medium Stream", () => testStream(5)); - AddStep("Miss Small Stream", () => testStream(7)); - AddStep("Hit Big Stream", () => testStream(2, true)); - AddStep("Hit Medium Stream", () => testStream(5, true)); - AddStep("Hit Small Stream", () => testStream(7, true)); + AddStep("Miss Big Single", () => SetContents(() => testSingle(2))); + AddStep("Miss Medium Single", () => SetContents(() => testSingle(5))); + AddStep("Miss Small Single", () => SetContents(() => testSingle(7))); + AddStep("Hit Big Single", () => SetContents(() => testSingle(2, true))); + AddStep("Hit Medium Single", () => SetContents(() => testSingle(5, true))); + AddStep("Hit Small Single", () => SetContents(() => testSingle(7, true))); + AddStep("Miss Big Stream", () => SetContents(() => testStream(2))); + AddStep("Miss Medium Stream", () => SetContents(() => testStream(5))); + AddStep("Miss Small Stream", () => SetContents(() => testStream(7))); + AddStep("Hit Big Stream", () => SetContents(() => testStream(2, true))); + AddStep("Hit Medium Stream", () => SetContents(() => testStream(5, true))); + AddStep("Hit Small Stream", () => SetContents(() => testStream(7, true))); } - private void testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null) + private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null) { positionOffset = positionOffset ?? Vector2.Zero; @@ -70,18 +64,22 @@ namespace osu.Game.Rulesets.Osu.Tests foreach (var mod in Mods.Value.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); - Add(drawable); + return drawable; } - private void testStream(float circleSize, bool auto = false) + private Drawable testStream(float circleSize, bool auto = false) { + var container = new Container { RelativeSizeAxes = Axes.Both }; + Vector2 pos = new Vector2(-250, 0); for (int i = 0; i <= 1000; i += 100) { - testSingle(circleSize, auto, i, pos); + container.Add(testSingle(circleSize, auto, i, pos)); pos.X += 50; } + + return container; } protected class TestDrawableHitCircle : DrawableHitCircle diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 70abfac501..19997e8844 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -45,7 +45,7 @@ namespace osu.Game.Skinning CurrentSkinInfo.Value = SkinInfo.Default; }; - CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = getSkin(skin.NewValue); + CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = GetSkin(skin.NewValue); CurrentSkin.ValueChanged += skin => { if (skin.NewValue.SkinInfo != CurrentSkinInfo.Value) @@ -80,7 +80,7 @@ namespace osu.Game.Skinning { await base.Populate(model, archive, cancellationToken); - Skin reference = getSkin(model); + Skin reference = GetSkin(model); if (!string.IsNullOrEmpty(reference.Configuration.SkinInfo.Name)) { @@ -99,7 +99,7 @@ namespace osu.Game.Skinning ///

0xJlZ?;ZV^q_mZ9Q!A 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..69b601e006d36419bf3392ec832097b3035b8676 GIT binary patch literal 262013 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

/// The skin to lookup. /// A instance correlating to the provided . - private Skin getSkin(SkinInfo skinInfo) + public Skin GetSkin(SkinInfo skinInfo) { if (skinInfo == SkinInfo.Default) return new DefaultSkin(); diff --git a/osu.Game.Tests/Visual/UserInterface/OsuGridTestScene.cs b/osu.Game/Tests/Visual/OsuGridTestScene.cs similarity index 97% rename from osu.Game.Tests/Visual/UserInterface/OsuGridTestScene.cs rename to osu.Game/Tests/Visual/OsuGridTestScene.cs index 096ac951de..c09f4d6218 100644 --- a/osu.Game.Tests/Visual/UserInterface/OsuGridTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGridTestScene.cs @@ -5,7 +5,7 @@ using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -namespace osu.Game.Tests.Visual.UserInterface +namespace osu.Game.Tests.Visual { /// /// An abstract test case which exposes small cells arranged in a grid. diff --git a/osu.iOS.props b/osu.iOS.props index 55d1afa645..e87e759492 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -35,7 +35,7 @@ prompt 4 - iPhone Distribution + iPhone Developer true true Entitlements.plist From 466297df552500a5d789aa8e846469474332b729 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2019 17:05:13 +0900 Subject: [PATCH 0406/2815] Fix shaking test --- osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs | 12 +++++++----- osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs | 15 +++++++-------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index 8692744c0d..84a7bfc53e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -55,11 +55,7 @@ namespace osu.Game.Rulesets.Osu.Tests circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize }); - var drawable = new TestDrawableHitCircle(circle, auto) - { - Anchor = Anchor.Centre, - Depth = depthIndex++ - }; + var drawable = CreateDrawableHitCircle(circle, auto); foreach (var mod in Mods.Value.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); @@ -67,6 +63,12 @@ namespace osu.Game.Rulesets.Osu.Tests return drawable; } + protected virtual TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto) => new TestDrawableHitCircle(circle, auto) + { + Anchor = Anchor.Centre, + Depth = depthIndex++ + }; + private Drawable testStream(float circleSize, bool auto = false) { var container = new Container { RelativeSizeAxes = Axes.Both }; diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs index 3d8afd66f4..84a73c7cfc 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.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.Graphics; using osu.Framework.MathUtils; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Tests { public class TestSceneShaking : TestSceneHitCircle { - public override void Add(Drawable drawable) + protected override TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto) { - base.Add(drawable); + var drawableHitObject = base.CreateDrawableHitCircle(circle, auto); - if (drawable is TestDrawableHitCircle hitObject) - { - Scheduler.AddDelayed(() => hitObject.TriggerJudgement(), - hitObject.HitObject.StartTime - (hitObject.HitObject.HitWindows.HalfWindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current); - } + Scheduler.AddDelayed(() => drawableHitObject.TriggerJudgement(), + drawableHitObject.HitObject.StartTime - (drawableHitObject.HitObject.HitWindows.HalfWindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current); + + return drawableHitObject; } } } From e579bce18afab63f9c8e63a0bd32418a12378b9d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2019 18:14:06 +0900 Subject: [PATCH 0407/2815] Don't report delta patch failures to sentry --- osu.Desktop/Updater/SquirrelUpdateManager.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 78a1e680ec..fa41c061b5 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -27,6 +27,8 @@ namespace osu.Desktop.Updater public Task PrepareUpdateAsync() => UpdateManager.RestartAppWhenExited(); + private static readonly Logger logger = Logger.GetLogger("updater"); + [BackgroundDependencyLoader] private void load(NotificationOverlay notification, OsuGameBase game) { @@ -77,7 +79,7 @@ namespace osu.Desktop.Updater { if (useDeltaPatching) { - Logger.Error(e, @"delta patching failed!"); + logger.Add(@"delta patching failed; will attempt full download!"); //could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959) //try again without deltas. @@ -163,16 +165,11 @@ namespace osu.Desktop.Updater { public LogLevel Level { get; set; } = LogLevel.Info; - private Logger logger; - public void Write(string message, LogLevel logLevel) { if (logLevel < Level) return; - if (logger == null) - logger = Logger.GetLogger("updater"); - logger.Add(message); } From 98813222afbeadbf823af00724fa23a4a9a55bbd Mon Sep 17 00:00:00 2001 From: Dan Balasescu <1329837+smoogipoo@users.noreply.github.com> Date: Mon, 29 Jul 2019 18:35:22 +0900 Subject: [PATCH 0408/2815] Adjust comment --- osu.Game/Skinning/SkinReloadableDrawable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinReloadableDrawable.cs b/osu.Game/Skinning/SkinReloadableDrawable.cs index 397c6656a5..4bbdeafba5 100644 --- a/osu.Game/Skinning/SkinReloadableDrawable.cs +++ b/osu.Game/Skinning/SkinReloadableDrawable.cs @@ -38,7 +38,7 @@ namespace osu.Game.Skinning private void onChange() => // schedule required to avoid calls after disposed. - // note that this has the side-effect of components only performance a skin change when they are alive. + // note that this has the side-effect of components only performing a skin change when they are alive. Scheduler.AddOnce(() => SkinChanged(skin, allowDefaultFallback)); protected override void LoadAsyncComplete() From ac01e9fbebbe271c368361aa31e29193e4140e1a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2019 18:36:07 +0900 Subject: [PATCH 0409/2815] Fix legacy scores with no online ID being imported with a non-null ID --- osu.Game/Scoring/Legacy/LegacyScoreParser.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs index 0fdbd56c92..2e4b4b3a9a 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs @@ -80,6 +80,9 @@ namespace osu.Game.Scoring.Legacy else if (version >= 20121008) scoreInfo.OnlineScoreID = sr.ReadInt32(); + if (scoreInfo.OnlineScoreID <= 0) + scoreInfo.OnlineScoreID = null; + if (compressedReplay?.Length > 0) { using (var replayInStream = new MemoryStream(compressedReplay)) From 396892da1aa4bd2aeff96155d477189db3b1cb26 Mon Sep 17 00:00:00 2001 From: Dan Balasescu <1329837+smoogipoo@users.noreply.github.com> Date: Mon, 29 Jul 2019 18:43:05 +0900 Subject: [PATCH 0410/2815] Describe how the ratio came to be --- osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs index 8b6b483618..f7888f8022 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs @@ -18,6 +18,7 @@ namespace osu.Game.Rulesets.Osu.UI Anchor = Anchor.Centre; Origin = Anchor.Centre; + // Calculated from osu!stable as 512 (default gamefield size) / 640 (default window size) Size = new Vector2(0.8f); InternalChild = new Container From e8c039bb8a7d6fb5babe3628f1fd898402194969 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 29 Jul 2019 18:45:16 +0900 Subject: [PATCH 0411/2815] Use a receptor model instead --- .../UserInterface/TestSceneBackButton.cs | 4 +- osu.Game/Graphics/UserInterface/BackButton.cs | 38 +++++++++++-------- .../Input/Bindings/GlobalActionContainer.cs | 29 +------------- osu.Game/OsuGame.cs | 5 ++- 4 files changed, 32 insertions(+), 44 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBackButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBackButton.cs index 38a9af05d8..05d14abe30 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBackButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBackButton.cs @@ -22,6 +22,7 @@ namespace osu.Game.Tests.Visual.UserInterface public TestSceneBackButton() { BackButton button; + BackButton.BackButtonReceptor receptor = new BackButton.BackButtonReceptor(); Child = new Container { @@ -31,12 +32,13 @@ namespace osu.Game.Tests.Visual.UserInterface Masking = true, Children = new Drawable[] { + receptor, new Box { RelativeSizeAxes = Axes.Both, Colour = Color4.SlateGray }, - button = new BackButton + button = new BackButton(receptor) { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, diff --git a/osu.Game/Graphics/UserInterface/BackButton.cs b/osu.Game/Graphics/UserInterface/BackButton.cs index 48bf0848ae..fe82a5d8e2 100644 --- a/osu.Game/Graphics/UserInterface/BackButton.cs +++ b/osu.Game/Graphics/UserInterface/BackButton.cs @@ -10,14 +10,16 @@ using osu.Game.Input.Bindings; namespace osu.Game.Graphics.UserInterface { - public class BackButton : VisibilityContainer, IKeyBindingHandler + public class BackButton : VisibilityContainer { public Action Action; private readonly TwoLayerButton button; - public BackButton() + public BackButton(BackButtonReceptor receptor) { + receptor.OnBackPressed += () => Action.Invoke(); + Size = TwoLayerButton.SIZE_EXTENDED; Child = button = new TwoLayerButton @@ -37,19 +39,6 @@ namespace osu.Game.Graphics.UserInterface button.HoverColour = colours.PinkDark; } - public bool OnPressed(GlobalAction action) - { - if (action == GlobalAction.Back) - { - Action?.Invoke(); - return true; - } - - return false; - } - - public bool OnReleased(GlobalAction action) => action == GlobalAction.Back; - protected override void PopIn() { button.MoveToX(0, 400, Easing.OutQuint); @@ -61,5 +50,24 @@ namespace osu.Game.Graphics.UserInterface button.MoveToX(-TwoLayerButton.SIZE_EXTENDED.X / 2, 400, Easing.OutQuint); button.FadeOut(400, Easing.OutQuint); } + + public class BackButtonReceptor : Drawable, IKeyBindingHandler + { + public Action OnBackPressed; + + public bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.Back: + OnBackPressed.Invoke(); + return true; + } + + return false; + } + + public bool OnReleased(GlobalAction action) => action == GlobalAction.Back; + } } } diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 373333696a..669fd62e45 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -7,7 +7,6 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Bindings; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Input.Bindings { @@ -56,32 +55,8 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed), }; - protected override IEnumerable KeyBindingInputQueue - { - get - { - var queue = base.KeyBindingInputQueue.ToList(); - - if (handler != null) - yield return handler; - - BackButton backButton = null; - - foreach (var drawable in queue) - { - if (drawable is BackButton button) - { - backButton = button; - continue; - } - - yield return drawable; - } - - if (backButton != null) - yield return backButton; - } - } + protected override IEnumerable KeyBindingInputQueue => + handler == null ? base.KeyBindingInputQueue : base.KeyBindingInputQueue.Prepend(handler); } public enum GlobalAction diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 64958c9a09..3018cea276 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -84,6 +84,8 @@ namespace osu.Game protected OsuScreenStack ScreenStack; protected BackButton BackButton; + private BackButton.BackButtonReceptor backButtonReceptor; + private VolumeOverlay volume; private OsuLogo osuLogo; @@ -407,8 +409,9 @@ namespace osu.Game RelativeSizeAxes = Axes.Both, Children = new Drawable[] { + backButtonReceptor = new BackButton.BackButtonReceptor(), ScreenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }, - BackButton = new BackButton + BackButton = new BackButton(backButtonReceptor) { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, From 0635680db52db1d6063c09dbdf88fa459a07b64f Mon Sep 17 00:00:00 2001 From: jorolf Date: Mon, 29 Jul 2019 11:49:59 +0200 Subject: [PATCH 0412/2815] fix some code quality --- osu.Game/Skinning/LegacySkin.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 24c6c9e4ec..c9c64c96f2 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -54,28 +54,33 @@ namespace osu.Game.Skinning public override Drawable GetDrawableComponent(string componentName) { bool animatable = false; - (bool looping, double frametime) animationData = (false, 1000 / 60d); + bool looping = true; + const double frametime = 1000 / 60d; switch (componentName) { case "Play/Miss": componentName = "hit0"; animatable = true; + looping = false; break; case "Play/Meh": componentName = "hit50"; animatable = true; + looping = false; break; case "Play/Good": componentName = "hit100"; animatable = true; + looping = false; break; case "Play/Great": componentName = "hit300"; animatable = true; + looping = false; break; case "Play/osu/number-text": @@ -93,7 +98,7 @@ namespace osu.Game.Skinning if (texture != null && animatable) { - var animation = new TextureAnimation { DefaultFrameLength = animationData.frametime }; + var animation = new TextureAnimation { DefaultFrameLength = frametime }; for (int i = 1; texture != null; i++) { @@ -101,7 +106,9 @@ namespace osu.Game.Skinning texture = GetTexture($"{componentName}-{i}"); } - animation.Repeat = animationData.looping; + // This comment can be removed once we have components which are looping + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + animation.Repeat = looping; return animation; } @@ -109,10 +116,7 @@ namespace osu.Game.Skinning { texture = GetTexture(componentName); - if (texture == null) - return null; - - return new Sprite { Texture = texture }; + return texture == null ? null : new Sprite { Texture = texture }; } } From c01461b9511c553c8e0c5b54e63f538cd739f78d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 29 Jul 2019 19:12:41 +0900 Subject: [PATCH 0413/2815] Recalculate path size when path radius changes --- .../Objects/Drawables/Pieces/SliderBody.cs | 2 +- .../Objects/Drawables/Pieces/SnakingSliderBody.cs | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs index 6bc19ee3b5..24a437c20e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces protected Path Path => path; - public float PathRadius + public virtual float PathRadius { get => path.PathRadius; set => path.PathRadius = value; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs index a3d3893c8b..0590ca1d96 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs @@ -24,6 +24,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces public double? SnakedStart { get; private set; } public double? SnakedEnd { get; private set; } + public override float PathRadius + { + get => base.PathRadius; + set + { + if (base.PathRadius == value) + return; + + base.PathRadius = value; + + Refresh(); + } + } + public override Vector2 PathOffset => snakedPathOffset; /// From b4bcdb1c7013f2013c147a794111d2d96c0dc384 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2019 19:44:58 +0900 Subject: [PATCH 0414/2815] 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 6744590f0d..2c5e54d4f5 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -63,6 +63,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 0b2baa982b..472f6e9d65 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -15,7 +15,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 55d1afa645..0a19eac2b5 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From f21a2f7e5e4ced31256d8bdd0ef9d64b24426e15 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2019 21:49:12 +0900 Subject: [PATCH 0415/2815] Fix version overlay displaying briefly before it should --- osu.Desktop/OsuGameDesktop.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 18f0cd1f80..cd39ca0699 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.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; @@ -52,11 +52,7 @@ namespace osu.Desktop if (!noVersionOverlay) { - LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, v => - { - Add(v); - v.Show(); - }); + LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, Add); if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) Add(new SquirrelUpdateManager()); From ece5a9622e53160e337b699c3d7cd2c164c29cb5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2019 06:31:45 +0900 Subject: [PATCH 0416/2815] Fix scores without an online ID not always presenting correctly --- osu.Game/OsuGame.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 9358a412d9..e71dd67bf2 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -265,7 +265,16 @@ namespace osu.Game { // The given ScoreInfo may have missing properties if it was retrieved from online data. Re-retrieve it from the database // to ensure all the required data for presenting a replay are present. - var databasedScoreInfo = ScoreManager.Query(s => s.OnlineScoreID == score.OnlineScoreID); + var databasedScoreInfo = score.OnlineScoreID != null + ? ScoreManager.Query(s => s.OnlineScoreID == score.OnlineScoreID) + : ScoreManager.Query(s => s.Hash == score.Hash); + + if (databasedScoreInfo == null) + { + Logger.Log("The requested score could not be found locally.", LoggingTarget.Information); + return; + } + var databasedScore = ScoreManager.GetScore(databasedScoreInfo); if (databasedScore.Replay == null) From cec26a270ecf65453e808f5c6b5d5634c5e59ade Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2019 07:03:59 +0900 Subject: [PATCH 0417/2815] Fix using right mouse button to drag at song select seeking incorrectly with many beatmaps loaded Closes #5195 --- .../Graphics/Containers/OsuScrollContainer.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuScrollContainer.cs b/osu.Game/Graphics/Containers/OsuScrollContainer.cs index 53092ddc9e..8fc8dec9fd 100644 --- a/osu.Game/Graphics/Containers/OsuScrollContainer.cs +++ b/osu.Game/Graphics/Containers/OsuScrollContainer.cs @@ -27,11 +27,12 @@ namespace osu.Game.Graphics.Containers private bool shouldPerformRightMouseScroll(MouseButtonEvent e) => RightMouseScrollbar && e.Button == MouseButton.Right; - private void scrollToRelative(float value) => ScrollTo(Clamp((value - Scrollbar.DrawSize[ScrollDim] / 2) / Scrollbar.Size[ScrollDim]), true, DistanceDecayOnRightMouseScrollbar); + private void scrollFromMouseEvent(MouseEvent e) => + ScrollTo(Clamp(ToLocalSpace(e.ScreenSpaceMousePosition)[ScrollDim] / DrawSize[ScrollDim]) * Content.DrawSize[ScrollDim], true, DistanceDecayOnRightMouseScrollbar); - private bool mouseScrollBarDragging; + private bool rightMouseDragging; - protected override bool IsDragging => base.IsDragging || mouseScrollBarDragging; + protected override bool IsDragging => base.IsDragging || rightMouseDragging; public OsuScrollContainer(Direction scrollDirection = Direction.Vertical) : base(scrollDirection) @@ -42,7 +43,7 @@ namespace osu.Game.Graphics.Containers { if (shouldPerformRightMouseScroll(e)) { - scrollToRelative(e.MousePosition[ScrollDim]); + scrollFromMouseEvent(e); return true; } @@ -51,9 +52,9 @@ namespace osu.Game.Graphics.Containers protected override bool OnDrag(DragEvent e) { - if (mouseScrollBarDragging) + if (rightMouseDragging) { - scrollToRelative(e.MousePosition[ScrollDim]); + scrollFromMouseEvent(e); return true; } @@ -64,7 +65,7 @@ namespace osu.Game.Graphics.Containers { if (shouldPerformRightMouseScroll(e)) { - mouseScrollBarDragging = true; + rightMouseDragging = true; return true; } @@ -73,9 +74,9 @@ namespace osu.Game.Graphics.Containers protected override bool OnDragEnd(DragEndEvent e) { - if (mouseScrollBarDragging) + if (rightMouseDragging) { - mouseScrollBarDragging = false; + rightMouseDragging = false; return true; } From 62f69581652254283a82b75d4e16382b2aa40bde Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2019 07:23:25 +0900 Subject: [PATCH 0418/2815] Fix tournament tests not running --- .../TestSceneDrawableTournamentMatch.cs | 3 +- osu.Game.Tournament.Tests/LadderTestScene.cs | 5 ++-- .../Screens/TestSceneGameplayScreen.cs | 3 +- .../Screens/TestSceneScheduleScreen.cs | 3 +- .../Screens/TestSceneShowcaseScreen.cs | 3 +- .../TestSceneTournamentSceneManager.cs | 3 +- .../TournamentTestScene.cs | 28 +++++++++++++++++++ 7 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 osu.Game.Tournament.Tests/TournamentTestScene.cs diff --git a/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentMatch.cs b/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentMatch.cs index f329623703..e65b708fea 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentMatch.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentMatch.cs @@ -5,14 +5,13 @@ using System; using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Tests.Visual; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; using osu.Game.Tournament.Screens.Ladder.Components; namespace osu.Game.Tournament.Tests.Components { - public class TestSceneDrawableTournamentMatch : OsuTestScene + public class TestSceneDrawableTournamentMatch : TournamentTestScene { public override IReadOnlyList RequiredTypes => new[] { diff --git a/osu.Game.Tournament.Tests/LadderTestScene.cs b/osu.Game.Tournament.Tests/LadderTestScene.cs index b49341d0d1..dae0721023 100644 --- a/osu.Game.Tournament.Tests/LadderTestScene.cs +++ b/osu.Game.Tournament.Tests/LadderTestScene.cs @@ -1,13 +1,14 @@ // 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.Game.Tests.Visual; using osu.Game.Tournament.Models; namespace osu.Game.Tournament.Tests { - public abstract class LadderTestScene : OsuTestScene + [TestFixture] + public abstract class LadderTestScene : TournamentTestScene { [Resolved] protected LadderInfo Ladder { get; private set; } diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs index 201736f38a..9de00818a5 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs @@ -2,13 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Game.Tests.Visual; using osu.Game.Tournament.Components; using osu.Game.Tournament.Screens.Gameplay; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneGameplayScreen : OsuTestScene + public class TestSceneGameplayScreen : TournamentTestScene { [Cached] private TournamentMatchChatDisplay chat = new TournamentMatchChatDisplay(); diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs index f3e65919eb..2277302e98 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs @@ -2,12 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Game.Tests.Visual; using osu.Game.Tournament.Screens.Schedule; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneScheduleScreen : OsuTestScene + public class TestSceneScheduleScreen : TournamentTestScene { [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneShowcaseScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneShowcaseScreen.cs index edf1477b06..8c43e25416 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneShowcaseScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneShowcaseScreen.cs @@ -2,12 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Game.Tests.Visual; using osu.Game.Tournament.Screens.Showcase; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneShowcaseScreen : OsuTestScene + public class TestSceneShowcaseScreen : TournamentTestScene { [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Tournament.Tests/TestSceneTournamentSceneManager.cs b/osu.Game.Tournament.Tests/TestSceneTournamentSceneManager.cs index 378614343a..4d134ce4af 100644 --- a/osu.Game.Tournament.Tests/TestSceneTournamentSceneManager.cs +++ b/osu.Game.Tournament.Tests/TestSceneTournamentSceneManager.cs @@ -3,11 +3,10 @@ using osu.Framework.Allocation; using osu.Framework.Platform; -using osu.Game.Tests.Visual; namespace osu.Game.Tournament.Tests { - public class TestSceneTournamentSceneManager : OsuTestScene + public class TestSceneTournamentSceneManager : TournamentTestScene { [BackgroundDependencyLoader] private void load(Storage storage) diff --git a/osu.Game.Tournament.Tests/TournamentTestScene.cs b/osu.Game.Tournament.Tests/TournamentTestScene.cs new file mode 100644 index 0000000000..18ac3230da --- /dev/null +++ b/osu.Game.Tournament.Tests/TournamentTestScene.cs @@ -0,0 +1,28 @@ +// 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.Testing; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tournament.Tests +{ + public abstract class TournamentTestScene : OsuTestScene + { + protected override ITestSceneTestRunner CreateRunner() => new TournamentTestSceneTestRunner(); + + public class TournamentTestSceneTestRunner : TournamentGameBase, ITestSceneTestRunner + { + private TestSceneTestRunner.TestRunner runner; + + protected override void LoadAsyncComplete() + { + // this has to be run here rather than LoadComplete because + // TestScene.cs is checking the IsLoaded state (on another thread) and expects + // the runner to be loaded at that point. + Add(runner = new TestSceneTestRunner.TestRunner()); + } + + public void RunTestBlocking(TestScene test) => runner.RunTestBlocking(test); + } + } +} From 1d1372d639066b0086f50750e08dbda679727bbb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2019 07:42:19 +0900 Subject: [PATCH 0419/2815] Remove forceful resizing --- osu.Game.Tournament/TournamentGameBase.cs | 70 ++++++++++++++++------- 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 06fb52da77..dbfa70704b 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -9,15 +9,20 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Game.Beatmaps; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests; using osu.Game.Tournament.IPC; using osu.Game.Tournament.Models; +using osuTK.Graphics; using osuTK.Input; namespace osu.Game.Tournament @@ -35,6 +40,8 @@ namespace osu.Game.Tournament private Bindable windowSize; private FileBasedIPC ipc; + private Drawable heightWarning; + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { return dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); @@ -53,6 +60,12 @@ namespace osu.Game.Tournament this.storage = storage; windowSize = frameworkConfig.GetBindable(FrameworkSetting.WindowedSize); + windowSize.BindValueChanged(size => ScheduleAfterChildren(() => + { + var minWidth = (int)(size.NewValue.Height / 9f * 16 + 400); + + heightWarning.Alpha = size.NewValue.Width < minWidth ? 1 : 0; + }), true); readBracket(); @@ -61,16 +74,43 @@ namespace osu.Game.Tournament dependencies.CacheAs(ipc = new FileBasedIPC()); Add(ipc); - Add(new OsuButton + AddRange(new[] { - Text = "Save Changes", - Width = 140, - Height = 50, - Depth = float.MinValue, - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Padding = new MarginPadding(10), - Action = SaveChanges, + new OsuButton + { + Text = "Save Changes", + Width = 140, + Height = 50, + Depth = float.MinValue, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Padding = new MarginPadding(10), + Action = SaveChanges, + }, + heightWarning = new Container + { + Masking = true, + CornerRadius = 5, + Depth = float.MinValue, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Red, + RelativeSizeAxes = Axes.Both, + }, + new SpriteText + { + Text = "Please make the window wider", + Font = OsuFont.Default.With(weight: "bold"), + Colour = Color4.White, + Padding = new MarginPadding(20) + } + } + }, }); } @@ -195,18 +235,6 @@ namespace osu.Game.Tournament base.LoadComplete(); } - protected override void Update() - { - base.Update(); - var minWidth = (int)(windowSize.Value.Height / 9f * 16 + 400); - - if (windowSize.Value.Width < minWidth) - { - // todo: can be removed after ppy/osu-framework#1975 - windowSize.Value = Host.Window.ClientSize = new Size(minWidth, windowSize.Value.Height); - } - } - protected virtual void SaveChanges() { foreach (var r in ladder.Rounds) From 6d1203a5993a0faec79e1ba4076522e11acc9cba Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 30 Jul 2019 12:00:04 +0900 Subject: [PATCH 0420/2815] Move screen pushes into function, rename receptor --- .../Visual/Menus/TestSceneExitingScreens.cs | 27 +++++++++---------- .../UserInterface/TestSceneBackButton.cs | 2 +- osu.Game/Graphics/UserInterface/BackButton.cs | 6 ++--- osu.Game/OsuGame.cs | 7 +++-- 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneExitingScreens.cs b/osu.Game.Tests/Visual/Menus/TestSceneExitingScreens.cs index 377d7b60fb..4780111e66 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneExitingScreens.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneExitingScreens.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 NUnit.Framework; using osu.Framework.Allocation; @@ -43,7 +44,7 @@ namespace osu.Game.Tests.Visual.Menus public void SetUpSteps() { AddUntilStep("wait for load", () => osuGame.IsLoaded); - AddUntilStep("wait for main menu", () => + AddUntilStep("exit to main menu", () => { var current = osuGame.ScreenStack.CurrentScreen; @@ -69,8 +70,7 @@ namespace osu.Game.Tests.Visual.Menus { TestSongSelect songSelect = null; - AddStep("Push song select", () => osuGame.ScreenStack.Push(songSelect = new TestSongSelect())); - AddUntilStep("Wait for song select", () => songSelect.IsCurrentScreen()); + pushAndConfirm(() => songSelect = new TestSongSelect(), "song select"); AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show()); AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible); AddStep("Press escape", () => pressAndRelease(Key.Escape)); @@ -83,8 +83,7 @@ namespace osu.Game.Tests.Visual.Menus { TestSongSelect songSelect = null; - AddStep("Push song select", () => osuGame.ScreenStack.Push(songSelect = new TestSongSelect())); - AddUntilStep("Wait for song select", () => songSelect.IsCurrentScreen()); + pushAndConfirm(() => songSelect = new TestSongSelect(), "song select"); AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show()); AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible); AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(osuGame.BackButton)); @@ -100,24 +99,24 @@ namespace osu.Game.Tests.Visual.Menus [Test] public void TestExitMultiWithEscape() { - Screens.Multi.Multiplayer multiplayer = null; - - AddStep("Push multiplayer", () => osuGame.ScreenStack.Push(multiplayer = new Screens.Multi.Multiplayer())); - AddUntilStep("Wait for multiplayer", () => multiplayer.IsCurrentScreen()); + pushAndConfirm(() => new Screens.Multi.Multiplayer(), "multiplayer"); exitViaEscapeAndConfirm(); } [Test] public void TestExitMultiWithBackButton() { - Screens.Multi.Multiplayer multiplayer = null; - - AddStep("Push multiplayer", () => osuGame.ScreenStack.Push(multiplayer = new Screens.Multi.Multiplayer())); - AddUntilStep("Wait for multiplayer", () => multiplayer.IsCurrentScreen()); - + pushAndConfirm(() => new Screens.Multi.Multiplayer(), "multiplayer"); exitViaBackButtonAndConfirm(); } + private void pushAndConfirm(Func newScreen, string screenName) + { + Screen screen = null; + AddStep($"Push new {screenName}", () => osuGame.ScreenStack.Push(screen = newScreen())); + AddUntilStep($"Wait for new {screenName}", () => screen.IsCurrentScreen()); + } + private void exitViaEscapeAndConfirm() { AddStep("Press escape", () => pressAndRelease(Key.Escape)); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBackButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBackButton.cs index 05d14abe30..b7d7053dcd 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBackButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBackButton.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.UserInterface public TestSceneBackButton() { BackButton button; - BackButton.BackButtonReceptor receptor = new BackButton.BackButtonReceptor(); + BackButton.Receptor receptor = new BackButton.Receptor(); Child = new Container { diff --git a/osu.Game/Graphics/UserInterface/BackButton.cs b/osu.Game/Graphics/UserInterface/BackButton.cs index fe82a5d8e2..9808eda4df 100644 --- a/osu.Game/Graphics/UserInterface/BackButton.cs +++ b/osu.Game/Graphics/UserInterface/BackButton.cs @@ -16,9 +16,9 @@ namespace osu.Game.Graphics.UserInterface private readonly TwoLayerButton button; - public BackButton(BackButtonReceptor receptor) + public BackButton(Receptor receptor) { - receptor.OnBackPressed += () => Action.Invoke(); + receptor.OnBackPressed = () => Action.Invoke(); Size = TwoLayerButton.SIZE_EXTENDED; @@ -51,7 +51,7 @@ namespace osu.Game.Graphics.UserInterface button.FadeOut(400, Easing.OutQuint); } - public class BackButtonReceptor : Drawable, IKeyBindingHandler + public class Receptor : Drawable, IKeyBindingHandler { public Action OnBackPressed; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 3018cea276..a889af94eb 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -84,8 +84,6 @@ namespace osu.Game protected OsuScreenStack ScreenStack; protected BackButton BackButton; - private BackButton.BackButtonReceptor backButtonReceptor; - private VolumeOverlay volume; private OsuLogo osuLogo; @@ -393,6 +391,7 @@ namespace osu.Game ScoreManager.PresentImport = items => PresentScore(items.First()); Container logoContainer; + BackButton.Receptor receptor; dependencies.CacheAs(idleTracker = new GameIdleTracker(6000)); @@ -409,9 +408,9 @@ namespace osu.Game RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - backButtonReceptor = new BackButton.BackButtonReceptor(), + receptor = new BackButton.Receptor(), ScreenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }, - BackButton = new BackButton(backButtonReceptor) + BackButton = new BackButton(receptor) { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, From 9e809a2342797ca55fbee818da5a7f98809b9cc1 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 30 Jul 2019 12:05:29 +0900 Subject: [PATCH 0421/2815] fix merge --- osu.Game.Tests/Visual/Menus/TestSceneExitingScreens.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneExitingScreens.cs b/osu.Game.Tests/Visual/Menus/TestSceneExitingScreens.cs index 4780111e66..f89e67db74 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneExitingScreens.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneExitingScreens.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Menus switch (current) { case null: - case Intro _: + case IntroScreen _: case Disclaimer _: return false; From 82fcb88f5c7e7cc5584ba3ffcb57ddca78d43bbd Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 30 Jul 2019 12:11:08 +0900 Subject: [PATCH 0422/2815] update names --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 4223184a68..77f16549fe 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -765,7 +765,7 @@ namespace osu.Game if (introScreen == null) return true; - if (!introScreen.DidLoadMenu || !(screenStack.CurrentScreen is IntroScreen)) + if (!introScreen.DidLoadMenu || !(ScreenStack.CurrentScreen is IntroScreen)) { Scheduler.Add(introScreen.MakeCurrent); return true; From a16c0f2aa0b3ac2962d8921d8145334d69e00a84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2019 12:44:08 +0900 Subject: [PATCH 0423/2815] Don't report stable storage msising to sentry --- osu.Desktop/OsuGameDesktop.cs | 4 ++-- osu.Game/Utils/RavenLogger.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 18f0cd1f80..4d68148a36 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -38,9 +38,9 @@ namespace osu.Desktop if (Host is DesktopGameHost desktopHost) return new StableStorage(desktopHost); } - catch (Exception e) + catch (Exception) { - Logger.Error(e, "Error while searching for stable install"); + Logger.Log("Could not find a stable install", LoggingTarget.Runtime, LogLevel.Important); } return null; diff --git a/osu.Game/Utils/RavenLogger.cs b/osu.Game/Utils/RavenLogger.cs index 7f4faa60ae..0a6f40a0a6 100644 --- a/osu.Game/Utils/RavenLogger.cs +++ b/osu.Game/Utils/RavenLogger.cs @@ -54,7 +54,7 @@ namespace osu.Game.Utils } lastException = exception; - queuePendingTask(raven.CaptureAsync(new SentryEvent(exception))); + queuePendingTask(raven.CaptureAsync(new SentryEvent(exception) { Message = entry.Message })); } else raven.AddTrail(new Breadcrumb(entry.Target.ToString(), BreadcrumbType.Navigation) { Message = entry.Message }); From 370a81d78a9963e3c2109cfe73b7a954fdf3b818 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2019 13:30:26 +0900 Subject: [PATCH 0424/2815] Don't forward timeout exceptions to sentry --- osu.Game/Utils/RavenLogger.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Utils/RavenLogger.cs b/osu.Game/Utils/RavenLogger.cs index 7f4faa60ae..80786d2c72 100644 --- a/osu.Game/Utils/RavenLogger.cs +++ b/osu.Game/Utils/RavenLogger.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Net; using System.Threading.Tasks; using osu.Framework.Logging; using SharpRaven; @@ -46,6 +47,16 @@ namespace osu.Game.Utils return; } + if (exception is WebException we) + { + switch (we.Status) + { + // more statuses may need to be blocked as we come across them. + case WebExceptionStatus.Timeout: + return; + } + } + // since we let unhandled exceptions go ignored at times, we want to ensure they don't get submitted on subsequent reports. if (lastException != null && lastException.Message == exception.Message && exception.StackTrace.StartsWith(lastException.StackTrace)) From 6f91a21f00d9f4acce39109953c8f384b2d60da9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2019 13:31:57 +0900 Subject: [PATCH 0425/2815] Use switch --- osu.Game/Utils/RavenLogger.cs | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/osu.Game/Utils/RavenLogger.cs b/osu.Game/Utils/RavenLogger.cs index 80786d2c72..2a396d35cf 100644 --- a/osu.Game/Utils/RavenLogger.cs +++ b/osu.Game/Utils/RavenLogger.cs @@ -37,24 +37,27 @@ namespace osu.Game.Utils if (exception != null) { - if (exception is IOException ioe) + switch (exception) { - // disk full exceptions, see https://stackoverflow.com/a/9294382 - const int hr_error_handle_disk_full = unchecked((int)0x80070027); - const int hr_error_disk_full = unchecked((int)0x80070070); + case IOException ioe: + // disk full exceptions, see https://stackoverflow.com/a/9294382 + const int hr_error_handle_disk_full = unchecked((int)0x80070027); + const int hr_error_disk_full = unchecked((int)0x80070070); - if (ioe.HResult == hr_error_handle_disk_full || ioe.HResult == hr_error_disk_full) - return; - } - - if (exception is WebException we) - { - switch (we.Status) - { - // more statuses may need to be blocked as we come across them. - case WebExceptionStatus.Timeout: + if (ioe.HResult == hr_error_handle_disk_full || ioe.HResult == hr_error_disk_full) return; - } + + break; + + case WebException we: + switch (we.Status) + { + // more statuses may need to be blocked as we come across them. + case WebExceptionStatus.Timeout: + return; + } + + break; } // since we let unhandled exceptions go ignored at times, we want to ensure they don't get submitted on subsequent reports. From 195609816674f0428a281d12e4c0757cba08169a Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 30 Jul 2019 13:58:08 +0900 Subject: [PATCH 0426/2815] Add a fallback for humanizer localization failure --- osu.Game/Graphics/DrawableDate.cs | 4 +-- .../BeatmapSet/Scores/TopScoreUserSection.cs | 4 +-- osu.Game/Utils/HumanizerUtils.cs | 36 +++++++++++++++++++ 3 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Utils/HumanizerUtils.cs diff --git a/osu.Game/Graphics/DrawableDate.cs b/osu.Game/Graphics/DrawableDate.cs index 125c994c92..533f02af7b 100644 --- a/osu.Game/Graphics/DrawableDate.cs +++ b/osu.Game/Graphics/DrawableDate.cs @@ -2,11 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; -using Humanizer; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Game.Graphics.Sprites; +using osu.Game.Utils; namespace osu.Game.Graphics { @@ -71,7 +71,7 @@ namespace osu.Game.Graphics Scheduler.AddDelayed(updateTimeWithReschedule, timeUntilNextUpdate); } - protected virtual string Format() => Date.Humanize(); + protected virtual string Format() => HumanizerUtils.Humanize(Date); private void updateTime() => Text = Format(); diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs index ffc39e5af2..38a909411a 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.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 Humanizer; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -14,6 +13,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Online.Leaderboards; using osu.Game.Scoring; using osu.Game.Users.Drawables; +using osu.Game.Utils; using osuTK; using osuTK.Graphics; @@ -132,7 +132,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { avatar.User = value.User; flag.Country = value.User.Country; - date.Text = $@"achieved {value.Date.Humanize()}"; + date.Text = $@"achieved {HumanizerUtils.Humanize(value.Date)}"; usernameText.Clear(); usernameText.AddUserLink(value.User); diff --git a/osu.Game/Utils/HumanizerUtils.cs b/osu.Game/Utils/HumanizerUtils.cs new file mode 100644 index 0000000000..398c76a09f --- /dev/null +++ b/osu.Game/Utils/HumanizerUtils.cs @@ -0,0 +1,36 @@ +// 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.Globalization; +using Humanizer; + +namespace osu.Game.Utils +{ + public static class HumanizerUtils + { + /// + /// Humanizes a string using the system culture, then falls back if one cannot be found. + /// + /// A localization lookup failure will throw an exception of type + /// + /// + /// The time to humanize. + /// A humanized string of the given time. + public static string Humanize(DateTimeOffset dateTimeOffset) + { + string offset; + + try + { + offset = dateTimeOffset.Humanize(); + } + catch (ArgumentException) + { + offset = dateTimeOffset.Humanize(culture: new CultureInfo("en-US")); + } + + return offset; + } + } +} From 20b611486350e69582f7a5a126463f4ad1e78db7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2019 17:52:06 +0900 Subject: [PATCH 0427/2815] Reduce method complexity --- osu.Game/Utils/RavenLogger.cs | 54 +++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/osu.Game/Utils/RavenLogger.cs b/osu.Game/Utils/RavenLogger.cs index 2a396d35cf..6aaf55e045 100644 --- a/osu.Game/Utils/RavenLogger.cs +++ b/osu.Game/Utils/RavenLogger.cs @@ -37,35 +37,13 @@ namespace osu.Game.Utils if (exception != null) { - switch (exception) - { - case IOException ioe: - // disk full exceptions, see https://stackoverflow.com/a/9294382 - const int hr_error_handle_disk_full = unchecked((int)0x80070027); - const int hr_error_disk_full = unchecked((int)0x80070070); - - if (ioe.HResult == hr_error_handle_disk_full || ioe.HResult == hr_error_disk_full) - return; - - break; - - case WebException we: - switch (we.Status) - { - // more statuses may need to be blocked as we come across them. - case WebExceptionStatus.Timeout: - return; - } - - break; - } + if (!shouldSubmitException(exception)) + return; // since we let unhandled exceptions go ignored at times, we want to ensure they don't get submitted on subsequent reports. if (lastException != null && lastException.Message == exception.Message && exception.StackTrace.StartsWith(lastException.StackTrace)) - { return; - } lastException = exception; queuePendingTask(raven.CaptureAsync(new SentryEvent(exception))); @@ -75,6 +53,34 @@ namespace osu.Game.Utils }; } + private bool shouldSubmitException(Exception exception) + { + switch (exception) + { + case IOException ioe: + // disk full exceptions, see https://stackoverflow.com/a/9294382 + const int hr_error_handle_disk_full = unchecked((int)0x80070027); + const int hr_error_disk_full = unchecked((int)0x80070070); + + if (ioe.HResult == hr_error_handle_disk_full || ioe.HResult == hr_error_disk_full) + return false; + + break; + + case WebException we: + switch (we.Status) + { + // more statuses may need to be blocked as we come across them. + case WebExceptionStatus.Timeout: + return false; + } + + break; + } + + return true; + } + private void queuePendingTask(Task task) { lock (tasks) tasks.Add(task); From 98bb6da9756940cf7ae89ec02ad11c880271087e Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 30 Jul 2019 18:16:17 +0900 Subject: [PATCH 0428/2815] rename test scene and create a new game each test --- ...creens.cs => TestSceneScreenNavigation.cs} | 40 +++++++------------ 1 file changed, 14 insertions(+), 26 deletions(-) rename osu.Game.Tests/Visual/Menus/{TestSceneExitingScreens.cs => TestSceneScreenNavigation.cs} (81%) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneExitingScreens.cs b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs similarity index 81% rename from osu.Game.Tests/Visual/Menus/TestSceneExitingScreens.cs rename to osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs index f89e67db74..e77a7a83c3 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneExitingScreens.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs @@ -20,49 +20,37 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Menus { - public class TestSceneExitingScreens : ManualInputManagerTestScene + public class TestSceneScreenNavigation : ManualInputManagerTestScene { - private readonly TestOsuGame osuGame = new TestOsuGame(); + private GameHost gameHost; + private TestOsuGame osuGame; [BackgroundDependencyLoader] private void load(GameHost gameHost) { - osuGame.SetHost(gameHost); + this.gameHost = gameHost; - Children = new Drawable[] + Child = new Box { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - osuGame + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, }; } [SetUpSteps] public void SetUpSteps() { - AddUntilStep("wait for load", () => osuGame.IsLoaded); - AddUntilStep("exit to main menu", () => + AddStep("Create new game instance", () => { - var current = osuGame.ScreenStack.CurrentScreen; + if (osuGame != null) + Remove(osuGame); - switch (current) - { - case null: - case IntroScreen _: - case Disclaimer _: - return false; + osuGame = new TestOsuGame(); + osuGame.SetHost(gameHost); - case MainMenu _: - return true; - - default: - current.Exit(); - return false; - } + Add(osuGame); }); + AddUntilStep("wait for load and menu is current", () => osuGame.IsLoaded && osuGame.ScreenStack.CurrentScreen is MainMenu); } [Test] From 0291a708d45f38c6ae85dc45fe8c9055ed12ceed Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 30 Jul 2019 18:18:03 +0900 Subject: [PATCH 0429/2815] fix step name and rename test --- osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs index e77a7a83c3..ca94f2b636 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs @@ -50,11 +50,11 @@ namespace osu.Game.Tests.Visual.Menus Add(osuGame); }); - AddUntilStep("wait for load and menu is current", () => osuGame.IsLoaded && osuGame.ScreenStack.CurrentScreen is MainMenu); + AddUntilStep("Wait for main menu", () => osuGame.IsLoaded && osuGame.ScreenStack.CurrentScreen is MainMenu); } [Test] - public void TestExitingSongSelectWithEscape() + public void TestExitSongSelectWithEscape() { TestSongSelect songSelect = null; @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual.Menus } [Test] - public void TestExitingSongSelectWithClick() + public void TestExitSongSelectWithClick() { TestSongSelect songSelect = null; From e8e5b2742d545d1344769afe965073aaeae00e3f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2019 18:31:57 +0900 Subject: [PATCH 0430/2815] Fix manual clock usage --- .../Visual/Gameplay/TestSceneBreakOverlay.cs | 65 +++++++++---------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs index eaae647dbc..ed5861b47d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Bindables; using osu.Framework.Timing; using osu.Game.Beatmaps.Timing; using osu.Game.Screens.Play; @@ -44,7 +43,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestShowBreaks() { - loadClockStep(false); + setClock(false); addShowBreakStep(2); addShowBreakStep(5); @@ -56,7 +55,7 @@ namespace osu.Game.Tests.Visual.Gameplay { var shortBreak = new BreakPeriod { EndTime = 500 }; - loadClockStep(true); + setClock(true); loadBreaksStep("short break", new[] { shortBreak }); addBreakSeeks(shortBreak, false); @@ -65,7 +64,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestMultipleBreaks() { - loadClockStep(true); + setClock(true); loadBreaksStep("multiple breaks", testBreaks); foreach (var b in testBreaks) @@ -75,7 +74,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestRewindBreaks() { - loadClockStep(true); + setClock(true); loadBreaksStep("multiple breaks", testBreaks); foreach (var b in testBreaks.Reverse()) @@ -85,7 +84,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestSkipBreaks() { - loadClockStep(true); + setClock(true); loadBreaksStep("multiple breaks", testBreaks); addBreakSeeks(testBreaks.Last(), false); @@ -103,46 +102,50 @@ namespace osu.Game.Tests.Visual.Gameplay }); } - private void loadClockStep(bool loadManual) + private void setClock(bool useManual) { - AddStep($"load {(loadManual ? "manual" : "normal")} clock", () => breakOverlay.SwitchClock(loadManual)); + AddStep($"set {(useManual ? "manual" : "realtime")} clock", () => breakOverlay.SwitchClock(useManual)); } private void loadBreaksStep(string breakDescription, IReadOnlyList breaks) { AddStep($"load {breakDescription}", () => breakOverlay.Breaks = breaks); - seekBreakStep("seek back to 0", 0, false); + seekAndAssertBreak("seek back to 0", 0, false); } private void addBreakSeeks(BreakPeriod b, bool isReversed) { if (isReversed) { - seekBreakStep("seek to break after end", b.EndTime + 500, false); - seekBreakStep("seek to break end", b.EndTime, false); - seekBreakStep("seek to break middle", b.StartTime + b.Duration / 2, b.HasEffect); - seekBreakStep("seek to break start", b.StartTime, b.HasEffect); + seekAndAssertBreak("seek to break after end", b.EndTime + 500, false); + seekAndAssertBreak("seek to break end", b.EndTime, false); + seekAndAssertBreak("seek to break middle", b.StartTime + b.Duration / 2, b.HasEffect); + seekAndAssertBreak("seek to break start", b.StartTime, b.HasEffect); } else { - seekBreakStep("seek to break start", b.StartTime, b.HasEffect); - seekBreakStep("seek to break middle", b.StartTime + b.Duration / 2, b.HasEffect); - seekBreakStep("seek to break end", b.EndTime, false); - seekBreakStep("seek to break after end", b.EndTime + 500, false); + seekAndAssertBreak("seek to break start", b.StartTime, b.HasEffect); + seekAndAssertBreak("seek to break middle", b.StartTime + b.Duration / 2, b.HasEffect); + seekAndAssertBreak("seek to break end", b.EndTime, false); + seekAndAssertBreak("seek to break after end", b.EndTime + 500, false); } } - private void seekBreakStep(string seekStepDescription, double time, bool onBreak) + private void seekAndAssertBreak(string seekStepDescription, double time, bool shouldBeBreak) { AddStep(seekStepDescription, () => breakOverlay.ManualClockTime = time); - AddAssert($"is{(!onBreak ? " not " : " ")}break time", () => breakOverlay.IsBreakTime.Value == onBreak); + AddAssert($"is{(!shouldBeBreak ? " not" : string.Empty)} break time", () => + { + breakOverlay.ProgressTime(); + return breakOverlay.IsBreakTime.Value == shouldBeBreak; + }); } private class TestBreakOverlay : BreakOverlay { private readonly FramedClock framedManualClock; private readonly ManualClock manualClock; - private IFrameBasedClock normalClock; + private IFrameBasedClock originalClock; public double ManualClockTime { @@ -150,29 +153,25 @@ namespace osu.Game.Tests.Visual.Gameplay set => manualClock.CurrentTime = value; } - public new IBindable IsBreakTime - { - get - { - // Manually call the update function as it might take up to 2 frames for an automatic update to happen - Update(); - - return base.IsBreakTime; - } - } - public TestBreakOverlay(bool letterboxing) : base(letterboxing) { framedManualClock = new FramedClock(manualClock = new ManualClock()); + ProcessCustomClock = false; } - public void SwitchClock(bool setManual) => Clock = setManual ? framedManualClock : normalClock; + public void ProgressTime() + { + framedManualClock.ProcessFrame(); + Update(); + } + + public void SwitchClock(bool setManual) => Clock = setManual ? framedManualClock : originalClock; protected override void LoadComplete() { base.LoadComplete(); - normalClock = Clock; + originalClock = Clock; } } } From eaaef61bc2ba264d1faf63ce16c69e292389f876 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2019 18:59:17 +0900 Subject: [PATCH 0431/2815] Revert unintended change --- osu.iOS.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.iOS.props b/osu.iOS.props index 897dc36dea..0a19eac2b5 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -35,7 +35,7 @@ prompt 4 - iPhone Developer + iPhone Distribution true true Entitlements.plist From 1222536f7ac5380b1d217b5f7d69c77bf9e7a1fb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 30 Jul 2019 19:10:21 +0900 Subject: [PATCH 0432/2815] Rename resource string --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index 37ac972f41..eb1977a13d 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, Anchor = Anchor.Centre, - Child = scaleTarget = new SkinnableDrawable("osu/Game/Rulesets/Osu/UI/Cursor/OsuCursor", _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling) + Child = scaleTarget = new SkinnableDrawable("Play/osu/cursor", _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling) { Origin = Anchor.Centre, Anchor = Anchor.Centre, diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 48888191e9..a567f85a02 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -73,7 +73,7 @@ namespace osu.Game.Skinning { switch (componentName) { - case "osu/Game/Rulesets/Osu/UI/Cursor/OsuCursor": + case "Play/osu/cursor": if (GetTexture("cursor") != null) return new LegacyCursor(); From de20d026727094eef78e75d36c239cd4ddb1fff6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2019 19:11:54 +0900 Subject: [PATCH 0433/2815] Make special skin more generic --- .../special-skin/approachcircle@2x.png | Bin 31796 -> 26840 bytes .../Resources/special-skin/hitcircle@2x.png | Bin 263521 -> 245645 bytes .../special-skin/hitcircleoverlay@2x.png | Bin 262013 -> 247101 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/approachcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/approachcircle@2x.png index 5d28b3f7cb5b7d682686c9a93a994ae506f68f2f..0a6ec6535c3b20c63fa9e47fa03beac92c64bee9 100755 GIT binary patch delta 24199 zcmb??Sv*^9^sbqrMO#x%F|-sh);w1YEv-^>%n_-%<{{YBSMv}vt)ZV*TSYZV#Y_j4 z7)oh{h@u2VP*cpDeE*Acaen9GT%4O+?Ckg5YrpTa-e;|6t^Hx2eqw|ksmZ{B&(kgd zsev@K6qMCfZ)j=BgH)81H5HXK6_r)N%IexTl(bb;P-`#-Jq@s`lC83qwvv{%vZfsE zS4l|%6dm|T`z|cp)7vxL^Ok2gOizL~Tnnu9f8Jb>%NQwjLm8$5yP@I@_EcBW0^fjY zD1$XsZ$QB}eAI6!X+hOgJ-xgz*O_q41$Wta3kF%l|DQ$_@UULy{y(3W&j#lD&k~T# zT~DZQPmXU3A`C6w#i4-mmdr;^HPD)|<#9~DEkHCG- zsK~uDcS^$1H%?~rM5cdlKYTvo>@eo$IQdUaQCTY7PEA{7U^;B>AAEiNgJiw3{HdCnQsJrzZbRqKjgG427^o8` z>1Q=P31xYB*2OVYb4J^p@KQQAf8ks#1lK!l7D;)oB&t)a*X3?l^40oKj2D^viZAJ} zMB+CpuCkyrVdo5?Z^qFtnxRLRL%VV*5Ful>(TrY_tY_F785Q^$JNt}&50q>r|ocU~Ec3bNc*g<06s75`OrT(Y6 zG+@-#p4@EV?^!*H3*h|Ru%}8rkbvEH>+unQL{bkNsQ~+snIkiK%!Z@q=-WSf9{j)D zZ}L&8|xg_hB(t2TIrAGD?j!0|pwDxpP0YFk5j=p?0slH{zV&D}S-a-*B=W z3lq;I4xyp#zc_~0W?S`Ecgedar_NAPLJGrnKJ8iMSK9w^zi}z30VnDY>xa^bV!*nI z{7+E*jKnYFVq(BRaa3vQ;4j2kBOxe7kah z_^4YMzfQCLO`>j~2um6QM4@k24(*WsB8o5=IU=bHEC!66wF-8Kajg$JPtc1F5lEP| z)Q^mqTNNP*1_SvxN?IVM)3dVqE+_fqqu_R_LsHCbTf@p%7dU$K=%~c^XYGF`O5;80 z*Y|Sialkh;ET^IpES+>|Q48mD#O`-gxdVKh=Cu8NMAyPG#k&5jxc#HZEFqv(196-b zlOu|mojOm2sDHg+Os(JOGZ=?|Yk>Ute;Ql*sWhW>)?Yi>4ZaqG{{{bo3n`{aBM3z-FLHfh>QPsm7Qot`?-q%p;DSeg{SL!c4QhC+=b1sQj6@r}gdC#xPLa z&i1lVGVzpW-xJ%Vg`n^;_%`WDl$w?yn7s6`A|3WSZF(M5r?8)brLdnhRWzLA8r=}p zMZ9nj)iBUV3n7nIP)B0JF4AxKGPRT>!hKdV2T5G)?*7WRs?Mx^Id}YCJ1Si_=N%<1 z?(^)6?)zZ7)O8ZVsO70WhE0!v9MKt{L^Q*Z`1b7XBZ4~0pDNj{V*?wwG&;Gy9txaU zB#bY!SwcbUC@BeU67<=pFdTaEE|cSH?z-O$4AXGy315MnvV;Xd)bEkLUCe8C{l{c5M z2CYW#bPz^m%Q9yz(cPi4ATe|?H~y$E0}+tU=b`Z{O#N5b$xNU)cNcB4m{}91UMmo{ zz7}Ho1>K`MYlTU`1G2GZ-Lc`?GuM**@qnFS0ibsuqBcrAN-(@IC1ac*pxY?zPZh~2 z0uS37>U)EEko-t4pYDxCS8%W$g74EC-h68Wr?rS2r&1QE!a_Y13?Ln|X zCerD4eK|Wmwf-voR<%<|qnU7$IsNDz_P=IVPo?)leN^tl|Uu{Ik_|U_cK_>t1&Wr!El5}{%EChN^ncGY-q>$P4qkcNw zo-fnKlDv9HaQZw&?6W#!CJS`RQ00pdX5reG*(_Gh-A|&HUF#aMYZ)r!KRTn%OW+iv z>u_Yj4<~ZQto$aZC^>rJFkxd@|AW?*G|P6eGjem!ZnNmpC3&2mY7%SMH;t4{52aS&A=Dy*e<(h|gIx@_f=zE@u(z z(-XhNI{$BxQB(f#l93VApH4IBo5+pJDVB!<+mftSDz=3#x9-_53HBd^D-D>=1I^w5 zINRU+)v@wl9%Dk>vx7^v-b>y%#3x<*JbFa;(gF*P=HJGITS*;hCMdQvNjx^rnqy9t z?Y^6#;jxO1YFy2d9;`N~Kv{a;=D^zBu}qA$od) zJ1s7iZb2Q9DgY;zq&{N#Wup3lz?)$({TSoAb3_pT{Tg{`@&UeY?Yh)6LM#vfVV{uO zod^caEJ=Sf;MSQ(EPkRSO+AkIX+7Z&H9Wgbi_&PgzsrU{Ldo24#Pp}LwTSjSqcEd= zdP=x~{Rk<^gxJV99?0j|Pt8dAeVvzutNv1wU7wWxaisU4*CE9GWIr@xCfmOXXns*A z$nkJ@-in4^22^6DFvNClgxU#7H{b&OtW{Ou5+p!5xSTl_ELR!~@;FV4 zQJV4WQZfG5zs@?YGt9R3qLUz$uXohA^*;mXkcg8x4Lh7-o2IGUnm=C_y<0zPghX!&btDD8=2nmR-{~S_O z6$wcQkyEAenbbjpGCQ5#|1!v7+T&nLn<)*fg5!I7Usi-^dFrnM z&V+^1pF;NT`&H(86b;TVh(Nnr{ypQ4vpHj})I(e@1Kuezpjj^t!kzOxpiVv9@lX}u zWDNQ6B4T7_oqg-s32)!iZl8pqEVd!hLBYZoSur)wU1;Fby9V0(Ji+SCm8OyW^^4P5 zdP!1q-o5tMJZFynUXan)-~McI1}3K2@v1sLnBVqXasH2t%E4j zcX)HnQ2q(PEOOw3_rjQlu$NJL%^k^r;~g=&4T&PbylhX*!r-U?W7ul*ldH+0^asdY zfyAjxJ!S5cfr;w+3B(t^euz2WE`!(Ij%=61%Gbl0Q$~+>WJA-Blo{qWkn+UgTg9+m zY)-WNeVq1_Sd4dfXId|sY|=rNc|h&fpq#8hJoHm01g*q9PMJq}qUn>Uj=exfrs4LL z(4uYrp8oxd2d)wL*3OdW%prx~BbC;ZpYlg*eZGE%@6N68|48sGFG9lx?B6$zU23E| zO9`KJ^?DN#c-udbpw{uES=55>AhxlOca|oz^tl5M8aK_NcrW=Q`!MXK^0W+h1nG zf>iOqYn{at$l6e4U3c~`h3q$pQ;n2R4UR$4Oo?Qa^JVRL%bZ`fg_O5P0# zrwu!%9x-vJ&1OzP)Z=Eg`yPYXG<1#0TR9c?vb;4>6D2IEAwW~R7}gI!-O%olQ>O|H zCKdBHrNv^)VwA)r&%TZ4UD^vXe-^{C42|Y5JKgT3>|!jtn`Fw@&amA@teE(Y=To%| zp;dfIG@lhCtgNmw< zJ%;EkLyxT;`9?NToPbt;8YJ*vW*@y;+2^|&e?3mGj>a6xdj)^EBd8&`cugz!Lz#hu zG+9jGkgfOLHL9xFOhHpa0Jg)w(t$<-%;XRerrw0j%12Ca<}F_-xmb>UwK;GlOjmB& zG?v(H0y^e;sk9G`x$dh`o`ell;^i~kxQ}qoz|7s;h~MHjip9Ug2|a8& zhp+^2Yeei#dB*L%G=pPVhNahe>iH4J4dT-4aQHCdBK@{|UR@336MPeE@^iW&+tcp8 zJ`*pJG|82v+CAZLCpzOxvQsT?!QzLo&qsZa5saFM%G8_TQ_0mZc(eymKBj)_GaN7ELOBZe-DDKgB78niGk{Mi<@m>TX4iRL2uz}wDiAwx*tQ_$ltE)3aIg)2Z3(fd%a=IVA_yS2wg3zYCK4b<7^ zW)1-k5HHVn<`j0orne?3DE_>F19_Y7WBUAv)v)84`t}p$7#ZW!&SDu5M1(I(GE<^I ztLf$1`Ps8u3R;TaMHSk!6Ze2H?;8YGbLnA`E%C;NfS5tmq2swPkN?^-pkUBj!>(aR zhZ3_9_kyLy+4s3 z8S^qaK-wAd>B$P184|3ctR#)A&KPS|gATs7oAd23&hefxxAuH+L!|=2xq%}q9hqp) zUnwDM2Jhtel`bFPPchEl54*P?O{?Jo2muWo&&1_Esy)Q2(j8X% zNS5IbOFmmi-=n&>-Ad(uURcIh^A_sZ8CE*1Zf%2<>`8M1`RTf;g&$5t`@HVuX@sw} z99^sxkLM~A{b7IKJB(xsSdayCBc@H9lzaZV_6WJL?D`CYH6b9ma`}H$l_0CtxCew? zbz|(YWIouyYWnxxf{Y{^|LA@Qh)Q;a^#s)DGUbYipDv!R@fOaVZlZ z=oQTS9ZakYX-ULaMhpfrqp=7SM7U?lJ3M2#bK3P4_;er#hOy&xUdr%!}36 z84AdFv;EF6E_%xeqgSlg%|FOKhp+y=c7D5!deG(qc(&YLyW8_Q`l+V)eHnvPN55E; z{bfopu8S4e^%hI+Bs|gc6^Z%pd39*{m^>pfo~&HukNdEf`E%o(N!-5teLh`=@)_Nr z-5P4pkiJ~b^YVz<1B+kDiTma(gK4p7pR;4m!NV4xK6QU=B30U+wCN}IV0Bf&3)+!9 zSO>&E?!3?9`(hYGczW}ofVjF7cZwQ*=M)-25frflAq)ZrHLxDktAW@d&HgNa{@9%@ zt4gBFmdW>dGtbNhm-)d+6z3%dljf+svJfBZ{RBlVHlMDA>6J!(!*2Y+ zR3I8I)qg8CnjGgh$HuX?IwQ_)=DZlv=Kx?$8~WE$wkDP`X-X0VyDC}pf{l#2r^~e{ zuQRlg)c`-{m_0M~h)Y+`3I=7LaN!fVItO=@a#Xyzyk^ohSfE$@^G|H5s2o1uveulN z!4JpYrQLf^t#vuNWWySDXRo!rFiaz%Jv=(&qi_*! zc{_4yUL8UeiMCF4qYAB7c%KeE;%FStBQi_M1WN=SjB~E77LYN-wP{P@*-0J)YDI=V z#-J;N3993Zyy=(}m+|k1HH{B?^MO6Z(PH~nDQ*z|L{qMA*h+{0 zK`&HEKj|mm8Ow5BLQcnrZ(sHwF!e&Ya}p^!>nhJ4_f7_vB*ZoaaGvR|f{(g=VS@=X z29cM1Gv7AjRTrS<2~yuHxZO4DCQuh1-v45`@t>A4Ouxi1rzm@qTyn2>DtT(`3%Cj13t#Wae?Q; zG5t6s_7w3FdqA&JTFctiu=jJTz(L=_EdsO`K7CL&aX!HcvfJ8sWI0Pg2o}e#7_Q8dq0^8j=-$slD-R%Yf28UJU-u-W#Flp zg}GYy>LIL`iDWNFdhTGZM?^oCt6OMF2()Uy|Lf-D8{pdYV9?c&u6r@(eYX`Q4ZE^k zPGa`gL7SufIctbq{RgWj@3QO#@!+~@yI{&v-TNY5g0cRS!sZjvq^;#y;FHpyc`>#u z1D_CqW|jG3IO}$H^n%b%hOaz?%9o-KHz-4ogI_khlUzHAnENE@Z%zUts2a`4x7m1o zl2i}|5PTjrb=nW%uVW`G*GX*UND9D{4ev>851bZ@BhnO>BlGHiLsPWpD`_#8fZ`l~ zqc@|z#1=7>qu<)*dF;f&*^XL}M4){^p8V#7q1Wtr@}0lxHZh*6WVRvj(dXV;zlDZO zJ7%=s;M=rC&7hdx$@Hjm%RPwHxLKUSC~*@I{KBTI^XJPuL_Ir~*E8QgXFnpGF4w(Q z<}l7}e$_nn=qOJ&AQ%+rAU$x%y!p>9?MGx0rv50T_@rI}dT1ceNb68uuGWqzv{?K@ zeFol)YX3xGDQ}>8M#3r7-ak&P48M?zfB(mSXH3wHmp9KnKygV{PX9`VJg3~M92|HC ztdYi>NL)zl5j}%fg`(C<$Fx^VXh({a)MO+$ZmjKZAaU5|^%mhte6HMBuLBSMtqFR* zzUN!c5b2h}xSJ^p;Bu|1?Js@4$g$uu{wVJt{jqMLDjP(=OciVNXBfC+xvVvkUn6ZyH6tAWf! zVmg(uMVCk7aMH-Oftk_uo*&{P8{*r7N7rvivcZ!L*n4X%JMUxpOqpPHY;kP>S8M!) zJMuzc(|X)Y`Wm^tUx^D7+^zl~$j&2r^)t|0)SvHg9&Zn&<^IyhhgvC|r_sQ(NYt@H z@)A`+rM1HidmugxmE5Gwt1O~k2uUBjGFseiI!Se6>R%#qY@U}goxYi0@C?&EaATJzn>mJ=cez3t{^y>PtV-%~BpMs0Q4+v~K6 zMMW<6`%@aa{uV#cOJMZ{NWe{%-{Wbk8_h_^1GRjo8J{k_BYCIsh6BxG7g4#AtBn_t z4JtBb#ZPWd4F7rMp`Sh_ayV}OYcrh;_(@M!3G|bC=_qwF0m)#2lGrA98UJTcYz`=3 zVc_?W(o~1W3Ry=PLD4XO6;~pzLx{!%y6!=ow)%}G{Ol5i>01Dp(=EyE`qY4i42v|o zf1hkxL*wyEznkrCvm&OOOn;k{gx^kP#g;);)GK{Z#|>fGhczbogZBByWq{L}u)W7vrfv3gT% zn=j>CzQesbIx>pVGL}>_CK{|_YuAxXOJq5N+BuUAP9NXUB&EYWNwWT#Eb*7KERO6J z8=Ykk*Rvwp$M|1-@5=me5XB<{PuNnHN|`o#n{&hTGVrmj^OJ5NZ|`)MBQ53OO{AzT z3hi+g=%-kIKVcayi%OY#yU&7>)i>$ggwVlvzY@crGS82C`WoD)q6IOXd5k_seECS- zByYUp#c}D?TQ2S@j{IHUNc58I@NB0IaHDtAR~yC{1Xwq7HG!kI>+R<#$}%0Yxi8gp zLuvmZa0a4gLn3qa>APG}dHZUhp8As+g`(KYO=2@$ z)pcq!_JLlWdl9Y4J)hn4CenP0A!c4HYCT<=R{D37vK@dTf2gn*i+qj!bz{-hLH{D4 z!&maVuiDJbAabb_?|9xwwAeQ+PSh+8Ah0aGP+0V5jcOv0+ zo)m6X>LRrJsh<15tw5J(MPe_yvBgnPF~%3n@mfX)dw&S+)n0y6cU}W|Fa6D8v`}ql zT-Cw9BgBOYgLfo?VvhEIhl!c^%)4sr(vfQTCKf`Ft|ok)K{}4ell&O_fJ)#=&(!Th zub<9*Bj_DxZBRR9q7n0ZBvRyj(ezDi$!Wv>NOX>F=lDeGpo3j>-Dkw3NRor?{=JUJ zA_rRkHX>88W;Qq=mAfO*x7-}ncwP;OViBFGAfRf*)# zy3cbrLSGSbQtBl){AV}J0j|w>st+f)?lbAKleFlfW9^R21sL&BFkB6retDH$8f0_ z-9YjFR*`9{22*Y+Mcq}|Xg$dVP8)|0dM%E)WO3Fv8y&gk6k+wtz} zdiyr<_`&r*gBVh#Hx651g_(E2OLMj5W+@%As`%UaSY(X}?nTZO-7%;^XK4)-V>7s~zfuF@+Mo zO!j9%h?^E4F!KmJldPCzw(K)089!tLr3FnMz5KI32P036q{5ug&M^*ou_ua1N1910 z^CiU9t4F2ZSTgD@7ru_t5hr-xP8)%UY@Ohj9cyZn$%eo?3H=ET${Jn>BhQDDrGW)k zQ-9v){<`lTHhDuxDkVWmA$b5z9_)IUJ(X$Lal^yy3P`7+b&ZgS{9|Lbn&W{ggjYxmfTz4XQqn1i(TwZdmEXOc{Csw{?0uKg`X+q|~jx=lmXQb96ovD87W} zNrUKzGA(m_ROFtKt*dWIaq(GpJhJyu@_A5bXYH(RLNM`hzh!5luMmqSgQczkNj-lY z&D^|Q_n(xswY( z8sxNsJ@?hcK23ZMMaJB_|0#9DFCRI;fLeIIe4< z4}aq37N2@U_?e)$pkW>Qac`UL)2GgXgyuD6HWHHwhHTYmwjpBXwS85}9;dx)-;Z@v z2krd?HS0=)BM^eEQW^T)_r&)Fjb8vxKXr2av^YN2!0ZT>oqGIVQ*y<=;?jJN{+Xyv(~j1GHOv&_>-+li7KycmQZPA9l6Y&0(V_94g#X(brg#1P562fx_RZ=;yKcrHh^@Uy$?+Hpz13> zR~yidS4duH!^oASN2_MqDK3W{hH=}YuCwYYgj3d+Ngs~i%jk&Ky^}F7DYk<;g%TR` z&p(~?G*3Kz(o>n6t0rY%W#;AJpN~nXE|N)tuz=vnMzIYX4SEUmttc=5=?p-Hvp`hm zOS7SitO#?{M_oQuXMo`~pQkq@Ktj0=YqEh{=Ikj*-TIl-@g}N#OtQ*&ojv~>Bt%RL z0u02#-5Oo3r$cArqk@u}(e#!&%^gn{{#u$Z=73_`}QU+qy^JTkL?0%W7w%_+jzi zHs^f;S;O5Tm&Xdk4L`Mhi0z+@S-hHiRWtUX1xGb;vHni&*@I#vEmrLA1jqh5LZlt`Swk&bF^YYcas8{Ab0s< z^PJb#Q(>ge@w|PbpY1x85+D*!V8urt%5nY($Z_>EQBo+%$w#Y;8O6vKN9IlZ&>Zwh z#GuN5efG`s<0u8alxFpQAv#b!R`%LpPL-T&v<`x4XrGI(W2~lR*!a3-PVhNV1N`&5 zO}tb9H29u4s=jY50X>hFr`Dc)oQT>96t67E+1j)@+ZW<%d?(XhZF}V>$DDoEubW=V zTX{xa`?sZDH9w4^D1r-LvwPRoFWKsBRVyYiZa!5I*Tak~{lsi>QA9XLITi^uklGLZ zD=4rEx7krtp5xCtJKDKYtjKeO=ebn;PBaGSH*|X`sYtUza&IByt(@VVfOjCh4lds# z9A6i-312g6Z8LzK2*E*4l(>+N9XH)hBQK`=ikMaqc`ZnvG+-bE*x5HE<9EQ71Dx|& z^s~s+FQx7-AcdksFR6jbh}P_%l5l;r+is;75c!p?b{*8NW4StcWRR!#`*%lrDR9Q0 zwxrK;Fane|1m3`)pznImoK@(J4>(n1ol&XTDnF%@P3&|oO9;LM%U zsT{>$_WsoEuK__I&YO8F0X^6EIp(b)*7+y)yO#Wr1T#5rUEH8;XkC3VBHM|VwlT%I zPfRWfbw3O+HDpO&4x=z{ReMyve8ovNQ81$9hHxgK2(q!uu#vkjsUZg0ua4(!t`m$r zIx>66cUg2s{28w!ba24&Kf9I87QU{QJopiQbsyJ>c|QkXE;rT`v19cw=Pe-bzp2di z7h2TB?DpQ_m?W78_MC^%VaspiGBZu;4P5=ROlyLb_VKa2Qv<2y1xd=5T==(c25i(y z!Da#N9hHHb0EI6ON8`U57ijw3Rn!q)AgP`&hC>ikty~HF3O%`3@Z)k4rThJ0zhhMH z@+Quu@YFdUkSdy`apmaB{rd_M|a zAk*MHZ58e_Wteq3-%}OH-zwbpZJM0}|F7j=T^X5@J8osWsifeq(~RExJMnV@KTs^v z0i^J^wikY%ez$z_!Ie}kfpme zApd&Od}l>;!9O&>1&kGl)CHoRWQ}I7D|HUgky9xZ4BXVpT8_4w(ck`qd{dsHcx&Mx z2^8>7FtqoHsJWHt1Ex?~gNXmx)nHoD{Kic#@)oW#0*C&^5MCRuwA+i!HOU^sqZ2g= zh=%k&pS}l-p>_NpUK1V_#JrgC7utM<7Hge_$m+%J_7ENG{a8=0WsZTY+D5(n40CQQ zvJ!C`$P1k`??e3AJ;a^Td?lmFoN@~V>?1=!B*%@-9BJkm^8q^VMoOmpi(&M4u~yux z_c)J@naia#@WGTvN$Kx$_q_2}R*8;J`WPqt`wgjX_lNoG4Qzb2{NYJeCVmF$CZAep z^F96y0s@O0j?fEnc{m{@-OaBDZ`2T`UICFWG+K)6*K-qOF4t9n50vw;4Rb}tx8 z#C^yz6jfc<5XzM+lmt1zU2AuK$Yr*ROvdX$QC)=|l^2mX-O5Tu$IcpMnw-U1W3#+@ zeZ^diF;tGQwvC=Ikc$VNT|`R4+#NY48irrKNaWj0<{;3v=;25-$@9!G$1ne6v(LBR zaC(^kVK9eiA4816^M;&Gn_4T@bl&VAqM$wRK8qLE8Dn@%BtukqEiGymzB|?{u+R0u z`|22IjpZL=wL|;H-qLAZu`BUK!*MpXI|f&_)1n4P>-r!$4_JV$gsu*hSl=oBh<=-_ z7(;6|{{upK>HL8JGb>zA<-;LQRgj)2EvcPU-wNfE+^HXq>|OsEm={g5I_LlDVb7YB z+Evir5StqH3RDl1ppsuB4^k|$w;Z`q1~m-&+7|B?85fGAEd@eNoln303mvCn(68h= zTiKIvzw=Ch4jZy0^y9b+gHzEr?wV%!7P^3|Nk2-x30`YE*^R!gqPh$ws>r~9faI^H z+0md(C~05itI(BsW^4y|H-TfXeV`_UeQLE`d2m^$Q(2=}H;{49cSdj8@4BHLjh%8| zFL8|BZopUwA_z?@6~V-_yR?bxOy*^twJI~Bfr^j%ZE{V(`skpmDhvegpA7(Bo=m|E zdDW06i$7(Ga?ogjbnQ`yvS#F7mB^y1cil8oP^dEtXz$*`^Iu?t0?3nW9)wS+(6j7! zwzB7)r6Q{xr@W6N1Jnrv=xC-bn9N9xY2>+TUcM+N3gNdsG?CWP&+1@RzB?tht$+Z6 zY0k1K;cRvD0i#M5U=4O}#i2FR%i}#eUc8geq}|&Z3&rEAd;NybXWCVs)8i~Fe8uLi z2)r6#$^HX&U*{G&i4}Zln3Hm!KhdJbtj<1*DESVS7UQeCP|MJ| z#K|Au$CXIJMC>%Plfj2Nf*t(!z(R114aBJ<>i|bBWk!!HY@>p5bCaUbL}g2UJU8T0 zgQANn8A3Lkrn@&-O!(!;U#q8}RC1@fQd6+5?K|gz=Ec`}uzw8+j>9ae-czMgS)P-I zDOmS_+-0t6(=*qtkyu>f15T&VaK2#Idpz|PHTe(g(Gr=;?cL1fre11521+DFb4DG9 z4xOk;RJOc?hju>a`^@uvL$zG1a|fsYMHlJW@$bZV;^UCo!rCI{qm#jR@0C|-h@0_q zw;Gt29_k6+<-scjIZP;$xGLi4ra;Z$9C~<}!4b8*X~2LMhm2+Xi^2tD#rkH=c&CK` zHB(=*`#uT0VkMjCY*qt`N1_bmqdtET)}dhnlEm!8Eq}`BNVP&L| zVVp?%uKk&f)swF;KhhX0m=YOd;AGzcCF~{EVVX)Re?-eZP$=?(g^ad8 zOs%|j*E;JL<2z-8;JZCcX@V-ar-^#GcMIsz)&obVT=>VX0f3JDC(|BSxr~6v`&Tr@ zfBBC`wpX%?_}*wBCv3Th7SLlHQJXP{)(|yb#+hbEi$DAZSu+yYDjtzpybjGIsRSA= z%%Gb8Fl?W1{OBUPrYMKayk;q#e()jtr0gY>~uQNK09ooETy&r|-(Vax` zFKa%cuMGnR6`s+0h-$+N$NJOwU2mx8Ua~lWf&pUNT;I)AIhjQlS*suX^W z7GYITF)(D}^EYoM7x!M>PxUymy*|9de`CXJC489Zicf#&3&;(*1vq?oZb*AS{%l=zrf^$p{z!!cO%;tgNo z_^(Zx3T5o|S2%R+=>e`euauz7;`DlYlp(g?(7uTi5$rHAABDs$pP#GMq=}H7(t`an z&Y}**dS8A)@fKFAr~u z-+gzZSoKVE{-zJa^1@&NV`#OBoP2mZ>;8r7Md|CeI&;d)s-W+|agpU}uh8H-HIeqh zUoU(@{V?aOyy`P*ES1tNNzLW&%a?Ar&PY36-y1wO@b}Z(QX|_)19E`QlN%{v9|AL# zxl01Awb}b84+Kg(?|QCfgqd@io^C2!Y@Y$4>UVM0>QfA=pJGF4geZP0@!~=U#f)eg z>(hNpA);DrPT=olo3l5V?xrjEKTMd~aw~|K97GFZ4!1VO(uon+yNzb>Yk&rCiIK1Dp*H+-R5+7 zOY2Touk5?x&ju;!3|iJKFn+&-Yt7&f*5zi`GzI-FomgD| zf<@F|A>W}6wA6M_H?Vz9?j`x#J@bu*{+f3N_IE|_;4@8N)vhV>$-73zvzrPrDx|rK zHj(qJ=~+&^P>#!QECw2B;_2J_epl63&Fl5Dd)(B|XhpEd#vk`OV(a9tonNYD6r{TO z?AW?QiyiRI8M%sgH~e6ph4-lSK@;EI$sDz|Ks{Wk2>#Aam0`f?57{aR(>O5kZta z$?Y@!(B*Bdn7do&UK*YVF6!(BBx49qSh0$$>9+S3z?sg0ns?0OECX6_@$hL3Ca}>} z3NST*ceP1m1iI(C*zx7kQY=1jFmaNvO1wF-&o5t*p(HO<7>E=T>KiY(T5@OLl?i6P z{>+3{k})t_e3#~z+ngtG!kJDiwo|rW!QO~uwiL#jmIaQ42|_=`3Dx=@Gt>s}I2#9{ z_b=vO6_efo=V;5vEzXL&5KMfX3Y*4&H`%%L6Z&VG`6Au_?BlKFr;NOIpiSvziNjlg8saD`tTJ@h_UdkP`8B5Rc2^Ux9abry_*6IhTwQyrdJI^I7D!&`{KMLj6#c-xz{&;SLxF)xuhL>w zzi1~PZdf1jf6J)$!uzMZ6&i~wG4N?UDfOmC>BqsrksY>5DOc&x?Uy3d&3h1ds@cx{ zuGFcL)k`nSuN{u$h!;Qc>9c073=W4nsZgFgGHSh{))b&#C1U1~kq7&YnTPk8ExOWF zSxCy{d?iH_E&J#QC}7?BPuEy^Z$QqxftFg>e0^Z`*B}7=Nki)?t z4A)({2bC_3RlA{wiyQjeWbWS86N%|^ZT9-4u!MAsP3i!CuCZJ!a2Hpe%=ld?{?p=h z4PB@gB?|bPvf>)2$NRw`^tpPv*a#Ol!WA zsO#HHPq()}&Zlory45IW@9wVbL5baSYe^Er zk_3}N;OgGxPlzLi|7>%}vm2oW+)783eVA0SGoz8diPvaxTXw`s%-KZO;;650Vmar< zWt74(ET=L;3uv&g$qeMs zgt+fMICW#{^yWk&vS!SNzV$_zBKp750x&w1pDSK)p_4_oPsx32qUPDg>?%s(PED0_ zr@e{oWU=ejAaT^zm5VyW^OnIN)Slp+BwXF3Py}xObu?nJwwRq(5%K6SFFsa_DUQ3a zneTq{+D~eUk5h0MSsHG~4=hNRB*>Zs4kc@DXTQL%H*`F}gOKj|x&4U5o~fBX!#VuB zxim7M^LABydX*&Rh9LtWjKvz$ng6BhzhXp|Uuv2Lxdx!4^cUnIDgC7u1`X&e$L1IH z6E`QY8~C^1^WHT|Dq{pYQbnW)r%B#8f=&<&*A`UN25zWU^cw{xCty*CY&CM`-f9IA zOKU#M_7?J0;`-JVBEr#>x9;9@4k(K8O{jC)`h`FWV=YY)X-xH5k-(gSP4>D*lb_9 zXzT7q&c!I1IlZ45rzSA8YAiG&9f@qyIQ5HAfL8yzaB!>&eX&)2g3d6sbwyfa`AmZp|RtbfVx9k5~0$~*Ja zMS@Gme>nsDI%y)CqNA#Dk2}M#lNY)g^W5g-2CEu&5lv2qK7U2mj;LKtO%kfN4nku_$R)LtDh{M*C z#`AJgX!H=)_HjC;DLve|BRxy0E!k1@)W$Elvkc`*`&g5(G?~@<+(kRhS&0znE?IT( zm2XHCFa19)@#?f{a<<}{;V>HN@BRUhmGdw`Lmj<9(tfZ$(x8U=2?|_3o~Py5O@I2> zyH7e@Xa62ZBr#Ybo5Haul>Ki>jmSXTZa|IeCiQB8@ ziko*?R;!zk!sE2ghCQA_+LzdFu%_Nf61EY+y=d zV*-xvlH{73aCQc(AukcdUX6WP!pyUBW^d{e&_vVhp{m`nwrHVc?hc&Nq+&mT zU#qT}lKD8)*Al8UbnL|e%u!mJXwiXF$iUkPlZ(Y%d8dLidE-wlWrR4XCy%D>F>bUUoNf)`WWNhEyBOPS}{zp@GB# z#aO#dg?#ZDkA%s`qIF&is!*EV6C-ayYnv5`YkLFpR9!kUhaPg|U#+^`@XOB7{?^aUf=8b{RiLk2b}A3-PbvvbARsh zdcQQCJyvFJAI!Qm!_rb_?CAFQqdg%SY#Z{=e=;rL`A zl`@n1XK`#R%ph)(ePPTJ23VbcDP!rrb?LYP3#6zM;gegqyT_ZIOSu;MUF@XVBHJeQ zsEt47*tdmt^@Bf$@JpZA;z0AJlqnQ8rZ_;W)20@{hc-VWpiE8=@-P5y`Z?Wqq~E~} zCp)gr@fV94_c(8a3T=LIHZLqwVpYBjuw5-Gyyx@1P;*_~kC+!W#KB6a+$6SZ zPF>ai%t~s%;k#9sPd#7dj(k_?1NRRJ3tw$T_T-3DQ53zu%4>HyZ~2DD)|mRs4`al7 zup3Hd=0xJ21E(6GlBoz%`laUhMJ&wXqsTN!n~5#tja1{(!T?t(xiyQ0y-PjlvLxE| zhhE^FNj@Pzy=+K!lC|S*qx!B3hGu`rzS>7A=w^d7k++F_pueL81w=Q!cWi8odw;~^ zVaM0hR1C9>p7@z}n0tOH48@Vw)D3X-$CB9>SDJbN-e}LNuH;8c<;C`*-4eH$HF?cl z2+7C!$^F*OX4k3Ags`5!MNQOy{5yQJaa#WQFC))mi!1$dx|%HM!v!f3HBjpM)!xGy&3ev`R<)lBKY#B#L7}BDBi&HK24vmkCO{76hYmixwgcI)))GJhR3hOgp=d(jZ_j(-9 zO!_T2yRx1+NCq4a-TH3Xcvt#5+SolhxB``c;v9+S-QRPvNf7tzSNi}W_#s>#8JNW0WUJ9mr+TUTX@jjY5Pr4yAjjKz+Ir?;F5nWv1n}n` z4O?<8KI}$0OpF^}JxO3qW1!I67Y`WgI;<6)Ro^nyTW3)(NBn(+m0$n+pLXJig+Sxy zki%W0FWy6GZM(<5gfp7b9ESJqp@0GWeRQdNmmlm}>E)^)VBGi|En3uMI^IRwWKT8t zpZ70??_Z77s}O1@?~0qvAEbl4W$!uRqj}Op?d?};2US;1-7$G%0r-kA_If2L zuy{f0^Plf%MRjtDMj zlBFk^FE?)*y4=UFESRbV*nPlqfUM+dk#6M#tolIDmw8f}2*~a^7bt%moxGCtV_F;V zeS>nD4mgN%fx4T#jlJXx%)C;!wgB_;YZKlbwbMjS-WMIFc~)-aZp#;c}vE zSRlC<8KuE$_p`q`AJtZe9Q^bvpnplP3I7f{v8M zW8T56to$r&c}dvl$o|7}C~`{6^e zIT-+389wl2)C8a}`We}Y+uBEZmI;j7auDqLtk?gkk2Xw%pXC$En3lJ}Hm(YtVw-Z6 zs@+$(^)6tBRIcg%ut@;X#F7*KX(E^T?mmIe>Y|?u*YPa2_M)zupTUPR-bu%lua_u!_rYai`_-kO_Lu8)&_>(X6>1;gXmIVtK!&aiz5q=ljFdFjXVC@cy7RmL z;7d|fJsTZGjy#?{1ST0x!(t-+1w?8v$#-rYYIw4B|GDIbTE0Z0cVM(VVt3NM*m4tI zRclo$Tp^bJLvT%g#2G{IE@?6^(J0bi)I9~qBzS87jq7n@#&cax7oyHok+_$#^8N&@ z7(1IELeZj{R1b^$wgwI-z`fhnn?{@~BeWVn<_QzczQ0p4;1>7AJ19KR{M;8va&s-fh#SqjnZ966XWpBNvr z%-dvnH*yXkJ4Y&W%EPh5vYO`p5gzSdAzOPV7qvEOcAAX6YRpdsXHvf1%I)u*a4Sb2+6U#9nmH3!~N2DWXkqS{@OsbmT&dQov zoVQCr&*Yu1C>}STAUBCfu_S+5b9Iu{23;pl9=aWPm56DK*z#(pgl>6}&HJnenZlN9 zOPrpbUpLZ5Y$=rqGTrUxDioPpmpqFODx`jxG!&F=^zqijhG#Yuf>MXNCBVFe_`~^O z-^bpYWDj}rP6Cx2B@9seNyA=(4{i@BgscZBC|Ov)2%TEfHi?^sNl#>R2l99X*o_Y^ z;31U8r%L4VjbTp}_t&I`)_F;hgv<%<4+vDZ}V&pfzr zHt!bCHaR}F1`PKu5oNMi*a)IGE3~hW_CuL?>Je3r!Ip3lQ1Ta|cdM#tC(mgIv+C}q zz39tusgE1?fhkCUSwUnzwmDh#jFaYi-n>*sg^?jB)aW%iI)@JdA<+19yU?b3-#Ll?JF zqrA&PE5ZSxo+RKVwJ6#xcuyENSjWKS%%j`mj0v+N9$<=ftG6UjRbE*Ch6-{j3uxHD zsU{h8znhgwT+R%saS)uBeX&>nbT{$>j%`cAeCZ4|g@Vyr)!HCP4h|P={+c+++_f&p zpB?vSTpmZZ$+eP|uyBxcmq-LVV8+>7dE0*g+B5Ug3#X3vOSB*{YY=|D01xpS*VRI&W33?+QxUC5W z-2ZFF;Ua>n*qcM-W=xV9Noeet8#ju+KjI-BeNy)-4^Xj@!&p+i$ zuh2;;?2TO1EuLTEU(nvUR(xYU>J)-L73_I8%*C-f>hF9;x*u*QrS>G}`SxidtDaYA z(i{{wY_OxM(Qs7LL0M8OvK{as#Yc2#D9STWb1d0+K_QsUWy|NA1!?G+2g~2aBRiA- zm8R0)s7hV91ILQ9w_bk3?KRD|h9?u^xO$*mADYZ!X^P>bw@{3Rd}f)oQnd?9e*OYi=6crdXg#TCEAj@ zuXgJT{mhrnU*}+sJME7@eN**QlY)T(coJQ35f8bvZ}QkS(Ye5nmGh>{@L+(P);GG0 zLw&z=N^GC^)(Y_44c>EZTN6Byfn%mTv~+Ihz%-U3%>*8PbvRhFY>?i{f`RZweTF)_ zRwPIoX8|bK7`vR0b$zd^b?*E7rtBQUGTl9U)_k}oPs}Habbbq0f5#Kt^JX^e;rDn4 z(h=G2s|0|Tzv#84^=<&JY~{>^?mHR28f!(z?PR`mlgp@-)pl2seL@A8*2&~{t;~yB z>hEm^)?imPtKqWH0QG&@4PgtiIoI5W62xX=kh+}np4?*+!KdaHuDFd+tXCjSbd^eC+(RQ+`<`Ot>z@}ALH0J zo^OhJ=Ax+gFZYD9$EexD6(+ZK;>!I6)oZ}R_yE0M;bp}G42=^h!q>PLFy=O-2{+@! zpdo1A@gsMIhUcDDVT<%<=XDUHr<51fFT9vUm7BsKoUgtb4y^KiXg?WdK&0` zfpElZAX6JPUUU(n4n7l)`b&n_@kgmNgFSq^7?PpSx#mA&6 zbIj^ScGJfF(zbJPL8&g24==0h-hn&ckn7d6vftV56)lcx!=4k|P|6;vP88nNyCoNj zRcK2pW)dROh7>cKVM?U#P>AjaVc;9G8(vlraQ33)+5&>ByWu2faE9o?k%|tM;L)u> z%lz}ys?pQjivTwFe1X~7zr|AG9H@OE#kZC40!^lbz#S11z?q@9`!HvengFV8M@kplQl+>pF{qaoEe_3)|(^s#2;(}1+up$$dS zzv2nOdhw_XQS|TZ9XUX7bHo0}zyHx@A*mkz6K`(^Xj)Kg&kI;R?VK>+SjDR|YP{|K zPD3&5iVfGb=VV|WhCpeo!fGSdv!_5OdQY`Yw#M;2ZFW+CoY8eL0}4CU4KD1g8i{J_ zJvk3On05xL1tId7XD#+lzpCqEb8T2j<0o8nfNfhrC*fstc>N2_sjTt3v+}ST$({~r zp;@Msk47m+jT0h6N^x9MaP8@B;xawbY$81DrNTB-_VWU8#Li9{$U%qD-X3V9RJ<|4A+mLpKvNIs7;$blpf@S?v)*IM?`nbWsQIs!{By~cNZO@^ z3%=njdx1HbZt;FzzC_kFl(lEZ6~XqjySePyu4~oG6~C=f9Poq&_E7zk=NoA&YX@jovugoa^A_bxX_=FIlKV^j~fK&R!a+F|l{H63@J3_Lj^O;7Wa8=#@g zPzU|26F6ZS)t4rM3E_8KYTydzu2x3W`=w`U^PoUs+R0zSVqw3@7GRHeYA<21>`9d)vx# zdD@HX1QMCb*v(KCBBLQ+wj*7$y-7c=!BFYgrV9px97msGP^k>>p8co-xI7qN zzPCo9>~m~=^U>{76ye#!jOOef`~E2zE*nFR8#kla|L~-@kB=>GpHzg5~!Ki^Qia&TV=6 z@J@CB&k}ifd1_+rRg4x`|NCOx+O(n874C;Tb7daGv5w0%!z~I;zYR%jy+zX{JnMQK zlLio6W6~Xe;P4`~Q44)zwk$c8bBTAf^4!a}9wX>fc}Ux64929ZKUj9pcn~z&bWnn0 z*c+9RL!;QwN09*$m+KPlc}1o~@A@tn5aoh+D73I4*%Jp~%#ogX>$fNANle*~fk6qp z)+4kIWrKa^TEY}7Q$%f^`_#NapC+;u)98loK?AUu4Wm<5_NDXts3^|OYXLeY;>APE zNNIbn=d>%5Q5IzKC9DGk>0NMi_Qg)gPM|wIcfKFp!n^0c{vU^$AoB$NbY)J*{-)1` z@er;ls08Px!?U^T-W~S8S8<$8T7szANmwK|9~Zn=U|Uf)FYrfR_f_IjpQXjjS3V&9 zrk3Zx!4b*NwbY^{8~@GTb=s@DXrp%7i>chhC@u){CE3?F!FIXKK(h-e3c7_cY4UE1 zxSJJ_W_qze+q3OLKe7@x;1f^00kSVfafbF1oi=deN9z1Ix5?H|)N)Z5NAm=ZF=U){ zjZxv~;Baucq==?T7fu&pHD5Rl)C)hVGVE04aLODlAFM%%U@=Ih4zzQbnDNBB%|$_; z8T%&E#*bgnX_{R!X572oaWAK#WIVuLcCF&cZ!_9y4ua1J>QZd5wa`NzZ{fL>jY_ef zh8*+DY3$06J+s%Ew+dDZeRO-xWl*b8MoA2mfqsCZ2sjyO-dsenCIFh23#`7)`R6(= z?qW$+B4LgXJfEwP{wosvO$Fcc4K%vbj@pLA`!r{3!hMglz2#I-Vn z81^@1DP`j&ppMKbSl-M4K@Jg4M?6lbR*o>OcXPg5UCxS#WglKnixIU}3T*7Qk$a!# zjp#Si-#<)6$lahqph**yu;kfe=cW}oFMiZwa{!dxZ$99O4m5ZPRt~$^sCeDQAym>-Xbbom7N_4rMOk;_{s)KKoNwTW%N~4)p z$$kx%jbY)5>r92bi_rhs@woT7`pC9Zpubm}M_!1R_4(hMQ@IFE;VHpCHRh62YD^`- z+fKS=T79%y!Cg`M&dbT?y`8M22)w}SXW~b zRW%cTk} zD+9QwrGK*V4F$P_6{0PEXE~wUfkavjC|Hy^Cjzqp;o)z$117#K5r8H7(L?ei>EqY{ z{a93>vl-k?2N^Z>_~!+64THEquCKbMd{19ox4vS}bf81O>rYJaW08SFuDM=4%y45d zY&2-o6>L{4=GY2eDRgyyc;FF8qDr(nW}u=LAf9s0opKi1rp%1=i` zPaVS^oDGAJli@W0ygpV{UL#Y2o%j@Q^4^9X=uZ*NF|u&n(nGV;S|I4@+rMf6xplm` zc*})Y2hwg=l(!+zz% zPpz89(%%kctRcb8(%6zbNxZ_kfG#K>vU5@p$K7>FRbn*EKG(NQdCbfF_KPx1bKjQZ&>qW{r9zcga7=~Mk<81i{x9>CU&ujwUy zGUtRGTdI-0ek*91c1mg&IJFFi}>ihvu@>&Yy&-Hcj0_&N0wx#YHW z3y?=|@@O^!&5T`W`MQEJ^;fl^up4?uI=b^Ztux;PW`ct9+!3sQXLgtU^REx*dCmqr zIJurp+FNX|0lUvfO)kdXt6ON+@pkQC;4 zASNy&DkdW;E+HZ&E-xl6FCkaE_Za8pOJWdJh!0fW@Ns~%n{$A(hI7ExJ<8@#}j?G_V0Oz*h+i3%aJOp=uF=-243W?SgbuQyA`hi>6W6UDbWb=lcS`jpR?(`d};*>*X(qP~ljdYE&v_mg})*E3IIMV*c!;!brR1 z30thV#+<@`fBBBbn@W-Y{^KgaS^M{!S&G$KT3YJC4G5}$1qB6}6G<|Xlai9O3jeR~ z|F1s-|Nkc6-c-U8vhsGx+3GvJTEFGIKEaohmH^-{8+_8mr?EX%0?f9e&J9KPM0U+BR{n*6f0L`+hgU z>Q@TpA2eaXL49BCoGos0<*#A!0}}I$n@KW^Gaqp5#~hlV*fuY}-;J=^?ZYYq6;=Z| z!jYT3gE1?RuJC6Tksa&uszmu~G+%5pT&ShtevDa3GSHgmOS~b3wU~FJl>Z%gL z|9qV~esvt z&%yP5wT4f-AAZ#sJPBnUd#O7u`ea5SFZ!K`@|U@ZD}AzGf$YK9&(;}NC-Csc_zI?= zP)S0w$Z$6v6=ZUJIt})@4<*4L;Vb2DC~M}HjY2edr_flSf@`rU(uc#>CcY7WH$buL zP2Vl&!VZr764*&8UaPkom}1evKAvaBLMt%_cS{oZF58Z&7~a1yiaF1pcsb?2eL=-V zx>1A`itjSRdw-(XQv7uT3aOcq*XqOCp+o>*enzzBKiK@|bqC3-*;x-+**CZXDr}V$ zRGZ$3ojNUEzs`|;*df>pPf3@BBk!R$P-Gn2&+xmneYo`x%gZZyfKS%UPJH>eV{Xx} z@vlFQzCYEc=-cUP6NMX9Md54&N-|TVZK#lBgh^5L*^7;9cG)kK4Z`U=v~iuXuavPv zdhZPFMU^QWNJ%E2GiwAMt4M#n`?R$&j2B)}9%@h5NKj*sv*{L1f#vhD0=kbA#8BCe zW$5zowgZ0*_utsSzrpL}37-4sXwWwtb*h^@{MyMhG!UI(ipPQz!t;P)We1zJD+sn| zdq3OOo7Cb2sbvBBPRx<)k)P^>XA)KA#S#nSc=WX?Xn`#RtBJs_N^68|T3@9}n%L;u z9Ltj#t%(h)-L!g{HVy=x?8`Zd*?kB|%w6ig`vR;2)Hv91Rv3pylzl6Sm^8ZesnW27 zb5V9ugYOp*%*7;WmZZg41&d(@u}(M!DFr>*j|BxXwM+yu2G~nfIjCu3#rIis5~ZBP zt&3xF@rt^2^nJ((2>WdHJe_<+{GF=1tv?B3RT5OFVM(K!PDzRxP-IxFJwLT4p0trU z7y`^g+Hn$zd(b^*J?xVtOho)AAk_GX(41Hl4E>(ml|APqSi;0ui@T2U6b#|=TR3VS zbYfM4L6NMkbE@LhV%hsg)n>aS-8QHcs{KGUvBn&Uu2_QUNIIOSdp&~KpVZ#p;|HTi z$Kp_3wokeW8d-1%XXmt1m8O*)Vwq+-*4;dpCp z{%CCasT;_GnWZ!ix*%&ZJ7b2Mp!Ug6#5i)W>%MEqd#UpeYn-2IL=qnPW9`}vV>A2k z*y2MgyDh3_y~{m`7u#x;>7hPc?!EA}-Sh+QE@ZR>omh>BBn!*eC?uTYT%#Ys!A3(} z(bsg$L0>`TaP^j;M;|z-QY|#KjQjp3kqQY)vd7z`LlRaKbz5><6{Bc!lDp&}e;zjG z2#kL`H1#9-baW|O4k!QBt0x1^BkitC{74zeN5sZFic5B0xvq`MiyySkv;LabxGU7( zQ7vA|qjSywQyS`^BJ&*m%j?Zh=)9U7WN?8Gdjn@(rDz4^K>@5~;e?Wh6$%NCN6E%R zx5z8_nIA>57Ffp}qWv0Ghz)u0+~ZIkX1h4Ku_)$4WEP~iJA^^js|r_a98M%cAgknI zYzyNqgE^cSJm*zw+QmLkS)n-m+_;F%=0UhQI8)~bYOJeJNk#3Ri!>p=v;1U~8l~Eg z@wa-cRVz-z43HCQ`*V~~4N<2gM29@*sl`Ft)Yn8oK}zgey-}>Sf0VRL3SK_~ZAOyL zz-22aJ1lHmq>3I@gR;rGi##-SIgsRUAfkAR@)kLd zL*+CsO2!{FtXKa~Xvq0YA-h8`o;-LP`cRecJ+e-!4b@?EOet&c#P&b?#T(L2NlKWb zu?INLsB6on{%qLj4kIhtXLbcnXvX-&8Jy$D>1-vGzT)cBJR3 zUBj3MeWK*X=R?RA@jE5r{VLb(s#=A3&hIU_I@mS1F!iZ^ha{NRzcX>~T4aza3E!X* zA!Po~yml08Qbp)$H~R;2vy}`y&csxJsEYk8zbWU`-pN2qG13gukmIFy#C4dI)xm*h zRk`kBp7sd#fGX{?tsnmPDS4|vs@^`KQ+4|5EkRkU)>yP|xKeV=b|)2P(_vlB1Bcl& zi*(Ts=QYQ#%^o<08)^FN#JyV)(y;ZYiEe$uG$5dI&U2^Z-OT4Y+O6;p0s)mK#v$$J zNeR#TIGs9-kvK4X9ab!BbxY!`pq#$Aa&%JG;-{S1;x!Ow3k9Snv}Z65IyX!Xjz&t2 zcGyrHS(5vHX5S@s-0TMR@L7cW@^(MKACDC3_G+5IzwVj6-O1>Cr0M?$Kb^*xV-q8$RD(M&0-I#p_9L`g8Z9mv`&DEAdcIh|vb~T_CKg35aEV{7ho%v> zq47d2KBVFnWj~!~5?7~VV3OFILVVWd;AX;bHW$rBpZvg%rsN~lb{>?U`;f11|#6)E4K9>JTvLnM7 zz&&G!kHG;94Ak+cuz8r0HS+azvw%!Am1+}Uar!fsY-4PF*-lnO)ixoGW*x*G1PVfU z3?7Qp-R$Y_`$!q~mIkpeuAlsUAL`3a=u8<6`2$jyCHz$&cDCK7mUtfAEeE+dhY=7i z`?s(+xJPZiIek3;Y?pN=p}w13BQzFxn4jG#8bf{%6UQ6*D0SvxfE&O#YONw52(i($%;>uF$mD7DMcmt_8h3`(_8!Qmm_+p-o zhV^=Lwc;(*ex)mnjS%hCMfw#^&Y+m@`uWP|Ktj1L=OD^hT}+0@UxFD$AU&|S!1$8* zjCQ#Hb#j48Y2c6$ouC7d`o@c6+Y7sWUOY(`t5S=;EA@R%ApYO6H!hO%+P>wZhcVnAW1nkHe6){aE5 z_NcwI7O)-0UAtt>pB^7mB$q*s2Tylg1TmHH{N zR)G*|!YJZi`F7)R#Q`34XRqo7uWwp>f~?LU)qc|R)xR1O>dcdqwMep|LJW3wJDa2a;Y zqm7h%p3~liyl3%XsAo>&f7ge5OzpJz@KcNvn-itW8=_ZD&kAt_UEj#@3i=n(92=Hh zG(w5nD;gpm=Xc2LB35dmM4`lO*RqbXl}TI7M?Yp^*V~YUt3hMjq1G+g#k7?L0Dpxp ztG7JW9*H<&*?$;@zbWJB^z~r8$6nacdQ>@eWwmW1r*T(GTFcPMHJ191Tiqv-o;L@a zKN#(Z|Lajz6$jh_Si&wo%)^ws$mOes1!)*M@WTc9VbCgV{)ei>y%#kM5s@(m3|+Zj z&v0{!R;tOuXHn7B;|o7u14Vz`9O~2BxX|Cd;)Ml7<>LMJ3FT7Wh_3FYSizta`qT*# zxz}AjReRbqN#_mg{;sWFUKClj8bh!KWSv-iWb00Q9C_UFe@N+1Dh|q6*tlrg=$x4; zYzt%@Zc!=qvfPCWkD+qCzZ}iVd67!h;VqKO>~#K4BPM1K0RiCq5-iVwF3sTr8RQ9d zZ(QGredZC>!3B4bYfUGAD66O(NMLYTrZVE(-YE~go8k_0y2S@mhcFTDbw2-zet+3z z+u4+J?$t~9al=j^)Vlp@UCyULf%5aZK?6(fFzbgWp|1an99Tnxk+ABlue0=nrnNQi ziJZe90Yufr-S_m4>>$(o%sy3Kx#4?<2$tu{-J1Q)FkLP{;GUWcIBF!SbySAU%pS&a zCp#OmIuKyNF4w3M{6SMto*GySjFK55JIODR-tgA?10qFqbOI>v8`C|a*p^V$7s zUP)@WIkSwh#^}nlyR$d>f3ww46fzQkjk_A~9Kh){X$EXXZ&r0zL5`(@4m8ordc*B% zhcw`@Wd@KP%B_8Pv-Wk9Sf_zYYs{@Ce}#?R)VY~k@LC9 zS=SwFo&7*=lC(=FMaTGqS0w?!<|Mbr+sjwW4AJLLBsyzzv{RcE1}$C2Yyip;@mK!J zeHsfz3CGs5)_)fXKM~u~4%lbH$k2%%B1y!Cl4J&N+Rze@;28KXpaZC)o1LluF@gcr zbNWMZu;6Xy&)^}!=LwN=AlCEHp^SKVZi2y+yKkR_&91Mx)|4nib+TAu<|Nbq28+e? zdwdp!nMBaaY^`zq`j7$w_H%6Av(Yrrs`85kgOf7x8xI~r63;4Xz1D~EcF9!!Yb?d@ zI6hY7%LV4CE$9)X`y9%XR#T?3j<}j&!_(7LC4$S@O2?s8a0wK> zOxj)1GWxT#)Oxl2zV9l>jd*qVo|4FO@=&UhuEodpeO}ga{<(^oKs|qav}{E6&LUYY z*oEj+bLi6|LEF4W%LI_#_VbKtH0qL~nqFk5RSV$k+}9)P610E;k(;l{1LtaoHYto>H)bgEh4UV@kiXTwbZ*r zz3b)ZMqa^)SzF&p;DGa4{)IMD`Gyl*ca^zbp17D{L#x=7xam@X=6(?}NxobH;tW>I ztrtcQ-nPpmBII^@nkiYm-=bhdO^jodFy)42tS^Nk{=1&{lx!pH^uW5qmQ|N__9v~` z3K{gAc!kx}mg`7$FV!)aNYmf3lfJOQ%{rCKSAr_zTX;@D$1DHna5sgidCk7BR|bu} zQo;xP!|ZXW+?IBkVEephVQ6Y_^i{@8UP$zgj&qYjO62wXjx@u}6Dt214J!>6pc71< znBg;VvU1xwBuBXQN%2*m(ie|;X%}nXhrDwX_n+L&#av?VUw=--JsAy1AIegL5`^dy z1!bd|gf2*dXv%H%ee2OEv!SjYsu`{ge+?(c*v+yX!D5w5MnM&$Ek-dTo&$O3lxBYJ zcFTTJng}756vu9!{i{MW0y%LZeuzymLg_&p1p949(kHi2DD-;PT|YpxM>{b(Aqcg& zqp@)b{lYM+-HN zmg1B(*9F^-YKpIXZriZqv?aYC3-9TCrG4ew4MHwr3Doe8JwM_=hTM+7Qq^gfG^|q( zz;A9pK+WqNUcC6i*v;_hL}!G@@h9K4+BKJgvb~<_&pZ1lxEB6U6v1~U%D5w!*AuU= z_OJ$M7Y{ezm80vZIz9;A3gdklurv*@p<5CHZrLtmC$yC(78LGY*s??4f5h$qT83O^Ii5L7F7fpo$ENzv~ZW`M;#z zJIX!DiKE)Z5*6rYLPa+q>p|>4Fsec9fYrxxWQjt4`)a=M^#~%Vz#yE0G%MFKa&Zg5 zb5@Xx)f2X(qeZX1TS>`+fTDx5&;W7w+ZOq6)hl((giAG%=TYfU-8{tcX`%fTJl`F8 zl59-UnLDnX}W*UyM!jZthB}D^5OAB=)F~JaOO2w*m9%q zn0_Eo;3xL@)2~bdmtBkzSub!oyZC@>tf@$*hpN_84>8BJ8pq9uen7Z41lx(7$?FwS z@6U2{Th@)1-fan0hibEhiDO zAIz7+^SKEYNBVpcO?jif!%%21;ya>TDc@ygymSFHukysF-{8lUDjS7)l8&z{4M)W%#H~K zF8$3N!_&eWR%nBlaqjwT=$30Cs+pMUgT`Wo)y_40LBV**@Y$ny8CCV7O|Wc&${Cb| z6N&5ZVNZ6H_1E^B*62BO3OFWRqm7_x;lT5gnmWd8*DvsE@cglcfX532+sJddG2XOe z-1QoIvrX@B3qy&&zvH&9`m(p~hIsz5rx-AI)ScR0cxMn|qj$^A12p91ZW%3wCZkDF z?6S#dig9%#)ezX*i#~r|TXu%Nw=UYq2DQof^wrr|YS@J=UI6=!4N{X1%hUt&?`4Xm6z$;nTA(ZWgRu+tAKzmTh?viR!|9MR72 ziWJX{9|H-$r!q?AA`lc~x1sDj38wx;TNepZ1|9Jep*uTR?yff!!9rycm1;~*f_M`G z{p{F0JayHV5Iwd)mC$$6=%Terb*0mY<1#!r6)R<8lB?J?^D43GyQ3t-sh$f4y`wMp z7?t&vlzpJA>q4lS7SmwmNh2aUT-iY52Bao6A8-ga6=(H&ke)o9RJ0M;?H#ixJH^0z z-WK81>%j!MZc9wgIgh&Q`lYnZ^4o2hF2qRhc1}Tb`_04ylcp0ngXyY=404-=kHc(> z*31dluys2a$6B{q-e?C76u3hyF^O6<7Jc{BDLmDeQMu=`59lYqILv)5o?=c7Cqv67 z0<(m!@-uF14CLQTyLa!eLid{0C!e)*o#MW2$GKv22Uw*?MWMr;bXj~~Q>kt_=+v0S z;*C?+U?>4Qc7ch_x#4m|rl-EMvi`do^QZPmK@Lg-G`I znPcx0h#qq;{vFW;DL64#Z>b+^-15;HP>7~WVJP7ZfvDUBNaBr^ZOO(y=stlpKvels z5^ry{A!%r;*_ow2@>U~DCxgm$8=i~LNT!ySQAIFnPL3 zd2)}*3n_AhedyQ{JL)jTO_X_hJFxl*;ZPG67?xT=P8vEdc;y|NjP4KYxh&d`0 z52zHR*qI+o%~*b#YB|!V%=u}?-Mj@S z7w!;-rqho^(~p#CEL(IXn;mbKqfwuv{hil*x}J#I!UgS3epGRUQ^R=gnBpg4&m7C1;O2w^En_6O>7iB0k7FM7tV&L;RyBj0XokE}21c(2 z9*(a2SboZ5?6r|wi5syrzu4jo)OCmTS}GS+NwAsCOVbVPT=d)hj5JO}d$f{{l)dk& zV7s`x2~!wov{8=p1|OQVC>^GmDO8FWk0F7Y4*@y&t=2+hU7Lo;|44JMKzX32yM-)Sem9a`6)rw&*D}S!aqBVGXcNtjA z-(-xSk6&Jv`urh@t%R=kjJkTy=!Zximfdj8ehmwVS#)`pH(NO7S{>i_w!HhJtuG8Y-zftrs#XvHCp^~mXFZOMG*(hv9mX~%*G9n@aW^aY`a;@3S3d1op}mWL?U(9$m5P; z*SVKm4hg^77iOb1*TYw&bG$9?G_t<7pvQeF{_SOXT~%+eF3W^jn)Wl{N?a%Jy>bANpsi^e@zFGc|`K6 zh-p!16y13M92nJN1-6nQshTsoqBIKX)2jf4$na%NAnLop4 z$r7z4fyM3IKM?G<_X-upYp(3~NHr~Q6u?oILw*KhsK~_6%_6^sCW0ZwmxQNAERv&H zEyUe@jNnHgKWW{LO`YojdYRwnxh-KR#4iOlGtJEMkNjd_fVps0&E}IWg4&LL1 zxP7=UcqB4?dXDj(kzPa%UGVlBKC{9qd%-vf7bB^+P9OI^W4CXtKHof^YvT{~iewQW zT+e!JitS0_x%9}ApVP!vqD*jHZ&=^5{~*c=)(O*7!pP9`$4yFDgO+rhk)$cf@!O>) zXFh39-bF)!(iWC|1&WbY>T>s36zIc6jQAoxMKqBOvFc-yp!ufLL#?V-ew$BMOwC$$ z*lYaqY%j*_m|n|JYOTgS(lj4Axi{Z)Vc}Z!lMiY;9bSO*j)VJmQ( zQSf-&U+;Vz@3nK+^ED8b_UF(OA7Y*q9EW;{%53}|73ln6CQf-~u7{{-t?JtVZm3EC z@+1wYaI(9^RI)QrEnNq?=w;$={bF}$eM5}$5@DySTp~kHQ$?kS)K*bf%T_6gGcpD) zOzHaNS9bEzq*7Po@zdYO8sz~)*4s)RDoBcKkO$TuXWFseb05e6K3@eHj>BqKzH(yR97KT zQm(|G;6yKc-3WV8<8|~GB?t~_)$3`af*O)Um=pVt5?wxT5-oi)sbl??#g4(bm_=*M z`F+EY>9H2BTmiV<<@bz%#Hs`{c%~B{gapKJ1%L!J*-nvNcB3TzRxQAVV98J@UqVEUh9gAZ7%Aq zf!Ivjt(d>1v2zx(4%~y~?RBMnBh)0$%*g6qz>+ppu@huR1jbNYSj3#>^yZKq=nki| zph|{9BymoeVssw;qUnK*=F~T{^1pq1LV=C+%|9-9iEG!J(sxMj`c6~7kHLZIQ_<@j zst$pa&f>vjnDzL84CKTU)H{5d^LI_G{M>8q#cge-ic(rI3(qnAmoYW5 z%ojpt$4yinT30Vf7hm1_L20XQRG#^|s(u%otL1j8&DYR9E%ss4`4+&IMw(N=i{Sc-VQ6l#YMFr2No0B9Gljy`N7ak`bAyi}zOY{PmvK8p!KS{MELNNO zh>p8o@HG8`%TfE;hdPpap>a&sXTN&a4UG%w5HzEOF&vv|1G zFaJEvQjE)w3vV_S1-$#XA)7mNa?X4myI!6&SN>eF1^TeM^!dwwC~RzQZ(?M5Q(V2< z6IfpP&F)$L>Xkt`t8o-V9E#Psb!PGFFTUAcg$X@&BVk=$@50;;m`Yk!9T;x%NP==5 z#bDh&l4SoOq_s@0Q$r~;nDj!Xy4+>Jvv2aTWuLSrI8pl(P*b|~l>f(!%K{gkKN+ee zT!KRFNsH34A0%Iw_aai??D(Tu2NzzuJY(gLKc;f}+C#1UOwtNwO9UL}QL-Rb(TGGu zKqZ|YX*c=w{o@{urCec3EL}^>Vy;BUXIPmO_)0Fz(|_efMH><4JAGTPdF=c7sH^c2 zm8_2kzf6)G0jBQy?Bo{=R@G$5FILOTan|zn65SkHJ{tuHKP_~f(!kum5nK+(Jn3p~ z!R81aUZ6n)4yeBj(bQD_0H^i`+m1J0GfoD>5F2(7_9PxCVxJtphPfA0%Fdpm_~r0d zQ$?Z{_&NBpgsdGCUtAZu4vqVJM_xCO??iTBLT3c9aLcR{O!9;L@h=q}W@ugjQ~>YM&ff6*-1+MK8|{@A)c6DBQBzbn`;G|9xuj^;JF zB%3XTzr_2%>}C}JDO@WQFBOs>*Bo}P6J_8D6FN|;cDUoM7VW3$3grw@T;UnTHa&i1 z>EI2JWzgobsS`%&h*Q1@QIeN7po3T@HNW?#8bTl`&lD>XJCsIUSSt!K2W!);ntbEE zLh-Kz;?4sYVvFtfgBFq&FrFV%uat96hnpj;;K`+4vJrdeOYh`VY&bWyk2_(k!CO(& z=j-}xoU*f7EuX;}LDDk7@TuHy>QH+gpH_f~=@<$YylTy^2g7|H=58gAqLc=I@Ue5> zmSBzR4||y%zo0_8w$|t^-q5!W$;QGi^6r(SVjV}iIgHn3oXh~_+w+%Z17g%kLV8E~ zoFO^4TNl#%aIY;MJBV&(MY(l&UlB65u#nawKaje46H<&MF?!NM9lmrn8ah1j2mtvJ z^@COm*EI7Vw~E%7<20KNe@?}Mt9?%F$O;z5(`$EJl+Zg=IFt`PxWL*BL#WEjQ`~cK z%z{)V6&jl42WEQC%2jafi>5;n!+Zm-mr2R9*{D!X*`kz~(B&(fVGqA;Bm z)8#~>us4pjnBEF12>lC3Nm`HWb+5a(65)PeMnC zbL-_1b)SCpbEtlU4KZ9cJoB0+o5S2!vJEk{bA=<1(|?TFqx<}=bgF(*NAH*&%lm4T zLw+5dcUiSKo>jw}AOVX~hcRQTm@Zs9dnO`tMIlY{?Q36*kw!RMTEZLz9H@Phx)zb3+v9CQNH6jI7zo~^ zrH$%fTd9Zlwr4$TVLt}S`2K?blJr5*c_b^gOcpx39IZee@-~XA)w8^+Rz#Y7NZk`7 z?arzH)E=hgY?;>>%ms9T2xW}g-;nym{y^4p)CLREP&U%IIhUt@n%Z)A!T;0P{+>Qg z?pyxLa%(pJX-k~h{`l0Xk63b6_EVQ$Ot*#GPm&B#p(xGilLlLq_;~q6Uif2-EN7%i zb#(o5&e1|rnq2pA{RIN1Oza3euduyJx-o#d!w`Q$U1dvffkQSybi~po0CW7= zb?t4gz!nKN1BYZc(CcFq6=W0H+x_9k(%nStMh&L1j9s7#knc zX$In~`{3)pDB|vm?x^C-b%9APGO#j$e3I1|2h-cHl9rcxPa7W=zYJ3&0&|6E@`IBe zFZ4{9N;HhPxOyi~;mA#An8^Ud^~d_c&ikfRlA1cprWSX=iX%gNUm9;O?jN?c7KHLU;li)&9oYJ-S0_E2j#hs{o5_1i!@ zdzb@}L*K8Q4zH!8nx_f6=a{1t)l#}8SxP#FKn&z|g(izVwzKGxP?KFyiz#1dTLT|% zs3<(+PiGzS3LX6RWZ4Z}E+lxKT%Lt41hP5|&RJFWQ8eVv5By5)O*=5FnxzsAwO{Dr zJKvv#X;TY?>Pj_f{;)7Izqet zXfBId((_df1x#oTBs&hUQhlTEt4;4%fE+`39m8%inoMIe{W{*&^TE>#RN6ekzBBM7 zOq}9NMJy1iFA1htVo&f7pO`kr9U>4UF79$>7Ltj!N7&4Lyb zA2BIGV&7k7P)nwDx|u5hd|QK8am~j6x7t0>bvH*g4W#E+m!!LuLN_^qF3$-(*Fl{N zj~@!2S6ZJB9oOF{h2DU+gV!oV#&ISDx_N#XJYshsp4d#o{uB_n2&}VX)E>;BrAs3u z7&Lyos;sy$2Ws6;(ry{ZC|6`VCUq1PPBN;$Qep9XIoF)96Vs~7l}!cly*jpe$M#yL z813?hnKgs(V(@3Br^JtsLcz5BEBoI9aow$om3CNU@@~OSk(1;8XtAFT?N6@|idF%Mnj-%8I^GGQwK<{b{Sk znqK?66Qj(!Z-L=T-ZxBU#i@Rg|NNHN_s!S93ZW!>Q;Y9_C;#*It2wr6(U0M7P{c)f zw7v&#gC#vquKID&nKbi=D?YpwvSBfn<3-7kJX2bZuB3wWVhoKix~sUkUlF4kAKkh$ zT~+GwR;sUQC`$J90&AAx?+;I5VhB~V*Vm9}x6lRe-kr=rvb80RCCNF4t3eAL-1e^1 zF()c0k2DYrV_JISdnX&~=O_aI7pu8G&V_&NLtn{Ni!Sk)-_HD5-%+crkWhk4!u?ih z!ZEI@L;@Xu@GUrEwYX&YcxH!k+b^#M`2zS@u5Qpc=)yv01UG}?^*V7io80F8{OlKA zMB~J%mGU?ZeRb5#pp2r22G@bRcd@yAT#R6VH9{^s`oyr+&U4=t>Ufp@RbF`bcb!mM zxscw2nym(b*$w|m!_%t6)9V%v0em-Ty|lxlYR_`6q-acnMFw}EdD5$8AvbJf}X%H)`xRCrmx@XoTvn4e|-Dv=U zKzTrfKz&FmNl(E5(OLnA1Ef$`P)LQJiXT(NOBC;sK-qraLU1_$q??lywD}}a4d9Py zmKc_uE&niXdkVp^f^Pw9wc;EWRugS_Nk zhA7|Zhv&{zZzaWEchq7Y!93cz8UkvoYA23Ws@7$itNlo2i{f@pzAj3|$R0^59qM{k zs*L}A^SJ2omtWj}#+_P)@yw?kdpP%}`GtdHPMN8vypk~|1OB&=wCB_OCIysshMo5| zfht-cXOw81=pFKbvyO#=t!z4IsNe-(i|$rc1Tje&3KKwBysys{`VhY(f`DeFnu>{X zenI)KN!LB{QHC8Cov(2ov?w7=yO};bx(2je{2Hhzq@Hlp_!D!*LWw^=q1hpwsnb^~ zXih&V-(ablqPuG;<*!=pnnPc$x!OJy&*?FYgPMAMlxKOj(=avD&lrW2cSg+MOA;$$ zq-dVpw|dFn6VDwlXy@eqS<4N}#9SCIlaSmQ+9APkAxii2GQ zA6=(Vj1^(S3)!IfEfEh{>cO%4TR%7&v`G5X8X>&OKtGub*H*?#Vu!^G9FmmeB$xH8 z;mLl~)duc^L`BGQCpXX|!R4#hY66YQk4n8YOZ~ups;th9Lx> z?>e_o7ZI-+G_3=GtZv_1)i3CbwcnoYjQkfE5J2~|7QEoXKI`rp%6^HYHu!#gTcF!v zsuo(QLIM1^$NDX+L#jDQPXCJHTMqq@RE0TK&$eh_l&|U3aK!|@q%f}B4@x0y_bG!0 zhhe@@a_7NPNiB|Zt-;P>9_E4=s?sMaI-ap?Eu;B|R?VXGB9UN0`MmhEf)>%M&Np?! zU8hH^1vyvGWOwd)J7prt&ieLd{!6Tf3S|w|;x+ zDDUi4jF5~wm71hs2mXR{is#48mI{ds5Ugcg-EcTEQoQFjsM^Dahf{toxP^7plw$T7 zcx6;BIun5P({^_DX>^Ss`LaoWU5Fmdx9`J0h}`^{?h|MZse30u*(+}ivHdZ*1CDU> zpaFgh2_N4kxyuMMgp_w>zG}`i>7-f)L7C zO-;>TBR2HvS4dt;H;=2f9z$^7C%vR8AbQS=HWF#Ry{6xX_HC$*4pehFW)+s1_pcAHpO{5cWG);Yk1TRBoX<}N6=O*e~U%Y9fQemh|W7{B*0 zhR(Vl7e&q-#gJBF08MM>Os1DQibdQd>(KD&n2N(49y7OaPL>)6JAMOCt(AqO!QgM9 z&ipgnpB()|ob71?WNU%kg~VEI7EZPn4NTiR6+6)(e@u^?{=G5uxDMEkuJFOGBac5p z{;BaaHJvi9=`4O%FX`q^>{SU&*`>J>9AEJngTqS}bLk?6wXsjOL__~QMO^)pEvCAB zdQR|3y8HRCTgT+y^s6W`CjJtmCLh{B`!BDKautee=`QF&?^0%d`)La}e-)5I2W4R_ zkT6d4<(Hhg?)219;9z|!u0Ae*RQ~L3vhTF%{ z@bfs1?S{MWqK*$vM!V6zOHk>^>+6W)_lx!fH@Uk$$(*#dgAx8wm~a#Oa5X-uG|Qmy z)ahfl#b|vWc`d(Ig*bpKcNQXV?6H?ZqFffZZ^nD`gIvmOyVDwzFN$ZSM; zV~gM=Oq zcNUJ7TUd2e)ivMdUIx|*=Ovj1GZN+3YyA9H4!;#=CjH(6SDC2l9bPsqrkuot_26tq z#LTQ!<0lr5vZ*v|%O7y(HnOc(_sdTCC8ask?-J|kpZM$@4>!^K_?%buM%A}CI&K+% zCr>Q=2hafXd;+?2>)LaZJh{qYdxw-aM9b|qC`&A!-oW+IjeahRWFg&v-bRsV#M%e#?67Tos65 zeN}OT%3zVvB6ekz$tY;22(6u%?`J5C!bNe+G3a5LqgZ^hkXN9CHY8Uq=E*I5yP|UF zv=%r#!kgVW#c^n^*Z@a?4N)j_#-uHB+hzx}JTym*^;MBjO74W5j@HMO$tl8qw!8>AUh z_jOw7D@%yRl6D>rgZ1qx>v4ts4Mvk#0k*f*5fgC;$(-|ab{T}?S`@KEeP&btT%-bj zqD*gG_ev+TuoV4pfw+RFV_hLD%B()VgFMqqxV29M!&V7AH0`e$4!MhwL;&h(^+Ln-+hC1(Sxw@4tlMN~0+ z?%s5WUNOWX>rr(+nSHq01fgM>QV@~Cr#I>!Aj>s^24diDBdelyXjq~Y@!5?xGkFGX zV5suSiBQ)J#U$YX^a~Tm3;C&~eEGrcNnC^Ah z4E+5`mmvtd??8f?DlCG?q>T<9Y$ld%*b~(WAD_u0>XHk zRYZ`2fnDq!&sRQj1#C9KeuG*%rru|ZjPL$I{u*(rph!<~-qa_*J+9i^X$mNl)#j^q zZ(G6vP0bP;d+MXM!R>M16HOKs5< zmy7YI)G?A0&OnWcC}<8gzx;9c5KX?ROS3dpfmk$@S7WT;{sq~8`qfR%m}s_HgoS_S zD%|q4C*(VT;g(t)lxazSxxYIHSw38FS!Ucv^D>X{oPk$7S$kVEUHLa7`jgZDF?i2A z`@h^j25*AmPSQm)YgB^}cJ=Jse|l_|W>RysKZxV}^x}WDAo?LF@$<(XG~3~;02kwx z_$Lkpe|h$!;rZzh6-dCmRNvXoj-5Lc0nZIrI{?I?6Sfo}M!S-#(J{1?JjN*`L#m2S zg09TUXNiiedon6jE47V_e99_o!q7&W5^$|s!@D}E913Myj2_Ys_fc}!F1~tnaaG?X zHLt|v^<+hfV&r!JSe@pX8(Vrg0h1QN8XFIA8m&)<>-*j40 z5rH4ieE-+?@aC$oS3~BIV-#1#=@93uRmYRXhIjwU6H|MvoT*z#mM|1?$hBWCwaKpB z4PEd&%hI%JQDPteLr>_zI?9h>^+4DMr(Kccx#~}mmL&GGyLO|jiyG=>WwIb}S#WmK zP1We?h~|U?|z&_OXDtqgtscc_(>O2RQ$rj?yNl zH1W;Byvg#tS>-f^b+Ba*khodnj?|yg^kaP_aP4*qB@0i#=lS|La{_1fDUj{T9?JjR zyh#)Dti8$Wxg&B%-P!TPs$zEdVz7%AciA=6`>oP=9isI}ES?%iZsb|evmp%!)2;hL z$xVlIjJ_*%bpi`6(I=U`YQP_4`r`&oB~1cjYqRmMwYx)~JD&iXqWZzi+8eAw3zfqfa9_%=@u3qX1q^f&PfZl@)g zBNF;L?IG+PDsOZf8yEeqjyr8n(-Txe0{%m3*j#KFp^XWKigiF#f6Yfz6R%NRq)(<0 z@G!xPjNx~a$dwAjJGt=Nd&t8BTz_x#*r0w*2)n`MApLCcm)RV`@-pQ!1d=yFa*t3V z@pMb_35WWX#>cvfm0Z+d(><@LM<+v!o&A_LKT#JFh2#q0PyLrbsrTP$qziFY3cr=M zeEEY>ruGJL74(75#l70v#fCT9Gi_ZJ<@DIr+k$ePw!F1$|EIS1jE1uh+kW-lyXY;^ zOQI8mC?SX*j248LC_@A>Tzc%Q;j-Q~;PYk%1D z$y#I0RsQ`v&*OI#gRvuAmBVRfn2t5@!_-nVSg6u@f~?*BWjsc5E54Jw!p=Q4jz;Gt zI1Iusc(MCUkfpS;D*w06h=a|$# zw|2Egq6MFu13t`1&4q^jL@b?0*xUC0^!QKY@OXn9?`!)Q8>8h547tiAcg`ozXakcw4QdV8X= zBk^6)yv{0$Eo}S&r}r9NU~|=MZDtP2yAk)5C|H$w={zkn6ci$!!2}L84K+;0XiLbp zPu7sB1&&|knl*IRfzl<^h^nz0c6_x}r zh~o(075?Eirub=WT6}|%MwyWDYfbU$RHVvlO8->2Zy)`VHT2v_i0aX5OtuQ*(LINi zf1|4o%^Sbtok)GQcpMK&;1E{E2@BiRf-TzGdAO!=(A1nSqv=0b;d$LB zUWGWv5zMU5j7A-1G>Ys8HRF0sqN6igngD!`7wXhn6kwPzcIP;aw9s+qB{_7qHY%r+ zx@bcuQ`An~3~6_^Z?T{~&?MO8KcqSTnP&keu9#Hl)L}Da6~X+-$Cc>HKA~1b=Xggt zq2-gZ6-^+x8*OXmZuMt3v);5%8GH;4CTQoidkZJB`1F^3WT>sWD-xwi@f%rj2l(fF zsc&S#W0)1iQPZRm(Agz1Xy{Ix2K47hyVyKvacfyjeANKB%kv>|PV%EkB#m z(OW7uXT|9{mWA?8hU*_PF3CU?46e2p5?2f++~6y@=vJvp4!$E`$WZ=`_m3#s3yaQm zoZ0fm)kN2nL3onU@O8Jf_&~5D7iXlGd}d#4uli z#Wr6(R&7177v%EuSj)K=3MP~i%&bKPtC;^YEwe=o3+|g>JGyo5w2&WZZFOX3Be8qS zv#e~4_*fKRBX|6P!>#RCFiuHld6;6Elm_NgaSI{(hdZ}*gLN+SaiK~&UY#wsPc=Ko+|E$n!*5Cg{;l(GNj;%=G13F#>cM&cCmj` z;*t>f9o(vSJw3!4bJ_z`vqt0hXnKY5btA6m%jx@(B$kFjxJ~Y8ef`7G1~@U?AUHVw z9BH6Q2v_ODnONZxQp^L;!Obmm^gni`=+Lommm zZD3}zHL*d&ULz#8M(lzp=Il*ha{xrHqpm)Ev~v9;3jRz?HKikG&>0k@*qtD;yy$)G zAa_!F%M!%&kB<=Xa#dLzL_e7Q-Pv%Q-L#ZvWc}3>G~7+*w8+U|(#3LjL@L22wl(qV zzgKuN%r|VsT!;U#dv$>xdQNnPnFGc{c_o%A;!V?)i%3)&BtAW5)?6_gF5CPnP@^I{F`ennR^&$Xs_L@JI8k z!P!+@eV1I9VTa(4=Ths6lXGPKZ`Pg-u#4Rr`%HY0=J$9(X}3&cLC~EyMT3&Cw&rbL z7DS@JX#``n>pIo}lT!zF%8>r_=r7}6F*G8FQ|e^nK`6~o?-YIEDzHowhjJ z4f+*N?8TG2ibNjPT^*Hc25rhTy~p3o_K==xgZ*xWAeesQ|; z$rS7!O_7A+xFsW7*67h3n7EfF=npS+f;N4p_?{pDi;cCz_Ali*9#4yAxf`4hHI>C< z?=WAR>ee^0^eQaMe@8ir?6G^$3`z_o59h3Ctpz23IJinA`@PHf78%be9w zXgwx1jnxgdHn+i1qP0egkQ=KC0j058rM#|Ghe;GTsh5<4K|YqzQbBoU|J<&hIfoX=_5!+r{Lq&E{d^R>Qsy#5 zOJWO1vbYdAFN-s!JnR?t6_R>G%2QEqUVSBSHt>0t3e7ou%)n2-bIsHJV;nR{?dWBV*{+gjHews&Yph{M5K9=mULjeN`fL@mYplu-GjR7}R?> zP@vxk?v6omj(VJu+ZkMyxBO>Z0lH zKCQIN_MX_9j-!VNU!kz-0k2*M(Cl#XTE$Z$t7x^l>f9E(Fz}N`CTmG4TqN{TM*OWZ zj|~w6qGc=HiLb3Z&Xb6rZ3xdK(vjWU6C&K|H=tpu)zvC2OR zHdz`*G*063bM^Lr2@TFhSdFH3LWGJ?IzsPRM}B^$*d;7RhCp9QQMCCUuq|Mt<5)Ct-Pi3N=?!Sl}nv09=b!S6>H5jxy~}s-<9ze zFty+aDCDy1#(b50*5pz96L8(so@$mfcn}ct^EHv2=!G0Idj+^xK_oVbV!!FJ@(}I| zHo45xTcs?mc=}5_hm)g+38zV;CQH=SfjzT}^mfcS`z-%i5sCzwiaPvZr6Loxk%1-K zwY7)Y6!$u3Ep{g!28QK)5kz|#>y?>*limAC?A(F);_8Yg;cQqG_0Z3f(F6lw+*_La zOW0gNHWG+Bd<58l0=`HyDeB6&7Yk)SE(U)GoO zS++7_F*0ClUR%lCJ&{b(lLo~jZa0>gI^2$d`eUCdDgUv`wLr(S78IOUfI5g5Q6r3f zXuDY9VJYaT2R=5zY6RA9TD~i*teFNl`g9P5ij*SX#^opR-`+?z z7%RwX&Nodn?2xWZ9N}KLYDL1#LmrxvhL0~-xrR{pVnxfFP#WV-EXA$nEddqr^A&&9 zXq0!|?7>G8x-Z5O82^lJXR#}wucNDe$7S#iSj_{D2ZVm{G_EL}zi)ThyL+Q$Pf4FO z2q{L1;d>WV5!g`;JA99ApthD)R*bQ!*s>HH{ha&q84Kgksd&#`-=+PYTS<+_D zZ4r&f1XDY6eb)#*e1Uqx0t|#FW69!JMx`Qp;^IV*kHUkaq4PD)#8qt^Pe+*h_n95f z*wgUUwuU0kU$&LS(eZc|fK|8Vn*u#$zRPzw(n9J+@U>d=MC;Z=)8VedU^QwLP3vRD z(&e{spwrCeiX3UEXlx);eRi*CeTD>pV6T@0U%KO~ILCEtuym&YRYjY(u4n^$rPA-z zF*4qSLccZfFJP?NWuiK6^+fxQ;s4s@-YFMeCrCYbRbN)HUBz7v!gC57f2eb7Z^fyZ z7#;FjodkQE`0^;H7e8d_`7n35Ff(@XI`fYWEy2#GRLj~cHVg3Bp73|CXJ_VWbfJ5* z94k0=xDNfP8wdFS9HjLAF4p;|bGqi0E&hubV}FffBncJ+e6x*tgTI z5h3Wf{bBWp?|h+1Z09_kX;_0%YtTWP;=c3EdGjXI#(TX)Ao1>cgb%>AT}y@`TO+-=gaB+?Mn7nmf)v$8f2ujn;L1 zz@cezh7Ga87R|jY;yb?uu4wFolTe#8U*$AU!r_M4v`RuA?uu3#t%XwPk*yg_OZax! z%eBbpL!~;(Jv}SIl7fPLWVZlZ|791;G?UgBI5qx~z94N~EE$f?f`Mwn1(=7^LV#5cxJ7xutso1+?*gIc+wgIK zyi5PZEP{=N*u)L>c5M)l2TPILVY0d^lv7f5Pm zKtIOBAtnU{-U;>rOS>|eVczn+bABlku6LRyEE!6J4q#Eq8lco_q?%&gWC}M}>v+~= z`StkcCl$93jHh7o{E7>HxEA^ccOZCsWC1zPsIETA0O-t{nSCImGN6FLXRR#dc`#l+ zQGYQ1LJGJWHif%YkReR)S9NTHy*<|mL)>@B;cJn`yNC@LJ6*eb)D#rEaP0S7FE4edTqXZ#Jwg4g**X(- zbo)b4BQ~}U!07tNFyej{YkO8lzlCE3Pp?S9L}v$|iPp7X1?v7J9v zuSnr)_HaBQLk3=D4!{QnWk1WPn~`Alf0eKDt&JcPP8j53M>=5in2ABZX+?hs@nujr zb^h-gR5is0O>Yg^J45sBIeH<4ya6WC2=>zG)dL_Pu0=7BO^*G7d38G>|9yO4_hS9} z4ZaF}+CbfDUyckSRzw)N;b@khF>>RIVjSUHv5cYv*`YQ8jx+LnSP%UjFRo;F;-Vf4%`nii5i< zt0M0j(Qx}A9+!|vBn~H9CeMrxN@1n@7Y8z7YNs<7b|vHyGIZG~hDm@e!y@qUup_fx zX+@G&CsR6SO_$$t^^CL4F%evL&(>Wz-oS-h+Nj=v`tK4O zq3TL>KD3IcYMR&IB(@bUVx+ z(Q5h%W99?jnx85q^zJv@jmz};p#PNSac*gD$XA)L0Tz|K38}ZFb5?%^3FLJANJ7JF zfWON7ewdw4oRb&Buscy}UxX8YOp{b^Bs=lwa+5+O$JUEbc{v6}A}@K^6AEZvYam#2 z+VRNRF?WPxv|^2EKkGdA21K$YIvM_Oa#C)jUw{4m^Wb;(jRLI6He0k}y;_uLx<;{E z`U+LQLGAq!HOnRzjN2HqaY3<@IKe-7fb92JV-Cl#7L6V%s$u4kWG}#kJ*xdHai!s( zNK#eS`&{E3+zPket#DQDV?KQT(nQ`JtaJLD@c81|ivC>Sk`1>V>Urhb z6B3YR)4|dMK*!L=UrT;ZxAxa2g>nUPi$7|m`ryXhLzeEq$gi-?YZj0idAF+OUfz+J z3GXXz0iZuIX;>fn#S5TsX^lO~lEB_>zM=99{Gw1E{8hCI86GHUZT>dd6XC3BK4FG$Otc&%Ta~RZQVr2`n*!Pp;LiBv~LtlNVME!b4!ty^11(djl zhWTlye1mYeleb;OZ-W`3XekEn0bPOftH0ug0r?c^?lz7YK)l{d8=u;;cWk2%1lICt zT0`T*Og?C0711K{f*|JPvfX~RJLavL%!rz~s5LWZuZ{i5C3cf2b7E@pi^AU){U_s& zW0rKmN*FGDmT6iT+CCZLtT|rbPw98lA_o)5vI(x~I`7Ft^O{}=AGfOyJWiUJKKd4{yGO|;byx+UA>ikpK9rFW{uDy^A!da)h-i-dXm|BIZ zF;{nr|Tx+b)4`16@+qcSDcRpb*OFoX^%G>&h z!M^KJtpB*T8<*tM(8mf!6^N+%5m<2lo~(#uS*AIo8d0rSm+%E6@P|)iGZ?xaK8VkG zYmliokJXLCTcHQ>NJSLs*RmARhXwiiw_1UYwb+&O+`sPNlKL(AqE_tx}=!r#_`9pv`I zu7eep&gBytn&=G`3r$+>-k^ra(>S~C(6cOgNk4(zPY3a#h=gSXYCjnS=s5bPu>>`~ zoA3{@!w@?n@K&f8!N}exttN6{SY+0*)4-a%N4EW|8Cm)9GxvmL5$G&pDU{*Lh$Q%t z65}&*E0yT@Oo=7M)o3JXT)RsX9iNLug#AgLP=pWjJ0CBq2@P>Y-3N1d8)xv(Cy2{i z2o*`c$`t`jUYHkEufha!q+x&Jk}Ph+i{>vQIPLt3je~KwxrpVljMxEgW6|A*;Zh#r zj&za+kMn;yuqSo1;jI)08`{V^|VT>+%t%+>EVHq9>mQLy8zQ&5RJgcL| z=5=zBCJC`<;ddkN%vb`?KEM2@iNX7ePQqPdFlCBQ9L8ol<(+0!rd&N`qpDkXFgpMt zM7A-S{mQ2w9%0tDj*zau0w6_KQXZSf_;a`d`G)LsjO{)^B&~NYW~6JD2$*+q>D`D- z^4)^>-@3(fdfpYx(NQ&$86pjDDOgdtk%uo(@9&@V=G2|^ivMI}Si-p&v}%`*>Ojlf z)P;E#J9$dkJSSh7`|R1W>VR=JVqnP&0lC$$@KO(To#Y9Xa_lwfh44R@VXUBy6Ct!w z64X#mLq@1`uhDmfj-o5rsQX z>Lf_b)UU;+V`?*%j1Sp(m>8Lw_9o_8CZrL8Oma__=rOr0i{CPb`s~4ZhPn?JJAlB zVEnGR-<0v5qyJ07Mv#wq#yA4KLa1Wmx<0>%I4kVkT!h{v0lp|J(yEWUGVXCgUl*ymL>s5J>)pGN}lUU;#3%euWWdNJ+g)dzyv317CSWwRWT zqg%yyHzmV$M}MxYtH8|wFc2%%uNk*Nni5iIjb~o)wB+u%UZ=ncTvG&L8;x; z;XmL+h*{KLO3-V~yl7=<#iXuzMj1qTm8<-5cK%4pCX&3@#Y;71Vp;9Vsy2x9bVcD$ z3x?POI;M>}{MZ2Z5cP9!6LbNCm3Qd;RD_cpK**(EP-AsIAo(8=nT^0no++I;G(g1# zdn)6{Vr13W=q(pjO7q*CQ4Z8f^Y}A&LA%fUO)d(By*Qvz@kt=dw}9{{$Q}`(!Ynl4 zwYybe!|7=E3Z5$%5Mz@XAJ?;E>ZIJOl+S6XnE*T>ZqdZ;VigU_&tzs)UTtS{%|Y! z4fo{^!#jcU#hpnq@{XOt<@h>ZEMjtwPUQihM9|B$ajq(vne$Xd^8W(ibmPv6i92@c zh7&2qCHSnpEM5JH>S7^Fv8anxi!u&e1?0490G7eb2FLpuG|WG`xIy?Mgf3zpghF7Z{KKY_N(hP9AUb(0`t z$cNUfQx)wV3JR}(x z?vvs1HxpVQTA&Hrt~2@pOpfp2?i0A!VY)O&7iH(EEu(zMMuTW!%n7Vu22KThb3A`* zfcElk-iUooc7Hq9OsKr%-n4BK#kKg=QWD60tzQT4YuE~e=(T5znmHLx-rnv0@2x`j+=pE%HgsX)7>mOZQ ztCM_mzAvdUFXSR`ID0v^xV#$^#B8D17_BEBz`c64(jMaEI zIA+Q2DzT(|#U`)Rec9box3I6c@e@tIcpI-K{rC6Gro$;j?VYW2xY6`y+@JJ}12jo` z9K@Ck)w95VolP;N?G4 zbPG@1mWn**sAZ$mHZrjBfKi(w_RC#grcRI3i(qkz1^v%%5HF2-WyRLD7VXX5&a5vSmGFl+mY z8@t!(3!We@izXOiHh4yP16cmeOilHa)AViI<9~72WZjdt9A`d?l$jiub7V;T8-(`f^FN z&!2VcEwECxM^XNj}Hjee`=>idIMyLe!vjT{^pWFFRI_#(57nO5~1ozmR04`~maKz4Xk z7!vUiRt^+mpZseC;G`Ey0kvicn(}^a|7cZ0FSC+j_TeZU@p>7ZVC8$VE)c~|CT<)| zIaE$pC8zOqVoGaF`#P;V$=z%6I2DH62@O(X)8u-H7&*^#8h@Kt;W7;}el)nwDBPWt zV;fc!R||uKxhPd@GNXw+^vhEX%LaF$joel;M?-+W54+IdV0Rjxh~q^0V0c7FR-aIveD$Ed3u+K0mI8 z-U`z0>2J!wkwOITr|qxJBCJ_#5i1jxSMLq5N0FJmd`neQV8uqMm|Wyq5=t37lm`hh z7rPQ^y8pxoRC3UU`ZwRA-55q=yEvfQebqnz8=p!kZAmm@C7}2`N`d$}wFm1DRuwmM zGn18<6dRG)G&*HI0txN_OCr9Jh^M;tk$KAx4ZhLl>6LCHKlV!n>N#-vixT+NuQ5d- zzydAzCa4T`(W;cBCs=>X)KKN7PP|te;)d62Q@CwBwf`jV%=BSj{=ZfngF@hd;}D}h z&T9EhLA~C$Lenet?mCe5L1e8oNDwWkhg1rCJRONQ_U1tH5^RL}pu9haUO##`k;A)3 z%*K=Z^I0WZum?qBEJO@@1jKe||Ur zW9TEG6De|w(@Fuh^}jpfDgz$t8&Gn_iMJ-M#8)+$GUoMCA+PZt9xu^&zMmLQF`+K< zZu33en)$PltnAbfbn)sf@iz;EIE|HS z#E|QKzDlr?4!``1+4kkdsT8Wg2e2W&WSW0Vsu)&{IHqz?>dkyQ|1snn>A$`21tItr zUM94!+HvIyh#4b=l+~z8Kh8F825mdiMR;0x2`o|}sbP9Q zG{5(=^1QR!=HAi1rIjaxdUTt1Q!X2Ij62Dqs6IV4h&Ni43wYq0t(Js~-%1f`R=FH)tHC$le&4{3g&Gd0R{NG!cW zw5(&PmY`5cGq!^Zogetda(>~Koio}!b=(HCbU~TD&D>FVD~$iG6nLwVsA{S19Z4%r zIw?eF01BtclH7b*6f4+Nm34AwhoLgnlqBB<(DyQHdcf3Q9sZ2hUYH}nFVemd_YW(- z5~zfOB;+1tYO^0)>L$-Pw|x$+k{V56ebZC^+n_PpLptBg z)vdxfAXzqKU-uPzE9!B!jz7`sEfmZYPRD(_hNH@+M~K_j^if_rH^D?4~2yB8KcN%9}eObt7UIS zYllgWDWurzGw|q2-lABTka6X&kFbJnJ%4)mIK0N*jQmM?R~gM~vT%>Rz`?sAV&ymu zb*a_P3UntlwsG0?dRcN1HMlxx$^n4xCxnBRA86i1cL%&!@j9GPE$PM;n-~1Nb1vU(YkZ>3@I@%gW>FsV;gq?@_T{pNVBQ)} zlE90?L4$@AAM_m>cjM2+J5;0*=T^b}-|b?{fj_#fH7@m~2OT&81k^~xe6;wROzici zgOq0{xiO&flKM@};yms02E@XFPY(1`3KlNJY5rT!*&aVIwkU?1jg$0%;WyuMq#t`* z19htg5BMx${~3%B4u;Fnj0XR#eAd&+(hr?yvCO!T*dg<;23b2xKT3DAPWD-MDd*lug^6z;unzjp3!zF5e zKcb{-<<}uQn})+YBQEtpR93K4H#J19wH+K!|M_2~%HWI!w|T58JGGE+NmXb4osgE# zJXdBD@^hy)qJrpoLDhD6T7DSf*hO{KQX@4gVt5)Jw%Vw{DgO6MbrivUzS(%Mdq+i{ zYYnz{SC4(3VgIrOwE0o?P-{;~4!IU0&4U(Le;%2%sVKSjdMu`ad!M13A80VyK5=58 z;j;dB6)wU#@14P4_fjHUJv*S$L6EOj-MoL}@$1ZGJYqjZi9Z{PQ8#%n) zFi+eAlbQeVLkbpFB+BjCd25S3HMm~Hz}7hj;@Dn~SM6^X;+=od`^T}&Cm(OQq_D=T z`l5;Y)8;SX-<`NXu1VztmU{y&N%2V|M2TVW;3nY|0dMVXubN<1&BDh%;YM7-(Kn-< zfuZ}qjBpQy()NBB&9qYGUS9}y?!M9Az5exMd}lih=5{GR<&B0mFLZ3bV-n_82@+OQ zi5z}7Hc9O=I8Ua*yTCIMy zSo*ntt5GGc^Zf*;1OGpgEF@n2KS#Jf$^?^5JL3DCcVf{cEN~DFw8MrsXN#i|>SbgL z`p;PwcS=cyMLFyA><{{V(X8>v(A;g}`Lr8ob8^FY##C7ddi&K&Ph!DL)IV&w6WmjI zHDLU{D#6zed)7FRSM1+F{k7x&)6M;Vz4)ISd?gIyX?2zl90&Y7(lmHjrEVYgzW^&sJ?j7f diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircle@2x.png index b4eb5221ffd0c2791d079ebfc6bafcf0ac7a31ba..919d8f405ca7f40c921e71b91e7a81655db1f94b 100755 GIT binary patch delta 17705 zcma&NX*`tQA3uD}HG>&s-$Fu$Y#|ZZCP|ho*|!jdtd*2?t|19!Nwy-iNY=4rDa#Cr zWEtO-Jtk{qnXzwU=0Cstestfj`@#KSo?P=f*E#2Nea`!{yx-?{GEWMFt2zn>M&5oF z1A8W^A$eB&l$xgc8EtJP$A65tFET(?WL;j<9+6=n$}sZ z2LUXP0$56DXC;cv5Fo!!7E@krnXS}pz=-rF=?w-5vcOzI&nz>+YW!1CF z|IhX1NZp4kbN}x@$&(h>yl)2I4GHzjkqU>4G)_jqjbMAL0bMKXXo zeJTEV9yjl3R7}jp#^7J~hPE9KVV}a58qO^t1}i7HT;*`gr=#a!cb6JI52K1)8;>3`3R6}9lpW&cKmHe2-V4c91z^AZ@YyTf(?rHsVW^6DlX=h%xE-_+Hi{1^0P808uE4)eA|mOL#5(BQZ?V+FWH zhCEqtZfa>F6usdUqjK3g3se0}01FQXr~wTOJso^J6=XnL&8<(vz;M?(wPv>M*|9Y? zBRoDsUvMajERjd6TZmOJ7O_L~ETccBmLUELq&z(@7l(F|!`{}JAlmvB;NC@A&=?~)o@mc*E&=X(vnA2-Ku{+4Cepe*vqZ0k^5G4DSRI1 zDOs~h?`_js7qj?q%=P``!Lfvw#ieNOi-}SlX=Crc6_v*&RqzwNjLFBP zBM+Rfv)F1YX*{>KWvT2$EB3lY$Y3^#^P278zB1<;7z1w>=ue2t!Dg&M1D8w zU6P5TAg{&o!KUB17Ked;>MZ(tKeRi@y6r8Ec$SvNVl%9_9c<7N)VM=10^BcL>1E}N zk}+xUMUoj@WS@I9j+e+6Ocx##xV~wuO}2uqA0{)7mb3Z?Rg92e1{Xn%IZu1o=Sp?X zB(j{L;eXhH;M6eCB+u-29#nMhLgxPRSM%8a;MF7VTto9C^$rRg_M^y+i@HVOrpyLW% zmi=y8r}qmM(*HUJxJ5={5#kR2Z5v{te;wXgj1TzS2G@cb7ryLZ+TtD8 z*U^KG!7?~L-ckR4IUom#`do+Kkt~^@U|IuO4AWgS>Q1T*z1{FB)x=EjmJDzy;p^li)vI6Y=BQ4|f zz0G|&nH&#d|`#!^9~boNQF>ce>!XV3YXahYM>E4_mP;m)L za~jb%b~}sFYb_DqjcM&;IGNicbge8YB^>`^R6dm?D#T6wO?z?tBYdE(s~o~zu{V+*jt zZ+}$S!vnUrkcse!{BKh4708?JI;6ty6IExHdl-}NjEc%Ig0TwX1&qWdryjP>Nh16n z4$<%&$ed5(Jxzo9wkwvbU=ZzdSND_IG*zW5F-CzZbX>%G?s#2Vy}<7luL6MzVY#D| zCkD0_Ydr&h)xYZCPe6F)oPZ2lED?-0+SMc*SU=*oLBsrRSI)tVv>rS8Z=d^QxF(c!UbT=@qZo# z_Q`(r*Tb?eKYAW2N($`cOE!9~E2aC2Q}$V8#x z$fynbW9Fd0%daAS?Ju{)-Mo7<9!y{W_GRz*a0Y3NF0!%GqTx

bF2s0)VHd_aN%?wwRSD#3e> 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 delta 35772 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 delta 34252 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 Date: Tue, 30 Jul 2019 19:28:02 +0900 Subject: [PATCH 0434/2815] Add maximum height to skin dropdown --- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 5d7542ca2b..35be930a2e 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -107,6 +107,8 @@ namespace osu.Game.Overlays.Settings.Sections private class SkinDropdownControl : DropdownControl { protected override string GenerateItemText(SkinInfo item) => item.ToString(); + + protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = 200); } } } From e6e315e07bd952625a01bdd9c1578224d41d3dd0 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 30 Jul 2019 13:29:41 +0300 Subject: [PATCH 0435/2815] Expose current break index --- .../Visual/Gameplay/TestSceneBreakOverlay.cs | 2 ++ osu.Game/Screens/Play/BreakOverlay.cs | 17 +++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs index ed5861b47d..bb89c85e93 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs @@ -147,6 +147,8 @@ namespace osu.Game.Tests.Visual.Gameplay private readonly ManualClock manualClock; private IFrameBasedClock originalClock; + public new int CurrentBreakIndex => base.CurrentBreakIndex; + public double ManualClockTime { get => manualClock.CurrentTime; diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index e3e4014eb3..93267e5f2a 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Play // reset index in case the new breaks list is smaller than last one isBreakTime.Value = false; - currentBreakIndex = 0; + CurrentBreakIndex = 0; initializeBreaks(); } @@ -47,7 +47,8 @@ namespace osu.Game.Screens.Play ///

public IBindable IsBreakTime => isBreakTime; - private int currentBreakIndex; + protected int CurrentBreakIndex; + private readonly BindableBool isBreakTime = new BindableBool(); private readonly Container remainingTimeAdjustmentBox; @@ -137,21 +138,21 @@ namespace osu.Game.Screens.Play var time = Clock.CurrentTime; - if (time > breaks[currentBreakIndex].EndTime) + if (time > breaks[CurrentBreakIndex].EndTime) { - while (time > breaks[currentBreakIndex].EndTime && currentBreakIndex < breaks.Count - 1) - currentBreakIndex++; + while (time > breaks[CurrentBreakIndex].EndTime && CurrentBreakIndex < breaks.Count - 1) + CurrentBreakIndex++; } else { - while (time < breaks[currentBreakIndex].StartTime && currentBreakIndex > 0) - currentBreakIndex--; + while (time < breaks[CurrentBreakIndex].StartTime && CurrentBreakIndex > 0) + CurrentBreakIndex--; } // This ensures that IsBreakTime is generally consistent with the overlay's transforms during a break. // If the current break doesn't have effects, IsBreakTime should be false. // We also assume that the overlay's fade out transform is "not break time". - var currentBreak = breaks[currentBreakIndex]; + var currentBreak = breaks[CurrentBreakIndex]; isBreakTime.Value = currentBreak.HasEffect && time >= currentBreak.StartTime && time <= currentBreak.EndTime - fade_duration; } From f2ab259c21aefc7900f983b1cffc087229f0a349 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 30 Jul 2019 13:30:26 +0300 Subject: [PATCH 0436/2815] Add an assert if current break index has skipped a break --- osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs index bb89c85e93..879e15c548 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs @@ -87,7 +87,12 @@ namespace osu.Game.Tests.Visual.Gameplay setClock(true); loadBreaksStep("multiple breaks", testBreaks); - addBreakSeeks(testBreaks.Last(), false); + seekAndAssertBreak("seek to break start", testBreaks[1].StartTime, true); + AddAssert("is skipped to break #2", () => breakOverlay.CurrentBreakIndex == 1); + + seekAndAssertBreak("seek to break middle", testBreaks[1].StartTime + testBreaks[1].Duration / 2, true); + seekAndAssertBreak("seek to break end", testBreaks[1].EndTime, false); + seekAndAssertBreak("seek to break after end", testBreaks[1].EndTime + 500, false); } private void addShowBreakStep(double seconds) From 333049e71220dedc33bda9ec84e02b6a506fd38e Mon Sep 17 00:00:00 2001 From: DTSDAO Date: Tue, 30 Jul 2019 19:32:53 +0800 Subject: [PATCH 0437/2815] Fix ITMS-90737 --- osu.iOS/Info.plist | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.iOS/Info.plist b/osu.iOS/Info.plist index 0775d1522d..4fbc67e27b 100644 --- a/osu.iOS/Info.plist +++ b/osu.iOS/Info.plist @@ -14,6 +14,8 @@ 0.1.0 LSRequiresIPhoneOS + LSSupportsOpeningDocumentsInPlace + MinimumOSVersion 10.0 UIDeviceFamily From 6ded53b3a91285e8f968c2a1772af347e16841f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2019 21:53:28 +0900 Subject: [PATCH 0438/2815] Reorder class --- .../SkinnableTestScene.cs | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs index ba31f15b3c..a2c058193b 100644 --- a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs +++ b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs @@ -16,30 +16,15 @@ namespace osu.Game.Rulesets.Osu.Tests { public abstract class SkinnableTestScene : OsuGridTestScene { - private static Skin getSkinFromResources(SkinManager skins, string name) - { - using (var storage = new DllResourceStore("osu.Game.Rulesets.Osu.Tests.dll")) - { - var tempName = Path.GetTempFileName(); - - File.Delete(tempName); - Directory.CreateDirectory(tempName); - - var files = storage.GetAvailableResources().Where(f => f.StartsWith($"Resources/{name}")); - - foreach (var file in files) - using (var stream = storage.GetStream(file)) - using (var newFile = File.Create(Path.Combine(tempName, Path.GetFileName(file)))) - stream.CopyTo(newFile); - - return skins.GetSkin(skins.Import(tempName).Result); - } - } - private Skin metricsSkin; private Skin defaultSkin; private Skin specialSkin; + protected SkinnableTestScene() + : base(2, 2) + { + } + [BackgroundDependencyLoader] private void load(AudioManager audio) { @@ -58,9 +43,24 @@ namespace osu.Game.Rulesets.Osu.Tests Cell(3).Child = new LocalSkinOverrideContainer(specialSkin) { RelativeSizeAxes = Axes.Both }.WithChild(creationFunction()); } - protected SkinnableTestScene() - : base(2, 2) + private static Skin getSkinFromResources(SkinManager skins, string name) { + using (var storage = new DllResourceStore("osu.Game.Rulesets.Osu.Tests.dll")) + { + var tempName = Path.GetTempFileName(); + + File.Delete(tempName); + Directory.CreateDirectory(tempName); + + var files = storage.GetAvailableResources().Where(f => f.StartsWith($"Resources/{name}")); + + foreach (var file in files) + using (var stream = storage.GetStream(file)) + using (var newFile = File.Create(Path.Combine(tempName, Path.GetFileName(file)))) + stream.CopyTo(newFile); + + return skins.GetSkin(skins.Import(tempName).Result); + } } } } From e849e68e9984741b2dbe59a17e7445f123b61fac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2019 22:24:03 +0900 Subject: [PATCH 0439/2815] 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 2c5e54d4f5..e64102c401 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -63,6 +63,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 472f6e9d65..1f91ce1cd8 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -15,7 +15,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 0a19eac2b5..66f398a927 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From 307a6c10953eb0cd8c830694b2c0a3d8654b08ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2019 22:38:29 +0900 Subject: [PATCH 0440/2815] Remove DefaultCirclePiece --- .../Objects/Drawables/Pieces/CirclePiece.cs | 24 +++++++++++-- .../Drawables/Pieces/DefaultCirclePiece.cs | 35 ------------------- osu.Game/Skinning/LegacySkin.cs | 6 ++-- 3 files changed, 25 insertions(+), 40 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultCirclePiece.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs index acbf5ba9ae..c92937ef09 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -1,9 +1,11 @@ // 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.Skinning; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces @@ -18,8 +20,26 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Anchor = Anchor.Centre; Origin = Anchor.Centre; + } - InternalChild = new SkinnableDrawable("Play/osu/hitcircle", _ => new DefaultCirclePiece()); + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + InternalChildren = new Drawable[] + { + new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Texture = textures.Get(@"Play/osu/disc"), + }, + new TrianglesPiece + { + RelativeSizeAxes = Axes.Both, + Blending = BlendingMode.Additive, + Alpha = 0.5f, + } + }; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultCirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultCirclePiece.cs deleted file mode 100644 index 047ff943ff..0000000000 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultCirclePiece.cs +++ /dev/null @@ -1,35 +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.Sprites; -using osu.Framework.Graphics.Textures; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces -{ - public class DefaultCirclePiece : Container - { - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - RelativeSizeAxes = Axes.Both; - Children = new Drawable[] - { - new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Texture = textures.Get(@"Play/osu/disc"), - }, - new TrianglesPiece - { - RelativeSizeAxes = Axes.Both, - Blending = BlendingMode.Additive, - Alpha = 0.5f, - } - }; - } - } -} diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 0a811a9514..dffcd00cc3 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -274,11 +274,11 @@ namespace osu.Game.Skinning [BackgroundDependencyLoader] private void load(DrawableHitObject drawableObject, ISkinSource skin) { - Sprite mainCircle; + Sprite hitCircleSprite; InternalChildren = new Drawable[] { - mainCircle = new Sprite + hitCircleSprite = new Sprite { Texture = skin.GetTexture("hitcircle"), Colour = drawableObject.AccentColour.Value @@ -298,7 +298,7 @@ namespace osu.Game.Skinning state.BindValueChanged(updateState, true); accentColour.BindTo(drawableObject.AccentColour); - accentColour.BindValueChanged(colour => mainCircle.Colour = colour.NewValue, true); + accentColour.BindValueChanged(colour => hitCircleSprite.Colour = colour.NewValue, true); } private void updateState(ValueChangedEvent state) From e6bd02d2767f6b7dbb265542dbf2d2369b15e9fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2019 22:41:20 +0900 Subject: [PATCH 0441/2815] Simplify namespace definition --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index df944fd9a6..ca124e9214 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return true; }, }, - new SkinnableDrawable("Play/Osu/Objects/Drawables/MainCircle", _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)), + new SkinnableDrawable("Play/osu/hitcircle", _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)), ApproachCircle = new ApproachCircle { Alpha = 0, diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index dffcd00cc3..fff7fd082e 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -78,7 +78,7 @@ namespace osu.Game.Skinning { switch (componentName) { - case "Play/Osu/Objects/Drawables/MainCircle": + case "Play/osu/hitcircle": if (!hasHitCircle) return null; From fb1f77bd0490ebaf7c921654d3b67792b47ea6d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2019 23:06:18 +0900 Subject: [PATCH 0442/2815] Move implementation and colour logic to legacy implementation --- .../Objects/Drawables/Pieces/SliderBall.cs | 13 +++---------- osu.Game/Skinning/LegacySkin.cs | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index 119005eb40..8b72b23ca3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -4,7 +4,6 @@ using System; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -57,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { RelativeSizeAxes = Axes.Both, // TODO: support skin filename animation (sliderb0, sliderb1...) - Child = new SkinnableDrawable("Play/osu/sliderb", _ => new DefaultSliderBall()), + Child = new SkinnableDrawable("Play/osu/sliderball", _ => new DefaultSliderBall()), } } }; @@ -204,10 +203,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces public class DefaultSliderBall : CompositeDrawable { - private readonly Bindable accentColour = new Bindable(); - - private Drawable drawableBall; - [BackgroundDependencyLoader] private void load(DrawableHitObject drawableObject, ISkinSource skin) { @@ -225,15 +220,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces BorderThickness = 10, BorderColour = Color4.White, Alpha = 1, - Child = drawableBall = new Box + Child = new Box { RelativeSizeAxes = Axes.Both, + Colour = Color4.White, Alpha = 0.4f, } }; - - accentColour.BindTo(drawableObject.AccentColour); - accentColour.BindValueChanged(colour => drawableBall.Colour = colour.NewValue, true); } } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index af9a24df42..84fa5ff97b 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Graphics; @@ -15,6 +16,7 @@ using osu.Framework.IO.Stores; using osu.Game.Database; using osu.Game.Graphics.Sprites; using osuTK; +using osuTK.Graphics; namespace osu.Game.Skinning { @@ -71,6 +73,12 @@ namespace osu.Game.Skinning { switch (componentName) { + case "Play/osu/sliderball": + if (GetTexture("sliderb") != null) + return new LegacySliderBall(); + + break; + case "Play/Miss": componentName = "hit0"; break; @@ -109,6 +117,16 @@ namespace osu.Game.Skinning return new Sprite { Texture = texture }; } + public class LegacySliderBall : Sprite + { + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + Texture = skin.GetTexture("sliderb"); + Colour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? Color4.White; + } + } + public override Texture GetTexture(string componentName) { float ratio = 2; From 6d279dba5ce40957bb8df20845c2409203f02d24 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2019 23:07:41 +0900 Subject: [PATCH 0443/2815] Fix centering being incorrect for some skins --- osu.Game/Skinning/LegacySkin.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index fff7fd082e..c381c6293b 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -281,7 +281,9 @@ namespace osu.Game.Skinning hitCircleSprite = new Sprite { Texture = skin.GetTexture("hitcircle"), - Colour = drawableObject.AccentColour.Value + Colour = drawableObject.AccentColour.Value, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, }, new SkinnableSpriteText("Play/osu/number-text", _ => new OsuSpriteText { @@ -291,7 +293,12 @@ namespace osu.Game.Skinning { Text = (((IHasComboInformation)drawableObject.HitObject).IndexInCurrentCombo + 1).ToString() }, - new Sprite { Texture = skin.GetTexture("hitcircleoverlay") } + new Sprite + { + Texture = skin.GetTexture("hitcircleoverlay"), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } }; state.BindTo(drawableObject.State); From d12de7dbfa0127ffac637e9736a54fc496d29bd9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2019 23:20:35 +0900 Subject: [PATCH 0444/2815] Add temporary scale hack to make cursors correct size --- osu.Game/Skinning/LegacySkin.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index a567f85a02..bd08de03d5 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -255,13 +255,13 @@ namespace osu.Game.Skinning { InternalChildren = new Drawable[] { - new Sprite + new NonPlayfieldSprite { Texture = skin.GetTexture("cursormiddle"), Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - new Sprite + new NonPlayfieldSprite { Texture = skin.GetTexture("cursor"), Anchor = Anchor.Centre, @@ -270,5 +270,18 @@ namespace osu.Game.Skinning }; } } + + private class NonPlayfieldSprite : Sprite + { + public override Texture Texture + { + get => base.Texture; + set + { + value.ScaleAdjust *= 2f; + base.Texture = value; + } + } + } } } From 21a8f566c3b34b78bbfd3be635a06ea5d169d93f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2019 23:24:28 +0900 Subject: [PATCH 0445/2815] Trim whitespace --- osu.Game/Skinning/LegacySkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index f01dc7f8dd..b6d9b014fd 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -87,7 +87,7 @@ namespace osu.Game.Skinning case "Play/osu/hitcircle": if (hasHitCircle) return new LegacyMainCirclePiece(); - + return null; case "Play/Miss": From 4c6cccb3a338294dd20b69615cddeb82ba77301b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2019 23:31:21 +0900 Subject: [PATCH 0446/2815] Update settings in line with framework changes --- .../Settings/Sections/Debug/GeneralSettings.cs | 5 ----- .../Debug/{GCSettings.cs => MemorySettings.cs} | 12 ++++++------ osu.Game/Overlays/Settings/Sections/DebugSection.cs | 2 +- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 2 +- 4 files changed, 8 insertions(+), 13 deletions(-) rename osu.Game/Overlays/Settings/Sections/Debug/{GCSettings.cs => MemorySettings.cs} (63%) diff --git a/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs index f063898a9f..7eec971b62 100644 --- a/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs @@ -27,11 +27,6 @@ namespace osu.Game.Overlays.Settings.Sections.Debug Bindable = frameworkConfig.GetBindable(FrameworkSetting.PerformanceLogging) }, new SettingsCheckbox - { - LabelText = "Bypass caching (slow)", - Bindable = config.GetBindable(DebugSetting.BypassCaching) - }, - new SettingsCheckbox { LabelText = "Bypass front-to-back render pass", Bindable = config.GetBindable(DebugSetting.BypassFrontToBackPass) diff --git a/osu.Game/Overlays/Settings/Sections/Debug/GCSettings.cs b/osu.Game/Overlays/Settings/Sections/Debug/MemorySettings.cs similarity index 63% rename from osu.Game/Overlays/Settings/Sections/Debug/GCSettings.cs rename to osu.Game/Overlays/Settings/Sections/Debug/MemorySettings.cs index 6897b42f4f..db64c9a8ac 100644 --- a/osu.Game/Overlays/Settings/Sections/Debug/GCSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Debug/MemorySettings.cs @@ -1,26 +1,26 @@ // 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.Configuration; using osu.Framework.Graphics; +using osu.Framework.Platform; namespace osu.Game.Overlays.Settings.Sections.Debug { - public class GCSettings : SettingsSubsection + public class MemorySettings : SettingsSubsection { - protected override string Header => "Garbage Collector"; + protected override string Header => "Memory"; [BackgroundDependencyLoader] - private void load(FrameworkDebugConfigManager config) + private void load(FrameworkDebugConfigManager config, GameHost host) { Children = new Drawable[] { new SettingsButton { - Text = "Force garbage collection", - Action = GC.Collect + Text = "Clear all caches", + Action = host.Collect }, }; } diff --git a/osu.Game/Overlays/Settings/Sections/DebugSection.cs b/osu.Game/Overlays/Settings/Sections/DebugSection.cs index 0149cab802..f62de0b243 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSection.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSection.cs @@ -17,7 +17,7 @@ namespace osu.Game.Overlays.Settings.Sections Children = new Drawable[] { new GeneralSettings(), - new GCSettings(), + new MemorySettings(), }; } } diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 2551ffe2fc..a9e4eaa9b3 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -141,6 +141,7 @@ namespace osu.Game.Screens.Select private readonly RulesetInfo ruleset; public BufferedWedgeInfo(WorkingBeatmap beatmap, RulesetInfo userRuleset) + : base(pixelSnapping: true) { this.beatmap = beatmap; ruleset = userRuleset ?? beatmap.BeatmapInfo.Ruleset; @@ -152,7 +153,6 @@ namespace osu.Game.Screens.Select var beatmapInfo = beatmap.BeatmapInfo; var metadata = beatmapInfo.Metadata ?? beatmap.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); - PixelSnapping = true; CacheDrawnFrameBuffer = true; RelativeSizeAxes = Axes.Both; From f6b6fa963393ed2e3130a96e737b33605fc91fcd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2019 23:44:47 +0900 Subject: [PATCH 0447/2815] Add disappeared null check --- osu.Game/Skinning/LegacySkin.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index d6709df646..906a81a007 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -365,7 +365,8 @@ namespace osu.Game.Skinning get => base.Texture; set { - value.ScaleAdjust *= 2f; + if (value != null) + value.ScaleAdjust *= 2f; base.Texture = value; } } From 9335ebeb5592d10a820bdcb5b3edc719294f7593 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2019 23:45:55 +0900 Subject: [PATCH 0448/2815] Fix missing anchor/origin specification --- osu.Game/Skinning/LegacySkin.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 906a81a007..19f04c9b7a 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -291,6 +291,8 @@ namespace osu.Game.Skinning new NonPlayfieldSprite { Texture = skin.GetTexture("cursor"), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, } }; } From 5aece2d5f2c718be42c1b5805e37efa381f18e0d Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 31 Jul 2019 16:03:05 +0900 Subject: [PATCH 0449/2815] fix tests --- .../Visual/Menus/TestSceneScreenNavigation.cs | 23 ++++++++++++++++--- osu.Game/OsuGame.cs | 7 +++--- osu.Game/Screens/Menu/ButtonSystem.cs | 2 +- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs index ca94f2b636..de6daf9618 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs @@ -13,8 +13,10 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; +using osu.Game.Screens; using osu.Game.Screens.Menu; using osu.Game.Screens.Select; +using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -25,6 +27,8 @@ namespace osu.Game.Tests.Visual.Menus private GameHost gameHost; private TestOsuGame osuGame; + private Vector2 backButtonPosition => osuGame.ToScreenSpace(new Vector2(0, osuGame.LayoutRectangle.Bottom)); + [BackgroundDependencyLoader] private void load(GameHost gameHost) { @@ -50,7 +54,8 @@ namespace osu.Game.Tests.Visual.Menus Add(osuGame); }); - AddUntilStep("Wait for main menu", () => osuGame.IsLoaded && osuGame.ScreenStack.CurrentScreen is MainMenu); + AddUntilStep("Wait for load", () => osuGame.IsLoaded); + AddUntilStep("Wait for main menu", () => osuGame.ScreenStack.CurrentScreen is MainMenu); } [Test] @@ -80,7 +85,7 @@ namespace osu.Game.Tests.Visual.Menus AddUntilStep("Back button is hovered", () => InputManager.HoveredDrawables.Any(d => d.Parent == osuGame.BackButton)); AddStep("Click back button", () => InputManager.Click(MouseButton.Left)); - AddAssert("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden); + AddUntilStep("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden); exitViaBackButtonAndConfirm(); } @@ -102,7 +107,7 @@ namespace osu.Game.Tests.Visual.Menus { Screen screen = null; AddStep($"Push new {screenName}", () => osuGame.ScreenStack.Push(screen = newScreen())); - AddUntilStep($"Wait for new {screenName}", () => screen.IsCurrentScreen()); + AddUntilStep($"Wait for new {screenName}", () => osuGame.ScreenStack.CurrentScreen == screen); } private void exitViaEscapeAndConfirm() @@ -129,11 +134,23 @@ namespace osu.Game.Tests.Visual.Menus public new ScreenStack ScreenStack => base.ScreenStack; public new BackButton BackButton => base.BackButton; + + protected override Loader CreateLoader() => new TestLoader(); } private class TestSongSelect : PlaySongSelect { public ModSelectOverlay ModSelectOverlay => ModSelect; } + + private class TestLoader : Loader + { + protected override ShaderPrecompiler CreateShaderPrecompiler() => new TestShaderPrecompiler(); + + private class TestShaderPrecompiler : ShaderPrecompiler + { + protected override bool AllLoaded => true; + } + } } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 77f16549fe..6d85623eb9 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -105,6 +105,8 @@ namespace osu.Game private readonly List visibleBlockingOverlays = new List(); + protected virtual Loader CreateLoader() => new Loader(); + public OsuGame(string[] args = null) { this.args = args; @@ -439,10 +441,7 @@ namespace osu.Game logoContainer.Add(logo); // Loader has to be created after the logo has finished loading as Loader performs logo transformations on entering. - ScreenStack.Push(new Loader - { - RelativeSizeAxes = Axes.Both - }); + ScreenStack.Push(CreateLoader().With(l => l.RelativeSizeAxes = Axes.Both)); }); loadComponentSingleFile(Toolbar = new Toolbar diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 1a3e1213b4..e9eab12557 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -316,7 +316,7 @@ namespace osu.Game.Screens.Menu logoDelayedAction = Scheduler.AddDelayed(() => { if (impact) - logo.Impact(); + logo?.Impact(); game?.Toolbar.Show(); }, 200); From 6bfac9f8e42546565c34151b0d30d0c5b501bd74 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 31 Jul 2019 17:50:13 +0900 Subject: [PATCH 0450/2815] Remove protected ctor --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 47ce28db4c..b3d7bfb91f 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -217,10 +217,6 @@ namespace osu.Game.Rulesets.Scoring private double baseScore; private double bonusScore; - protected ScoreProcessor() - { - } - public ScoreProcessor(DrawableRuleset drawableRuleset) { Debug.Assert(base_portion + combo_portion == 1.0); From e57663b39cdf2ba57984f54c6dd54a751ac225e7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 31 Jul 2019 17:55:22 +0900 Subject: [PATCH 0451/2815] Apply mod score multipliers --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index b3d7bfb91f..2e863f7edb 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -217,6 +217,8 @@ namespace osu.Game.Rulesets.Scoring private double baseScore; private double bonusScore; + private double scoreMultiplier = 1; + public ScoreProcessor(DrawableRuleset drawableRuleset) { Debug.Assert(base_portion + combo_portion == 1.0); @@ -235,6 +237,15 @@ namespace osu.Game.Rulesets.Scoring } Mode.ValueChanged += _ => updateScore(); + Mods.ValueChanged += mods => + { + scoreMultiplier = 1; + + foreach (var m in mods.NewValue) + scoreMultiplier *= m.ScoreMultiplier; + + updateScore(); + }; } ///
@@ -384,7 +395,7 @@ namespace osu.Game.Rulesets.Scoring if (rollingMaxBaseScore != 0) Accuracy.Value = baseScore / rollingMaxBaseScore; - TotalScore.Value = getScore(Mode.Value); + TotalScore.Value = getScore(Mode.Value) * scoreMultiplier; } private double getScore(ScoringMode mode) From 980686f6bff6a0bc3f7da03d7826e3a7b46806d7 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 31 Jul 2019 19:30:35 +0900 Subject: [PATCH 0452/2815] get tests running again --- .../Visual/Menus/TestSceneScreenNavigation.cs | 34 ++++++++++++++----- osu.Game/OsuGame.cs | 2 +- osu.Game/Screens/Menu/ButtonSystem.cs | 2 +- osu.Game/Screens/Multi/Multiplayer.cs | 4 ++- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs index de6daf9618..b4c7716e37 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs @@ -5,17 +5,23 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; +using osu.Game.Rulesets; using osu.Game.Screens; using osu.Game.Screens.Menu; using osu.Game.Screens.Select; +using osu.Game.Tests.Resources; using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -27,7 +33,7 @@ namespace osu.Game.Tests.Visual.Menus private GameHost gameHost; private TestOsuGame osuGame; - private Vector2 backButtonPosition => osuGame.ToScreenSpace(new Vector2(0, osuGame.LayoutRectangle.Bottom)); + private Vector2 backButtonPosition => osuGame.ToScreenSpace(new Vector2(25, osuGame.LayoutRectangle.Bottom - 25)); [BackgroundDependencyLoader] private void load(GameHost gameHost) @@ -55,7 +61,8 @@ namespace osu.Game.Tests.Visual.Menus Add(osuGame); }); AddUntilStep("Wait for load", () => osuGame.IsLoaded); - AddUntilStep("Wait for main menu", () => osuGame.ScreenStack.CurrentScreen is MainMenu); + AddUntilStep("Wait for intro", () => osuGame.ScreenStack.CurrentScreen is IntroScreen); + AddUntilStep("Wait for main menu", () => osuGame.ScreenStack.CurrentScreen is MainMenu menu && menu.IsLoaded); } [Test] @@ -79,7 +86,7 @@ namespace osu.Game.Tests.Visual.Menus pushAndConfirm(() => songSelect = new TestSongSelect(), "song select"); AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show()); AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible); - AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(osuGame.BackButton)); + AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition)); // BackButton handles hover using its child button, so this checks whether or not any of BackButton's children are hovered. AddUntilStep("Back button is hovered", () => InputManager.HoveredDrawables.Any(d => d.Parent == osuGame.BackButton)); @@ -92,22 +99,28 @@ namespace osu.Game.Tests.Visual.Menus [Test] public void TestExitMultiWithEscape() { - pushAndConfirm(() => new Screens.Multi.Multiplayer(), "multiplayer"); + pushAndConfirm(() => new TestMultiplayer(), "multiplayer"); exitViaEscapeAndConfirm(); } [Test] public void TestExitMultiWithBackButton() { - pushAndConfirm(() => new Screens.Multi.Multiplayer(), "multiplayer"); + pushAndConfirm(() => new TestMultiplayer(), "multiplayer"); exitViaBackButtonAndConfirm(); } private void pushAndConfirm(Func newScreen, string screenName) { Screen screen = null; - AddStep($"Push new {screenName}", () => osuGame.ScreenStack.Push(screen = newScreen())); - AddUntilStep($"Wait for new {screenName}", () => osuGame.ScreenStack.CurrentScreen == screen); + AddStep($"Push new {screenName}", () => + { + if (screenName == "song select") + Logger.Log("fuck"); + + osuGame.ScreenStack.Push(screen = newScreen()); + }); + AddUntilStep($"Wait for new {screenName}", () => osuGame.ScreenStack.CurrentScreen == screen && screen.IsLoaded); } private void exitViaEscapeAndConfirm() @@ -118,7 +131,7 @@ namespace osu.Game.Tests.Visual.Menus private void exitViaBackButtonAndConfirm() { - AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(osuGame.BackButton)); + AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition)); AddStep("Click back button", () => InputManager.Click(MouseButton.Left)); AddUntilStep("Wait for main menu", () => osuGame.ScreenStack.CurrentScreen is MainMenu); } @@ -143,6 +156,11 @@ namespace osu.Game.Tests.Visual.Menus public ModSelectOverlay ModSelectOverlay => ModSelect; } + private class TestMultiplayer : Screens.Multi.Multiplayer + { + protected override bool RequireOnline => false; + } + private class TestLoader : Loader { protected override ShaderPrecompiler CreateShaderPrecompiler() => new TestShaderPrecompiler(); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 38b9526364..9fa1eb8aa7 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -317,7 +317,7 @@ namespace osu.Game private void currentTrackCompleted() { - if (!Beatmap.Value.Track.Looping && !Beatmap.Disabled) + if (!Beatmap.Value.Track.Looping && !Beatmap.Disabled && musicController.IsLoaded) musicController.NextTrack(); } diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index e9eab12557..1a3e1213b4 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -316,7 +316,7 @@ namespace osu.Game.Screens.Menu logoDelayedAction = Scheduler.AddDelayed(() => { if (impact) - logo?.Impact(); + logo.Impact(); game?.Toolbar.Show(); }, 200); diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 90806bab6e..3d9997e236 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -34,6 +34,8 @@ namespace osu.Game.Screens.Multi public override bool DisallowExternalBeatmapRulesetChanges => true; + protected virtual bool RequireOnline => true; + private readonly MultiplayerWaveContainer waves; private readonly OsuButton createButton; @@ -166,7 +168,7 @@ namespace osu.Game.Screens.Multi public void APIStateChanged(IAPIProvider api, APIState state) { - if (state != APIState.Online) + if (RequireOnline && state != APIState.Online) forcefullyExit(); } From 351b6e6259769bd98384142ebf9f6d1713a80760 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 31 Jul 2019 19:47:41 +0900 Subject: [PATCH 0453/2815] Add new options test --- .../Visual/Menus/TestSceneScreenNavigation.cs | 23 ++++++++++++++----- osu.Game/OsuGame.cs | 15 ++++++------ 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs index b4c7716e37..f71e8dc2ce 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs @@ -5,7 +5,6 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -13,15 +12,12 @@ using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; -using osu.Game.Beatmaps; -using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; using osu.Game.Overlays.Mods; -using osu.Game.Rulesets; using osu.Game.Screens; using osu.Game.Screens.Menu; using osu.Game.Screens.Select; -using osu.Game.Tests.Resources; using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -35,6 +31,8 @@ namespace osu.Game.Tests.Visual.Menus private Vector2 backButtonPosition => osuGame.ToScreenSpace(new Vector2(25, osuGame.LayoutRectangle.Bottom - 25)); + private Vector2 optionsButtonPosition => osuGame.ToScreenSpace(new Vector2(25, 25)); + [BackgroundDependencyLoader] private void load(GameHost gameHost) { @@ -110,6 +108,17 @@ namespace osu.Game.Tests.Visual.Menus exitViaBackButtonAndConfirm(); } + [Test] + public void TestOpenOptionsAndExitWithEscape() + { + AddStep("Enter menu", () => pressAndRelease(Key.Enter)); + AddStep("Move mouse to options overlay", () => InputManager.MoveMouseTo(optionsButtonPosition)); + AddStep("Click options overlay", () => InputManager.Click(MouseButton.Left)); + AddAssert("Options overlay was opened", () => osuGame.Settings.State.Value == Visibility.Visible); + AddStep("Hide options overlay using escape", () => pressAndRelease(Key.Escape)); + AddAssert("Options overlay was closed", () => osuGame.Settings.State.Value == Visibility.Hidden); + } + private void pushAndConfirm(Func newScreen, string screenName) { Screen screen = null; @@ -117,7 +126,7 @@ namespace osu.Game.Tests.Visual.Menus { if (screenName == "song select") Logger.Log("fuck"); - + osuGame.ScreenStack.Push(screen = newScreen()); }); AddUntilStep($"Wait for new {screenName}", () => osuGame.ScreenStack.CurrentScreen == screen && screen.IsLoaded); @@ -148,6 +157,8 @@ namespace osu.Game.Tests.Visual.Menus public new BackButton BackButton => base.BackButton; + public new SettingsPanel Settings => base.Settings; + protected override Loader CreateLoader() => new TestLoader(); } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 9fa1eb8aa7..e7c0d01f31 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -82,8 +82,11 @@ namespace osu.Game public readonly Bindable OverlayActivationMode = new Bindable(); protected OsuScreenStack ScreenStack; + protected BackButton BackButton; + protected SettingsPanel Settings; + private VolumeOverlay volume; private OsuLogo osuLogo; @@ -97,8 +100,6 @@ namespace osu.Game private readonly string[] args; - private SettingsPanel settings; - private readonly List overlays = new List(); private readonly List toolbarElements = new List(); @@ -483,7 +484,7 @@ namespace osu.Game loadComponentSingleFile(social = new SocialOverlay(), overlayContent.Add, true); loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal, true); loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true); - loadComponentSingleFile(settings = new SettingsOverlay { GetToolbarHeight = () => ToolbarOffset }, leftFloatingOverlayContent.Add, true); + loadComponentSingleFile(Settings = new SettingsOverlay { GetToolbarHeight = () => ToolbarOffset }, leftFloatingOverlayContent.Add, true); var changelogOverlay = loadComponentSingleFile(new ChangelogOverlay(), overlayContent.Add, true); loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add, true); loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add, true); @@ -514,7 +515,7 @@ namespace osu.Game Add(externalLinkOpener = new ExternalLinkOpener()); - var singleDisplaySideOverlays = new OverlayContainer[] { settings, notifications }; + var singleDisplaySideOverlays = new OverlayContainer[] { Settings, notifications }; overlays.AddRange(singleDisplaySideOverlays); foreach (var overlay in singleDisplaySideOverlays) @@ -567,7 +568,7 @@ namespace osu.Game { float offset = 0; - if (settings.State.Value == Visibility.Visible) + if (Settings.State.Value == Visibility.Visible) offset += ToolbarButton.WIDTH / 2; if (notifications.State.Value == Visibility.Visible) offset -= ToolbarButton.WIDTH / 2; @@ -575,7 +576,7 @@ namespace osu.Game screenContainer.MoveToX(offset, SettingsPanel.TRANSITION_LENGTH, Easing.OutQuint); } - settings.State.ValueChanged += _ => updateScreenOffset(); + Settings.State.ValueChanged += _ => updateScreenOffset(); notifications.State.ValueChanged += _ => updateScreenOffset(); } @@ -720,7 +721,7 @@ namespace osu.Game return true; case GlobalAction.ToggleSettings: - settings.ToggleVisibility(); + Settings.ToggleVisibility(); return true; case GlobalAction.ToggleDirect: From 3af1aaeabe8571674c01f721e01dd0628b89edbe Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 31 Jul 2019 15:19:02 +0300 Subject: [PATCH 0454/2815] Unsubscribe from Completed event on old beatmap --- osu.Game/OsuGame.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e71dd67bf2..431e6c91f9 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -307,8 +307,11 @@ namespace osu.Game if (nextBeatmap?.Track != null) nextBeatmap.Track.Completed += currentTrackCompleted; - beatmap.OldValue?.Dispose(); + var oldBeatmap = beatmap.OldValue; + if (oldBeatmap?.Track != null) + oldBeatmap.Track.Completed -= currentTrackCompleted; + oldBeatmap?.Dispose(); nextBeatmap?.LoadBeatmapAsync(); } From 0f1dd8a46e4d5e43e0c1e138886d790a84539cea Mon Sep 17 00:00:00 2001 From: DTSDAO Date: Wed, 31 Jul 2019 21:44:57 +0800 Subject: [PATCH 0455/2815] Undo #5533 for causing crashes --- osu.iOS/Info.plist | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.iOS/Info.plist b/osu.iOS/Info.plist index 4fbc67e27b..0775d1522d 100644 --- a/osu.iOS/Info.plist +++ b/osu.iOS/Info.plist @@ -14,8 +14,6 @@ 0.1.0 LSRequiresIPhoneOS - LSSupportsOpeningDocumentsInPlace - MinimumOSVersion 10.0 UIDeviceFamily From 958e3fb68bffcd8bbad6f178c585f13bf849faa2 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 31 Jul 2019 22:42:23 +0300 Subject: [PATCH 0456/2815] Add a property for acquiring online API access --- osu.Game/Tests/Visual/OsuTestScene.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 27d72f3950..dd68ed93e6 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -16,6 +16,7 @@ using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Tests.Beatmaps; @@ -47,6 +48,12 @@ namespace osu.Game.Tests.Visual private readonly Lazy contextFactory; protected DatabaseContextFactory ContextFactory => contextFactory.Value; + /// + /// Whether this test scene requires API access + /// Setting this will cache an actual . + /// + protected virtual bool RequiresAPIAccess => false; + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { // This is the earliest we can get OsuGameBase, which is used by the dummy working beatmap to find textures @@ -57,7 +64,17 @@ namespace osu.Game.Tests.Visual Default = working }; - return Dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + Dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + if (!RequiresAPIAccess) + { + var dummyAPI = new DummyAPIAccess(); + + Dependencies.CacheAs(dummyAPI); + Add(dummyAPI); + } + + return Dependencies; } protected OsuTestScene() From 034345f1bd5dd28dcd9f5b91bdaca3a6fead35d2 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 31 Jul 2019 22:43:05 +0300 Subject: [PATCH 0457/2815] Resolve API for dummy-caching tests --- osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs | 7 +------ .../Online/TestSceneAccountCreationOverlay.cs | 14 ++++++++------ 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs b/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs index f2718b8e80..13116de320 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs @@ -10,14 +10,9 @@ namespace osu.Game.Tests.Visual.Menus { public class TestSceneDisclaimer : ScreenTestScene { - [Cached(typeof(IAPIProvider))] - private readonly DummyAPIAccess api = new DummyAPIAccess(); - [BackgroundDependencyLoader] - private void load() + private void load(IAPIProvider api) { - Add(api); - AddStep("load disclaimer", () => LoadScreen(new Disclaimer())); AddStep("toggle support", () => diff --git a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs index 35449f5687..66ab1fe18a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs @@ -25,17 +25,14 @@ namespace osu.Game.Tests.Visual.Online typeof(AccountCreationScreen), }; - [Cached(typeof(IAPIProvider))] - private DummyAPIAccess api = new DummyAPIAccess(); + private readonly Container userPanelArea; public TestSceneAccountCreationOverlay() { - Container userPanelArea; AccountCreationOverlay accountCreation; Children = new Drawable[] { - api, accountCreation = new AccountCreationOverlay(), userPanelArea = new Container { @@ -46,11 +43,16 @@ namespace osu.Game.Tests.Visual.Online }, }; + AddStep("show", () => accountCreation.Show()); + } + + [BackgroundDependencyLoader] + private void load(IAPIProvider api) + { api.Logout(); api.LocalUser.BindValueChanged(user => { userPanelArea.Child = new UserPanel(user.NewValue) { Width = 200 }; }, true); - AddStep("show", () => accountCreation.Show()); - AddStep("logout", () => api.Logout()); + AddStep("logout", api.Logout); } } } From 849ed0c69df35d11c0b2c6e133e943143bb19e01 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 31 Jul 2019 22:44:44 +0300 Subject: [PATCH 0458/2815] Acquire api access for online tests --- .../Multiplayer/TestSceneMatchLeaderboard.cs | 7 +++---- .../Visual/Multiplayer/TestSceneMultiScreen.cs | 2 ++ .../Visual/Online/TestSceneBeatmapSetOverlay.cs | 2 ++ .../Visual/Online/TestSceneChangelogOverlay.cs | 2 ++ .../Visual/Online/TestSceneDirectOverlay.cs | 2 ++ .../Visual/Online/TestSceneHistoricalSection.cs | 17 +++++++++-------- .../Visual/Online/TestSceneSocialOverlay.cs | 2 ++ .../Visual/Online/TestSceneUserProfileHeader.cs | 2 ++ .../Online/TestSceneUserProfileOverlay.cs | 2 ++ .../Visual/Online/TestSceneUserRanks.cs | 2 ++ ...estSceneUpdateableBeatmapBackgroundSprite.cs | 2 ++ 11 files changed, 30 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs index fa3c392b2e..723e5fc03d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs @@ -14,6 +14,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMatchLeaderboard : MultiplayerTestScene { + protected override bool RequiresAPIAccess => true; + public TestSceneMatchLeaderboard() { Room.RoomID.Value = 3; @@ -27,11 +29,8 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } - [Resolved] - private IAPIProvider api { get; set; } - [BackgroundDependencyLoader] - private void load() + private void load(IAPIProvider api) { var req = new GetRoomScoresRequest(); req.Success += v => { }; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs index 069e133c2b..b646433846 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs @@ -12,6 +12,8 @@ namespace osu.Game.Tests.Visual.Multiplayer [TestFixture] public class TestSceneMultiScreen : ScreenTestScene { + protected override bool RequiresAPIAccess => true; + public override IReadOnlyList RequiredTypes => new[] { typeof(Screens.Multi.Multiplayer), diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index daee419b52..edb232359f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -42,6 +42,8 @@ namespace osu.Game.Tests.Visual.Online typeof(BeatmapAvailability), }; + protected override bool RequiresAPIAccess => true; + private RulesetInfo taikoRuleset; private RulesetInfo maniaRuleset; diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs index cf8bac7642..324291c9d7 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs @@ -27,6 +27,8 @@ namespace osu.Game.Tests.Visual.Online typeof(Comments), }; + protected override bool RequiresAPIAccess => true; + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs index 75c2a2a6a1..14ae975806 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs @@ -13,6 +13,8 @@ namespace osu.Game.Tests.Visual.Online { private DirectOverlay direct; + protected override bool RequiresAPIAccess => true; + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs b/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs index 838347800f..c98f98c23d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs @@ -17,14 +17,15 @@ namespace osu.Game.Tests.Visual.Online [TestFixture] public class TestSceneHistoricalSection : OsuTestScene { - public override IReadOnlyList RequiredTypes => - new[] - { - typeof(HistoricalSection), - typeof(PaginatedMostPlayedBeatmapContainer), - typeof(DrawableMostPlayedBeatmap), - typeof(DrawableProfileRow) - }; + protected override bool RequiresAPIAccess => true; + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(HistoricalSection), + typeof(PaginatedMostPlayedBeatmapContainer), + typeof(DrawableMostPlayedBeatmap), + typeof(DrawableProfileRow) + }; public TestSceneHistoricalSection() { diff --git a/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs index 5cb96c7ed2..806b36e855 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs @@ -13,6 +13,8 @@ namespace osu.Game.Tests.Visual.Online [TestFixture] public class TestSceneSocialOverlay : OsuTestScene { + protected override bool RequiresAPIAccess => true; + public override IReadOnlyList RequiredTypes => new[] { typeof(UserPanel), diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs index 2285c9b799..555d5334d8 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs @@ -17,6 +17,8 @@ namespace osu.Game.Tests.Visual.Online { public class TestSceneUserProfileHeader : OsuTestScene { + protected override bool RequiresAPIAccess => true; + public override IReadOnlyList RequiredTypes => new[] { typeof(ProfileHeader), diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index c2376aa153..39ba0ea3da 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -19,6 +19,8 @@ namespace osu.Game.Tests.Visual.Online [TestFixture] public class TestSceneUserProfileOverlay : OsuTestScene { + protected override bool RequiresAPIAccess => true; + private readonly TestUserProfileOverlay profile; [Resolved] diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs b/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs index 9f0a8c769a..d777f9766a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs @@ -18,6 +18,8 @@ namespace osu.Game.Tests.Visual.Online [TestFixture] public class TestSceneUserRanks : OsuTestScene { + protected override bool RequiresAPIAccess => true; + public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableProfileScore), typeof(RanksSection) }; public TestSceneUserRanks() diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs index 9cdfcb6cc4..fdc50be3fa 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs @@ -20,6 +20,8 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestSceneUpdateableBeatmapBackgroundSprite : OsuTestScene { + protected override bool RequiresAPIAccess => true; + private BeatmapSetInfo testBeatmap; private IAPIProvider api; private RulesetStore rulesets; From 7b95741dab566f5ef85ae01d1b169dbd3e9f18a0 Mon Sep 17 00:00:00 2001 From: jorolf Date: Wed, 31 Jul 2019 21:55:56 +0200 Subject: [PATCH 0459/2815] fix crash and add some tests - still missing special skin textures --- .../Resources/default-skin/hit0@2x.png | Bin 0 -> 16112 bytes .../Resources/default-skin/hit100@2x.png | Bin 0 -> 31228 bytes .../Resources/default-skin/hit100k@2x.png | Bin 0 -> 21318 bytes .../Resources/default-skin/hit300@2x.png | Bin 0 -> 36873 bytes .../Resources/default-skin/hit300g@2x.png | Bin 0 -> 39840 bytes .../Resources/default-skin/hit50@2x.png | Bin 0 -> 26015 bytes .../Resources/metrics-skin/hit0@2x.PNG | Bin 0 -> 9492 bytes .../Resources/metrics-skin/hit100@2x.PNG | Bin 0 -> 8371 bytes .../Resources/metrics-skin/hit300@2x.PNG | Bin 0 -> 9589 bytes .../Resources/metrics-skin/hit50@2x.PNG | Bin 0 -> 9299 bytes .../TestSceneDrawableJudgement.cs | 39 ++++++++++++++++++ osu.Game/Skinning/LegacySkin.cs | 2 +- 12 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit0@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit100@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit100k@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300g@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit50@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit0@2x.PNG create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit100@2x.PNG create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit300@2x.PNG create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit50@2x.PNG create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit0@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..bdb2bcbc41085fef9d4bd351bdeae81f0da331f4 GIT binary patch literal 16112 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit100@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit100@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7db8eb312403e739d8dc61b280f930443ad0d81c GIT binary patch literal 31228 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..2c7c07852ff6e84edc2164b4487aaaa6cb4d8fe4 GIT binary patch literal 36873 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%0DE1j{G{fQ$Fey0U z2R#5S0}4Y2sll>g2l-GBpks0;f3-|n88Lztw=KzCtYfelY{<&lVA7umPTvbY&)D%? zp2tC!ci}6ArU*~P2wv-%?{u@{IlxQ>C-=}%SBBG2x0{wmp{bM9t{>o@L1#NNi{^id5$h2K%|ahW&;i8Dm>f|(9-A85fm*X zL3PD+EAZrC5I#nw(C%3v7NXjb3SBkS5gzP~Muz9%7O@NC@Et$Jolt0SS*q}8(g7Kl zjo%b4OdE_}>b1uuQDYf)XznHMnkJZpy#peWvYQ!dx=9G?>#r5@w3f zpg6!&lFb)7$Vd|yn-?H2up3Mt!GTUdfvI?IhjmFAv_SAUxMU&~f+1!x^G+CiZr(8Q zN)v~fFzGx>Vhu0tSk_>mh#Z~hgDn&+hTU)Kb)z@4| zYM2Vw^>EA|z2>fvpwL`v=$jq0-)PeDJ7o%wr!?52c|3*~Q+!d^$I0r-RJ39Oy&z`s zd23lL4}pOQkmAz|56_J`&tC1T5J}L&uuEz!^DQK@40eg0V#CZ5TQ_A7&1+i*tGWbB zgFBi84Oa+N?7>Lp)B4!A`{(WaADL^;*|BvMI@1D63$*XVb%D&K&A|P%2ZA4Z(5PN5 zZvs7}gEUYJBk={lVPBXP&0gprn|B*Zrr<{V6TEPIVEljaBA7|vTCsNc7iMe`w^$8$ zkZ5HgUM43mY2!3Qytry5n+wXofKawKh--t@HINjb2PE5f;V?!Hr_QsS%U9r}okz$% z^)6nXkU9@Wae@&6n_?QHC>5H{O|zc0LkveLSSwC|+um}i%Par|uQcKmuZqF$0&$uv zpw?%_SzS;^1e~f@+B9oIqkv#)`i@S4`*Nv$fvvb|!y9j|td5siv7o(!4ta$L1j3V~ z?GFgivZz$6yTX_@)GV5a7ori61sc_AAwK3NKzJ4sFaOXu5L4@-i;@wzL}QA@q1b5E zXd&FJvhiNk7h1QhK`-;b&~Y?_e^(`jeO`^56Nd?86B*Oo6t z8<9JDn%rv;3}(sVQ6QI4cBD-l#sPCDXMx%^$L@JIuTG;_7iGg$MpIuICf&%Cli5z_-b3N%+nH)n$KZKBP=qI@q9y*630kO zB~slRZS**S6D+a0wXgl)4j6>kLQ^=|DK-sl_@UG(~L zan@yCaM#CK8>zECrbKoHzToX}8hI0&?s!~s>RP249GM6V?{XiZQ33N2 zYZN0GQ@TQhqRpZD{!n5bHaz(LwUXdMmk1eawQA%Z~gKzg8gatMcZispd; zEbz?T5Kq~VY4Xo9XlA-irm>lyJp@I1TB3QG1J5_U52eL9L~M9J8a|1$&01mth8@Dn ztl`t_!c{NlOl`A#rON>fVOr(JjiyrjW^0u|7P9c0og% zZ^DY(W&&c#L#&96dfg0;wMyw!M4%ifD|XbjrN`-jXJjTQKmbFtUFw1NxY)4m$+!^W zMZNL61}1EHo|d*?MhS=+OPW3?-fzRYG5Lh^=y9K=QyZ%aUahJYb^k8UXA@iY2v5c6 z`JegB2-{|baqAsLBaN(gtt=XTV!gAp`ErT?QBJz-z6U z$stZd3e+=gGHRKBV1$QSgoJ7Zr^->h2#s}Y5QnA$WCS1Ad+$LjbDwq%_~M#jErXLX zi_tdC;qOJpJG$+n?peLxeJ?nDYONy#Uol zFip!KvDRF_xC2J;TE>+1OVCMJ{63NB%Gnlb#3B<$7Ttn1IAGYlMvnmkPw? zp}}gqF&Z6m_Tf>vBt7n;onw9Q2edxNYB(xtuC}ZK1Pkb<$XJY(nlN<0X&GaNw8PiX z0)~6>kB-9eYm6n2`!-Y*bUiZKcrsx` z{V`c$n774vQE*b{x@f6)1ci2w{4ut!2_%%2$Tx99yIqDH;^xNP#p<+T%?bk4uOm6c z%{7J(!Vf3~=hEPhHP2A5PDFD~&3@;8-n#>D{-6kYhSKnxVIV-PfbMa7e$lOW)#?w> zk}H?(CdW6(a|ID)q!1BzC9c~ixNf>|FpOs0NDP{2Lbx_dg4h>eKLBF_#u5{Ao#or> z&=sOdAs$0qk^sc_d(FLsb&vM#L7}+~477y5Wt4Q-<(y;l{kop8nx0`n8Ram=B03$` zkq<@$k;kVBV}-FdE?4R=2drw|Wmf&WaHZ`!1Hoa=D!noLr<5*Vv{b(HLn z241mxL2N1pd-w^!1Z%|{Vc~lf#2RLl!`^uUQyFRX#?{doIK80?P$G@geT~3jZO5nT zorMgoZw1HxKr@7YD^5o-V9DzU4lGTZPHFI2zbb__oEvp}N-+#^KBCZwf2-i+i}8l= zc#-@4GZdhqCK@u$=v6evr^OOg##xZ$){Zrac-2)QO|i={ZcPyw5@9M>zN2n+7|4j( zxznogan!3}rGplcj-=t2FU=Z3FXtv9m=n5CM~s?68!1TyAg3MCV~XrFrX%I?Tc7wV|@u zg7d6;*ZUZ~jMsg%>Iqvw(L^gc&LmjWTaRxuFW^995@={{o*$ydWd|_N1-ImcWR_0trf9)EU zsAC*|K!hhQoyR-)#O_;|Xd)9KR0>53^t|768-#Onb*voFLZQLg&bf{TqxnQzhaYK$ zNYCDWA0mHD5M!^?vgZCZjj6v~^XI|P`}H^Ssd`%YU|oSXG-8wleNE2Q##8-+;lC~mw8bRpJG zn9_bjd7j01N7U;Orwmc+_PN^(-tH8k9!T11*6nrZ{uB08XzKL6LEQ?(4Jr?@m5bl~ z@0k{7+ar5I4!`WQ(IM9qqS_YnmOgy@N3Or?mEzX9&x#0d(5R+E(erGbm8z$8pR7B7 zDQA0VnjGCOL_|iyFkagXC0BFpcfF?dWN5ZI?U~2#9%7^5tFrsMjusxqD(9eEtWt7cvG&$w3rQOl%JHpr-)gb*n{ul|ml@l4`d(NnHzxu9Nhjr_7hB*8BwQk)WGtTnR^Z$Nl z!P$K5nMC!*0TX$8$fsu{FVqdDI&(JqOI$8 z$iY^6)En5J!s~b6hS__bAnprc-N0#)E`s&Y8;9GsVSR!)hsCJJk4 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300g@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300g@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..1ce746e3a494f4446cc451f5924240f4c8eecb4b GIT binary patch literal 39840 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%0DP1I3KX^ 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 eY4UfV&i_BJT8gC9ZPy+E0000_|)(jNwS@P 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit50@2x.PNG b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit50@2x.PNG new file mode 100644 index 0000000000000000000000000000000000000000..f64feded0cc2fc38672c081da38fe214d2546df3 GIT binary patch literal 9299 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. 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.Extensions; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneDrawableJudgement : SkinnableTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(DrawableJudgement), + typeof(DrawableOsuJudgement) + }; + + public TestSceneDrawableJudgement() + { + foreach (HitResult result in Enum.GetValues(typeof(HitResult))) + { + JudgementResult judgement = new JudgementResult(null) + { + Type = result, + }; + + AddStep("Show " + result.GetDescription(), () => SetContents(() => new DrawableOsuJudgement(judgement, null) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + })); + } + } + } +} diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 5357a75e00..3426ddaf16 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -140,7 +140,7 @@ namespace osu.Game.Skinning if (texture != null && animatable) { - var animation = new TextureAnimation { DefaultFrameLength = frametime }; + var animation = new TextureAnimation { DefaultFrameLength = frametime, AutoSizeAxes = Axes.None }; for (int i = 1; texture != null; i++) { From 852079d43852a255b7d238f1691c3214d5221830 Mon Sep 17 00:00:00 2001 From: Roman Kapustin Date: Thu, 1 Aug 2019 01:35:42 +0300 Subject: [PATCH 0460/2815] Remove redundant ScreenshotManager.Update override --- osu.Game/Graphics/ScreenshotManager.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index 5ad5e5569a..524a4742c0 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -92,7 +92,8 @@ namespace osu.Game.Graphics using (var image = await host.TakeScreenshotAsync()) { - Interlocked.Decrement(ref screenShotTasks); + if (Interlocked.Decrement(ref screenShotTasks) == 0 && cursorVisibility.Value == false) + cursorVisibility.Value = true; var fileName = getFileName(); if (fileName == null) return; @@ -125,14 +126,6 @@ namespace osu.Game.Graphics } }); - protected override void Update() - { - base.Update(); - - if (cursorVisibility.Value == false && Interlocked.CompareExchange(ref screenShotTasks, 0, 0) == 0) - cursorVisibility.Value = true; - } - private string getFileName() { var dt = DateTime.Now; From 8a64ab03847c9ebdb7c2d9d5081e306410f9ee1b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Aug 2019 12:35:17 +0900 Subject: [PATCH 0461/2815] Remove generics from IApplicableToBeatmap --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 3 +-- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 3 +-- .../Mods/ManiaModMirror.cs | 4 +-- .../Mods/ManiaModRandom.cs | 4 +-- osu.Game.Rulesets.Osu/OsuRuleset.cs | 3 +-- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 3 +-- .../Rulesets/Mods/IApplicableToBeatmap.cs | 13 ++++------ osu.Game/Rulesets/Mods/ModTimeRamp.cs | 26 +++++++------------ osu.Game/Rulesets/Mods/ModWindDown.cs | 6 ++--- osu.Game/Rulesets/Mods/ModWindUp.cs | 6 ++--- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- 11 files changed, 28 insertions(+), 45 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index ea9f225cc1..6f1a7873ec 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -11,7 +11,6 @@ using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Beatmaps.Legacy; @@ -108,7 +107,7 @@ namespace osu.Game.Rulesets.Catch case ModType.Fun: return new Mod[] { - new MultiMod(new ModWindUp(), new ModWindDown()) + new MultiMod(new ModWindUp(), new ModWindDown()) }; default: diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index d83033f9c6..8966b5058f 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Game.Graphics; -using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Beatmaps.Legacy; @@ -154,7 +153,7 @@ namespace osu.Game.Rulesets.Mania case ModType.Fun: return new Mod[] { - new MultiMod(new ModWindUp(), new ModWindDown()) + new MultiMod(new ModWindUp(), new ModWindDown()) }; default: diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs index 17f4098420..485595cea9 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Mania.Beatmaps; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModMirror : Mod, IApplicableToBeatmap + public class ManiaModMirror : Mod, IApplicableToBeatmap { public override string Name => "Mirror"; public override string Acronym => "MR"; @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Mods public override double ScoreMultiplier => 1; public override bool Ranked => true; - public void ApplyToBeatmap(Beatmap beatmap) + public void ApplyToBeatmap(IBeatmap beatmap) { var availableColumns = ((ManiaBeatmap)beatmap).TotalColumns; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs index ba16140644..9275371a61 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs @@ -13,7 +13,7 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModRandom : Mod, IApplicableToBeatmap + public class ManiaModRandom : Mod, IApplicableToBeatmap { public override string Name => "Random"; public override string Acronym => "RD"; @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Mods public override string Description => @"Shuffle around the keys!"; public override double ScoreMultiplier => 1; - public void ApplyToBeatmap(Beatmap beatmap) + public void ApplyToBeatmap(IBeatmap beatmap) { var availableColumns = ((ManiaBeatmap)beatmap).TotalColumns; var shuffledColumns = Enumerable.Range(0, availableColumns).OrderBy(item => RNG.Next()).ToList(); diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 7f45fbe1dd..d50d4f401c 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -14,7 +14,6 @@ using osu.Game.Overlays.Settings; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Beatmaps.Legacy; @@ -136,7 +135,7 @@ namespace osu.Game.Rulesets.Osu new OsuModWiggle(), new OsuModSpinIn(), new MultiMod(new OsuModGrow(), new OsuModDeflate()), - new MultiMod(new ModWindUp(), new ModWindDown()), + new MultiMod(new ModWindUp(), new ModWindDown()), }; case ModType.System: diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index a67004e9c7..83356b77c2 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Replays.Types; -using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Replays; using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Difficulty; @@ -107,7 +106,7 @@ namespace osu.Game.Rulesets.Taiko case ModType.Fun: return new Mod[] { - new MultiMod(new ModWindUp(), new ModWindDown()) + new MultiMod(new ModWindUp(), new ModWindDown()) }; default: diff --git a/osu.Game/Rulesets/Mods/IApplicableToBeatmap.cs b/osu.Game/Rulesets/Mods/IApplicableToBeatmap.cs index a634976b3c..cff669bf53 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToBeatmap.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToBeatmap.cs @@ -2,21 +2,18 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mods { /// - /// Interface for a that applies changes to a - /// after conversion and post-processing has completed. + /// Interface for a that applies changes to a after conversion and post-processing has completed. /// - public interface IApplicableToBeatmap : IApplicableMod - where TObject : HitObject + public interface IApplicableToBeatmap : IApplicableMod { /// - /// Applies this to a . + /// Applies this to an . /// - /// The to apply to. - void ApplyToBeatmap(Beatmap beatmap); + /// The to apply to. + void ApplyToBeatmap(IBeatmap beatmap); } } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index a5f96087c0..9edf57ad00 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -13,27 +13,21 @@ using osuTK; namespace osu.Game.Rulesets.Mods { - public abstract class ModTimeRamp : Mod + public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToClock, IApplicableToBeatmap { - public override Type[] IncompatibleMods => new[] { typeof(ModTimeAdjust) }; - - protected abstract double FinalRateAdjustment { get; } - } - - public abstract class ModTimeRamp : ModTimeRamp, IUpdatableByPlayfield, IApplicableToClock, IApplicableToBeatmap - where T : HitObject - { - private double finalRateTime; - - private double beginRampTime; - - private IAdjustableClock clock; - /// /// The point in the beatmap at which the final ramping rate should be reached. /// private const double final_rate_progress = 0.75f; + public override Type[] IncompatibleMods => new[] { typeof(ModTimeAdjust) }; + + protected abstract double FinalRateAdjustment { get; } + + private double finalRateTime; + private double beginRampTime; + private IAdjustableClock clock; + public virtual void ApplyToClock(IAdjustableClock clock) { this.clock = clock; @@ -44,7 +38,7 @@ namespace osu.Game.Rulesets.Mods applyAdjustment(1); } - public virtual void ApplyToBeatmap(Beatmap beatmap) + public virtual void ApplyToBeatmap(IBeatmap beatmap) { HitObject lastObject = beatmap.HitObjects.LastOrDefault(); diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs index 5d71c8950b..b2e3abb59d 100644 --- a/osu.Game/Rulesets/Mods/ModWindDown.cs +++ b/osu.Game/Rulesets/Mods/ModWindDown.cs @@ -4,12 +4,10 @@ using System; using System.Linq; using osu.Framework.Graphics.Sprites; -using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mods { - public class ModWindDown : ModTimeRamp - where T : HitObject + public class ModWindDown : ModTimeRamp { public override string Name => "Wind Down"; public override string Acronym => "WD"; @@ -19,6 +17,6 @@ namespace osu.Game.Rulesets.Mods protected override double FinalRateAdjustment => -0.25; - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindUp)).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindUp)).ToArray(); } } diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs index aae85cec19..8df35a1de2 100644 --- a/osu.Game/Rulesets/Mods/ModWindUp.cs +++ b/osu.Game/Rulesets/Mods/ModWindUp.cs @@ -4,12 +4,10 @@ using System; using System.Linq; using osu.Framework.Graphics.Sprites; -using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mods { - public class ModWindUp : ModTimeRamp - where T : HitObject + public class ModWindUp : ModTimeRamp { public override string Name => "Wind Up"; public override string Acronym => "WU"; @@ -19,6 +17,6 @@ namespace osu.Game.Rulesets.Mods protected override double FinalRateAdjustment => 0.5; - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindDown)).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindDown)).ToArray(); } } diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 52fba9cab3..faa6b98c86 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -278,7 +278,7 @@ namespace osu.Game.Rulesets.UI if (mods == null) return; - foreach (var mod in mods.OfType>()) + foreach (var mod in mods.OfType()) mod.ApplyToBeatmap(Beatmap); } From bc80fa11bbeee2b39174ec1b68132cc3a6468bfe Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Aug 2019 12:41:46 +0900 Subject: [PATCH 0462/2815] Mode IApplicableToBeatmap application to WorkingBeatmap --- osu.Game/Beatmaps/WorkingBeatmap.cs | 3 +++ osu.Game/Rulesets/UI/DrawableRuleset.cs | 15 --------------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 949a2aab6f..baf921ddfc 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -141,6 +141,9 @@ namespace osu.Game.Beatmaps processor?.PostProcess(); + foreach (var mod in mods.OfType()) + mod.ApplyToBeatmap(Beatmap); + return converted; } diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index faa6b98c86..ac81fdc719 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -109,8 +109,6 @@ namespace osu.Game.Rulesets.UI Beatmap = (Beatmap)workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods); - applyBeatmapMods(mods); - KeyBindingInputManager = CreateInputManager(); playfield = new Lazy(CreatePlayfield); @@ -269,19 +267,6 @@ namespace osu.Game.Rulesets.UI public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this); - /// - /// Applies the active mods to the Beatmap. - /// - /// - private void applyBeatmapMods(IReadOnlyList mods) - { - if (mods == null) - return; - - foreach (var mod in mods.OfType()) - mod.ApplyToBeatmap(Beatmap); - } - /// /// Applies the active mods to this DrawableRuleset. /// From 0108700793069bd524b390e50e44ea7b00869247 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 31 Jul 2019 19:48:50 +0900 Subject: [PATCH 0463/2815] Make beatmap conversion test use WorkingBeatmap --- .../ManiaBeatmapConversionTest.cs | 39 ++++++++-- osu.Game/Beatmaps/WorkingBeatmap.cs | 10 ++- .../Tests/Beatmaps/BeatmapConversionTest.cs | 72 ++++++++++++++----- 3 files changed, 94 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs index 6b95975059..51c7ba029a 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs @@ -35,11 +35,37 @@ namespace osu.Game.Rulesets.Mania.Tests }; } - protected override ManiaConvertMapping CreateConvertMapping() => new ManiaConvertMapping(Converter); + private readonly Dictionary rngSnapshots = new Dictionary(); + + protected override void OnConversionGenerated(HitObject original, IEnumerable result, IBeatmapConverter beatmapConverter) + { + base.OnConversionGenerated(original, result, beatmapConverter); + + rngSnapshots[original] = new RngSnapshot(beatmapConverter); + } + + protected override ManiaConvertMapping CreateConvertMapping(HitObject source) => new ManiaConvertMapping(rngSnapshots[source]); protected override Ruleset CreateRuleset() => new ManiaRuleset(); } + public class RngSnapshot + { + public readonly uint RandomW; + public readonly uint RandomX; + public readonly uint RandomY; + public readonly uint RandomZ; + + public RngSnapshot(IBeatmapConverter converter) + { + var maniaConverter = (ManiaBeatmapConverter)converter; + RandomW = maniaConverter.Random.W; + RandomX = maniaConverter.Random.X; + RandomY = maniaConverter.Random.Y; + RandomZ = maniaConverter.Random.Z; + } + } + public class ManiaConvertMapping : ConvertMapping, IEquatable { public uint RandomW; @@ -51,13 +77,12 @@ namespace osu.Game.Rulesets.Mania.Tests { } - public ManiaConvertMapping(IBeatmapConverter converter) + public ManiaConvertMapping(RngSnapshot snapshot) { - var maniaConverter = (ManiaBeatmapConverter)converter; - RandomW = maniaConverter.Random.W; - RandomX = maniaConverter.Random.X; - RandomY = maniaConverter.Random.Y; - RandomZ = maniaConverter.Random.Z; + RandomW = snapshot.RandomW; + RandomX = snapshot.RandomX; + RandomY = snapshot.RandomY; + RandomZ = snapshot.RandomZ; } public bool Equals(ManiaConvertMapping other) => other != null && RandomW == other.RandomW && RandomX == other.RandomX && RandomY == other.RandomY && RandomZ == other.RandomZ; diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index baf921ddfc..6b3a21a2c1 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -89,6 +89,14 @@ namespace osu.Game.Beatmaps return path; } + /// + /// Creates a to convert a for a specified . + /// + /// The to be converted. + /// The for which should be converted. + /// The applicable . + protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap); + /// /// Constructs a playable from using the applicable converters for a specific . /// @@ -104,7 +112,7 @@ namespace osu.Game.Beatmaps { var rulesetInstance = ruleset.CreateInstance(); - IBeatmapConverter converter = rulesetInstance.CreateBeatmapConverter(Beatmap); + IBeatmapConverter converter = CreateBeatmapConverter(Beatmap, rulesetInstance); // Check if the beatmap can be converted if (!converter.CanConvert) diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 6a5e17eb38..1f2d457624 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -4,13 +4,16 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Reflection; using Newtonsoft.Json; using NUnit.Framework; -using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Audio.Track; +using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; namespace osu.Game.Tests.Beatmaps @@ -25,8 +28,6 @@ namespace osu.Game.Tests.Beatmaps protected abstract string ResourceAssembly { get; } - protected IBeatmapConverter Converter { get; private set; } - protected void Test(string name) { var ourResult = convert(name); @@ -98,26 +99,33 @@ namespace osu.Game.Tests.Beatmaps var rulesetInstance = CreateRuleset(); beatmap.BeatmapInfo.Ruleset = beatmap.BeatmapInfo.RulesetID == rulesetInstance.RulesetInfo.ID ? rulesetInstance.RulesetInfo : new RulesetInfo(); - Converter = rulesetInstance.CreateBeatmapConverter(beatmap); + var converterResult = new Dictionary>(); - var result = new ConvertResult(); - - Converter.ObjectConverted += (orig, converted) => + var working = new ConversionWorkingBeatmap(beatmap) { - converted.ForEach(h => h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty)); - - var mapping = CreateConvertMapping(); - mapping.StartTime = orig.StartTime; - - foreach (var obj in converted) - mapping.Objects.AddRange(CreateConvertValue(obj)); - result.Mappings.Add(mapping); + ConversionGenerated = (o, r, c) => + { + converterResult[o] = r; + OnConversionGenerated(o, r, c); + } }; - IBeatmap convertedBeatmap = Converter.Convert(); - rulesetInstance.CreateBeatmapProcessor(convertedBeatmap)?.PostProcess(); + working.GetPlayableBeatmap(rulesetInstance.RulesetInfo, Array.Empty()); - return result; + return new ConvertResult + { + Mappings = converterResult.Select(r => + { + var mapping = CreateConvertMapping(r.Key); + mapping.StartTime = r.Key.StartTime; + mapping.Objects.AddRange(r.Value.SelectMany(CreateConvertValue)); + return mapping; + }).ToList() + }; + } + + protected virtual void OnConversionGenerated(HitObject original, IEnumerable result, IBeatmapConverter beatmapConverter) + { } private ConvertResult read(string name) @@ -154,7 +162,7 @@ namespace osu.Game.Tests.Beatmaps /// This should be used to validate the integrity of the conversion process after a conversion has occurred. /// /// - protected virtual TConvertMapping CreateConvertMapping() => new TConvertMapping(); + protected virtual TConvertMapping CreateConvertMapping(HitObject source) => new TConvertMapping(); /// public ResumeOverlay ResumeOverlay { get; protected set; } + /// + /// Returns first available provided by a . + /// + [CanBeNull] + public HitWindows FirstAvailableHitWindows + { + get + { + foreach (var h in Objects) + { + if (h.HitWindows != null) + return h.HitWindows; + + foreach (var n in h.NestedHitObjects) + if (n.HitWindows != null) + return n.HitWindows; + } + + return null; + } + } + protected virtual ResumeOverlay CreateResumeOverlay() => null; /// diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index eac45f9214..eee7235a6e 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -259,9 +258,7 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20, Right = 10 }, }; - protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay( - scoreProcessor, - drawableRuleset.Objects.Concat(drawableRuleset.Objects.SelectMany(h => h.NestedHitObjects)).FirstOrDefault(h => h.HitWindows != null)?.HitWindows); + protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset.FirstAvailableHitWindows); protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay(); From d1cdf49dd51a6d5b66f006abb2afc5417e04c3e7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Sep 2019 14:21:54 +0900 Subject: [PATCH 1063/2815] Revert SkinnableSprite lookups to old behaviour --- osu.Game.Rulesets.Catch/CatchSkinComponent.cs | 2 +- osu.Game.Rulesets.Catch/CatchSkinComponents.cs | 1 - osu.Game.Rulesets.Catch/UI/CatcherSprite.cs | 2 +- .../Objects/Drawables/Pieces/ApproachCircle.cs | 2 +- osu.Game/Skinning/SkinnableSprite.cs | 16 ++++++++++++++-- 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchSkinComponent.cs b/osu.Game.Rulesets.Catch/CatchSkinComponent.cs index 0a3e43dcfc..8bf53e53e3 100644 --- a/osu.Game.Rulesets.Catch/CatchSkinComponent.cs +++ b/osu.Game.Rulesets.Catch/CatchSkinComponent.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Catch { } - protected override string RulesetPrefix => CatchRuleset.SHORT_NAME; + protected override string RulesetPrefix => "catch"; // todo: use CatchRuleset.SHORT_NAME; protected override string ComponentName => Component.ToString().ToLower(); } diff --git a/osu.Game.Rulesets.Catch/CatchSkinComponents.cs b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs index c03fe42af7..7e482d4045 100644 --- a/osu.Game.Rulesets.Catch/CatchSkinComponents.cs +++ b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs @@ -5,6 +5,5 @@ namespace osu.Game.Rulesets.Catch { public enum CatchSkinComponents { - Catcher } } diff --git a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs index 1c2fe3517a..e3c6c93d01 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.UI [BackgroundDependencyLoader] private void load() { - InternalChild = new SkinnableSprite(new CatchSkinComponent(CatchSkinComponents.Catcher)) + InternalChild = new SkinnableSprite("Gameplay/catch/fruit-catcher-idle") { RelativeSizeAxes = Axes.Both, Anchor = Anchor.TopCentre, diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs index c17c276205..1b474f265c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private class SkinnableApproachCircle : SkinnableSprite { public SkinnableApproachCircle() - : base(new OsuSkinComponent(OsuSkinComponents.ApproachCircle)) + : base("Gameplay/osu/approachcircle") { } diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs index 0081aef520..4b78493e97 100644 --- a/osu.Game/Skinning/SkinnableSprite.cs +++ b/osu.Game/Skinning/SkinnableSprite.cs @@ -19,11 +19,23 @@ namespace osu.Game.Skinning [Resolved] private TextureStore textures { get; set; } - public SkinnableSprite(ISkinComponent component, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) - : base(component, allowFallback, confineMode) + public SkinnableSprite(string textureName, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) + : base(new SpriteComponent(textureName), allowFallback, confineMode) { } protected override Drawable CreateDefault(ISkinComponent component) => new Sprite { Texture = textures.Get(component.LookupName) }; + + private class SpriteComponent : ISkinComponent + { + private readonly string textureName; + + public SpriteComponent(string textureName) + { + this.textureName = textureName; + } + + public string LookupName => textureName; + } } } From bebc3309ced768307cac8398cebf804055dbf79c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Sep 2019 17:57:34 +0900 Subject: [PATCH 1064/2815] Refactor skin configuration to be infinitely extensible --- .../TestSceneCatcher.cs | 4 +- .../TestSceneSkinFallbacks.cs | 2 + .../Objects/Drawables/DrawableSlider.cs | 9 ++-- .../Objects/Drawables/Pieces/SliderBall.cs | 3 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- .../Skinning/LegacySliderBall.cs | 2 +- ...acySkin.cs => OsuLegacySkinTransformer.cs} | 51 +++++++++--------- .../Skinning/OsuSkinColour.cs | 12 +++++ .../Skinning/OsuSkinConfiguration.cs | 14 +++++ osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 3 +- .../Gameplay/TestSceneSkinnableDrawable.cs | 10 ++-- .../Objects/Drawables/DrawableHitObject.cs | 6 ++- osu.Game/Screens/Menu/LogoVisualisation.cs | 2 +- osu.Game/Screens/Menu/MenuSideFlashes.cs | 2 +- osu.Game/Skinning/DefaultSkin.cs | 5 +- osu.Game/Skinning/DefaultSkinConfiguration.cs | 4 -- osu.Game/Skinning/GameplaySkinComponent.cs | 2 +- osu.Game/Skinning/GlobalSkinColour.cs | 10 ++++ osu.Game/Skinning/GlobalSkinConfiguration.cs | 10 ++++ osu.Game/Skinning/ISkin.cs | 4 +- osu.Game/Skinning/LegacySkin.cs | 43 +++++++++++++++ osu.Game/Skinning/LegacySkinDecoder.cs | 54 +++++++------------ osu.Game/Skinning/Skin.cs | 8 +-- osu.Game/Skinning/SkinConfigManager.cs | 16 ++++++ osu.Game/Skinning/SkinConfiguration.cs | 10 +--- osu.Game/Skinning/SkinCustomColourLookup.cs | 15 ++++++ osu.Game/Skinning/SkinManager.cs | 4 +- osu.Game/Skinning/SkinProvidingContainer.cs | 14 +++-- 28 files changed, 214 insertions(+), 107 deletions(-) rename osu.Game.Rulesets.Osu/Skinning/{OsuLegacySkin.cs => OsuLegacySkinTransformer.cs} (70%) create mode 100644 osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs create mode 100644 osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs create mode 100644 osu.Game/Skinning/GlobalSkinColour.cs create mode 100644 osu.Game/Skinning/GlobalSkinConfiguration.cs create mode 100644 osu.Game/Skinning/SkinConfigManager.cs create mode 100644 osu.Game/Skinning/SkinCustomColourLookup.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index c89cd95f36..6a4294a178 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osuTK.Graphics; using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; using osu.Framework.Graphics.Textures; using osu.Game.Audio; @@ -99,8 +100,7 @@ namespace osu.Game.Rulesets.Catch.Tests public Texture GetTexture(string componentName) => throw new NotImplementedException(); - public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => - throw new NotImplementedException(); + public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); } } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index fe73e7c861..02c65db6ad 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; @@ -135,6 +136,7 @@ namespace osu.Game.Rulesets.Osu.Tests public SampleChannel GetSample(ISampleInfo sampleInfo) => null; public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => default; + public IBindable GetConfig(TLookup lookup) => null; public event Action SourceChanged; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 1749ea1f60..00c953c393 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -12,6 +12,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Configuration; +using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Scoring; using osuTK.Graphics; using osu.Game.Skinning; @@ -166,12 +167,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.SkinChanged(skin, allowFallback); - Body.BorderSize = skin.GetValue(s => s.SliderBorderSize) ?? SliderBody.DEFAULT_BORDER_SIZE; - sliderPathRadius = skin.GetValue(s => s.SliderPathRadius) ?? OsuHitObject.OBJECT_RADIUS; + Body.BorderSize = skin.GetConfig(OsuSkinConfiguration.SliderBorderSize)?.Value ?? SliderBody.DEFAULT_BORDER_SIZE; + sliderPathRadius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS; updatePathRadius(); - Body.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderTrackOverride") ? s.CustomColours["SliderTrackOverride"] : (Color4?)null) ?? AccentColour.Value; - Body.BorderColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBorder") ? s.CustomColours["SliderBorder"] : (Color4?)null) ?? Color4.White; + Body.AccentColour = skin.GetConfig(OsuSkinColour.SliderTrackOverride)?.Value ?? AccentColour.Value; + Body.BorderColour = skin.GetConfig(OsuSkinColour.SliderBorder)?.Value ?? Color4.White; } private void updatePathRadius() => Body.PathRadius = slider.Scale * sliderPathRadius; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index 7c871c6ccd..ef7b077480 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -11,6 +11,7 @@ using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Skinning; using osuTK.Graphics; using osu.Game.Skinning; using osuTK; @@ -218,7 +219,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { RelativeSizeAxes = Axes.Both; - float radius = skin.GetValue(s => s.SliderPathRadius) ?? OsuHitObject.OBJECT_RADIUS; + float radius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS; InternalChild = new CircularContainer { diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 27899ab56e..ceb9ed9343 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -167,7 +167,7 @@ namespace osu.Game.Rulesets.Osu public override RulesetSettingsSubsection CreateSettings() => new OsuSettingsSubsection(this); - public override ISkin CreateLegacySkinProvider(ISkinSource source) => new OsuLegacySkin(source); + public override ISkin CreateLegacySkinProvider(ISkinSource source) => new OsuLegacySkinTransformer(source); public override int? LegacyID => 0; diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs index ec838c596d..81c02199d0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Skinning [BackgroundDependencyLoader] private void load(ISkinSource skin, DrawableHitObject drawableObject) { - animationContent.Colour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? Color4.White; + animationContent.Colour = skin.GetConfig(OsuSkinColour.SliderBall)?.Value ?? Color4.White; InternalChildren = new[] { diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs similarity index 70% rename from osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs rename to osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index e3e302b81c..284259705a 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -3,21 +3,19 @@ using System; using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Game.Audio; using osu.Game.Skinning; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning { - public class OsuLegacySkin : ISkin + public class OsuLegacySkinTransformer : ISkin { private readonly ISkin source; - private Lazy configuration; - private Lazy hasHitCircle; /// @@ -27,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Skinning /// private const float legacy_circle_radius = 64 - 5; - public OsuLegacySkin(ISkinSource source) + public OsuLegacySkinTransformer(ISkinSource source) { this.source = source; @@ -37,21 +35,6 @@ namespace osu.Game.Rulesets.Osu.Skinning private void sourceChanged() { - // these need to be lazy in order to ensure they aren't called before the dependencies have been loaded into our source. - configuration = new Lazy(() => - { - var config = new SkinConfiguration(); - if (hasHitCircle.Value) - config.SliderPathRadius = legacy_circle_radius; - - // defaults should only be applied for non-beatmap skins (which are parsed via this constructor). - config.CustomColours["SliderBall"] = - source.GetValue(s => s.CustomColours.TryGetValue("SliderBall", out var val) ? val : (Color4?)null) - ?? new Color4(2, 170, 255, 255); - - return config; - }); - hasHitCircle = new Lazy(() => source.GetTexture("hitcircle") != null); } @@ -96,8 +79,8 @@ namespace osu.Game.Rulesets.Osu.Skinning return null; case OsuSkinComponents.HitCircleText: - string font = GetValue(config => config.HitCircleFont); - var overlap = GetValue(config => config.HitCircleOverlap); + var font = GetConfig(OsuSkinConfiguration.HitCircleFont)?.Value ?? "default"; + var overlap = GetConfig(OsuSkinConfiguration.HitCircleOverlap)?.Value ?? 0; return !hasFont(font) ? null @@ -116,13 +99,27 @@ namespace osu.Game.Rulesets.Osu.Skinning public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample); - public TValue GetValue(Func query) where TConfiguration : SkinConfiguration + public IBindable GetConfig(TLookup lookup) { - TValue val; - if (configuration.Value is TConfiguration conf && (val = query.Invoke(conf)) != null) - return val; + switch (lookup) + { + case OsuSkinColour colour: + return source.GetConfig(new SkinCustomColourLookup(colour)); - return source.GetValue(query); + case OsuSkinConfiguration osuLookup: + switch (osuLookup) + { + case OsuSkinConfiguration.SliderPathRadius: + if (hasHitCircle.Value) + return new BindableFloat(legacy_circle_radius) as Bindable; + + break; + } + + break; + } + + return source.GetConfig(lookup); } private bool hasFont(string fontName) => source.GetTexture($"{fontName}-0") != null; diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs new file mode 100644 index 0000000000..4e6d3ef0e4 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.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.Osu.Skinning +{ + public enum OsuSkinColour + { + SliderTrackOverride, + SliderBorder, + SliderBall + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs new file mode 100644 index 0000000000..a6b87150ae --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs @@ -0,0 +1,14 @@ +// 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.Osu.Skinning +{ + public enum OsuSkinConfiguration + { + HitCircleFont, + HitCircleOverlap, + SliderBorderSize, + SliderPathRadius, + CursorExpand, + } +} diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index 869c27dcac..ac641ecfbc 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -38,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor protected override void SkinChanged(ISkinSource skin, bool allowFallback) { - cursorExpand = skin.GetValue(s => s.CursorExpand ?? true); + cursorExpand = skin.GetConfig(OsuSkinConfiguration.CursorExpand)?.Value ?? true; } [BackgroundDependencyLoader] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index ee5552c6e0..91ee16cab7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Linq; using NUnit.Framework; using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -137,7 +138,8 @@ 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) + public ExposedSkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null, + ConfineMode confineMode = ConfineMode.ScaleDownToFit) : base(new TestSkinComponent(name), defaultImplementation, allowFallback, confineMode) { } @@ -256,7 +258,7 @@ namespace osu.Game.Tests.Visual.Gameplay public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); - public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException(); + public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); } private class SecondarySource : ISkin @@ -267,7 +269,7 @@ namespace osu.Game.Tests.Visual.Gameplay public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); - public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException(); + public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); } private class SkinSourceContainer : Container, ISkin @@ -278,7 +280,7 @@ namespace osu.Game.Tests.Visual.Gameplay public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); - public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException(); + public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); } private class TestSkinComponent : ISkinComponent diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 1a224b2cea..a6d0aad880 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -241,7 +241,11 @@ namespace osu.Game.Rulesets.Objects.Drawables base.SkinChanged(skin, allowFallback); if (HitObject is IHasComboInformation combo) - AccentColour.Value = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White; + { + var comboColours = skin.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value; + + AccentColour.Value = comboColours?.Count > 0 ? comboColours[combo.ComboIndex % comboColours.Count] : Color4.White; + } } /// diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 6984959e9c..59ab6ad265 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -122,7 +122,7 @@ namespace osu.Game.Screens.Menu Color4 defaultColour = Color4.White.Opacity(0.2f); if (user.Value?.IsSupporter ?? false) - AccentColour = skin.Value.GetValue(s => s.CustomColours.ContainsKey("MenuGlow") ? s.CustomColours["MenuGlow"] : (Color4?)null) ?? defaultColour; + AccentColour = skin.Value.GetConfig(GlobalSkinColour.MenuGlow)?.Value ?? defaultColour; else AccentColour = defaultColour; } diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs index 393964561c..55a6a33e89 100644 --- a/osu.Game/Screens/Menu/MenuSideFlashes.cs +++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs @@ -112,7 +112,7 @@ namespace osu.Game.Screens.Menu Color4 baseColour = colours.Blue; if (user.Value?.IsSupporter ?? false) - baseColour = skin.Value.GetValue(s => s.CustomColours.ContainsKey("MenuGlow") ? s.CustomColours["MenuGlow"] : (Color4?)null) ?? baseColour; + baseColour = skin.Value.GetConfig(GlobalSkinColour.MenuGlow)?.Value ?? baseColour; // linear colour looks better in this case, so let's use it for now. Color4 gradientDark = baseColour.Opacity(0).ToLinear(); diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 9eda5d597a..4dee70a47f 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -1,7 +1,8 @@ -// 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 osu.Framework.Audio.Sample; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Game.Audio; @@ -21,5 +22,7 @@ namespace osu.Game.Skinning public override Texture GetTexture(string componentName) => null; public override SampleChannel GetSample(ISampleInfo sampleInfo) => null; + + public override IBindable GetConfig(TLookup lookup) => null; } } diff --git a/osu.Game/Skinning/DefaultSkinConfiguration.cs b/osu.Game/Skinning/DefaultSkinConfiguration.cs index 722b35f102..f52fac6077 100644 --- a/osu.Game/Skinning/DefaultSkinConfiguration.cs +++ b/osu.Game/Skinning/DefaultSkinConfiguration.cs @@ -12,8 +12,6 @@ namespace osu.Game.Skinning { public DefaultSkinConfiguration() { - HitCircleFont = "default"; - ComboColours.AddRange(new[] { new Color4(17, 136, 170, 255), @@ -21,8 +19,6 @@ namespace osu.Game.Skinning new Color4(204, 102, 0, 255), new Color4(121, 9, 13, 255) }); - - CursorExpand = true; } } } diff --git a/osu.Game/Skinning/GameplaySkinComponent.cs b/osu.Game/Skinning/GameplaySkinComponent.cs index 8695b3d720..2aa380fa90 100644 --- a/osu.Game/Skinning/GameplaySkinComponent.cs +++ b/osu.Game/Skinning/GameplaySkinComponent.cs @@ -5,7 +5,7 @@ using System.Linq; namespace osu.Game.Skinning { - public class GameplaySkinComponent : ISkinComponent where T : struct + public class GameplaySkinComponent : ISkinComponent { public readonly T Component; diff --git a/osu.Game/Skinning/GlobalSkinColour.cs b/osu.Game/Skinning/GlobalSkinColour.cs new file mode 100644 index 0000000000..d039be98ce --- /dev/null +++ b/osu.Game/Skinning/GlobalSkinColour.cs @@ -0,0 +1,10 @@ +// 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 enum GlobalSkinColour + { + MenuGlow + } +} diff --git a/osu.Game/Skinning/GlobalSkinConfiguration.cs b/osu.Game/Skinning/GlobalSkinConfiguration.cs new file mode 100644 index 0000000000..66dc9a9395 --- /dev/null +++ b/osu.Game/Skinning/GlobalSkinConfiguration.cs @@ -0,0 +1,10 @@ +// 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 enum GlobalSkinConfiguration + { + ComboColours + } +} diff --git a/osu.Game/Skinning/ISkin.cs b/osu.Game/Skinning/ISkin.cs index bc1ae634c9..841ff3d357 100644 --- a/osu.Game/Skinning/ISkin.cs +++ b/osu.Game/Skinning/ISkin.cs @@ -1,8 +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 osu.Framework.Audio.Sample; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Game.Audio; @@ -20,6 +20,6 @@ namespace osu.Game.Skinning SampleChannel GetSample(ISampleInfo sampleInfo); - TValue GetValue(Func query) where TConfiguration : SkinConfiguration; + IBindable GetConfig(TLookup lookup); } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 535471f455..53f7c54003 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -1,10 +1,12 @@ // 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; using System.Linq; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; @@ -48,6 +50,47 @@ namespace osu.Game.Skinning Samples?.Dispose(); } + public override IBindable GetConfig(TLookup lookup) + { + switch (lookup) + { + case GlobalSkinConfiguration global: + switch (global) + { + case GlobalSkinConfiguration.ComboColours: + return new Bindable>(Configuration.ComboColours) as IBindable; + } + + break; + + case GlobalSkinColour colour: + return getCustomColour(colour.ToString()) as IBindable; + + case SkinCustomColourLookup customColour: + return getCustomColour(customColour.Lookup.ToString()) as IBindable; + + default: + try + { + if (Configuration.ConfigDictionary.TryGetValue(lookup.ToString(), out var val)) + { + var bindable = new Bindable(); + bindable.Parse(val); + return bindable; + } + } + catch + { + } + + break; + } + + return null; + } + + private IBindable getCustomColour(string lookup) => Configuration.CustomColours.TryGetValue(lookup, out var col) ? new Bindable(col) : null; + public override Drawable GetDrawableComponent(ISkinComponent component) { switch (component) diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs index 0160755eed..1912c4cd05 100644 --- a/osu.Game/Skinning/LegacySkinDecoder.cs +++ b/osu.Game/Skinning/LegacySkinDecoder.cs @@ -14,47 +14,31 @@ namespace osu.Game.Skinning protected override void ParseLine(DefaultSkinConfiguration skin, Section section, string line) { - line = StripComments(line); - - var pair = SplitKeyVal(line); - - switch (section) + if (section != Section.Colours) { - case Section.General: - switch (pair.Key) - { - case @"Name": - skin.SkinInfo.Name = pair.Value; - break; + line = StripComments(line); - case @"Author": - skin.SkinInfo.Creator = pair.Value; - break; + var pair = SplitKeyVal(line); - case @"CursorExpand": - skin.CursorExpand = pair.Value != "0"; - break; + switch (section) + { + case Section.General: + switch (pair.Key) + { + case @"Name": + skin.SkinInfo.Name = pair.Value; + return; - case @"SliderBorderSize": - skin.SliderBorderSize = Parsing.ParseFloat(pair.Value); - break; - } + case @"Author": + skin.SkinInfo.Creator = pair.Value; + return; + } - break; + break; + } - case Section.Fonts: - switch (pair.Key) - { - case "HitCirclePrefix": - skin.HitCircleFont = pair.Value; - break; - - case "HitCircleOverlap": - skin.HitCircleOverlap = int.Parse(pair.Value); - break; - } - - break; + if (!string.IsNullOrEmpty(pair.Key)) + skin.ConfigDictionary[$"{section}/{pair.Key}"] = pair.Value; } base.ParseLine(skin, section, line); diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 299f257e57..fa4aebd8a5 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -1,8 +1,9 @@ -// 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; using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Game.Audio; @@ -13,7 +14,7 @@ namespace osu.Game.Skinning { public readonly SkinInfo SkinInfo; - public virtual SkinConfiguration Configuration { get; protected set; } + public SkinConfiguration Configuration { get; protected set; } public abstract Drawable GetDrawableComponent(ISkinComponent componentName); @@ -21,8 +22,7 @@ namespace osu.Game.Skinning public abstract Texture GetTexture(string componentName); - public TValue GetValue(Func query) where TConfiguration : SkinConfiguration - => Configuration is TConfiguration conf ? query.Invoke(conf) : default; + public abstract IBindable GetConfig(TLookup lookup); protected Skin(SkinInfo skin) { diff --git a/osu.Game/Skinning/SkinConfigManager.cs b/osu.Game/Skinning/SkinConfigManager.cs new file mode 100644 index 0000000000..896444d1d2 --- /dev/null +++ b/osu.Game/Skinning/SkinConfigManager.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Configuration; + +namespace osu.Game.Skinning +{ + public class SkinConfigManager : ConfigManager where T : struct + { + protected override void PerformLoad() + { + } + + protected override bool PerformSave() => false; + } +} diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs index d585c58ef1..54aac86e3c 100644 --- a/osu.Game/Skinning/SkinConfiguration.cs +++ b/osu.Game/Skinning/SkinConfiguration.cs @@ -18,14 +18,6 @@ namespace osu.Game.Skinning public Dictionary CustomColours { get; set; } = new Dictionary(); - public string HitCircleFont { get; set; } - - public int HitCircleOverlap { get; set; } - - public float? SliderBorderSize { get; set; } - - public float? SliderPathRadius { get; set; } - - public bool? CursorExpand { get; set; } + public readonly Dictionary ConfigDictionary = new Dictionary(); } } diff --git a/osu.Game/Skinning/SkinCustomColourLookup.cs b/osu.Game/Skinning/SkinCustomColourLookup.cs new file mode 100644 index 0000000000..b8e5ac9b53 --- /dev/null +++ b/osu.Game/Skinning/SkinCustomColourLookup.cs @@ -0,0 +1,15 @@ +// 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 SkinCustomColourLookup + { + public readonly object Lookup; + + public SkinCustomColourLookup(object lookup) + { + Lookup = lookup; + } + } +} diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index a55a128dff..aa3b3981c2 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.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; @@ -131,6 +131,6 @@ namespace osu.Game.Skinning public SampleChannel GetSample(ISampleInfo sampleInfo) => CurrentSkin.Value.GetSample(sampleInfo); - public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => CurrentSkin.Value.GetValue(query); + public IBindable GetConfig(TLookup lookup) => CurrentSkin.Value.GetConfig(lookup); } } diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 85a80655ea..ef7f5f381b 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; @@ -64,13 +65,16 @@ namespace osu.Game.Skinning return fallbackSource?.GetSample(sampleInfo); } - public TValue GetValue(Func query) where TConfiguration : SkinConfiguration + public IBindable GetConfig(TLookup lookup) { - TValue val; - if (AllowConfigurationLookup && skin != null && (val = skin.GetValue(query)) != null) - return val; + if (AllowConfigurationLookup && skin != null) + { + var bindable = skin.GetConfig(lookup); + if (bindable != null) + return bindable; + } - return fallbackSource == null ? default : fallbackSource.GetValue(query); + return fallbackSource?.GetConfig(lookup); } protected virtual void TriggerSourceChanged() => SourceChanged?.Invoke(); From 097012dc95c05e829864d451b35be143844cdc7e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Sep 2019 18:05:16 +0900 Subject: [PATCH 1065/2815] Move slider ball colouring to DefaultLegacySkin for now --- osu.Game/Skinning/DefaultLegacySkin.cs | 2 ++ osu.Game/Skinning/LegacySkin.cs | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs index b35c9c7b97..98f158c725 100644 --- a/osu.Game/Skinning/DefaultLegacySkin.cs +++ b/osu.Game/Skinning/DefaultLegacySkin.cs @@ -3,6 +3,7 @@ using osu.Framework.Audio; using osu.Framework.IO.Stores; +using osuTK.Graphics; namespace osu.Game.Skinning { @@ -11,6 +12,7 @@ namespace osu.Game.Skinning public DefaultLegacySkin(IResourceStore storage, AudioManager audioManager) : base(Info, storage, audioManager, string.Empty) { + Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255); } public static SkinInfo Info { get; } = new SkinInfo diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 53f7c54003..5f0afae075 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -25,8 +25,6 @@ namespace osu.Game.Skinning public LegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager) : this(skin, new LegacySkinResourceStore(skin, storage), audioManager, "skin.ini") { - // defaults should only be applied for non-beatmap skins (which are parsed via this constructor). - if (!Configuration.CustomColours.ContainsKey("SliderBall")) Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255); } protected LegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager, string filename) From 352fd3efdaa1fcddd2a19bc87287134b02ebed97 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2019 09:18:24 +0000 Subject: [PATCH 1066/2815] Bump ppy.osu.Game.Resources from 2019.903.0 to 2019.903.1 Bumps [ppy.osu.Game.Resources](https://github.com/ppy/osu-resources) from 2019.903.0 to 2019.903.1. - [Release notes](https://github.com/ppy/osu-resources/releases) - [Commits](https://github.com/ppy/osu-resources/compare/2019.903.0...2019.903.1) Signed-off-by: dependabot-preview[bot] --- 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 743508baf8..96706f2bdc 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -60,7 +60,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 03207dfdf7..ca69bb2295 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -14,7 +14,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index ec76ceaf95..86a2a40940 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -117,7 +117,7 @@ - + From 2f74ef513140bfcd0e8b69c16a4d0731f87b5790 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Sep 2019 18:21:57 +0900 Subject: [PATCH 1067/2815] Add test for changing of a source --- .../Gameplay/TestSceneSkinnableDrawable.cs | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index ee5552c6e0..80015099cf 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -5,6 +5,7 @@ using System; using System.Globalization; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -133,6 +134,48 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1); } + [Test] + public void TestSwitchOff() + { + SkinConsumer consumer = null; + SwitchableSkinProvidingContainer target = null; + + AddStep("setup layout", () => + { + Child = new SkinSourceContainer + { + RelativeSizeAxes = Axes.Both, + Child = target = new SwitchableSkinProvidingContainer(new SecondarySource()) + { + RelativeSizeAxes = Axes.Both, + } + }; + }); + + AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true))); + AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox); + AddStep("disable", () => target.Disable()); + AddAssert("consumer using base source", () => consumer.Drawable is BaseSourceBox); + } + + private class SwitchableSkinProvidingContainer : SkinProvidingContainer + { + private bool allow = true; + + protected override bool AllowDrawableLookup(ISkinComponent component) => allow; + + public void Disable() + { + allow = false; + TriggerSourceChanged(); + } + + public SwitchableSkinProvidingContainer(ISkin skin) + : base(skin) + { + } + } + private class ExposedSkinnableDrawable : SkinnableDrawable { public new Drawable Drawable => base.Drawable; @@ -270,7 +313,8 @@ namespace osu.Game.Tests.Visual.Gameplay public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException(); } - private class SkinSourceContainer : Container, ISkin + [Cached(typeof(ISkinSource))] + private class SkinSourceContainer : Container, ISkinSource { public Drawable GetDrawableComponent(ISkinComponent componentName) => new BaseSourceBox(); @@ -279,6 +323,8 @@ namespace osu.Game.Tests.Visual.Gameplay public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException(); + + public event Action SourceChanged; } private class TestSkinComponent : ISkinComponent From 002de80c30b2f65eb842e8089382ac74cf2e1964 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Sep 2019 18:30:22 +0900 Subject: [PATCH 1068/2815] Add xmldoc to ISkin --- osu.Game/Skinning/ISkin.cs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game/Skinning/ISkin.cs b/osu.Game/Skinning/ISkin.cs index 841ff3d357..cb2a379b8e 100644 --- a/osu.Game/Skinning/ISkin.cs +++ b/osu.Game/Skinning/ISkin.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.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -14,12 +15,36 @@ namespace osu.Game.Skinning /// public interface ISkin { + /// + /// Retrieve a component implementation. + /// + /// The requested component. + /// A drawable representation for the requested component, or null if unavailable. + [CanBeNull] Drawable GetDrawableComponent(ISkinComponent component); + /// + /// Retrieve a . + /// + /// The requested texture. + /// A matching texture, or null if unavailable. + [CanBeNull] Texture GetTexture(string componentName); + /// + /// Retrieve a . + /// + /// The requested sample. + /// A matching sample channel, or null if unavailable. + [CanBeNull] SampleChannel GetSample(ISampleInfo sampleInfo); + /// + /// Retrieve a configuration value. + /// + /// The requested configuration value. + /// A matching value boxed in an , or null if unavailable. + [CanBeNull] IBindable GetConfig(TLookup lookup); } } From f58ca823986f79491946fd4fe3394ec9fbd4afa1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Sep 2019 18:56:01 +0900 Subject: [PATCH 1069/2815] Don't include section for now --- osu.Game/Skinning/LegacySkinDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs index 1912c4cd05..e97664e75e 100644 --- a/osu.Game/Skinning/LegacySkinDecoder.cs +++ b/osu.Game/Skinning/LegacySkinDecoder.cs @@ -38,7 +38,7 @@ namespace osu.Game.Skinning } if (!string.IsNullOrEmpty(pair.Key)) - skin.ConfigDictionary[$"{section}/{pair.Key}"] = pair.Value; + skin.ConfigDictionary[pair.Key] = pair.Value; } base.ParseLine(skin, section, line); From 343af28ed578cc247ba842c9983d72e4dce138a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Sep 2019 18:59:11 +0900 Subject: [PATCH 1070/2815] Add extra legacy skin parsing tests --- osu.Game.Tests/Resources/skin.ini | 1 + osu.Game.Tests/Skins/LegacySkinDecoderTest.cs | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/osu.Game.Tests/Resources/skin.ini b/osu.Game.Tests/Resources/skin.ini index 0e5737b4ea..7f7f0b32a6 100644 --- a/osu.Game.Tests/Resources/skin.ini +++ b/osu.Game.Tests/Resources/skin.ini @@ -1,5 +1,6 @@ [General] Name: test skin +TestLookup: TestValue [Colours] Combo1 : 142,199,255 diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs index 24ef9e4535..8bd846518b 100644 --- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs +++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs @@ -41,5 +41,20 @@ namespace osu.Game.Tests.Skins Assert.AreEqual(expectedColors[i], comboColors[i]); } } + + [Test] + public void TestDecodeGeneral() + { + var decoder = new LegacySkinDecoder(); + + using (var resStream = TestResources.OpenResource("skin.ini")) + using (var stream = new StreamReader(resStream)) + { + var config = decoder.Decode(stream); + + Assert.AreEqual("test skin", config.SkinInfo.Name); + Assert.AreEqual("TestValue", config.ConfigDictionary["TestLookup"]); + } + } } } From 299d528654f823689e14ffc6fb56c9f2db55ae5f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Sep 2019 19:20:23 +0900 Subject: [PATCH 1071/2815] Simplify implementation --- osu.Game/Graphics/UserInterface/HoverClickSounds.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs index e64b9259f1..38e6a82bab 100644 --- a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs @@ -36,11 +36,9 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnMouseUp(MouseUpEvent e) { - bool shouldPlayEffect = buttons.Contains(e.Button); - // examine the button pressed first for short-circuiting - // in most usages it is more likely that another button was pressed than that the cursor left the drawable bounds - if (shouldPlayEffect && Contains(e.ScreenSpaceMousePosition)) + if (buttons.Contains(e.Button) && Contains(e.ScreenSpaceMousePosition)) sampleClick?.Play(); + return base.OnMouseUp(e); } From e98059267d4348a92e4d16fe6b75d9ebe45e3912 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Sep 2019 19:21:24 +0900 Subject: [PATCH 1072/2815] Improve xmldoc --- osu.Game/Graphics/UserInterface/HoverClickSounds.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs index 38e6a82bab..1fb73efa65 100644 --- a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs @@ -21,12 +21,12 @@ namespace osu.Game.Graphics.UserInterface private readonly MouseButton[] buttons; /// - /// Creates an instance that adds sounds on hover and on click for any of the buttons specified. + /// a container which plays sounds on hover and click for any specified s. /// /// Set of click samples to play. /// /// Array of button codes which should trigger the click sound. - /// If this optional parameter is omitted or set to null, the click sound will only be added on left click. + /// If this optional parameter is omitted or set to null, the click sound will only be played on left click. /// public HoverClickSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal, MouseButton[] buttons = null) : base(sampleSet) From 4b2cb8854e06735d859568ba1b22e4ceb3a71541 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Sep 2019 19:28:10 +0900 Subject: [PATCH 1073/2815] Fix storyboard samples not stopping on exit --- osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index b04f1d4518..f3f8308964 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -64,5 +64,11 @@ namespace osu.Game.Storyboards.Drawables LifetimeEnd = sampleInfo.StartTime; } } + + protected override void Dispose(bool isDisposing) + { + channel?.Stop(); + base.Dispose(isDisposing); + } } } From f8c1afa539e47e6ea8ee5bef22f3c4df35a4a053 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Sep 2019 20:17:39 +0900 Subject: [PATCH 1074/2815] Fix two more cases of judgements appearing on hit error display when they shouldn't --- osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs | 3 +++ osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs index 63713541b4..5bd480c0ff 100644 --- a/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs @@ -5,6 +5,7 @@ using System; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; namespace osu.Game.Rulesets.Osu.Objects @@ -28,5 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects } public override Judgement CreateJudgement() => new OsuJudgement(); + + protected override HitWindows CreateHitWindows() => null; } } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index 4f2af64161..c53a88337e 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -23,5 +23,7 @@ namespace osu.Game.Rulesets.Osu.Objects } public override Judgement CreateJudgement() => new OsuSliderTailJudgement(); + + protected override HitWindows CreateHitWindows() => null; } } From a8f16503e2faa8dcfe98d1f28297e32149f2cd01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 3 Sep 2019 23:18:39 +0200 Subject: [PATCH 1075/2815] Add backslash escaping to new link format For users to be able to add square brackets inside of links using the new format, the regular expression used for parsing those links contained a balancing group, which can be used for matching pairs of tokens (in this case, opening and closing brackets, in that order). However, this means that users could not post links with unmatched brackets inside of them (ie. ones that contain single brackets, or a closing bracket and then an opening one). Allow for escaping opening and closing brackets using the backslash character. The change substitutes this old fragment of the regex in the display text group: [^\[\]]* // any character other than closing/opening bracket for this one: (((?<=\\)[\[\]])|[^\[\]])* The second pattern in the alternative remains the same; the first one performs the escaping, as follows: ( (?<=\\) // positive lookbehind expression: // this match will succeed, if the next expression // is preceded by a single backslash [\[\]] // either an opening or closing brace ) Since the entire display group is matched, unfortunately the lookbehind expression does not actually strip the backslashes, so they are manually stripped in handleMatches. As demonstrated in the unit tests attached, this also allows balanced brackets to be mixed with escaped ones. --- osu.Game.Tests/Chat/MessageFormatterTests.cs | 36 +++++++++++++++++++ .../Visual/Online/TestSceneChatLink.cs | 1 + osu.Game/Online/Chat/MessageFormatter.cs | 11 ++++-- 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index 0d6ed67767..1de6280531 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -131,6 +131,42 @@ namespace osu.Game.Tests.Chat Assert.AreEqual(11, result.Links[0].Length); } + [Test] + public void TestNewFormatLinkWithEscapedBrackets() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh nasty link with escaped brackets: \\] and \\[]" }); + + Assert.AreEqual("This is a nasty link with escaped brackets: ] and [", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(41, result.Links[0].Length); + } + + [Test] + public void TestNewFormatLinkWithBackslashesInside() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh link \\ with \\ backslashes \\]" }); + + Assert.AreEqual("This is a link \\ with \\ backslashes \\", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(27, result.Links[0].Length); + } + + [Test] + public void TestNewFormatLinkWithEscapedAndBalancedBrackets() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh [link [with \\] too many brackets \\[ ]]]" }); + + Assert.AreEqual("This is a [link [with ] too many brackets [ ]]", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(36, result.Links[0].Length); + } + [Test] public void TestMarkdownFormatLink() { diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index c18e0e3064..056ccafe79 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -127,6 +127,7 @@ namespace osu.Game.Tests.Visual.Online addMessageWithChecks("is now playing [https://osu.ppy.sh/b/252238 IMAGE -MATERIAL- ]", 1, true, expectedActions: LinkAction.OpenBeatmap); addMessageWithChecks("Let's (try)[https://osu.ppy.sh/home] [https://osu.ppy.sh/b/252238 multiple links] https://osu.ppy.sh/home", 3, expectedActions: new[] { LinkAction.External, LinkAction.OpenBeatmap, LinkAction.External }); + addMessageWithChecks("[https://osu.ppy.sh/home New link format with escaped [and \\[ paired] braces]", expectedActions: LinkAction.External); // note that there's 0 links here (they get removed if a channel is not found) addMessageWithChecks("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present)."); addMessageWithChecks("I am important!", 0, false, true); diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index db26945ef3..a9fffc196c 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text.RegularExpressions; namespace osu.Game.Online.Chat @@ -16,7 +17,7 @@ namespace osu.Game.Online.Chat private static readonly Regex old_link_regex = new Regex(@"\(([^\)]*)\)\[([a-z]+://[^ ]+)\]"); // [https://osu.ppy.sh/b/1234 Beatmap [Hard] (poop)] -> Beatmap [hard] (poop) (https://osu.ppy.sh/b/1234) - private static readonly Regex new_link_regex = new Regex(@"\[([a-z]+://[^ ]+) ([^\[\]]*(((?\[)[^\[\]]*)+((?\])[^\[\]]*)+)*(?(open)(?!)))\]"); + private static readonly Regex new_link_regex = new Regex(@"\[([a-z]+://[^ ]+) ((((?<=\\)[\[\]])|[^\[\]])*(((?\[)(((?<=\\)[\[\]])|[^\[\]])*)+((?\])(((?<=\\)[\[\]])|[^\[\]])*)+)*(?(open)(?!)))\]"); // [test](https://osu.ppy.sh/b/1234) -> test (https://osu.ppy.sh/b/1234) aka correct markdown format private static readonly Regex markdown_link_regex = new Regex(@"\[([^\]]*)\]\(([a-z]+://[^ ]+)\)"); @@ -48,7 +49,7 @@ namespace osu.Game.Online.Chat // Unicode emojis private static readonly Regex emoji_regex = new Regex(@"(\uD83D[\uDC00-\uDE4F])"); - private static void handleMatches(Regex regex, string display, string link, MessageFormatterResult result, int startIndex = 0, LinkAction? linkActionOverride = null) + private static void handleMatches(Regex regex, string display, string link, MessageFormatterResult result, int startIndex = 0, LinkAction? linkActionOverride = null, char[] escapeChars = null) { int captureOffset = 0; @@ -68,6 +69,10 @@ namespace osu.Game.Online.Chat if (displayText.Length == 0 || linkText.Length == 0) continue; + // Remove backslash escapes in front of the characters provided in escapeChars + if (escapeChars != null) + displayText = escapeChars.Aggregate(displayText, (current, c) => current.Replace($"\\{c}", c.ToString())); + // Check for encapsulated links if (result.Links.Find(l => (l.Index <= index && l.Index + l.Length >= index + m.Length) || (index <= l.Index && index + m.Length >= l.Index + l.Length)) == null) { @@ -183,7 +188,7 @@ namespace osu.Game.Online.Chat var result = new MessageFormatterResult(toFormat); // handle the [link display] format - handleMatches(new_link_regex, "{2}", "{1}", result, startIndex); + handleMatches(new_link_regex, "{2}", "{1}", result, startIndex, escapeChars: new[] { '[', ']' }); // handle the standard markdown []() format handleMatches(markdown_link_regex, "{1}", "{2}", result, startIndex); From 24d4f0372c851280eb017553101712ad86731133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 3 Sep 2019 23:56:07 +0200 Subject: [PATCH 1076/2815] Refactor link parsing regexes to use named groups For the sake of readability, consistency and to make further changes easier, introduce named groups (?) and (?) to all link parsing regexes which have parts containing the desired link text and (optionally) URL. The introduction of the named groups additionally simplifies handleMatches() and makes all calls to it consistent. --- osu.Game/Online/Chat/MessageFormatter.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index a9fffc196c..23b5cdc0a6 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -11,16 +11,16 @@ namespace osu.Game.Online.Chat public static class MessageFormatter { // [[Performance Points]] -> wiki:Performance Points (https://osu.ppy.sh/wiki/Performance_Points) - private static readonly Regex wiki_regex = new Regex(@"\[\[([^\]]+)\]\]"); + private static readonly Regex wiki_regex = new Regex(@"\[\[(?[^\]]+)\]\]"); // (test)[https://osu.ppy.sh/b/1234] -> test (https://osu.ppy.sh/b/1234) - private static readonly Regex old_link_regex = new Regex(@"\(([^\)]*)\)\[([a-z]+://[^ ]+)\]"); + private static readonly Regex old_link_regex = new Regex(@"\((?[^\)]*)\)\[(?[a-z]+://[^ ]+)\]"); // [https://osu.ppy.sh/b/1234 Beatmap [Hard] (poop)] -> Beatmap [hard] (poop) (https://osu.ppy.sh/b/1234) - private static readonly Regex new_link_regex = new Regex(@"\[([a-z]+://[^ ]+) ((((?<=\\)[\[\]])|[^\[\]])*(((?\[)(((?<=\\)[\[\]])|[^\[\]])*)+((?\])(((?<=\\)[\[\]])|[^\[\]])*)+)*(?(open)(?!)))\]"); + private static readonly Regex new_link_regex = new Regex(@"\[(?[a-z]+://[^ ]+) (?(((?<=\\)[\[\]])|[^\[\]])*(((?\[)(((?<=\\)[\[\]])|[^\[\]])*)+((?\])(((?<=\\)[\[\]])|[^\[\]])*)+)*(?(open)(?!)))\]"); // [test](https://osu.ppy.sh/b/1234) -> test (https://osu.ppy.sh/b/1234) aka correct markdown format - private static readonly Regex markdown_link_regex = new Regex(@"\[([^\]]*)\]\(([a-z]+://[^ ]+)\)"); + private static readonly Regex markdown_link_regex = new Regex(@"\[(?[^\]]*)\]\((?[a-z]+://[^ ]+)\)"); // advanced, RFC-compatible regular expression that matches any possible URL, *but* allows certain invalid characters that are widely used // This is in the format (, [optional]): @@ -59,13 +59,13 @@ namespace osu.Game.Online.Chat var displayText = string.Format(display, m.Groups[0], - m.Groups.Count > 1 ? m.Groups[1].Value : "", - m.Groups.Count > 2 ? m.Groups[2].Value : "").Trim(); + m.Groups["text"].Value, + m.Groups["url"].Value).Trim(); var linkText = string.Format(link, m.Groups[0], - m.Groups.Count > 1 ? m.Groups[1].Value : "", - m.Groups.Count > 2 ? m.Groups[2].Value : "").Trim(); + m.Groups["text"].Value, + m.Groups["url"].Value).Trim(); if (displayText.Length == 0 || linkText.Length == 0) continue; @@ -188,7 +188,7 @@ namespace osu.Game.Online.Chat var result = new MessageFormatterResult(toFormat); // handle the [link display] format - handleMatches(new_link_regex, "{2}", "{1}", result, startIndex, escapeChars: new[] { '[', ']' }); + handleMatches(new_link_regex, "{1}", "{2}", result, startIndex, escapeChars: new[] { '[', ']' }); // handle the standard markdown []() format handleMatches(markdown_link_regex, "{1}", "{2}", result, startIndex); From f04add6d9edda7bbdf080adc7e6959ee16e6f222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 4 Sep 2019 00:01:26 +0200 Subject: [PATCH 1077/2815] Add bracket handling to Markdown link format Allow users to put both balanced brackets, as well as unbalanced escaped ones, in Markdown link text. The implementation is the exact same as in the case of new format links. For completion's sake, tests also included. --- osu.Game.Tests/Chat/MessageFormatterTests.cs | 47 +++++++++++++++++++ .../Visual/Online/TestSceneChatLink.cs | 1 + osu.Game/Online/Chat/MessageFormatter.cs | 4 +- 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index 1de6280531..198267d78a 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -179,6 +179,53 @@ namespace osu.Game.Tests.Chat Assert.AreEqual(11, result.Links[0].Length); } + [Test] + public void TestMarkdownFormatLinkWithBalancedBrackets() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [tricky [one]](https://osu.ppy.sh)!" }); + + Assert.AreEqual("This is a tricky [one]!", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(12, result.Links[0].Length); + } + + [Test] + public void TestMarkdownFormatLinkWithEscapedBrackets() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is [another loose bracket \\]](https://osu.ppy.sh)." }); + + Assert.AreEqual("This is another loose bracket ].", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); + Assert.AreEqual(8, result.Links[0].Index); + Assert.AreEqual(23, result.Links[0].Length); + } + + [Test] + public void TestMarkdownFormatWithBackslashes() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This link [should end with a backslash \\](https://osu.ppy.sh)." }); + Assert.AreEqual("This link should end with a backslash \\.", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(29, result.Links[0].Length); + } + + [Test] + public void TestMarkdownFormatLinkWithEscapedAndBalancedBrackets() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [\\]super\\[\\[ tricky [one]](https://osu.ppy.sh)!" }); + + Assert.AreEqual("This is a ]super[[ tricky [one]!", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(21, result.Links[0].Length); + } + [Test] public void TestChannelLink() { diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index 056ccafe79..61c7d3f5b6 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -128,6 +128,7 @@ namespace osu.Game.Tests.Visual.Online addMessageWithChecks("Let's (try)[https://osu.ppy.sh/home] [https://osu.ppy.sh/b/252238 multiple links] https://osu.ppy.sh/home", 3, expectedActions: new[] { LinkAction.External, LinkAction.OpenBeatmap, LinkAction.External }); addMessageWithChecks("[https://osu.ppy.sh/home New link format with escaped [and \\[ paired] braces]", expectedActions: LinkAction.External); + addMessageWithChecks("[Markdown link format with escaped [and \\[ paired] braces](https://osu.ppy.sh/home)", expectedActions: LinkAction.External); // note that there's 0 links here (they get removed if a channel is not found) addMessageWithChecks("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present)."); addMessageWithChecks("I am important!", 0, false, true); diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 23b5cdc0a6..e40bb05381 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -20,7 +20,7 @@ namespace osu.Game.Online.Chat private static readonly Regex new_link_regex = new Regex(@"\[(?[a-z]+://[^ ]+) (?(((?<=\\)[\[\]])|[^\[\]])*(((?\[)(((?<=\\)[\[\]])|[^\[\]])*)+((?\])(((?<=\\)[\[\]])|[^\[\]])*)+)*(?(open)(?!)))\]"); // [test](https://osu.ppy.sh/b/1234) -> test (https://osu.ppy.sh/b/1234) aka correct markdown format - private static readonly Regex markdown_link_regex = new Regex(@"\[(?[^\]]*)\]\((?[a-z]+://[^ ]+)\)"); + private static readonly Regex markdown_link_regex = new Regex(@"\[(?(((?<=\\)[\[\]])|[^\[\]])*(((?\[)(((?<=\\)[\[\]])|[^\[\]])*)+((?\])(((?<=\\)[\[\]])|[^\[\]])*)+)*(?(open)(?!)))\]\((?[a-z]+://[^ ]+)\)"); // advanced, RFC-compatible regular expression that matches any possible URL, *but* allows certain invalid characters that are widely used // This is in the format (, [optional]): @@ -191,7 +191,7 @@ namespace osu.Game.Online.Chat handleMatches(new_link_regex, "{1}", "{2}", result, startIndex, escapeChars: new[] { '[', ']' }); // handle the standard markdown []() format - handleMatches(markdown_link_regex, "{1}", "{2}", result, startIndex); + handleMatches(markdown_link_regex, "{1}", "{2}", result, startIndex, escapeChars: new[] { '[', ']' }); // handle the ()[] link format handleMatches(old_link_regex, "{1}", "{2}", result, startIndex); From 08350a1acaf4c3bb70176e384c845fc83735d497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 4 Sep 2019 00:17:52 +0200 Subject: [PATCH 1078/2815] Add parenthesis handling to old link format Allow users to put both balanced round parentheses, as well as unbalanced escaped ones, in old style link text. The implementation is the same as for Markdown and new style links, except for swapping all instances of \[\] to \(\) for obvious reasons (different type of parenthesis requiring escaping). Tests also included. --- osu.Game.Tests/Chat/MessageFormatterTests.cs | 47 +++++++++++++++++++ .../Visual/Online/TestSceneChatLink.cs | 5 +- osu.Game/Online/Chat/MessageFormatter.cs | 4 +- 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index 198267d78a..9b4a90e9a9 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -119,6 +119,53 @@ namespace osu.Game.Tests.Chat Assert.AreEqual(11, result.Links[0].Length); } + [Test] + public void TestOldFormatLinkWithBalancedBrackets() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a (tricky (one))[https://osu.ppy.sh]!" }); + + Assert.AreEqual("This is a tricky (one)!", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(12, result.Links[0].Length); + } + + [Test] + public void TestOldFormatLinkWithEscapedBrackets() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is (another loose bracket \\))[https://osu.ppy.sh]." }); + + Assert.AreEqual("This is another loose bracket ).", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); + Assert.AreEqual(8, result.Links[0].Index); + Assert.AreEqual(23, result.Links[0].Length); + } + + [Test] + public void TestOldFormatWithBackslashes() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This link (should end with a backslash \\)[https://osu.ppy.sh]." }); + Assert.AreEqual("This link should end with a backslash \\.", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(29, result.Links[0].Length); + } + + [Test] + public void TestOldFormatLinkWithEscapedAndBalancedBrackets() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a (\\)super\\(\\( tricky (one))[https://osu.ppy.sh]!" }); + + Assert.AreEqual("This is a )super(( tricky (one)!", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(21, result.Links[0].Length); + } + [Test] public void TestNewFormatLink() { diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index 61c7d3f5b6..a1c77e2db0 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -127,8 +127,9 @@ namespace osu.Game.Tests.Visual.Online addMessageWithChecks("is now playing [https://osu.ppy.sh/b/252238 IMAGE -MATERIAL- ]", 1, true, expectedActions: LinkAction.OpenBeatmap); addMessageWithChecks("Let's (try)[https://osu.ppy.sh/home] [https://osu.ppy.sh/b/252238 multiple links] https://osu.ppy.sh/home", 3, expectedActions: new[] { LinkAction.External, LinkAction.OpenBeatmap, LinkAction.External }); - addMessageWithChecks("[https://osu.ppy.sh/home New link format with escaped [and \\[ paired] braces]", expectedActions: LinkAction.External); - addMessageWithChecks("[Markdown link format with escaped [and \\[ paired] braces](https://osu.ppy.sh/home)", expectedActions: LinkAction.External); + addMessageWithChecks("[https://osu.ppy.sh/home New link format with escaped [and \\[ paired] braces]", 1, expectedActions: LinkAction.External); + addMessageWithChecks("[Markdown link format with escaped [and \\[ paired] braces](https://osu.ppy.sh/home)", 1, expectedActions: LinkAction.External); + addMessageWithChecks("(Old link format with escaped (and \\( paired) parentheses)[https://osu.ppy.sh/home] and [[also a rogue wiki link]]", 2, expectedActions: new[] { LinkAction.External, LinkAction.External }); // note that there's 0 links here (they get removed if a channel is not found) addMessageWithChecks("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present)."); addMessageWithChecks("I am important!", 0, false, true); diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index e40bb05381..24d17612ee 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -14,7 +14,7 @@ namespace osu.Game.Online.Chat private static readonly Regex wiki_regex = new Regex(@"\[\[(?[^\]]+)\]\]"); // (test)[https://osu.ppy.sh/b/1234] -> test (https://osu.ppy.sh/b/1234) - private static readonly Regex old_link_regex = new Regex(@"\((?[^\)]*)\)\[(?[a-z]+://[^ ]+)\]"); + private static readonly Regex old_link_regex = new Regex(@"\((?(((?<=\\)[\(\)])|[^\(\)])*(((?\()(((?<=\\)[\(\)])|[^\(\)])*)+((?\))(((?<=\\)[\(\)])|[^\(\)])*)+)*(?(open)(?!)))\)\[(?[a-z]+://[^ ]+)\]"); // [https://osu.ppy.sh/b/1234 Beatmap [Hard] (poop)] -> Beatmap [hard] (poop) (https://osu.ppy.sh/b/1234) private static readonly Regex new_link_regex = new Regex(@"\[(?[a-z]+://[^ ]+) (?(((?<=\\)[\[\]])|[^\[\]])*(((?\[)(((?<=\\)[\[\]])|[^\[\]])*)+((?\])(((?<=\\)[\[\]])|[^\[\]])*)+)*(?(open)(?!)))\]"); @@ -194,7 +194,7 @@ namespace osu.Game.Online.Chat handleMatches(markdown_link_regex, "{1}", "{2}", result, startIndex, escapeChars: new[] { '[', ']' }); // handle the ()[] link format - handleMatches(old_link_regex, "{1}", "{2}", result, startIndex); + handleMatches(old_link_regex, "{1}", "{2}", result, startIndex, escapeChars: new[] { '(', ')' }); // handle wiki links handleMatches(wiki_regex, "{1}", "https://osu.ppy.sh/wiki/{1}", result, startIndex); From 9ec16bc2b260fd35a7d0b2722432cb5636c6f4f2 Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 3 Sep 2019 16:56:45 -0700 Subject: [PATCH 1079/2815] Add test for initial button hover --- .../Gameplay/TestSceneGameplayMenuOverlay.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs index 0d8a84fa51..ef7345bda7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs @@ -63,6 +63,7 @@ namespace osu.Game.Tests.Visual.Gameplay testKeyDownWrapping(); testHideResets(); + testInitialButtonHover(); testMouseSelectionAfterKeySelection(); testKeySelectionAfterMouseSelection(); @@ -159,6 +160,25 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected.Value)); } + /// + /// Tests that entering menu with cursor initially on button selects it. + /// + private void testInitialButtonHover() + { + AddStep("Show overlay", () => pauseOverlay.Show()); + + var firstButton = pauseOverlay.Buttons.First(); + + AddStep("Hover first button", () => InputManager.MoveMouseTo(firstButton)); + + AddStep("Hide overlay", () => pauseOverlay.Hide()); + AddStep("Show overlay", () => pauseOverlay.Show()); + + AddAssert("First button selected", () => firstButton.Selected.Value); + + AddStep("Hide overlay", () => pauseOverlay.Hide()); + } + /// /// Tests that hovering a button that was previously selected with the keyboard correctly selects the new button and deselects the previous button. /// From 5c10a228771ba0f3bb5459ff80b5d6f74a8e586f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2019 10:34:56 +0900 Subject: [PATCH 1080/2815] Update tests to use [Test] attributes --- .../Gameplay/TestSceneGameplayMenuOverlay.cs | 57 +++++++++---------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs index ef7345bda7..8b2fbe8aae 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs @@ -3,8 +3,8 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; +using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -16,7 +16,7 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { - [Description("player pause/fail screens")] + [System.ComponentModel.Description("player pause/fail screens")] public class TestSceneGameplayMenuOverlay : ManualInputManagerTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseOverlay) }; @@ -55,29 +55,13 @@ namespace osu.Game.Tests.Visual.Gameplay retryCount++; pauseOverlay.Retries = failOverlay.Retries = retryCount; }); - - testEnterWithoutSelection(); - testKeyUpFromInitial(); - testKeyDownFromInitial(); - testKeyUpWrapping(); - testKeyDownWrapping(); - - testHideResets(); - testInitialButtonHover(); - - testMouseSelectionAfterKeySelection(); - testKeySelectionAfterMouseSelection(); - - testMouseDeselectionResets(); - - testClickSelection(); - testEnterKeySelection(); } /// /// Tests that pressing enter after an overlay shows doesn't trigger an event because a selection hasn't occurred. /// - private void testEnterWithoutSelection() + [Test] + public void TestEnterWithoutSelection() { AddStep("Show overlay", () => pauseOverlay.Show()); @@ -90,7 +74,8 @@ namespace osu.Game.Tests.Visual.Gameplay /// /// Tests that pressing the up arrow from the initial state selects the last button. /// - private void testKeyUpFromInitial() + [Test] + public void TestKeyUpFromInitial() { AddStep("Show overlay", () => pauseOverlay.Show()); @@ -103,7 +88,8 @@ namespace osu.Game.Tests.Visual.Gameplay /// /// Tests that pressing the down arrow from the initial state selects the first button. /// - private void testKeyDownFromInitial() + [Test] + public void TestKeyDownFromInitial() { AddStep("Show overlay", () => pauseOverlay.Show()); @@ -116,7 +102,8 @@ namespace osu.Game.Tests.Visual.Gameplay /// /// Tests that pressing the up arrow repeatedly causes the selected button to wrap correctly. /// - private void testKeyUpWrapping() + [Test] + public void TestKeyUpWrapping() { AddStep("Show overlay", () => failOverlay.Show()); @@ -133,7 +120,8 @@ namespace osu.Game.Tests.Visual.Gameplay /// /// Tests that pressing the down arrow repeatedly causes the selected button to wrap correctly. /// - private void testKeyDownWrapping() + [Test] + public void TestKeyDownWrapping() { AddStep("Show overlay", () => failOverlay.Show()); @@ -150,7 +138,8 @@ namespace osu.Game.Tests.Visual.Gameplay /// /// Test that hiding the overlay after hovering a button will reset the overlay to the initial state with no buttons selected. /// - private void testHideResets() + [Test] + public void TestHideResets() { AddStep("Show overlay", () => failOverlay.Show()); @@ -163,7 +152,8 @@ namespace osu.Game.Tests.Visual.Gameplay /// /// Tests that entering menu with cursor initially on button selects it. /// - private void testInitialButtonHover() + [Test] + public void TestInitialButtonHover() { AddStep("Show overlay", () => pauseOverlay.Show()); @@ -182,7 +172,8 @@ namespace osu.Game.Tests.Visual.Gameplay /// /// Tests that hovering a button that was previously selected with the keyboard correctly selects the new button and deselects the previous button. /// - private void testMouseSelectionAfterKeySelection() + [Test] + public void TestMouseSelectionAfterKeySelection() { AddStep("Show overlay", () => pauseOverlay.Show()); @@ -199,7 +190,8 @@ namespace osu.Game.Tests.Visual.Gameplay /// /// Tests that pressing a key after selecting a button with a hover event correctly selects a new button and deselects the previous button. /// - private void testKeySelectionAfterMouseSelection() + [Test] + public void TestKeySelectionAfterMouseSelection() { AddStep("Show overlay", () => { @@ -220,7 +212,8 @@ namespace osu.Game.Tests.Visual.Gameplay /// /// Tests that deselecting with the mouse by losing hover will reset the overlay to the initial state. /// - private void testMouseDeselectionResets() + [Test] + public void TestMouseDeselectionResets() { AddStep("Show overlay", () => pauseOverlay.Show()); @@ -237,7 +230,8 @@ namespace osu.Game.Tests.Visual.Gameplay /// /// Tests that clicking on a button correctly causes a click event for that button. /// - private void testClickSelection() + [Test] + public void TestClickSelection() { AddStep("Show overlay", () => pauseOverlay.Show()); @@ -260,7 +254,8 @@ namespace osu.Game.Tests.Visual.Gameplay /// /// Tests that pressing the enter key with a button selected correctly causes a click event for that button. /// - private void testEnterKeySelection() + [Test] + public void TestEnterKeySelection() { AddStep("Show overlay", () => pauseOverlay.Show()); From 40c61894effd9afcc1dc2db5393f4b37058c1076 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2019 10:44:24 +0900 Subject: [PATCH 1081/2815] Update some case sensitive resources lookups in-line with resources --- osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs | 2 +- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index c89cd95f36..e96c7d8f92 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Catch.Tests { switch (component.LookupName) { - case "Gameplay/Catch/fruit-catcher-idle": + case "Gameplay/catch/fruit-catcher-idle": return new CatcherCustomSkin(); } diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 9766da9a24..5234ae1f69 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -132,10 +132,10 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load(TextureStore textures, OsuColour colours) { - rim.Texture = textures.Get(@"Gameplay/Taiko/taiko-drum-outer"); - rimHit.Texture = textures.Get(@"Gameplay/Taiko/taiko-drum-outer-hit"); - centre.Texture = textures.Get(@"Gameplay/Taiko/taiko-drum-inner"); - centreHit.Texture = textures.Get(@"Gameplay/Taiko/taiko-drum-inner-hit"); + rim.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer"); + rimHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer-hit"); + centre.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner"); + centreHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner-hit"); rimHit.Colour = colours.Blue; centreHit.Colour = colours.Pink; From 7cbcc7b9069178bc07c38bdf8802fbf3eaf7333b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2019 11:36:09 +0900 Subject: [PATCH 1082/2815] Further test refactors --- .../Gameplay/TestSceneGameplayMenuOverlay.cs | 121 ++++++++---------- 1 file changed, 54 insertions(+), 67 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs index 8b2fbe8aae..cc275009ba 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; +using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Screens.Play; using osuTK; @@ -29,32 +30,43 @@ namespace osu.Game.Tests.Visual.Gameplay [BackgroundDependencyLoader] private void load(OsuGameBase game) { - Child = globalActionContainer = new GlobalActionContainer(game) - { - Children = new Drawable[] - { - pauseOverlay = new PauseOverlay - { - OnResume = () => Logger.Log(@"Resume"), - OnRetry = () => Logger.Log(@"Retry"), - OnQuit = () => Logger.Log(@"Quit"), - }, - failOverlay = new FailOverlay + Child = globalActionContainer = new GlobalActionContainer(game); + } - { - OnRetry = () => Logger.Log(@"Retry"), - OnQuit = () => Logger.Log(@"Quit"), - } + [SetUp] + public void SetUp() => Schedule(() => + { + globalActionContainer.Children = new Drawable[] + { + pauseOverlay = new PauseOverlay + { + OnResume = () => Logger.Log(@"Resume"), + OnRetry = () => Logger.Log(@"Retry"), + OnQuit = () => Logger.Log(@"Quit"), + }, + failOverlay = new FailOverlay + + { + OnRetry = () => Logger.Log(@"Retry"), + OnQuit = () => Logger.Log(@"Quit"), } }; + InputManager.MoveMouseTo(Vector2.Zero); + }); + + [Test] + public void TestAdjustRetryCount() + { + showOverlay(); + var retryCount = 0; - AddStep("Add retry", () => + AddRepeatStep("Add retry", () => { retryCount++; pauseOverlay.Retries = failOverlay.Retries = retryCount; - }); + }, 10); } /// @@ -63,12 +75,10 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestEnterWithoutSelection() { - AddStep("Show overlay", () => pauseOverlay.Show()); + showOverlay(); AddStep("Press select", () => press(GlobalAction.Select)); AddAssert("Overlay still open", () => pauseOverlay.State.Value == Visibility.Visible); - - AddStep("Hide overlay", () => pauseOverlay.Hide()); } /// @@ -77,12 +87,10 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestKeyUpFromInitial() { - AddStep("Show overlay", () => pauseOverlay.Show()); + showOverlay(); AddStep("Up arrow", () => press(Key.Up)); AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected.Value); - - AddStep("Hide overlay", () => pauseOverlay.Hide()); } /// @@ -91,12 +99,10 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestKeyDownFromInitial() { - AddStep("Show overlay", () => pauseOverlay.Show()); + showOverlay(); AddStep("Down arrow", () => press(Key.Down)); - AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected.Value); - - AddStep("Hide overlay", () => pauseOverlay.Hide()); + AddAssert("First button selected", () => getButton(0).Selected.Value); } /// @@ -113,8 +119,6 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value); AddStep("Up arrow", () => press(Key.Up)); AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value); - - AddStep("Hide overlay", () => failOverlay.Hide()); } /// @@ -131,8 +135,6 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value); AddStep("Down arrow", () => press(Key.Down)); AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value); - - AddStep("Hide overlay", () => failOverlay.Hide()); } /// @@ -155,18 +157,14 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestInitialButtonHover() { - AddStep("Show overlay", () => pauseOverlay.Show()); + showOverlay(); - var firstButton = pauseOverlay.Buttons.First(); - - AddStep("Hover first button", () => InputManager.MoveMouseTo(firstButton)); + AddStep("Hover first button", () => InputManager.MoveMouseTo(getButton(0))); AddStep("Hide overlay", () => pauseOverlay.Hide()); - AddStep("Show overlay", () => pauseOverlay.Show()); + showOverlay(); - AddAssert("First button selected", () => firstButton.Selected.Value); - - AddStep("Hide overlay", () => pauseOverlay.Hide()); + AddAssert("First button selected", () => getButton(0).Selected.Value); } /// @@ -175,16 +173,12 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestMouseSelectionAfterKeySelection() { - AddStep("Show overlay", () => pauseOverlay.Show()); - - var secondButton = pauseOverlay.Buttons.Skip(1).First(); + showOverlay(); AddStep("Down arrow", () => press(Key.Down)); - AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton)); - AddAssert("First button not selected", () => !pauseOverlay.Buttons.First().Selected.Value); - AddAssert("Second button selected", () => secondButton.Selected.Value); - - AddStep("Hide overlay", () => pauseOverlay.Hide()); + AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1))); + AddAssert("First button not selected", () => !getButton(0).Selected.Value); + AddAssert("Second button selected", () => getButton(1).Selected.Value); } /// @@ -196,17 +190,12 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Show overlay", () => { pauseOverlay.Show(); - InputManager.MoveMouseTo(Vector2.Zero); }); - var secondButton = pauseOverlay.Buttons.Skip(1).First(); - - AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton)); + AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1))); AddStep("Up arrow", () => press(Key.Up)); - AddAssert("Second button not selected", () => !secondButton.Selected.Value); - AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected.Value); - - AddStep("Hide overlay", () => pauseOverlay.Hide()); + AddAssert("Second button not selected", () => !getButton(1).Selected.Value); + AddAssert("First button selected", () => getButton(0).Selected.Value); } /// @@ -215,16 +204,12 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestMouseDeselectionResets() { - AddStep("Show overlay", () => pauseOverlay.Show()); + showOverlay(); - var secondButton = pauseOverlay.Buttons.Skip(1).First(); - - AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton)); + AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1))); AddStep("Unhover second button", () => InputManager.MoveMouseTo(Vector2.Zero)); AddStep("Down arrow", () => press(Key.Down)); - AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected.Value); // Initial state condition - - AddStep("Hide overlay", () => pauseOverlay.Hide()); + AddAssert("First button selected", () => getButton(0).Selected.Value); // Initial state condition } /// @@ -233,9 +218,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestClickSelection() { - AddStep("Show overlay", () => pauseOverlay.Show()); - - var retryButton = pauseOverlay.Buttons.Skip(1).First(); + showOverlay(); bool triggered = false; AddStep("Click retry button", () => @@ -243,7 +226,7 @@ namespace osu.Game.Tests.Visual.Gameplay var lastAction = pauseOverlay.OnRetry; pauseOverlay.OnRetry = () => triggered = true; - retryButton.Click(); + getButton(1).Click(); pauseOverlay.OnRetry = lastAction; }); @@ -257,7 +240,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestEnterKeySelection() { - AddStep("Show overlay", () => pauseOverlay.Show()); + showOverlay(); AddStep("Select second button", () => { @@ -287,6 +270,10 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("Overlay is closed", () => pauseOverlay.State.Value == Visibility.Hidden); } + private void showOverlay() => AddStep("Show overlay", () => pauseOverlay.Show()); + + private DialogButton getButton(int index) => pauseOverlay.Buttons.Skip(index).First(); + private void press(Key key) { InputManager.PressKey(key); From 4c563232d627b17a6f1c31350b156673c7befb7d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2019 11:37:02 +0900 Subject: [PATCH 1083/2815] HoverClickSounds should handle click event instead of MouseUp --- osu.Game/Graphics/UserInterface/HoverClickSounds.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs index 1fb73efa65..4f678b7218 100644 --- a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs @@ -34,12 +34,12 @@ namespace osu.Game.Graphics.UserInterface this.buttons = buttons ?? new[] { MouseButton.Left }; } - protected override bool OnMouseUp(MouseUpEvent e) + protected override bool OnClick(ClickEvent e) { if (buttons.Contains(e.Button) && Contains(e.ScreenSpaceMousePosition)) sampleClick?.Play(); - return base.OnMouseUp(e); + return base.OnClick(e); } [BackgroundDependencyLoader] From 5efd455ce4821e48919eb462531325db12d54f65 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 4 Sep 2019 12:47:10 +0900 Subject: [PATCH 1084/2815] Fix taiko sample namespace --- .../Objects/Drawables/DrawableTaikoHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 5424ccb4de..423f65b2d3 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables // Normal and clap samples are handled by the drum protected override IEnumerable GetSamples() => HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP); - protected override string SampleNamespace => "Taiko"; + protected override string SampleNamespace => "taiko"; protected virtual TaikoPiece CreateMainPiece() => new CirclePiece(); From 024aa4dd7b3ea19e04cd59cf8e66a26f74a62cc2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 4 Sep 2019 13:05:05 +0900 Subject: [PATCH 1085/2815] 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 96706f2bdc..90d1854c39 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -60,7 +60,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ca69bb2295..7d106f0484 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -14,7 +14,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 86a2a40940..8390a2229b 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -117,7 +117,7 @@ - + From 9edfe6800f7a7157b3a0b8902a2a9ad57b599814 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2019 04:20:46 +0000 Subject: [PATCH 1086/2815] Bump ppy.osu.Game.Resources from 2019.903.1 to 2019.904.0 Bumps [ppy.osu.Game.Resources](https://github.com/ppy/osu-resources) from 2019.903.1 to 2019.904.0. - [Release notes](https://github.com/ppy/osu-resources/releases) - [Commits](https://github.com/ppy/osu-resources/compare/2019.903.1...2019.904.0) Signed-off-by: dependabot-preview[bot] --- 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 96706f2bdc..90d1854c39 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -60,7 +60,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ca69bb2295..7d106f0484 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -14,7 +14,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 86a2a40940..8390a2229b 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -117,7 +117,7 @@ - + From 04c2c33c64bbc908324510672c822681bf40667b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2019 13:29:55 +0900 Subject: [PATCH 1087/2815] Allow LegacySkin to be constructed with all nulls --- osu.Game/Skinning/LegacySkin.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 5f0afae075..7bdb980eaf 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -30,14 +30,14 @@ namespace osu.Game.Skinning protected LegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager, string filename) : base(skin) { - Stream stream = storage.GetStream(filename); + Stream stream = storage?.GetStream(filename); if (stream != null) using (StreamReader reader = new StreamReader(stream)) Configuration = new LegacySkinDecoder().Decode(reader); else Configuration = new DefaultSkinConfiguration(); - Samples = audioManager.GetSampleStore(storage); + Samples = audioManager?.GetSampleStore(storage); Textures = new TextureStore(new TextureLoaderStore(storage)); } @@ -72,6 +72,10 @@ namespace osu.Game.Skinning { if (Configuration.ConfigDictionary.TryGetValue(lookup.ToString(), out var val)) { + // special case for handling skins which use 1 or 0 to signify a boolean state. + if (typeof(TValue) == typeof(bool)) + val = val == "1" ? "true" : "false"; + var bindable = new Bindable(); bindable.Parse(val); return bindable; From f655cd451681e8fc64e0f6c8faebcdc1c0ab08ea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2019 13:30:46 +0900 Subject: [PATCH 1088/2815] Fix parsing of null configuration elements --- osu.Game/Skinning/LegacySkin.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 7bdb980eaf..94e2a49908 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -77,7 +77,8 @@ namespace osu.Game.Skinning val = val == "1" ? "true" : "false"; var bindable = new Bindable(); - bindable.Parse(val); + if (val != null) + bindable.Parse(val); return bindable; } } From fb3d050209bb8b72c02435d5225b8f7318afd98f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2019 13:36:50 +0900 Subject: [PATCH 1089/2815] Add comprehensive configuration lookup tests --- .../Skins/SkinConfigurationLookupTest.cs | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 osu.Game.Tests/Skins/SkinConfigurationLookupTest.cs diff --git a/osu.Game.Tests/Skins/SkinConfigurationLookupTest.cs b/osu.Game.Tests/Skins/SkinConfigurationLookupTest.cs new file mode 100644 index 0000000000..1344d20d9f --- /dev/null +++ b/osu.Game.Tests/Skins/SkinConfigurationLookupTest.cs @@ -0,0 +1,137 @@ +// 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.Framework.Allocation; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Textures; +using osu.Game.Audio; +using osu.Game.Skinning; +using osu.Game.Tests.Visual; +using osuTK.Graphics; + +namespace osu.Game.Tests.Skins +{ + [TestFixture] + public class TestSceneSkinConfigurationLookup : OsuTestScene + { + private LegacySkin source1; + private LegacySkin source2; + private SkinRequester requester; + + [SetUp] + public void SetUp() => Schedule(() => + { + Add(new SkinProvidingContainer(source1 = new SkinSource()) + .WithChild(new SkinProvidingContainer(source2 = new SkinSource()) + .WithChild(requester = new SkinRequester()))); + }); + + [Test] + public void TestBasicLookup() + { + AddStep("Add config values", () => + { + source1.Configuration.ConfigDictionary["Lookup"] = "source1"; + source2.Configuration.ConfigDictionary["Lookup"] = "source2"; + }); + + AddAssert("Check lookup finds source2", () => requester.GetConfig("Lookup")?.Value == "source2"); + } + + [Test] + public void TestParsingLookup() + { + AddStep("Add config values", () => + { + source1.Configuration.ConfigDictionary["FloatTest"] = "1.1"; + source2.Configuration.ConfigDictionary["BoolTest"] = "1"; + }); + + AddAssert("Check float parse lookup", () => requester.GetConfig("FloatTest")?.Value == 1.1f); + AddAssert("Check bool parse lookup", () => requester.GetConfig("BoolTest")?.Value == true); + } + + [Test] + public void TestEnumLookup() + { + AddStep("Add config values", () => { source1.Configuration.ConfigDictionary["Test"] = "Test2"; }); + + AddAssert("Check float parse lookup", () => requester.GetConfig(LookupType.Test)?.Value == ValueType.Test2); + } + + [Test] + public void TestLookupFailure() + { + AddAssert("Check lookup failure", () => requester.GetConfig("Lookup") == null); + } + + [Test] + public void TestLookupNull() + { + AddStep("Add config values", () => { source1.Configuration.ConfigDictionary["Lookup"] = null; }); + + AddAssert("Check lookup null", () => + { + var bindable = requester.GetConfig("Lookup"); + return bindable != null && bindable.Value == null; + }); + } + + [Test] + public void TestColourLookup() + { + AddStep("Add config colour", () => { source1.Configuration.CustomColours["Lookup"] = Color4.Red; }); + AddAssert("Check colour lookup", () => requester.GetConfig(new SkinCustomColourLookup("Lookup"))?.Value == Color4.Red); + } + + [Test] + public void TestGlobalLookup() + { + AddAssert("Check combo colours", () => requester.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value?.Count > 0); + } + + public enum LookupType + { + Test + } + + public enum ValueType + { + Test1, + Test2, + Test3 + } + + public class SkinSource : LegacySkin + { + public SkinSource() + : base(new SkinInfo(), null, null, string.Empty) + { + } + } + + public class SkinRequester : Drawable, ISkin + { + private ISkinSource skin; + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + this.skin = skin; + } + + public Drawable GetDrawableComponent(ISkinComponent component) => skin.GetDrawableComponent(component); + + public Texture GetTexture(string componentName) => skin.GetTexture(componentName); + + public SampleChannel GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo); + + public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup); + } + } +} From 8d48cc3533ef470fba9045e8206763caac0a7820 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2019 13:40:36 +0900 Subject: [PATCH 1090/2815] Fix filename --- ...igurationLookupTest.cs => TestSceneSkinConfigurationLookup.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game.Tests/Skins/{SkinConfigurationLookupTest.cs => TestSceneSkinConfigurationLookup.cs} (100%) diff --git a/osu.Game.Tests/Skins/SkinConfigurationLookupTest.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs similarity index 100% rename from osu.Game.Tests/Skins/SkinConfigurationLookupTest.cs rename to osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs From 69b9d70a35c2c1f42214d16f083b6e73f43ddc4c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2019 15:56:18 +0900 Subject: [PATCH 1091/2815] Add minimal configuration to support nuget package creation --- osu.Game/osu.Game.csproj | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 7d106f0484..3147ca749f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -6,6 +6,17 @@ AnyCPU true + + osu! + ppy.osu.Game + ppy Pty Ltd + https://github.com/ppy/osu/blob/master/LICENCE.md + https://github.com/ppy/osu + https://github.com/ppy/osu + Automated release. + Copyright (c) 2019 ppy Pty Ltd + osu game + @@ -15,7 +26,7 @@ - + From 8ea82123e4afc36ac897735d24743a17b259d25b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2019 15:59:09 +0900 Subject: [PATCH 1092/2815] Fix nullref on test disposal --- osu.Game/Skinning/LegacySkin.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 94e2a49908..cd2ad2d61c 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; @@ -18,8 +19,10 @@ namespace osu.Game.Skinning { public class LegacySkin : Skin { + [CanBeNull] protected TextureStore Textures; + [CanBeNull] protected IResourceStore Samples; public LegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager) @@ -37,8 +40,11 @@ namespace osu.Game.Skinning else Configuration = new DefaultSkinConfiguration(); - Samples = audioManager?.GetSampleStore(storage); - Textures = new TextureStore(new TextureLoaderStore(storage)); + if (storage != null) + { + Samples = audioManager?.GetSampleStore(storage); + Textures = new TextureStore(new TextureLoaderStore(storage)); + } } protected override void Dispose(bool isDisposing) @@ -125,12 +131,12 @@ namespace osu.Game.Skinning componentName = getFallbackName(componentName); float ratio = 2; - var texture = Textures.Get($"{componentName}@2x"); + var texture = Textures?.Get($"{componentName}@2x"); if (texture == null) { ratio = 1; - texture = Textures.Get(componentName); + texture = Textures?.Get(componentName); } if (texture != null) @@ -143,7 +149,7 @@ namespace osu.Game.Skinning { foreach (var lookup in sampleInfo.LookupNames) { - var sample = Samples.Get(getFallbackName(lookup)); + var sample = Samples?.Get(getFallbackName(lookup)); if (sample != null) return sample; @@ -151,7 +157,7 @@ namespace osu.Game.Skinning if (sampleInfo is HitSampleInfo hsi) // Try fallback to non-bank samples. - return Samples.Get(hsi.Name); + return Samples?.Get(hsi.Name); return null; } From 07f662071d5c7dc31424e692fb9e438587cff662 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 4 Sep 2019 18:14:55 +0900 Subject: [PATCH 1093/2815] Remove judgementOccurred --- .../Objects/Drawables/DrawableHitObject.cs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 1a224b2cea..f7efa625a5 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -76,8 +76,6 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public JudgementResult Result { get; private set; } - private bool judgementOccurred; - public override bool RemoveWhenNotAlive => false; public override bool RemoveCompletedTransforms => false; protected override bool RequiresChildrenUpdate => true; @@ -342,8 +340,6 @@ namespace osu.Game.Rulesets.Objects.Drawables if (!Result.HasResult) throw new InvalidOperationException($"{GetType().ReadableName()} applied a {nameof(JudgementResult)} but did not update {nameof(JudgementResult.Type)}."); - judgementOccurred = true; - // Ensure that the judgement is given a valid time offset, because this may not get set by the caller var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; Result.TimeOffset = Time.Current - endTime; @@ -376,21 +372,13 @@ namespace osu.Game.Rulesets.Objects.Drawables if (Time.Elapsed < 0) return false; - judgementOccurred = false; - - if (AllJudged) + if (Judged) return false; - foreach (var d in NestedHitObjects) - judgementOccurred |= d.UpdateResult(userTriggered); - - if (judgementOccurred || Judged) - return judgementOccurred; - var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; CheckForResult(userTriggered, Time.Current - endTime); - return judgementOccurred; + return Judged; } /// From a87a1e60314352fc3bd241935d5116f9e07cc075 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 4 Sep 2019 19:38:12 +0900 Subject: [PATCH 1094/2815] Don't redraw certain buffered containers on scale change --- osu.Game/Graphics/Backgrounds/Background.cs | 3 ++- osu.Game/Overlays/NowPlayingOverlay.cs | 5 ++++- osu.Game/Screens/Play/SquareGraph.cs | 1 + osu.Game/Screens/Select/BeatmapInfoWedge.cs | 2 ++ .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 1 + 5 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/Background.cs b/osu.Game/Graphics/Backgrounds/Background.cs index d13475189d..0f923c3a28 100644 --- a/osu.Game/Graphics/Backgrounds/Background.cs +++ b/osu.Game/Graphics/Backgrounds/Background.cs @@ -57,8 +57,9 @@ namespace osu.Game.Graphics.Backgrounds AddInternal(bufferedContainer = new BufferedContainer { - CacheDrawnFrameBuffer = true, RelativeSizeAxes = Axes.Both, + CacheDrawnFrameBuffer = true, + RedrawOnScale = false, Child = Sprite }); } diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index a3243a655e..c8361c6114 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -346,10 +346,13 @@ namespace osu.Game.Overlays public Background(WorkingBeatmap beatmap = null) { this.beatmap = beatmap; - CacheDrawnFrameBuffer = true; + Depth = float.MaxValue; RelativeSizeAxes = Axes.Both; + CacheDrawnFrameBuffer = true; + RedrawOnScale = false; + Children = new Drawable[] { sprite = new Sprite diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index 9c56725c4e..05f6128ac2 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -103,6 +103,7 @@ namespace osu.Game.Screens.Play var newColumns = new BufferedContainer { CacheDrawnFrameBuffer = true, + RedrawOnScale = false, RelativeSizeAxes = Axes.Both, }; diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 5f6307e3b4..65ecd7b812 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -154,6 +154,8 @@ namespace osu.Game.Screens.Select var metadata = beatmapInfo.Metadata ?? beatmap.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); CacheDrawnFrameBuffer = true; + RedrawOnScale = false; + RelativeSizeAxes = Axes.Both; titleBinding = localisation.GetLocalisedString(new LocalisedString((metadata.TitleUnicode, metadata.Title))); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 97b6a78804..699e01bca7 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -146,6 +146,7 @@ namespace osu.Game.Screens.Select.Carousel public PanelBackground(WorkingBeatmap working) { CacheDrawnFrameBuffer = true; + RedrawOnScale = false; Children = new Drawable[] { From b80a8296cd591db9dd34b8746fdbb85570694be2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2019 20:27:27 +0900 Subject: [PATCH 1095/2815] Fix unavailable rulesets crashing at song select --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 2 +- osu.Game/Rulesets/RulesetInfo.cs | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 81f517dd86..8014631eca 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -84,7 +84,7 @@ namespace osu.Game.Beatmaps.Drawables Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, // the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment) - Icon = ruleset?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle } + Icon = ruleset?.CreateInstance()?.CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle } } }; } diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index d9cff86265..c982ef7be1 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; using Newtonsoft.Json; namespace osu.Game.Rulesets @@ -20,7 +21,13 @@ namespace osu.Game.Rulesets [JsonIgnore] public bool Available { get; set; } - public virtual Ruleset CreateInstance() => (Ruleset)Activator.CreateInstance(Type.GetType(InstantiationInfo), this); + [CanBeNull] + public virtual Ruleset CreateInstance() + { + if (!Available) return null; + + return (Ruleset)Activator.CreateInstance(Type.GetType(InstantiationInfo), this); + } public bool Equals(RulesetInfo other) => other != null && ID == other.ID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo; From c59a2bf9bb6bce70906c6fd3b3e99417ff314249 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2019 20:28:04 +0900 Subject: [PATCH 1096/2815] Fix tests crashing if a ruleset doesn't provide a NoFail mod --- osu.Game/Tests/Visual/PlayerTestScene.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index ccd996098c..2c5a51ca02 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -50,7 +50,11 @@ namespace osu.Game.Tests.Visual Beatmap.Value = CreateWorkingBeatmap(beatmap); if (!AllowFail) - Mods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; + { + var noFailMod = ruleset.GetAllMods().FirstOrDefault(m => m is ModNoFail); + if (noFailMod != null) + Mods.Value = new[] { noFailMod }; + } if (Autoplay) { From 6197c7fd31d3edcbae5a07c1b06d7157b3eb25b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2019 20:28:21 +0900 Subject: [PATCH 1097/2815] Add automatic resource mapping for rulesets to their own dll --- osu.Game/Rulesets/Ruleset.cs | 3 +++ osu.Game/Rulesets/UI/DrawableRuleset.cs | 20 ++++++++++++++++++++ osu.Game/Skinning/SkinnableSound.cs | 14 ++++++++------ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index b63292757d..197c089f71 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; +using osu.Framework.IO.Stores; using osu.Game.Beatmaps; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Edit; @@ -83,6 +84,8 @@ namespace osu.Game.Rulesets public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.QuestionCircle }; + public virtual IResourceStore CreateReourceStore() => new NamespacedResourceStore(new DllResourceStore(GetType().Assembly.Location), @"Resources"); + public abstract string Description { get; } public virtual RulesetSettingsSubsection CreateSettings() => null; diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index a32407d180..562b2c4667 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -14,10 +14,14 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using JetBrains.Annotations; +using osu.Framework.Audio; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.Input.Events; +using osu.Framework.IO.Stores; using osu.Game.Configuration; using osu.Game.Graphics.Cursor; using osu.Game.Input.Handlers; @@ -51,6 +55,10 @@ namespace osu.Game.Rulesets.UI private readonly Lazy playfield; + private TextureStore textureStore; + + private ISampleStore sampleStore; + /// /// The playfield. /// @@ -142,6 +150,18 @@ namespace osu.Game.Rulesets.UI { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + var resources = Ruleset.CreateReourceStore(); + + if (resources != null) + { + textureStore = new TextureStore(new TextureLoaderStore(new NamespacedResourceStore(resources, "Textures"))); + textureStore.AddStore(dependencies.Get()); + dependencies.Cache(textureStore); + + sampleStore = dependencies.Get().GetSampleStore(new NamespacedResourceStore(resources, "Samples")); + dependencies.CacheAs(sampleStore); + } + onScreenDisplay = dependencies.Get(); Config = dependencies.Get().GetConfigFor(Ruleset); diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 3d0219ed93..bdf8be773b 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Audio; @@ -20,7 +21,7 @@ namespace osu.Game.Skinning private SampleChannel[] channels; - private AudioManager audio; + private ISampleStore samples; public SkinnableSound(IEnumerable hitSamples) { @@ -33,9 +34,9 @@ namespace osu.Game.Skinning } [BackgroundDependencyLoader] - private void load(AudioManager audio) + private void load(ISampleStore samples) { - this.audio = audio; + this.samples = samples; } private bool looping; @@ -81,7 +82,7 @@ namespace osu.Game.Skinning if (ch == null && allowFallback) foreach (var lookup in s.LookupNames) - if ((ch = audio.Samples.Get($"Gameplay/{lookup}")) != null) + if ((ch = samples.Get($"Gameplay/{lookup}")) != null) break; if (ch != null) @@ -102,8 +103,9 @@ namespace osu.Game.Skinning { base.Dispose(isDisposing); - foreach (var c in channels) - c.Dispose(); + if (channels != null) + foreach (var c in channels) + c.Dispose(); } } } From 8e8f33ec7b5fb0a24f279692c76ba3d214997bc2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2019 21:35:59 +0900 Subject: [PATCH 1098/2815] Remove null hinting for now --- osu.Game/Rulesets/RulesetInfo.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index c982ef7be1..6a69fd8dd0 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -3,7 +3,6 @@ using System; using System.Diagnostics.CodeAnalysis; -using JetBrains.Annotations; using Newtonsoft.Json; namespace osu.Game.Rulesets @@ -21,7 +20,6 @@ namespace osu.Game.Rulesets [JsonIgnore] public bool Available { get; set; } - [CanBeNull] public virtual Ruleset CreateInstance() { if (!Available) return null; From f9fa5988e69a679a7a344baa0a1855703d7f7caf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2019 23:16:56 +0900 Subject: [PATCH 1099/2815] Add basic nuget deploy support --- appveyor.yml | 2 -- appveyor_deploy.yml | 10 ++++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 appveyor_deploy.yml diff --git a/appveyor.yml b/appveyor.yml index 4dcaa7b45e..be1727e7d7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,7 +2,5 @@ clone_depth: 1 version: '{branch}-{build}' image: Previous Visual Studio 2017 test: off -install: - - cmd: git submodule update --init --recursive --depth=5 build_script: - cmd: PowerShell -Version 2.0 .\build.ps1 diff --git a/appveyor_deploy.yml b/appveyor_deploy.yml new file mode 100644 index 0000000000..d36298175b --- /dev/null +++ b/appveyor_deploy.yml @@ -0,0 +1,10 @@ +clone_depth: 1 +version: '{build}' +image: Previous Visual Studio 2017 +test: off +skip_non_tags: true +build_script: + - cmd: PowerShell -Version 2.0 .\build.ps1 +deploy: + - provider: Environment + name: nuget From 50de4d1a3a4569da2532c52b3b74371f28f46ae7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2019 01:11:53 +0900 Subject: [PATCH 1100/2815] Remove PrivateAssets changes for now --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3147ca749f..f2a605e7a7 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + From afac512a1bfd09d115fc60b9dcfc5d8c0b7e776e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2019 02:12:17 +0900 Subject: [PATCH 1101/2815] Fix databased config save performance Adds proper save debounce logic. Closes #5991. --- .../Configuration/DatabasedConfigManager.cs | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/osu.Game/Configuration/DatabasedConfigManager.cs b/osu.Game/Configuration/DatabasedConfigManager.cs index d5cdd7e4bc..6aa89cdd69 100644 --- a/osu.Game/Configuration/DatabasedConfigManager.cs +++ b/osu.Game/Configuration/DatabasedConfigManager.cs @@ -16,11 +16,11 @@ namespace osu.Game.Configuration private readonly int? variant; - private readonly List databasedSettings; + private List databasedSettings; private readonly RulesetInfo ruleset; - private readonly bool legacySettingsExist; + private bool legacySettingsExist; protected DatabasedConfigManager(SettingsStore settings, RulesetInfo ruleset = null, int? variant = null) { @@ -28,21 +28,34 @@ namespace osu.Game.Configuration this.ruleset = ruleset; this.variant = variant; - databasedSettings = settings.Query(ruleset?.ID, variant); - legacySettingsExist = databasedSettings.Any(s => int.TryParse(s.Key, out var _)); + Load(); InitialiseDefaults(); } protected override void PerformLoad() { + databasedSettings = settings.Query(ruleset?.ID, variant); + legacySettingsExist = databasedSettings.Any(s => int.TryParse(s.Key, out var _)); } protected override bool PerformSave() { + lock (dirtySettings) + { + if (dirtySettings.Count > 0) + { + foreach (var setting in dirtySettings) + settings.Update(setting); + dirtySettings.Clear(); + } + } + return true; } + private readonly List dirtySettings = new List(); + protected override void AddBindable(T lookup, Bindable bindable) { base.AddBindable(lookup, bindable); @@ -80,7 +93,9 @@ namespace osu.Game.Configuration bindable.ValueChanged += b => { setting.Value = b.NewValue; - settings.Update(setting); + lock (dirtySettings) + if (!dirtySettings.Contains(setting)) + dirtySettings.Add(setting); }; } } From a1c580f27ecae7f58bc6fbf6b58de8b6cef9b885 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 5 Sep 2019 05:56:21 +0300 Subject: [PATCH 1102/2815] Create "none selected" placeholder state --- osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs | 1 + osu.Game/Online/Leaderboards/Leaderboard.cs | 4 ++++ osu.Game/Online/Leaderboards/PlaceholderState.cs | 1 + .../Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 8 +++++++- 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs index 8e358a77db..186f27a8b2 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs @@ -42,6 +42,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep(@"No supporter", () => leaderboard.SetRetrievalState(PlaceholderState.NotSupporter)); AddStep(@"Not logged in", () => leaderboard.SetRetrievalState(PlaceholderState.NotLoggedIn)); AddStep(@"Unavailable", () => leaderboard.SetRetrievalState(PlaceholderState.Unavailable)); + AddStep(@"None selected", () => leaderboard.SetRetrievalState(PlaceholderState.NoneSelected)); foreach (BeatmapSetOnlineStatus status in Enum.GetValues(typeof(BeatmapSetOnlineStatus))) AddStep($"{status} beatmap", () => showBeatmapWithStatus(status)); } diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 98f15599fc..147556b78b 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -133,6 +133,10 @@ namespace osu.Game.Online.Leaderboards }); break; + case PlaceholderState.NoneSelected: + replacePlaceholder(new MessagePlaceholder(@"Please select a beatmap!")); + break; + case PlaceholderState.Unavailable: replacePlaceholder(new MessagePlaceholder(@"Leaderboards are not available for this beatmap!")); break; diff --git a/osu.Game/Online/Leaderboards/PlaceholderState.cs b/osu.Game/Online/Leaderboards/PlaceholderState.cs index 930e1df484..297241fa73 100644 --- a/osu.Game/Online/Leaderboards/PlaceholderState.cs +++ b/osu.Game/Online/Leaderboards/PlaceholderState.cs @@ -9,6 +9,7 @@ namespace osu.Game.Online.Leaderboards Retrieving, NetworkFailure, Unavailable, + NoneSelected, NoScores, NotLoggedIn, NotSupporter, diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index cb45c00f66..33f040755e 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -83,6 +83,12 @@ namespace osu.Game.Screens.Select.Leaderboards protected override APIRequest FetchScores(Action> scoresCallback) { + if (Beatmap == null) + { + PlaceholderState = PlaceholderState.NoneSelected; + return null; + } + if (Scope == BeatmapLeaderboardScope.Local) { var scores = scoreManager @@ -113,7 +119,7 @@ namespace osu.Game.Screens.Select.Leaderboards return null; } - if (Beatmap?.OnlineBeatmapID == null || Beatmap?.Status <= BeatmapSetOnlineStatus.Pending) + if (Beatmap.OnlineBeatmapID == null || Beatmap?.Status <= BeatmapSetOnlineStatus.Pending) { PlaceholderState = PlaceholderState.Unavailable; return null; From 1b0123a60cb22e89f009b2befbc024960dee706f Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 5 Sep 2019 05:56:52 +0300 Subject: [PATCH 1103/2815] Set beatmap of leaderboard to null if NoBeatmapsAvailable is selected --- osu.Game/Screens/Select/BeatmapDetailArea.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs index b66a2ffe0f..bf8fc8cf07 100644 --- a/osu.Game/Screens/Select/BeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs @@ -27,8 +27,8 @@ namespace osu.Game.Screens.Select set { beatmap = value; - Leaderboard.Beatmap = beatmap?.BeatmapInfo; Details.Beatmap = beatmap?.BeatmapInfo; + Leaderboard.Beatmap = beatmap is NoBeatmapsAvailableWorkingBeatmap ? null : beatmap?.BeatmapInfo; } } From d40129aabe9a8947add0a28b6a51fa85b7017365 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Sep 2019 13:36:37 +0900 Subject: [PATCH 1104/2815] Remove unnecessary count check --- osu.Game/Configuration/DatabasedConfigManager.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Configuration/DatabasedConfigManager.cs b/osu.Game/Configuration/DatabasedConfigManager.cs index 6aa89cdd69..0046f4fa7f 100644 --- a/osu.Game/Configuration/DatabasedConfigManager.cs +++ b/osu.Game/Configuration/DatabasedConfigManager.cs @@ -43,12 +43,9 @@ namespace osu.Game.Configuration { lock (dirtySettings) { - if (dirtySettings.Count > 0) - { - foreach (var setting in dirtySettings) - settings.Update(setting); - dirtySettings.Clear(); - } + foreach (var setting in dirtySettings) + settings.Update(setting); + dirtySettings.Clear(); } return true; From 070a005294668614deaf2779450eed7b1e90051e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Sep 2019 13:37:11 +0900 Subject: [PATCH 1105/2815] Add braces to lock() Personal preference, I want to be sure that everything is wrapped correctly. --- osu.Game/Configuration/DatabasedConfigManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Configuration/DatabasedConfigManager.cs b/osu.Game/Configuration/DatabasedConfigManager.cs index 0046f4fa7f..02382cfd2b 100644 --- a/osu.Game/Configuration/DatabasedConfigManager.cs +++ b/osu.Game/Configuration/DatabasedConfigManager.cs @@ -90,9 +90,12 @@ namespace osu.Game.Configuration bindable.ValueChanged += b => { setting.Value = b.NewValue; + lock (dirtySettings) + { if (!dirtySettings.Contains(setting)) dirtySettings.Add(setting); + } }; } } From 2e6af84ca839d5e22f6fdab8fe790c0fd80894c4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Sep 2019 14:39:02 +0900 Subject: [PATCH 1106/2815] Don't redraw leaderboard scores --- osu.Game/Graphics/Sprites/GlowingSpriteText.cs | 9 ++++++++- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs index 24816deeb5..892b27da75 100644 --- a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs +++ b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs @@ -12,6 +12,7 @@ namespace osu.Game.Graphics.Sprites public class GlowingSpriteText : Container, IHasText { private readonly OsuSpriteText spriteText, blurredText; + private readonly BufferedContainer buffer; public string Text { @@ -43,13 +44,19 @@ namespace osu.Game.Graphics.Sprites set => blurredText.Colour = value; } + public bool RedrawOnScale + { + get => buffer.RedrawOnScale; + set => buffer.RedrawOnScale = value; + } + public GlowingSpriteText() { AutoSizeAxes = Axes.Both; Children = new Drawable[] { - new BufferedContainer + buffer = new BufferedContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 008f8208eb..e29748060e 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -193,6 +193,7 @@ namespace osu.Game.Online.Leaderboards GlowColour = OsuColour.FromHex(@"83ccfa"), Text = score.TotalScore.ToString(@"N0"), Font = OsuFont.Numeric.With(size: 23), + RedrawOnScale = false, }, RankContainer = new Container { @@ -338,6 +339,7 @@ namespace osu.Game.Online.Leaderboards GlowColour = OsuColour.FromHex(@"83ccfa"), Text = statistic.Value, Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold), + RedrawOnScale = false }, }, }; From 99579255ade422b7985b80c2dce5034735d8038c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Sep 2019 14:41:02 +0900 Subject: [PATCH 1107/2815] Force glowing sprite text to never redraw --- osu.Game/Graphics/Sprites/GlowingSpriteText.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs index 892b27da75..12688da9df 100644 --- a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs +++ b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs @@ -12,7 +12,6 @@ namespace osu.Game.Graphics.Sprites public class GlowingSpriteText : Container, IHasText { private readonly OsuSpriteText spriteText, blurredText; - private readonly BufferedContainer buffer; public string Text { @@ -44,24 +43,19 @@ namespace osu.Game.Graphics.Sprites set => blurredText.Colour = value; } - public bool RedrawOnScale - { - get => buffer.RedrawOnScale; - set => buffer.RedrawOnScale = value; - } - public GlowingSpriteText() { AutoSizeAxes = Axes.Both; Children = new Drawable[] { - buffer = new BufferedContainer + new BufferedContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, BlurSigma = new Vector2(4), CacheDrawnFrameBuffer = true, + RedrawOnScale = false, RelativeSizeAxes = Axes.Both, Blending = BlendingParameters.Additive, Size = new Vector2(3f), From a1d7291ffa8623eeb91bebb947bba1423e0ee1ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2019 16:31:10 +0900 Subject: [PATCH 1108/2815] Fix pause menu keyboard navigation being affected by initial cursor hover --- .../Visual/Gameplay/TestSceneGameplayMenuOverlay.cs | 9 +++++++-- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 3 +++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs index cc275009ba..c1635ffc83 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs @@ -17,7 +17,7 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { - [System.ComponentModel.Description("player pause/fail screens")] + [Description("player pause/fail screens")] public class TestSceneGameplayMenuOverlay : ManualInputManagerTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseOverlay) }; @@ -152,7 +152,8 @@ namespace osu.Game.Tests.Visual.Gameplay } /// - /// Tests that entering menu with cursor initially on button selects it. + /// Tests that entering menu with cursor initially on button doesn't selects it immediately. + /// This is to allow for stable keyboard navigation. /// [Test] public void TestInitialButtonHover() @@ -164,6 +165,10 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Hide overlay", () => pauseOverlay.Hide()); showOverlay(); + AddAssert("First button not selected", () => !getButton(0).Selected.Value); + + AddStep("Move slightly", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(1))); + AddAssert("First button selected", () => getButton(0).Selected.Value); } diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index f93d5d8b02..c5202fa792 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -304,6 +304,9 @@ namespace osu.Game.Screens.Play private class Button : DialogButton { + // required to ensure keyboard navigation always starts from an extremity (unless the cursor is moved) + protected override bool OnHover(HoverEvent e) => true; + protected override bool OnMouseMove(MouseMoveEvent e) { Selected.Value = true; From 55c6feab6408d37f69c26706eb981906d5e61bc9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2019 16:33:14 +0900 Subject: [PATCH 1109/2815] 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 90d1854c39..adc340a734 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -61,6 +61,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 7d106f0484..cb30eee33a 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -15,7 +15,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 8390a2229b..88d181454f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,8 +118,8 @@ - - + + From 174f8ddb31b75b9a4e925c05871ea22f37a97fe0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2019 16:37:47 +0900 Subject: [PATCH 1110/2815] Remove incorrect usages --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index e29748060e..008f8208eb 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -193,7 +193,6 @@ namespace osu.Game.Online.Leaderboards GlowColour = OsuColour.FromHex(@"83ccfa"), Text = score.TotalScore.ToString(@"N0"), Font = OsuFont.Numeric.With(size: 23), - RedrawOnScale = false, }, RankContainer = new Container { @@ -339,7 +338,6 @@ namespace osu.Game.Online.Leaderboards GlowColour = OsuColour.FromHex(@"83ccfa"), Text = statistic.Value, Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold), - RedrawOnScale = false }, }, }; From bda21998c4de0a87d57ed019a6c77b0c86925223 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Sep 2019 16:39:58 +0900 Subject: [PATCH 1111/2815] Add helper method to make direct casts be used --- .../Skinning/OsuLegacySkinTransformer.cs | 2 +- .../Skins/TestSceneSkinConfigurationLookup.cs | 19 +++++++++++++++++++ osu.Game/Skinning/LegacySkin.cs | 6 +++--- osu.Game/Skinning/SkinUtils.cs | 18 ++++++++++++++++++ 4 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Skinning/SkinUtils.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 284259705a..5957b81d7e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Osu.Skinning { case OsuSkinConfiguration.SliderPathRadius: if (hasHitCircle.Value) - return new BindableFloat(legacy_circle_radius) as Bindable; + return SkinUtils.As(new BindableFloat(legacy_circle_radius)); break; } diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs index 1344d20d9f..71df038311 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs @@ -95,6 +95,25 @@ namespace osu.Game.Tests.Skins AddAssert("Check combo colours", () => requester.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value?.Count > 0); } + [Test] + public void TestWrongColourType() + { + AddStep("Add config colour", () => { source1.Configuration.CustomColours["Lookup"] = Color4.Red; }); + + AddAssert("perform incorrect lookup", () => + { + try + { + requester.GetConfig(new SkinCustomColourLookup("Lookup")); + return false; + } + catch + { + return true; + } + }); + } + public enum LookupType { Test diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index cd2ad2d61c..0b1076be01 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -62,16 +62,16 @@ namespace osu.Game.Skinning switch (global) { case GlobalSkinConfiguration.ComboColours: - return new Bindable>(Configuration.ComboColours) as IBindable; + return SkinUtils.As(new Bindable>(Configuration.ComboColours)); } break; case GlobalSkinColour colour: - return getCustomColour(colour.ToString()) as IBindable; + return SkinUtils.As(getCustomColour(colour.ToString())); case SkinCustomColourLookup customColour: - return getCustomColour(customColour.Lookup.ToString()) as IBindable; + return SkinUtils.As(getCustomColour(customColour.Lookup.ToString())); default: try diff --git a/osu.Game/Skinning/SkinUtils.cs b/osu.Game/Skinning/SkinUtils.cs new file mode 100644 index 0000000000..18059bc4bf --- /dev/null +++ b/osu.Game/Skinning/SkinUtils.cs @@ -0,0 +1,18 @@ +using osu.Framework.Bindables; + +namespace osu.Game.Skinning +{ + /// + /// Contains helper methods to assist in implementing s. + /// + public static class SkinUtils + { + /// + /// Converts an to a . Used for returning configuration values of specific types. + /// + /// The value. + /// The type of value , and the type of the resulting bindable. + /// The resulting bindable. + public static Bindable As(object value) => (Bindable)value; + } +} From 696802e6743088e1d450ad822f940afe348c4f1e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2019 16:52:53 +0900 Subject: [PATCH 1112/2815] Don't use in music player for now --- osu.Game/Overlays/NowPlayingOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index c8361c6114..cf42c8005a 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -351,7 +351,6 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both; CacheDrawnFrameBuffer = true; - RedrawOnScale = false; Children = new Drawable[] { From 8e204ba2e90182dd3f691d94fa58ac526b91232e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Sep 2019 16:55:24 +0900 Subject: [PATCH 1113/2815] Refactor tests --- .../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 71df038311..bbcc4140a9 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs @@ -44,24 +44,24 @@ namespace osu.Game.Tests.Skins } [Test] - public void TestParsingLookup() + public void TestFloatLookup() { - AddStep("Add config values", () => - { - source1.Configuration.ConfigDictionary["FloatTest"] = "1.1"; - source2.Configuration.ConfigDictionary["BoolTest"] = "1"; - }); - + AddStep("Add config values", () => source1.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"); AddAssert("Check bool parse lookup", () => requester.GetConfig("BoolTest")?.Value == true); } [Test] public void TestEnumLookup() { - AddStep("Add config values", () => { source1.Configuration.ConfigDictionary["Test"] = "Test2"; }); - - AddAssert("Check float parse lookup", () => requester.GetConfig(LookupType.Test)?.Value == ValueType.Test2); + AddStep("Add config values", () => source1.Configuration.ConfigDictionary["Test"] = "Test2"); + AddAssert("Check enum parse lookup", () => requester.GetConfig(LookupType.Test)?.Value == ValueType.Test2); } [Test] @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Skins [Test] public void TestLookupNull() { - AddStep("Add config values", () => { source1.Configuration.ConfigDictionary["Lookup"] = null; }); + AddStep("Add config values", () => source1.Configuration.ConfigDictionary["Lookup"] = null); AddAssert("Check lookup null", () => { @@ -85,7 +85,7 @@ namespace osu.Game.Tests.Skins [Test] public void TestColourLookup() { - AddStep("Add config colour", () => { source1.Configuration.CustomColours["Lookup"] = Color4.Red; }); + AddStep("Add config colour", () => source1.Configuration.CustomColours["Lookup"] = Color4.Red); AddAssert("Check colour lookup", () => requester.GetConfig(new SkinCustomColourLookup("Lookup"))?.Value == Color4.Red); } @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Skins [Test] public void TestWrongColourType() { - AddStep("Add config colour", () => { source1.Configuration.CustomColours["Lookup"] = Color4.Red; }); + AddStep("Add config colour", () => source1.Configuration.CustomColours["Lookup"] = Color4.Red); AddAssert("perform incorrect lookup", () => { From 90985b6af65a6008b55fd968d523e7c9ebc40d05 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Sep 2019 17:00:43 +0900 Subject: [PATCH 1114/2815] Add missing license header --- osu.Game/Skinning/SkinUtils.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Skinning/SkinUtils.cs b/osu.Game/Skinning/SkinUtils.cs index 18059bc4bf..e3bc5e28b8 100644 --- a/osu.Game/Skinning/SkinUtils.cs +++ b/osu.Game/Skinning/SkinUtils.cs @@ -1,3 +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.Bindables; namespace osu.Game.Skinning From 0a6c42972c8896ef0943133b93661895cb129841 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2019 23:01:35 +0900 Subject: [PATCH 1115/2815] Add back missing sample fallback to default skin --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 8 ++- osu.Game/Rulesets/UI/FallbackSampleStore.cs | 64 +++++++++++++++++++++ 2 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Rulesets/UI/FallbackSampleStore.cs diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 562b2c4667..2a8f64c42e 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.UI private TextureStore textureStore; - private ISampleStore sampleStore; + private ISampleStore localSampleStore; /// /// The playfield. @@ -158,8 +158,8 @@ namespace osu.Game.Rulesets.UI textureStore.AddStore(dependencies.Get()); dependencies.Cache(textureStore); - sampleStore = dependencies.Get().GetSampleStore(new NamespacedResourceStore(resources, "Samples")); - dependencies.CacheAs(sampleStore); + localSampleStore = dependencies.Get().GetSampleStore(new NamespacedResourceStore(resources, "Samples")); + dependencies.CacheAs(new FallbackSampleStore(localSampleStore, dependencies.Get())); } onScreenDisplay = dependencies.Get(); @@ -334,6 +334,8 @@ namespace osu.Game.Rulesets.UI { base.Dispose(isDisposing); + localSampleStore?.Dispose(); + if (Config != null) { onScreenDisplay?.StopTracking(this, Config); diff --git a/osu.Game/Rulesets/UI/FallbackSampleStore.cs b/osu.Game/Rulesets/UI/FallbackSampleStore.cs new file mode 100644 index 0000000000..64e273b72e --- /dev/null +++ b/osu.Game/Rulesets/UI/FallbackSampleStore.cs @@ -0,0 +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.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Audio.Track; +using osu.Framework.Bindables; + +namespace osu.Game.Rulesets.UI +{ + public class FallbackSampleStore : ISampleStore + { + private readonly ISampleStore primary; + private readonly ISampleStore secondary; + + public FallbackSampleStore(ISampleStore primary, ISampleStore secondary) + { + this.primary = primary; + this.secondary = secondary; + } + + public void Dispose() + { + primary.Dispose(); + secondary.Dispose(); + } + + public SampleChannel Get(string name) => primary.Get(name) ?? secondary.Get(name); + + public Task GetAsync(string name) => primary.GetAsync(name) ?? secondary.GetAsync(name); + + public Stream GetStream(string name) => primary.GetStream(name) ?? secondary.GetStream(name); + + public IEnumerable GetAvailableResources() => primary.GetAvailableResources().Concat(secondary.GetAvailableResources()); + + public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable) + { + primary.AddAdjustment(type, adjustBindable); + secondary.AddAdjustment(type, adjustBindable); + } + + public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable) + { + primary.RemoveAdjustment(type, adjustBindable); + primary.RemoveAdjustment(type, adjustBindable); + } + + public BindableDouble Volume => primary.Volume; + + public BindableDouble Balance => primary.Balance; + + public BindableDouble Frequency => primary.Frequency; + + public int PlaybackConcurrency + { + get => primary.PlaybackConcurrency; + set => primary.PlaybackConcurrency = value; + } + } +} From 60c2d113b80eed1630d0ccdc951a3b1fa954b9e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2019 23:07:03 +0900 Subject: [PATCH 1116/2815] Fix typo --- osu.Game/Rulesets/UI/FallbackSampleStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/FallbackSampleStore.cs b/osu.Game/Rulesets/UI/FallbackSampleStore.cs index 64e273b72e..cdefae458a 100644 --- a/osu.Game/Rulesets/UI/FallbackSampleStore.cs +++ b/osu.Game/Rulesets/UI/FallbackSampleStore.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.UI public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable) { primary.RemoveAdjustment(type, adjustBindable); - primary.RemoveAdjustment(type, adjustBindable); + secondary.RemoveAdjustment(type, adjustBindable); } public BindableDouble Volume => primary.Volume; From a0aeccf2322d06c782c9fc15d5eda86d1b15df6b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Sep 2019 23:24:13 +0900 Subject: [PATCH 1117/2815] Fix fallback to default combo colours not working --- osu.Game/Skinning/DefaultSkin.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 4dee70a47f..c0d6bb34e0 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -1,11 +1,13 @@ // 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.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Game.Audio; +using osuTK.Graphics; namespace osu.Game.Skinning { @@ -23,6 +25,21 @@ namespace osu.Game.Skinning public override SampleChannel GetSample(ISampleInfo sampleInfo) => null; - public override IBindable GetConfig(TLookup lookup) => null; + public override IBindable GetConfig(TLookup lookup) + { + switch (lookup) + { + case GlobalSkinConfiguration global: + switch (global) + { + case GlobalSkinConfiguration.ComboColours: + return SkinUtils.As(new Bindable>(Configuration.ComboColours)); + } + + break; + } + + return null; + } } } From dafe9da851437098d15f6104ce720d7eba2fc351 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Sep 2019 00:22:35 +0900 Subject: [PATCH 1118/2815] Dispose config managers ahead of time to avoid database errors --- osu.Game/Rulesets/RulesetConfigCache.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Rulesets/RulesetConfigCache.cs b/osu.Game/Rulesets/RulesetConfigCache.cs index 8c9e3c94e2..abaf7a96f2 100644 --- a/osu.Game/Rulesets/RulesetConfigCache.cs +++ b/osu.Game/Rulesets/RulesetConfigCache.cs @@ -36,5 +36,13 @@ namespace osu.Game.Rulesets return configCache.GetOrAdd(ruleset.RulesetInfo.ID.Value, _ => ruleset.CreateConfig(settingsStore)); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + foreach (var c in configCache.Values) + (c as IDisposable)?.Dispose(); + } } } From 5b094f8e1de30a7473e9b29c9b7933696f77c226 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Sep 2019 01:13:58 +0900 Subject: [PATCH 1119/2815] Actually register the RulesetConfigCache as a component --- osu.Game/OsuGameBase.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index de8f316b06..d6b8ad3e67 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -207,6 +207,7 @@ namespace osu.Game FileStore.Cleanup(); AddInternal(API); + AddInternal(RulesetConfigCache); GlobalActionContainer globalBinding; From d6cdde552daa84e04a76e6bf1c1d61872002c0bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Sep 2019 01:14:37 +0900 Subject: [PATCH 1120/2815] Add comment explaining dispose logic --- osu.Game/Rulesets/RulesetConfigCache.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/RulesetConfigCache.cs b/osu.Game/Rulesets/RulesetConfigCache.cs index abaf7a96f2..cdcd2666cf 100644 --- a/osu.Game/Rulesets/RulesetConfigCache.cs +++ b/osu.Game/Rulesets/RulesetConfigCache.cs @@ -41,6 +41,7 @@ namespace osu.Game.Rulesets { base.Dispose(isDisposing); + // ensures any potential database operations are finalised before game destruction. foreach (var c in configCache.Values) (c as IDisposable)?.Dispose(); } From 50985d1b1d952b7d922c0a4ccd3936218061cc26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Sep 2019 10:43:19 +0900 Subject: [PATCH 1121/2815] Fix disposal logic --- osu.Game/Rulesets/UI/FallbackSampleStore.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/UI/FallbackSampleStore.cs b/osu.Game/Rulesets/UI/FallbackSampleStore.cs index cdefae458a..f9e1c85e38 100644 --- a/osu.Game/Rulesets/UI/FallbackSampleStore.cs +++ b/osu.Game/Rulesets/UI/FallbackSampleStore.cs @@ -23,12 +23,6 @@ namespace osu.Game.Rulesets.UI this.secondary = secondary; } - public void Dispose() - { - primary.Dispose(); - secondary.Dispose(); - } - public SampleChannel Get(string name) => primary.Get(name) ?? secondary.Get(name); public Task GetAsync(string name) => primary.GetAsync(name) ?? secondary.GetAsync(name); @@ -58,7 +52,15 @@ namespace osu.Game.Rulesets.UI public int PlaybackConcurrency { get => primary.PlaybackConcurrency; - set => primary.PlaybackConcurrency = value; + set + { + primary.PlaybackConcurrency = value; + secondary.PlaybackConcurrency = value; + } + } + + public void Dispose() + { } } } From 43aed7fea7c1be8da0e906f5f18d6dafc17f6716 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 6 Sep 2019 02:58:58 +0000 Subject: [PATCH 1122/2815] Bump Humanizer from 2.6.2 to 2.7.2 Bumps [Humanizer](https://github.com/Humanizr/Humanizer) from 2.6.2 to 2.7.2. - [Release notes](https://github.com/Humanizr/Humanizer/releases) - [Changelog](https://github.com/Humanizr/Humanizer/blob/master/release_notes.md) - [Commits](https://github.com/Humanizr/Humanizer/compare/v2.6.2...v2.7.2) 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 8e4ce03e1a..5f2aad24dc 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -21,7 +21,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 88d181454f..5027a4ef8c 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -113,7 +113,7 @@ - + From 7d1f5310d20a866a9b8c9283eb3a2a1bc6903840 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Sep 2019 12:03:29 +0900 Subject: [PATCH 1123/2815] Don't implement anything --- osu.Game/Rulesets/UI/FallbackSampleStore.cs | 30 +++++++-------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/osu.Game/Rulesets/UI/FallbackSampleStore.cs b/osu.Game/Rulesets/UI/FallbackSampleStore.cs index f9e1c85e38..f1df8bf359 100644 --- a/osu.Game/Rulesets/UI/FallbackSampleStore.cs +++ b/osu.Game/Rulesets/UI/FallbackSampleStore.cs @@ -1,9 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Threading.Tasks; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -29,34 +29,22 @@ namespace osu.Game.Rulesets.UI public Stream GetStream(string name) => primary.GetStream(name) ?? secondary.GetStream(name); - public IEnumerable GetAvailableResources() => primary.GetAvailableResources().Concat(secondary.GetAvailableResources()); + public IEnumerable GetAvailableResources() => throw new NotImplementedException(); - public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable) - { - primary.AddAdjustment(type, adjustBindable); - secondary.AddAdjustment(type, adjustBindable); - } + public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => throw new NotImplementedException(); - public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable) - { - primary.RemoveAdjustment(type, adjustBindable); - secondary.RemoveAdjustment(type, adjustBindable); - } + public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => throw new NotImplementedException(); - public BindableDouble Volume => primary.Volume; + public BindableDouble Volume => throw new NotImplementedException(); - public BindableDouble Balance => primary.Balance; + public BindableDouble Balance => throw new NotImplementedException(); - public BindableDouble Frequency => primary.Frequency; + public BindableDouble Frequency => throw new NotImplementedException(); public int PlaybackConcurrency { - get => primary.PlaybackConcurrency; - set - { - primary.PlaybackConcurrency = value; - secondary.PlaybackConcurrency = value; - } + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); } public void Dispose() From bf6f803e691edbc776896433beb8f8c4f0f2f3ea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Sep 2019 12:12:27 +0900 Subject: [PATCH 1124/2815] Nest temporary class --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 49 +++++++++++++++++++ osu.Game/Rulesets/UI/FallbackSampleStore.cs | 54 --------------------- 2 files changed, 49 insertions(+), 54 deletions(-) delete mode 100644 osu.Game/Rulesets/UI/FallbackSampleStore.cs diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 2a8f64c42e..a34bb6e8ea 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -11,10 +11,13 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading; +using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics.Cursor; @@ -465,4 +468,50 @@ namespace osu.Game.Rulesets.UI { } } + + /// + /// A sample store which adds a fallback source. + /// + /// + /// This is a temporary implementation to workaround ISampleStore limitations. + /// + public class FallbackSampleStore : ISampleStore + { + private readonly ISampleStore primary; + private readonly ISampleStore secondary; + + public FallbackSampleStore(ISampleStore primary, ISampleStore secondary) + { + this.primary = primary; + this.secondary = secondary; + } + + public SampleChannel Get(string name) => primary.Get(name) ?? secondary.Get(name); + + public Task GetAsync(string name) => primary.GetAsync(name) ?? secondary.GetAsync(name); + + public Stream GetStream(string name) => primary.GetStream(name) ?? secondary.GetStream(name); + + public IEnumerable GetAvailableResources() => throw new NotImplementedException(); + + public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => throw new NotImplementedException(); + + public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => throw new NotImplementedException(); + + public BindableDouble Volume => throw new NotImplementedException(); + + public BindableDouble Balance => throw new NotImplementedException(); + + public BindableDouble Frequency => throw new NotImplementedException(); + + public int PlaybackConcurrency + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + + public void Dispose() + { + } + } } diff --git a/osu.Game/Rulesets/UI/FallbackSampleStore.cs b/osu.Game/Rulesets/UI/FallbackSampleStore.cs deleted file mode 100644 index f1df8bf359..0000000000 --- a/osu.Game/Rulesets/UI/FallbackSampleStore.cs +++ /dev/null @@ -1,54 +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 System; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; -using osu.Framework.Audio.Track; -using osu.Framework.Bindables; - -namespace osu.Game.Rulesets.UI -{ - public class FallbackSampleStore : ISampleStore - { - private readonly ISampleStore primary; - private readonly ISampleStore secondary; - - public FallbackSampleStore(ISampleStore primary, ISampleStore secondary) - { - this.primary = primary; - this.secondary = secondary; - } - - public SampleChannel Get(string name) => primary.Get(name) ?? secondary.Get(name); - - public Task GetAsync(string name) => primary.GetAsync(name) ?? secondary.GetAsync(name); - - public Stream GetStream(string name) => primary.GetStream(name) ?? secondary.GetStream(name); - - public IEnumerable GetAvailableResources() => throw new NotImplementedException(); - - public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => throw new NotImplementedException(); - - public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => throw new NotImplementedException(); - - public BindableDouble Volume => throw new NotImplementedException(); - - public BindableDouble Balance => throw new NotImplementedException(); - - public BindableDouble Frequency => throw new NotImplementedException(); - - public int PlaybackConcurrency - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - - public void Dispose() - { - } - } -} From f4f95197616cce29716fb31f1f36d3cb03bde0b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Sep 2019 12:16:20 +0900 Subject: [PATCH 1125/2815] Add todo comment --- osu.Game/Skinning/DefaultSkin.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index c0d6bb34e0..529c1afca5 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -29,6 +29,8 @@ namespace osu.Game.Skinning { switch (lookup) { + // todo: this code is pulled from LegacySkin and should not exist. + // will likely change based on how databased storage of skin configuration goes. case GlobalSkinConfiguration global: switch (global) { From f925e781a9ac8a1a770d0e2fc09d0b421b54af47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Sep 2019 15:24:00 +0900 Subject: [PATCH 1126/2815] Refactor HitWindows for legibility --- .../Objects/CatchHitObject.cs | 1 + .../{Objects => Scoring}/CatchHitWindows.cs | 3 +- .../Scoring/CatchScoreProcessor.cs | 1 - .../Difficulty/ManiaDifficultyCalculator.cs | 6 +- osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 2 +- .../Objects/HoldNoteTick.cs | 2 +- .../Objects/ManiaHitObject.cs | 2 + .../Objects/ManiaHitWindows.cs | 35 ----- .../Scoring/ManiaHitWindows.cs | 11 ++ .../Scoring/ManiaScoreProcessor.cs | 1 - .../TestSceneShaking.cs | 2 +- .../Difficulty/OsuDifficultyCalculator.cs | 5 +- .../Objects/Drawables/DrawableHitCircle.cs | 6 +- .../Objects/Drawables/DrawableRepeatPoint.cs | 2 +- .../Objects/Drawables/DrawableSliderTick.cs | 2 +- .../Objects/Drawables/DrawableSpinner.cs | 2 +- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 2 + .../Objects/OsuHitWindows.cs | 29 ---- osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 1 + .../Objects/SliderTailCircle.cs | 1 + osu.Game.Rulesets.Osu/Objects/SliderTick.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 2 +- .../Replays/OsuAutoGenerator.cs | 20 +-- .../Scoring/OsuHitWindows.cs | 20 +++ .../TestSceneTaikoPlayfield.cs | 2 +- .../Difficulty/TaikoDifficultyCalculator.cs | 5 +- .../Objects/Drawables/DrawableHit.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 2 +- .../Objects/DrumRollTick.cs | 2 +- .../Objects/StrongHitObject.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/Swell.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/SwellTick.cs | 2 +- .../Objects/TaikoHitObject.cs | 2 + .../Objects/TaikoHitWindows.cs | 41 ------ .../Scoring/TaikoHitWindows.cs | 19 +++ .../Scoring/TaikoScoreProcessor.cs | 1 - .../Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- .../Gameplay/TestSceneBarHitErrorMeter.cs | 20 +-- .../Objects/Drawables/DrawableHitObject.cs | 2 +- osu.Game/Rulesets/Objects/HitObject.cs | 1 + .../Objects/Legacy/Mania/ConvertHit.cs | 1 + .../Objects/Legacy/Mania/ConvertHold.cs | 1 + .../Objects/Legacy/Mania/ConvertSlider.cs | 1 + .../Objects/Legacy/Mania/ConvertSpinner.cs | 1 + .../Rulesets/Objects/Legacy/Osu/ConvertHit.cs | 1 + .../Objects/Legacy/Osu/ConvertSlider.cs | 1 + .../Objects/Legacy/Osu/ConvertSpinner.cs | 1 + .../Objects/Legacy/Taiko/ConvertHit.cs | 2 + .../Objects/Legacy/Taiko/ConvertSlider.cs | 2 + .../Objects/Legacy/Taiko/ConvertSpinner.cs | 1 + osu.Game/Rulesets/Scoring/HitResult.cs | 4 + .../{Objects => Scoring}/HitWindows.cs | 131 +++++++++++++----- osu.Game/Scoring/ScoreInfo.cs | 2 +- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 1 - .../HUD/HitErrorMeters/BarHitErrorMeter.cs | 3 +- .../Play/HUD/HitErrorMeters/HitErrorMeter.cs | 2 +- 57 files changed, 225 insertions(+), 199 deletions(-) rename osu.Game.Rulesets.Catch/{Objects => Scoring}/CatchHitWindows.cs (87%) delete mode 100644 osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs create mode 100644 osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs delete mode 100644 osu.Game.Rulesets.Osu/Objects/OsuHitWindows.cs create mode 100644 osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs delete mode 100644 osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs create mode 100644 osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs rename osu.Game/Rulesets/{Objects => Scoring}/HitWindows.cs (56%) diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index 19a1b59752..a25d9cb67e 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -6,6 +6,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Catch.Objects { diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitWindows.cs b/osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs similarity index 87% rename from osu.Game.Rulesets.Catch/Objects/CatchHitWindows.cs rename to osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs index 837662f5fe..ff793a372e 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitWindows.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs @@ -1,10 +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 osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; -namespace osu.Game.Rulesets.Catch.Objects +namespace osu.Game.Rulesets.Catch.Scoring { public class CatchHitWindows : HitWindows { diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 99b22b2d56..18785d65ea 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -4,7 +4,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index d945abdb04..37cba1fd3c 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -11,9 +11,9 @@ using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Difficulty.Preprocessing; using osu.Game.Rulesets.Mania.Difficulty.Skills; using osu.Game.Rulesets.Mania.Mods; -using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Difficulty { @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty StarRating = difficultyValue(skills) * star_scaling_factor, Mods = mods, // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future - GreatHitWindow = (int)(hitWindows.Great / 2) / clockRate, + GreatHitWindow = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate, Skills = skills }; } diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index d28d04b3c1..0c82cf7bbc 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -5,8 +5,8 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Judgements; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Objects { diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs index 6bb21633b6..d0125f8793 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs @@ -3,7 +3,7 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Judgements; -using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Objects { diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs index 70720a926b..995e1516cb 100644 --- a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs @@ -3,7 +3,9 @@ using osu.Framework.Bindables; using osu.Game.Rulesets.Mania.Objects.Types; +using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Objects { diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs deleted file mode 100644 index 5f2ceab48b..0000000000 --- a/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs +++ /dev/null @@ -1,35 +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 System.Collections.Generic; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Mania.Objects -{ - public class ManiaHitWindows : HitWindows - { - private static readonly IReadOnlyDictionary base_ranges = new Dictionary - { - { HitResult.Perfect, (44.8, 38.8, 27.8) }, - { HitResult.Great, (128, 98, 68) }, - { HitResult.Good, (194, 164, 134) }, - { HitResult.Ok, (254, 224, 194) }, - { HitResult.Meh, (302, 272, 242) }, - { HitResult.Miss, (376, 346, 316) }, - }; - - public override bool IsHitResultAllowed(HitResult result) => true; - - public override void SetDifficulty(double difficulty) - { - Perfect = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Perfect]); - Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]); - Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]); - Ok = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Ok]); - Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]); - Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]); - } - } -} diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs new file mode 100644 index 0000000000..549f0f9214 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs @@ -0,0 +1,11 @@ +// 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.Scoring; + +namespace osu.Game.Rulesets.Mania.Scoring +{ + public class ManiaHitWindows : HitWindows + { + } +} diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 5caf08fb1e..49894a644c 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -4,7 +4,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs index 585fdb9cb4..863d0eda09 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Tests Debug.Assert(drawableHitObject.HitObject.HitWindows != null); - double delay = drawableHitObject.HitObject.StartTime - (drawableHitObject.HitObject.HitWindows.HalfWindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current; + double delay = drawableHitObject.HitObject.StartTime - (drawableHitObject.HitObject.HitWindows.WindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current; Scheduler.AddDelayed(() => drawableHitObject.TriggerJudgement(), delay); return drawableHitObject; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 61e9f60cdd..b0d261a1cc 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -9,11 +9,12 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Skills; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Scoring; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Difficulty { @@ -39,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); // Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future - double hitWindowGreat = (int)(hitWindows.Great / 2) / clockRate; + double hitWindowGreat = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate; double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate; int maxCombo = beatmap.HitObjects.Count; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 3decc4e51f..985dcbca86 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -10,8 +10,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; -using osuTK; using osu.Game.Rulesets.Scoring; +using osuTK; using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu.Objects.Drawables @@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (result == HitResult.None) { - Shake(Math.Abs(timeOffset) - HitObject.HitWindows.HalfWindowFor(HitResult.Miss)); + Shake(Math.Abs(timeOffset) - HitObject.HitWindows.WindowFor(HitResult.Miss)); return; } @@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables hitArea.HitAction = null; // override lifetime end as FadeIn may have been changed externally, causing out expiration to be too early. - LifetimeEnd = HitObject.StartTime + HitObject.HitWindows.HalfWindowFor(HitResult.Miss); + LifetimeEnd = HitObject.StartTime + HitObject.HitWindows.WindowFor(HitResult.Miss); break; case ArmedState.Miss: diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index 50187781f6..00a943a67f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -9,8 +9,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.MathUtils; using osu.Game.Rulesets.Objects.Drawables; -using osuTK; using osu.Game.Rulesets.Scoring; +using osuTK; using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu.Objects.Drawables diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index c5fa5f0af5..ba931976a8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -8,9 +8,9 @@ using osu.Game.Rulesets.Objects.Drawables; using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index a0bd301fdb..49aaa2aaea 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -13,8 +13,8 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; -using osu.Game.Screens.Ranking; using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Ranking; namespace osu.Game.Rulesets.Osu.Objects.Drawables { diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index b52bfcd181..2cf877b000 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -7,6 +7,8 @@ using osu.Game.Rulesets.Objects; using osuTK; using osu.Game.Rulesets.Objects.Types; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Osu.Scoring; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects { diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitWindows.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitWindows.cs deleted file mode 100644 index add8fd53c7..0000000000 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitWindows.cs +++ /dev/null @@ -1,29 +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 System.Collections.Generic; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Osu.Objects -{ - public class OsuHitWindows : HitWindows - { - private static readonly IReadOnlyDictionary base_ranges = new Dictionary - { - { HitResult.Great, (160, 100, 40) }, - { HitResult.Good, (280, 200, 120) }, - { HitResult.Meh, (400, 300, 200) }, - { HitResult.Miss, (400, 400, 400) }, - }; - - public override void SetDifficulty(double difficulty) - { - Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]); - Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]); - Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]); - Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]); - } - } -} diff --git a/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs index 5bd480c0ff..a794e57c9e 100644 --- a/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs @@ -5,8 +5,8 @@ using System; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects { diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 93231844bb..2805494021 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -13,6 +13,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects { diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index c53a88337e..7e540a577b 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -5,6 +5,7 @@ using osu.Framework.Bindables; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects { diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs index 60e9084ed3..af7cf5b144 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs @@ -4,8 +4,8 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects { diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 69c779a182..2e7b763966 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -6,8 +6,8 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Types; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects { diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index e5fa571d4d..24320b6579 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -10,9 +10,9 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Graphics; using osu.Game.Replays; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Replays @@ -118,29 +118,29 @@ namespace osu.Game.Rulesets.Osu.Replays Debug.Assert(hitWindows != null); // Make the cursor stay at a hitObject as long as possible (mainly for autopilot). - if (h.StartTime - hitWindows.HalfWindowFor(HitResult.Miss) > endTime + hitWindows.HalfWindowFor(HitResult.Meh) + 50) + if (h.StartTime - hitWindows.WindowFor(HitResult.Miss) > endTime + hitWindows.WindowFor(HitResult.Meh) + 50) { if (!(prev is Spinner) && h.StartTime - endTime < 1000) - AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); + AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.WindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); if (!(h is Spinner)) - AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.HalfWindowFor(HitResult.Miss), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); + AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.WindowFor(HitResult.Miss), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); } - else if (h.StartTime - hitWindows.HalfWindowFor(HitResult.Meh) > endTime + hitWindows.HalfWindowFor(HitResult.Meh) + 50) + else if (h.StartTime - hitWindows.WindowFor(HitResult.Meh) > endTime + hitWindows.WindowFor(HitResult.Meh) + 50) { if (!(prev is Spinner) && h.StartTime - endTime < 1000) - AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); + AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.WindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); if (!(h is Spinner)) - AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.HalfWindowFor(HitResult.Meh), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); + AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.WindowFor(HitResult.Meh), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); } - else if (h.StartTime - hitWindows.HalfWindowFor(HitResult.Good) > endTime + hitWindows.HalfWindowFor(HitResult.Good) + 50) + else if (h.StartTime - hitWindows.WindowFor(HitResult.Good) > endTime + hitWindows.WindowFor(HitResult.Good) + 50) { if (!(prev is Spinner) && h.StartTime - endTime < 1000) - AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.HalfWindowFor(HitResult.Good), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); + AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.WindowFor(HitResult.Good), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); if (!(h is Spinner)) - AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.HalfWindowFor(HitResult.Good), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); + AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.WindowFor(HitResult.Good), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); } } diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs new file mode 100644 index 0000000000..8f258c9e3d --- /dev/null +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs @@ -0,0 +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 osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Osu.Scoring +{ + public class OsuHitWindows : HitWindows + { + private static readonly DifficultyRange[] osu_ranges = + { + new DifficultyRange(HitResult.Great, 80, 50, 20), + new DifficultyRange(HitResult.Good, 140, 100, 60), + new DifficultyRange(HitResult.Meh, 200, 150, 100), + new DifficultyRange(HitResult.Miss, 200, 200, 200), + }; + + protected override DifficultyRange[] GetRanges() => osu_ranges; + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs index 6fd16c213b..3aa461e779 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs @@ -14,13 +14,13 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.Taiko.UI; using osu.Game.Tests.Visual; using osuTK; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Taiko.Tests { diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index fc93bccb94..32d49ea39c 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -8,11 +8,12 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; using osu.Game.Rulesets.Taiko.Difficulty.Skills; using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Scoring; namespace osu.Game.Rulesets.Taiko.Difficulty { @@ -38,7 +39,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty StarRating = skills.Single().DifficultyValue() * star_scaling_factor, Mods = mods, // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future - GreatHitWindow = (int)(hitWindows.Great / 2) / clockRate, + GreatHitWindow = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate, MaxCombo = beatmap.HitObjects.Count(h => h is Hit), Skills = skills }; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 0942b37f58..676ecd5a0b 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables validActionPressed = false; UnproxyContent(); - this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire(); + this.Delay(HitObject.HitWindows.WindowFor(HitResult.Miss)).Expire(); break; case ArmedState.Miss: diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 3ed52f21f0..4e02c76a8b 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -6,7 +6,7 @@ using System; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Judgements; namespace osu.Game.Rulesets.Taiko.Objects diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs index 39e2b45e24..c466ca7c8a 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Judgements; namespace osu.Game.Rulesets.Taiko.Objects diff --git a/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs index 830e640242..d660149528 100644 --- a/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Judgements; namespace osu.Game.Rulesets.Taiko.Objects diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs index e7812841bf..f96c033dce 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs @@ -4,7 +4,7 @@ using System; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Judgements; namespace osu.Game.Rulesets.Taiko.Objects diff --git a/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs index 049fa7de5f..68212e8f12 100644 --- a/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Judgements; namespace osu.Game.Rulesets.Taiko.Objects diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs index 3592d73004..6f4fbd0651 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs @@ -4,7 +4,9 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Judgements; +using osu.Game.Rulesets.Taiko.Scoring; namespace osu.Game.Rulesets.Taiko.Objects { diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs deleted file mode 100644 index f232919cbf..0000000000 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs +++ /dev/null @@ -1,41 +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 System.Collections.Generic; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Taiko.Objects -{ - public class TaikoHitWindows : HitWindows - { - private static readonly IReadOnlyDictionary base_ranges = new Dictionary - { - { HitResult.Great, (100, 70, 40) }, - { HitResult.Good, (240, 160, 100) }, - { HitResult.Miss, (270, 190, 140) }, - }; - - public override bool IsHitResultAllowed(HitResult result) - { - switch (result) - { - case HitResult.Great: - case HitResult.Good: - case HitResult.Miss: - return true; - - default: - return false; - } - } - - public override void SetDifficulty(double difficulty) - { - Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]); - Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]); - Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]); - } - } -} diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs new file mode 100644 index 0000000000..77d59f9638 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.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.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Taiko.Scoring +{ + public class TaikoHitWindows : HitWindows + { + private static readonly DifficultyRange[] taiko_ranges = + { + new DifficultyRange(HitResult.Great, 50, 35, 20), + new DifficultyRange(HitResult.Good, 120, 80, 50), + new DifficultyRange(HitResult.Miss, 135, 95, 70), + }; + + protected override DifficultyRange[] GetRanges() => taiko_ranges; + } +} diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index 68ddf2db19..75a27ff639 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -3,7 +3,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.UI; diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index a725c58462..4859abbb8e 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -9,8 +9,8 @@ using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO.Serialization; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Scoring; using osu.Game.Tests.Resources; using osuTK; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs index e9c15dab9b..a934d22b5d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs @@ -3,18 +3,18 @@ using NUnit.Framework; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Catch.Objects; using System; using System.Collections.Generic; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Scoring; using osu.Framework.MathUtils; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Catch.Scoring; +using osu.Game.Rulesets.Mania.Scoring; +using osu.Game.Rulesets.Osu.Scoring; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Scoring; using osu.Game.Screens.Play.HUD.HitErrorMeters; namespace osu.Game.Tests.Visual.Gameplay @@ -36,8 +36,8 @@ namespace osu.Game.Tests.Visual.Gameplay AddRepeatStep("New random judgement", () => newJudgement(), 40); - AddRepeatStep("New max negative", () => newJudgement(-hitWindows.HalfWindowFor(HitResult.Meh)), 20); - AddRepeatStep("New max positive", () => newJudgement(hitWindows.HalfWindowFor(HitResult.Meh)), 20); + AddRepeatStep("New max negative", () => newJudgement(-hitWindows.WindowFor(HitResult.Meh)), 20); + AddRepeatStep("New max positive", () => newJudgement(hitWindows.WindowFor(HitResult.Meh)), 20); AddStep("New fixed judgement (50ms)", () => newJudgement(50)); } @@ -85,9 +85,9 @@ namespace osu.Game.Tests.Visual.Gameplay AutoSizeAxes = Axes.Both, Children = new[] { - new SpriteText { Text = $@"Great: {hitWindows?.Great}" }, - new SpriteText { Text = $@"Good: {hitWindows?.Good}" }, - new SpriteText { Text = $@"Meh: {hitWindows?.Meh}" }, + new SpriteText { Text = $@"Great: {hitWindows?.WindowFor(HitResult.Great)}" }, + new SpriteText { Text = $@"Good: {hitWindows?.WindowFor(HitResult.Good)}" }, + new SpriteText { Text = $@"Meh: {hitWindows?.WindowFor(HitResult.Meh)}" }, } }); diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index a6d0aad880..5dfab5ef77 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Objects.Drawables { UpdateInitialTransforms(); - var judgementOffset = Math.Min(HitObject.HitWindows?.HalfWindowFor(HitResult.Miss) ?? double.MaxValue, Result?.TimeOffset ?? 0); + var judgementOffset = Math.Min(HitObject.HitWindows?.WindowFor(HitResult.Miss) ?? double.MaxValue, Result?.TimeOffset ?? 0); using (BeginDelayedSequence(InitialLifetimeOffset + judgementOffset, true)) { diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 5e029139d9..96297ab44f 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -9,6 +9,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Objects { diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs index 06fde576d2..609bdd571a 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Objects.Legacy.Mania { diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs index 096c07f7d2..350ee3185d 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Objects.Legacy.Mania { diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs index 226d91bb86..e372fbd273 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Objects.Legacy.Mania { diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs index eb20fa67f1..067377d300 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Objects.Legacy.Mania { diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs index 84b66a4c26..c9851a0074 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Scoring; using osuTK; namespace osu.Game.Rulesets.Objects.Legacy.Osu diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs index c850feb189..1c1180702b 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Scoring; using osuTK; namespace osu.Game.Rulesets.Objects.Legacy.Osu diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs index e5a8884aa2..bc94ea1803 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Scoring; using osuTK; namespace osu.Game.Rulesets.Objects.Legacy.Osu diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs index 5cecc2a59f..709345170f 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs @@ -1,6 +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 osu.Game.Rulesets.Scoring; + namespace osu.Game.Rulesets.Objects.Legacy.Taiko { /// diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs index 5cedc6e2e5..c173b3e11a 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs @@ -1,6 +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 osu.Game.Rulesets.Scoring; + namespace osu.Game.Rulesets.Objects.Legacy.Taiko { /// diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs index ca9fdd53ed..9a35ad2776 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Objects.Legacy.Taiko { diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 2376f12e9e..7ba88d3df8 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -16,6 +16,10 @@ namespace osu.Game.Rulesets.Scoring /// /// Indicates that the object has been judged as a miss. /// + /// + /// This miss window should determine how early a hit can be before it is considered for judgement (as opposed to being ignored as + /// "too far in the future). It should also define when a forced miss should be triggered (as a result of no user input in time). + /// [Description(@"Miss")] Miss, diff --git a/osu.Game/Rulesets/Objects/HitWindows.cs b/osu.Game/Rulesets/Scoring/HitWindows.cs similarity index 56% rename from osu.Game/Rulesets/Objects/HitWindows.cs rename to osu.Game/Rulesets/Scoring/HitWindows.cs index e88af67c7c..beba62044a 100644 --- a/osu.Game/Rulesets/Objects/HitWindows.cs +++ b/osu.Game/Rulesets/Scoring/HitWindows.cs @@ -4,51 +4,61 @@ using System; using System.Collections.Generic; using osu.Game.Beatmaps; -using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Objects; -namespace osu.Game.Rulesets.Objects +namespace osu.Game.Rulesets.Scoring { + /// + /// A structure containing timing data for hit window based gameplay. + /// public class HitWindows { - private static readonly IReadOnlyDictionary base_ranges = new Dictionary + private static readonly DifficultyRange[] base_ranges = { - { HitResult.Perfect, (44.8, 38.8, 27.8) }, - { HitResult.Great, (128, 98, 68) }, - { HitResult.Good, (194, 164, 134) }, - { HitResult.Ok, (254, 224, 194) }, - { HitResult.Meh, (302, 272, 242) }, - { HitResult.Miss, (376, 346, 316) }, + new DifficultyRange(HitResult.Perfect, 22.4D, 19.4D, 13.9D), + new DifficultyRange(HitResult.Great, 64, 49, 34), + new DifficultyRange(HitResult.Good, 97, 82, 67), + new DifficultyRange(HitResult.Ok, 127, 112, 97), + new DifficultyRange(HitResult.Meh, 151, 136, 121), + new DifficultyRange(HitResult.Miss, 188, 173, 158), }; /// /// Hit window for a result. /// - public double Perfect { get; protected set; } + private double perfect; /// /// Hit window for a result. /// - public double Great { get; protected set; } + /// + /// Note that this value includes both the early and late region. + /// + private double great; /// /// Hit window for a result. /// - public double Good { get; protected set; } + private double good; /// /// Hit window for an result. /// - public double Ok { get; protected set; } + private double ok; /// /// Hit window for a result. /// - public double Meh { get; protected set; } + private double meh; /// /// Hit window for a result. /// - public double Miss { get; protected set; } + /// + /// This miss window should determine how early a hit can be before it is considered for judgement (as opposed to being ignored as + /// "too far in the future). It should also define when a forced miss should be triggered (as a result of no user input in time). + /// + private double miss; /// /// Retrieves the with the largest hit window that produces a successful hit. @@ -66,15 +76,15 @@ namespace osu.Game.Rulesets.Objects } /// - /// Retrieves a mapping of s to their half window timing for all allowed s. + /// Retrieves a mapping of s to their timing windows for all allowed s. /// /// - public IEnumerable<(HitResult result, double length)> GetAllAvailableHalfWindows() + public IEnumerable<(HitResult result, double length)> GetAllAvailableWindows() { for (var result = HitResult.Meh; result <= HitResult.Perfect; ++result) { if (IsHitResultAllowed(result)) - yield return (result, HalfWindowFor(result)); + yield return (result, WindowFor(result)); } } @@ -100,14 +110,39 @@ namespace osu.Game.Rulesets.Objects /// Sets hit windows with values that correspond to a difficulty parameter. /// /// The parameter. - public virtual void SetDifficulty(double difficulty) + public void SetDifficulty(double difficulty) { - Perfect = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Perfect]); - Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]); - Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]); - Ok = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Ok]); - Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]); - Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]); + foreach (var range in GetRanges()) + { + var value = BeatmapDifficulty.DifficultyRange(difficulty, (range.Min, range.Average, range.Max)); + + switch (range.Result) + { + case HitResult.Miss: + miss = value; + break; + + case HitResult.Meh: + meh = value; + break; + + case HitResult.Ok: + ok = value; + break; + + case HitResult.Good: + good = value; + break; + + case HitResult.Great: + great = value; + break; + + case HitResult.Perfect: + perfect = value; + break; + } + } } /// @@ -121,7 +156,7 @@ namespace osu.Game.Rulesets.Objects for (var result = HitResult.Perfect; result >= HitResult.Miss; --result) { - if (IsHitResultAllowed(result) && timeOffset <= HalfWindowFor(result)) + if (IsHitResultAllowed(result) && timeOffset <= WindowFor(result)) return result; } @@ -129,32 +164,32 @@ namespace osu.Game.Rulesets.Objects } /// - /// Retrieves half the hit window for a . - /// This is useful if the hit window for one half of the hittable range of a is required. + /// Retrieves the hit window for a . + /// This is the number of +/- milliseconds allowed for the requested result (so the actual hittable range is double this). /// /// The expected . /// One half of the hit window for . - public double HalfWindowFor(HitResult result) + public double WindowFor(HitResult result) { switch (result) { case HitResult.Perfect: - return Perfect / 2; + return perfect; case HitResult.Great: - return Great / 2; + return great; case HitResult.Good: - return Good / 2; + return good; case HitResult.Ok: - return Ok / 2; + return ok; case HitResult.Meh: - return Meh / 2; + return meh; case HitResult.Miss: - return Miss / 2; + return miss; default: throw new ArgumentException(nameof(result)); @@ -167,6 +202,30 @@ namespace osu.Game.Rulesets.Objects /// /// The time offset. /// Whether the can be hit at any point in the future from this time offset. - public bool CanBeHit(double timeOffset) => timeOffset <= HalfWindowFor(LowestSuccessfulHitResult()); + public bool CanBeHit(double timeOffset) => timeOffset <= WindowFor(LowestSuccessfulHitResult()); + + /// + /// Retrieve a valid list of s representing hit windows. + /// Defaults are provided but can be overridden to customise for a ruleset. + /// + protected virtual DifficultyRange[] GetRanges() => base_ranges; + } + + public struct DifficultyRange + { + public readonly HitResult Result; + + public double Min; + public double Average; + public double Max; + + public DifficultyRange(HitResult result, double min, double average, double max) + { + Result = result; + + Min = min; + Average = average; + Max = max; + } } } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 266725a739..d3c37bd4f4 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -11,8 +11,8 @@ using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Users; using osu.Game.Rulesets.Scoring; +using osu.Game.Users; namespace osu.Game.Scoring { diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index adda94d629..920d11c910 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Configuration; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD.HitErrorMeters; diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 594dd64e52..03a0f23fb6 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osuTK; using osuTK.Graphics; @@ -146,7 +145,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters private void createColourBars(OsuColour colours) { - var windows = HitWindows.GetAllAvailableHalfWindows().ToArray(); + var windows = HitWindows.GetAllAvailableWindows().ToArray(); maxHitWindow = windows.First().length; diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs index da1d9fff0d..dee25048ed 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs @@ -3,7 +3,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Screens.Play.HUD.HitErrorMeters { From 6cdc87bd29cfade223c0fe44fe7c0808a8634c80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Sep 2019 15:37:20 +0900 Subject: [PATCH 1127/2815] Fix allowed results omissions --- osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs | 14 ++++++++++++++ osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs | 13 +++++++++++++ 2 files changed, 27 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs index 8f258c9e3d..a6491bb3f3 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs @@ -15,6 +15,20 @@ namespace osu.Game.Rulesets.Osu.Scoring new DifficultyRange(HitResult.Miss, 200, 200, 200), }; + public override bool IsHitResultAllowed(HitResult result) + { + switch (result) + { + case HitResult.Great: + case HitResult.Good: + case HitResult.Meh: + case HitResult.Miss: + return true; + } + + return false; + } + protected override DifficultyRange[] GetRanges() => osu_ranges; } } diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs index 77d59f9638..9d273392ff 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs @@ -14,6 +14,19 @@ namespace osu.Game.Rulesets.Taiko.Scoring new DifficultyRange(HitResult.Miss, 135, 95, 70), }; + public override bool IsHitResultAllowed(HitResult result) + { + switch (result) + { + case HitResult.Great: + case HitResult.Good: + case HitResult.Miss: + return true; + } + + return false; + } + protected override DifficultyRange[] GetRanges() => taiko_ranges; } } From 4031f51745ae15add836cbeb2d5c9b6116ac37d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Sep 2019 15:37:30 +0900 Subject: [PATCH 1128/2815] More permissive IsHitResultAllow by default --- osu.Game/Rulesets/Scoring/HitWindows.cs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitWindows.cs b/osu.Game/Rulesets/Scoring/HitWindows.cs index beba62044a..6d8107cf9c 100644 --- a/osu.Game/Rulesets/Scoring/HitWindows.cs +++ b/osu.Game/Rulesets/Scoring/HitWindows.cs @@ -93,18 +93,7 @@ namespace osu.Game.Rulesets.Scoring /// /// The result type to check. /// Whether the can be achieved. - public virtual bool IsHitResultAllowed(HitResult result) - { - switch (result) - { - case HitResult.Perfect: - case HitResult.Ok: - return false; - - default: - return true; - } - } + public virtual bool IsHitResultAllowed(HitResult result) => true; /// /// Sets hit windows with values that correspond to a difficulty parameter. From 985375d1c64daec3290130d80957ea980ee8b836 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Sep 2019 15:47:01 +0900 Subject: [PATCH 1129/2815] Remove private field xmldoc --- osu.Game/Rulesets/Scoring/HitWindows.cs | 30 ------------------------- 1 file changed, 30 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitWindows.cs b/osu.Game/Rulesets/Scoring/HitWindows.cs index 6d8107cf9c..efc4cd9f5c 100644 --- a/osu.Game/Rulesets/Scoring/HitWindows.cs +++ b/osu.Game/Rulesets/Scoring/HitWindows.cs @@ -23,41 +23,11 @@ namespace osu.Game.Rulesets.Scoring new DifficultyRange(HitResult.Miss, 188, 173, 158), }; - /// - /// Hit window for a result. - /// private double perfect; - - /// - /// Hit window for a result. - /// - /// - /// Note that this value includes both the early and late region. - /// private double great; - - /// - /// Hit window for a result. - /// private double good; - - /// - /// Hit window for an result. - /// private double ok; - - /// - /// Hit window for a result. - /// private double meh; - - /// - /// Hit window for a result. - /// - /// - /// This miss window should determine how early a hit can be before it is considered for judgement (as opposed to being ignored as - /// "too far in the future). It should also define when a forced miss should be triggered (as a result of no user input in time). - /// private double miss; /// From b89fb5cdf7ca5e5b1ac8932ed6b3e79fb7a8bedd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Sep 2019 16:51:30 +0900 Subject: [PATCH 1130/2815] Fix failing test --- .../Visual/Gameplay/TestSceneFailJudgement.cs | 10 +--------- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 9 +++++++-- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs index bb0901524f..d57ec44f39 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs @@ -26,15 +26,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddUntilStep("wait for fail", () => Player.HasFailed); AddUntilStep("wait for multiple judged objects", () => ((FailPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.Count(h => h.AllJudged) > 1); - AddAssert("total judgements == 1", () => - { - int count = 0; - - foreach (var stat in (HitResult[])Enum.GetValues(typeof(HitResult))) - count += ((FailPlayer)Player).ScoreProcessor.GetStatistic(stat); - - return count == 1; - }); + AddAssert("total judgements == 1", () => ((FailPlayer)Player).ScoreProcessor.JudgedHits == 1); } private class FailPlayer : ReplayPlayer diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 9f2c79df33..e4f20c27b4 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -92,6 +92,11 @@ namespace osu.Game.Rulesets.Scoring /// public virtual bool HasCompleted => false; + /// + /// The total number of judged s at the current point in time. + /// + public int JudgedHits { get; protected set; } + /// /// Whether this ScoreProcessor has already triggered the failed state. /// @@ -142,6 +147,8 @@ namespace osu.Game.Rulesets.Scoring Rank.Value = ScoreRank.X; HighestCombo.Value = 0; + JudgedHits = 0; + HasFailed = false; } @@ -208,7 +215,6 @@ namespace osu.Game.Rulesets.Scoring public sealed override bool HasCompleted => JudgedHits == MaxHits; protected int MaxHits { get; private set; } - protected int JudgedHits { get; private set; } private double maxHighestCombo; @@ -441,7 +447,6 @@ namespace osu.Game.Rulesets.Scoring base.Reset(storeResults); - JudgedHits = 0; baseScore = 0; rollingMaxBaseScore = 0; bonusScore = 0; From 88d0756107f6e2c0a1955f64a68de6553d680631 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Sep 2019 17:17:30 +0900 Subject: [PATCH 1131/2815] Allow global actions to propagate through pause screen --- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index c5202fa792..f54d638584 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -313,5 +313,22 @@ namespace osu.Game.Screens.Play return base.OnMouseMove(e); } } + + [Resolved] + private GlobalActionContainer globalAction { get; set; } + + protected override bool Handle(UIEvent e) + { + switch (e) + { + case ScrollEvent _: + if (ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) + return globalAction.TriggerEvent(e); + + break; + } + + return base.Handle(e); + } } } From 95baae5088de6333deebece25b66c42869793a7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Sep 2019 18:27:54 +0900 Subject: [PATCH 1132/2815] Fix dragging from inside to outside an overlay incorrectly hiding --- .../Containers/OsuFocusedOverlayContainer.cs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 9c948d6f90..a4121acfca 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -62,21 +62,31 @@ namespace osu.Game.Graphics.Containers protected override bool OnClick(ClickEvent e) { - closeIfOutside(e); + if (!base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) + Hide(); return base.OnClick(e); } - protected override bool OnDragEnd(DragEndEvent e) - { - closeIfOutside(e); - return base.OnDragEnd(e); - } + private bool closeOnDragEnd; - private void closeIfOutside(MouseEvent e) + protected override bool OnDragStart(DragStartEvent e) { if (!base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) + closeOnDragEnd = true; + + return base.OnDragStart(e); + } + + protected override bool OnDragEnd(DragEndEvent e) + { + if (closeOnDragEnd) + { Hide(); + closeOnDragEnd = false; + } + + return base.OnDragEnd(e); } public virtual bool OnPressed(GlobalAction action) From 374479f837b3e56e527b6682d9d7234f78ea4adf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Sep 2019 19:00:49 +0900 Subject: [PATCH 1133/2815] Add truncatino of long usernames in chat --- .../Online/TestSceneChatLineTruncation.cs | 108 ++++++++++++++++++ osu.Game/Overlays/Chat/ChatLine.cs | 10 +- 2 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneChatLineTruncation.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLineTruncation.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLineTruncation.cs new file mode 100644 index 0000000000..888e55ab0a --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLineTruncation.cs @@ -0,0 +1,108 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Containers; +using osu.Game.Online.Chat; +using osu.Game.Overlays.Chat; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Online +{ + [TestFixture] + public class TestSceneChatLineTruncation : OsuTestScene + { + private readonly TestChatLineContainer textContainer; + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ChatLine), + typeof(Message), + typeof(LinkFlowContainer), + typeof(MessageFormatter) + }; + + public TestSceneChatLineTruncation() + { + Add(textContainer = new TestChatLineContainer + { + Padding = new MarginPadding { Left = 20, Right = 20 }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + }); + } + + [BackgroundDependencyLoader] + private void load() + { + testFormatting(); + } + + private void clear() => AddStep("clear messages", textContainer.Clear); + + private void addMessageWithChecks(string text, bool isAction = false, bool isImportant = false, string username = null) + { + int index = textContainer.Count + 1; + var newLine = new ChatLine(new DummyMessage(text, isAction, isImportant, index, username)); + textContainer.Add(newLine); + } + + private void testFormatting() + { + for (int a = 0; a < 25; a++) + addMessageWithChecks($"Wide {a} character username.", username: new string('w', a)); + addMessageWithChecks("Short name with spaces.", username: "sho rt name"); + addMessageWithChecks("Long name with spaces.", username: "long name with s p a c e s"); + } + + private class DummyMessage : Message + { + private static long messageCounter; + + internal static readonly User TEST_SENDER_BACKGROUND = new User + { + Username = @"i-am-important", + Id = 42, + Colour = "#250cc9", + }; + + internal static readonly User TEST_SENDER = new User + { + Username = @"Somebody", + Id = 1, + }; + + public new DateTimeOffset Timestamp = DateTimeOffset.Now; + + public DummyMessage(string text, bool isAction = false, bool isImportant = false, int number = 0, string username = null) + : base(messageCounter++) + { + Content = text; + IsAction = isAction; + Sender = new User + { + Username = username ?? $"user {number}", + Id = number, + Colour = isImportant ? "#250cc9" : null, + }; + } + } + + private class TestChatLineContainer : FillFlowContainer + { + protected override int Compare(Drawable x, Drawable y) + { + var xC = (ChatLine)x; + var yC = (ChatLine)y; + + return xC.Message.CompareTo(yC.Message); + } + } + } +} diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 2576b38ec8..a07b6472a3 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -31,6 +31,8 @@ namespace osu.Game.Overlays.Chat protected virtual float MessagePadding => default_message_padding; + private const float timestamp_padding = 70; + private const float default_horizontal_padding = 15; protected virtual float HorizontalPadding => default_horizontal_padding; @@ -87,7 +89,10 @@ namespace osu.Game.Overlays.Chat { Shadow = false, Colour = hasBackground ? customUsernameColour : username_colours[message.Sender.Id % username_colours.Length], - Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Bold, italics: true) + Truncate = true, + EllipsisString = ".. :", + Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Bold, italics: true), + RelativeSizeAxes = Axes.Both, }; if (hasBackground) @@ -141,7 +146,8 @@ namespace osu.Game.Overlays.Chat }, new MessageSender(message.Sender) { - AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = timestamp_padding }, + RelativeSizeAxes = Axes.Both, Origin = Anchor.TopRight, Anchor = Anchor.TopRight, Child = effectedUsername, From c6b8f2db77313f29bdfa106a042a9ba211b009fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Sep 2019 19:05:50 +0900 Subject: [PATCH 1134/2815] Update historic licence header --- osu.Game.Tests/Visual/Online/TestSceneChatLineTruncation.cs | 4 ++-- osu.Game/Overlays/Chat/ChatLine.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLineTruncation.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLineTruncation.cs index 888e55ab0a..4773e84a5e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatLineTruncation.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLineTruncation.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/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; using System.Collections.Generic; diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index a07b6472a3..d812e007a0 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Chat protected virtual float MessagePadding => default_message_padding; - private const float timestamp_padding = 70; + private const float timestamp_padding = 70; private const float default_horizontal_padding = 15; From 7f2d14416a16d7b849e7c9be682bd4f84493c024 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 7 Sep 2019 14:44:44 +0900 Subject: [PATCH 1135/2815] Reset DrawableHitObject lifetimes on state change --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index db87d4b4f2..e3390c8cf0 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -153,6 +153,9 @@ namespace osu.Game.Rulesets.Objects.Drawables if (UseTransformStateManagement) { + lifetimeStart = null; + LifetimeEnd = double.MaxValue; + double transformTime = HitObject.StartTime - InitialLifetimeOffset; base.ApplyTransformsAt(transformTime, true); From 55b2bc1ed5b4466ef0a21cf3faadacb76b6dc951 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sat, 7 Sep 2019 18:03:04 +0300 Subject: [PATCH 1136/2815] Set Health default value to 1 --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index e4f20c27b4..f350eef146 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Scoring /// /// The current health. /// - public readonly BindableDouble Health = new BindableDouble { MinValue = 0, MaxValue = 1 }; + public readonly BindableDouble Health = new BindableDouble(1) { MinValue = 0, MaxValue = 1 }; /// /// The current combo. From c397ab62821a91ccca252b2e627f90f9c89bdc1e Mon Sep 17 00:00:00 2001 From: miterosan Date: Sat, 7 Sep 2019 17:04:13 +0200 Subject: [PATCH 1137/2815] Fix Android Builds. --- osu.Android.props | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index adc340a734..03c6889a69 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -14,7 +14,7 @@ v9.0 false - + True portable False @@ -30,12 +30,12 @@ armeabi-v7a;x86;arm64-v8a true - + false None True prompt - true + true false SdkOnly False From fdd36874371909a1dd4391dbf828bb015cf441cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 8 Sep 2019 00:09:24 +0900 Subject: [PATCH 1138/2815] Fix catcher additive sprites staying on screen during rewind --- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index ceda643335..592a45c865 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.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; @@ -197,6 +197,7 @@ namespace osu.Game.Rulesets.Catch.UI additive.Anchor = Anchor; additive.OriginPosition = additive.OriginPosition + new Vector2(DrawWidth / 2, 0); // also temporary to align sprite correctly. + additive.LifetimeStart = Clock.CurrentTime; additive.Position = Position; additive.Scale = Scale; additive.Colour = HyperDashing ? Color4.Red : Color4.White; From ec7a50b75f946453c76d351e181162136a7d88b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 8 Sep 2019 00:10:31 +0900 Subject: [PATCH 1139/2815] Fix already caught osu!catch objects not correctly disappearing --- .../Objects/Drawable/DrawableCatchHitObject.cs | 4 ++++ osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 13 ++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs index 00734810b3..ce90319846 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs @@ -50,6 +50,10 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable public Func CheckPosition; + public bool IsOnPlate; + + public override bool RemoveWhenNotAlive => IsOnPlate; + protected override void CheckForResult(bool userTriggered, double timeOffset) { if (CheckPosition == null) return; diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 592a45c865..330f6e6b24 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -69,6 +69,7 @@ namespace osu.Game.Rulesets.Catch.UI caughtFruit.RelativePositionAxes = Axes.None; caughtFruit.Position = new Vector2(MovableCatcher.ToLocalSpace(fruit.ScreenSpaceDrawQuad.Centre).X - MovableCatcher.DrawSize.X / 2, 0); + caughtFruit.IsOnPlate = true; caughtFruit.Anchor = Anchor.TopCentre; caughtFruit.Origin = Anchor.Centre; @@ -384,6 +385,12 @@ namespace osu.Game.Rulesets.Catch.UI X = hyperDashTargetPosition; SetHyperDashState(); } + + if (Clock.ElapsedFrameTime < 0) + { + AdditiveTarget.RemoveAll(d => Clock.CurrentTime < d.LifetimeStart); + caughtFruit.RemoveAll(d => d.HitObject.StartTime > Clock.CurrentTime); + } } /// @@ -407,7 +414,7 @@ namespace osu.Game.Rulesets.Catch.UI f.MoveToY(f.Y + 75, 750, Easing.InSine); f.FadeOut(750); - f.Expire(); + f.Expire(true); } } @@ -437,11 +444,11 @@ namespace osu.Game.Rulesets.Catch.UI ExplodingFruitTarget.Add(fruit); } + fruit.ClearTransforms(); fruit.MoveToY(fruit.Y - 50, 250, Easing.OutSine).Then().MoveToY(fruit.Y + 50, 500, Easing.InSine); fruit.MoveToX(fruit.X + originalX * 6, 1000); fruit.FadeOut(750); - - fruit.Expire(); + fruit.Expire(true); } } } From be803fa9217d3cc5ecc808d32a0c603e7278ad5b Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sat, 7 Sep 2019 18:15:49 +0300 Subject: [PATCH 1140/2815] Reset score processor before starting the simulation --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index f350eef146..4ca9ddd183 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -269,6 +269,8 @@ namespace osu.Game.Rulesets.Scoring /// The to simulate. protected virtual void SimulateAutoplay(Beatmap beatmap) { + Reset(false); + foreach (var obj in beatmap.HitObjects) simulate(obj); From 6581e51d6af714cae1763a1f28d79013f9756a25 Mon Sep 17 00:00:00 2001 From: miterosan Date: Sat, 7 Sep 2019 18:03:33 +0200 Subject: [PATCH 1141/2815] Add a default for the configuration into the android props. This allows building even if no configuration is specified. --- osu.Android.props | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Android.props b/osu.Android.props index 03c6889a69..4962064853 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -1,5 +1,6 @@ + Debug bin\$(Configuration) 4 2.0 From 3435e2a8d3f2e7ab5ef9c3cf0090c72dbe0d0f21 Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Sun, 8 Sep 2019 13:36:58 +0800 Subject: [PATCH 1142/2815] open login on enter main menu --- osu.Game/Screens/Menu/MainMenu.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 499b5089f6..160ff95632 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -11,6 +11,7 @@ using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Charts; @@ -44,6 +45,12 @@ namespace osu.Game.Screens.Menu [Resolved(canBeNull: true)] private MusicController music { get; set; } + [Resolved(canBeNull: true)] + private LoginOverlay login { get; set; } + + [Resolved] + private IAPIProvider api { get; set; } + private BackgroundScreenDefault background; protected override BackgroundScreen CreateBackground() => background; @@ -128,6 +135,9 @@ namespace osu.Game.Screens.Menu track.Seek(metadata.PreviewTime != -1 ? metadata.PreviewTime : 0.4f * track.Length); track.Start(); } + + if (api?.State == APIState.Offline) + login?.ToggleVisibility(); } Beatmap.ValueChanged += beatmap_ValueChanged; From a67a2899a9d763b94f8b3e689fbeb6a5bc323cde Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Sun, 8 Sep 2019 16:18:15 +0800 Subject: [PATCH 1143/2815] move api state check to it's own clause --- osu.Game/Screens/Menu/MainMenu.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 160ff95632..10b2f827af 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -135,11 +135,11 @@ namespace osu.Game.Screens.Menu track.Seek(metadata.PreviewTime != -1 ? metadata.PreviewTime : 0.4f * track.Length); track.Start(); } - - if (api?.State == APIState.Offline) - login?.ToggleVisibility(); } + if (last is IntroScreen && api?.State == APIState.Offline) + login?.ToggleVisibility(); + Beatmap.ValueChanged += beatmap_ValueChanged; } From 3d8b27abfaa698c7ff8e4ba2b07a3dbb48f7a798 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 8 Sep 2019 16:13:36 +0300 Subject: [PATCH 1144/2815] RotationAbsolute -> BidirectionalRotation --- osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 5 +++-- .../Objects/Drawables/Pieces/SpinnerDisc.cs | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs index 3ed3f3e981..5d7acb77bb 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Tests if (auto && !userTriggered && Time.Current > Spinner.StartTime + Spinner.Duration / 2 && Progress < 1) { // force completion only once to not break human interaction - Disc.RotationAbsolute = Spinner.SpinsRequired * 360; + Disc.BidirectionalRotation = Spinner.SpinsRequired * 360; auto = false; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 49aaa2aaea..c7bcf2526a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.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.Graphics; using osu.Framework.Graphics.Containers; @@ -136,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables positionBindable.BindTo(HitObject.PositionBindable); } - public float Progress => MathHelper.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1); + public float Progress => MathHelper.Clamp(Disc.BidirectionalRotation / 360 / Spinner.SpinsRequired, 0, 1); protected override void CheckForResult(bool userTriggered, double timeOffset) { @@ -188,7 +189,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables circle.Rotation = Disc.Rotation; Ticks.Rotation = Disc.Rotation; - spmCounter.SetRotation(Disc.RotationAbsolute); + spmCounter.SetRotation(Disc.BidirectionalRotation); float relativeCircleScale = Spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight; Disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index 448a2eada7..b9d5674b34 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -82,11 +82,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private float lastAngle; private float currentRotation; - public float RotationAbsolute; + public float BidirectionalRotation; private int completeTick; - private bool updateCompleteTick() => completeTick != (completeTick = (int)(RotationAbsolute / 360)); + private bool updateCompleteTick() => completeTick != (completeTick = (int)(BidirectionalRotation / 360)); private bool rotationTransferred; protected override void Update() @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces lastAngle -= 360; currentRotation += thisAngle - lastAngle; - RotationAbsolute += Math.Abs(thisAngle - lastAngle); + BidirectionalRotation += Math.Abs(thisAngle - lastAngle) * Math.Sign(Time.Current - lastTime); } lastAngle = thisAngle; From f5f2713a17c62037786bb0010b2fd3cd480cd97c Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 8 Sep 2019 16:14:14 +0300 Subject: [PATCH 1145/2815] Account angle change negatively on rewind --- osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index b9d5674b34..5b2b3bcd42 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -85,8 +85,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces public float BidirectionalRotation; private int completeTick; + private double lastTime; private bool updateCompleteTick() => completeTick != (completeTick = (int)(BidirectionalRotation / 360)); + private bool rotationTransferred; protected override void Update() @@ -112,6 +114,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces currentRotation += thisAngle - lastAngle; BidirectionalRotation += Math.Abs(thisAngle - lastAngle) * Math.Sign(Time.Current - lastTime); + lastTime = Time.Current; } lastAngle = thisAngle; From d790656b7ed9feee90fd419c56e85797aa3f9e3a Mon Sep 17 00:00:00 2001 From: miterosan Date: Sun, 8 Sep 2019 15:33:16 +0200 Subject: [PATCH 1146/2815] Revert shortening the condition --- osu.Android.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 4962064853..a84b877a98 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -15,7 +15,7 @@ v9.0 false - + True portable False @@ -31,7 +31,7 @@ armeabi-v7a;x86;arm64-v8a true - + false None True From 8862cdab9c719a093d47c49dd9be546fc655f8eb Mon Sep 17 00:00:00 2001 From: miterosan Date: Sun, 8 Sep 2019 15:33:51 +0200 Subject: [PATCH 1147/2815] Also set the default for Platform --- osu.Android.props | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Android.props b/osu.Android.props index a84b877a98..896b10133d 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -1,6 +1,7 @@ Debug + AnyCPU bin\$(Configuration) 4 2.0 From 9951011e6c5d469f149ee9b0d08405f0afb6a250 Mon Sep 17 00:00:00 2001 From: miterosan Date: Sun, 8 Sep 2019 16:27:25 +0200 Subject: [PATCH 1148/2815] Remove not needed androidnativelibrary itemgroup --- osu.Android.props | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Android.props b/osu.Android.props index adc340a734..51bfdca064 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -49,7 +49,6 @@ osu.licenseheader - From 9defcb0e9945785dc4edf44a444a6015f78517f6 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 8 Sep 2019 21:37:50 +0300 Subject: [PATCH 1149/2815] Remove redundant using directive --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index c7bcf2526a..a8dc275fb5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.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.Graphics; using osu.Framework.Graphics.Containers; From be4f0cc2dd19c8adfced41dd82731c22a951a523 Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Mon, 9 Sep 2019 06:14:49 +0800 Subject: [PATCH 1150/2815] remove null conditional --- 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 10b2f827af..276c653345 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -137,7 +137,7 @@ namespace osu.Game.Screens.Menu } } - if (last is IntroScreen && api?.State == APIState.Offline) + if (last is IntroScreen && api.State == APIState.Offline) login?.ToggleVisibility(); Beatmap.ValueChanged += beatmap_ValueChanged; From 55cc3de57e3346419b3eaf05403a0d2e7ea01e3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Sep 2019 11:59:58 +0900 Subject: [PATCH 1151/2815] Always specify a configuration --- fastlane/Fastfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index f6eb95ca3d..dd35fa0b46 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -6,9 +6,7 @@ desc 'Deploy to play store' # update csproj version update_version(options) - build( - build_configuration: 'Release', - ) + build() client = HTTPClient.new changelog = client.get_content 'https://gist.githubusercontent.com/peppy/ab89c29dcc0dce95f39eb218e8fad197/raw' @@ -22,6 +20,7 @@ desc 'Deploy to play store' ) souyuz( + build_configuration: 'Release', solution_path: 'osu.Android.sln', platform: "android", ) From eeebd517f3e5cafc0245e60668407d1bb131f5e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Sep 2019 12:08:59 +0900 Subject: [PATCH 1152/2815] Use MaxWidth specification --- osu.Game/Overlays/Chat/ChatLine.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index d812e007a0..4c37d626c0 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Chat protected virtual float MessagePadding => default_message_padding; - private const float timestamp_padding = 70; + private const float timestamp_padding = 65; private const float default_horizontal_padding = 15; @@ -92,7 +92,9 @@ namespace osu.Game.Overlays.Chat Truncate = true, EllipsisString = ".. :", Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Bold, italics: true), - RelativeSizeAxes = Axes.Both, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + MaxWidth = default_message_padding - timestamp_padding }; if (hasBackground) From 04a4f9c9a347f45549233517479a35bf4f864f85 Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Mon, 9 Sep 2019 11:26:51 +0800 Subject: [PATCH 1153/2815] use IsLoggedIn and remove useless clause --- 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 276c653345..79a3993874 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -137,7 +137,7 @@ namespace osu.Game.Screens.Menu } } - if (last is IntroScreen && api.State == APIState.Offline) + if (!api.IsLoggedIn) login?.ToggleVisibility(); Beatmap.ValueChanged += beatmap_ValueChanged; From 7adfae37843adc5a225244bb76a7b433a8c9db7a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Sep 2019 12:35:15 +0900 Subject: [PATCH 1154/2815] Reorder CursorTrail members --- .../UI/Cursor/CursorTrail.cs | 51 ++++++++----------- 1 file changed, 21 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 05eb0ffdbf..a50c3a2fea 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -22,28 +22,14 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { internal class CursorTrail : Drawable, IRequireHighFrequencyMousePosition { - private int currentIndex; - - private IShader shader; - private Texture texture; - - private Vector2 size => texture.Size * Scale; - - private double timeOffset; - - private float time; - - public override bool IsPresent => true; - private const int max_sprites = 2048; private readonly TrailPart[] parts = new TrailPart[max_sprites]; - - private Vector2? lastPosition; - - private readonly InputResampler resampler = new InputResampler(); - - protected override DrawNode CreateDrawNode() => new TrailDrawNode(this); + private int currentIndex; + private IShader shader; + private Texture texture; + private double timeOffset; + private float time; public CursorTrail() { @@ -60,8 +46,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor } } - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; - [BackgroundDependencyLoader] private void load(ShaderManager shaders, TextureStore textures) { @@ -76,6 +60,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor resetTime(); } + public override bool IsPresent => true; + protected override void Update() { base.Update(); @@ -101,6 +87,13 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor timeOffset = Time.Current; } + private Vector2 size => texture.Size * Scale; + + private Vector2? lastPosition; + private readonly InputResampler resampler = new InputResampler(); + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + protected override bool OnMouseMove(MouseMoveEvent e) { Vector2 pos = e.ScreenSpaceMousePosition; @@ -127,21 +120,19 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor for (float d = interval; d < distance; d += interval) { lastPosition = pos1 + direction * d; - addPosition(lastPosition.Value); + + parts[currentIndex].Position = lastPosition.Value; + parts[currentIndex].Time = time; + ++parts[currentIndex].InvalidationID; + + currentIndex = (currentIndex + 1) % max_sprites; } } return base.OnMouseMove(e); } - private void addPosition(Vector2 pos) - { - parts[currentIndex].Position = pos; - parts[currentIndex].Time = time; - ++parts[currentIndex].InvalidationID; - - currentIndex = (currentIndex + 1) % max_sprites; - } + protected override DrawNode CreateDrawNode() => new TrailDrawNode(this); private struct TrailPart { From af09ed1b7fe648930f4bc084e2dd00b7307cf1e1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Sep 2019 12:48:34 +0900 Subject: [PATCH 1155/2815] Make cursor test scene more automated --- .../TestSceneGameplayCursor.cs | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs index ebb6cd3a5a..f4bc172d9c 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs @@ -6,7 +6,9 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Testing.Input; using osu.Game.Rulesets.Osu.UI.Cursor; +using osuTK; namespace osu.Game.Rulesets.Osu.Tests { @@ -18,11 +20,35 @@ namespace osu.Game.Rulesets.Osu.Tests [BackgroundDependencyLoader] private void load() { - SetContents(() => new OsuCursorContainer + SetContents(() => new MovingCursorInputManager { - RelativeSizeAxes = Axes.Both, - Masking = true, + Child = new OsuCursorContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + } }); } + + private class MovingCursorInputManager : ManualInputManager + { + public MovingCursorInputManager() + { + UseParentInput = false; + } + + protected override void Update() + { + base.Update(); + + const double spin_duration = 5000; + double currentTime = Time.Current; + + double angle = (currentTime % spin_duration) / spin_duration * 2 * Math.PI; + Vector2 rPos = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle)); + + MoveMouseTo(ToScreenSpace(DrawSize / 2 + DrawSize / 3 * rPos)); + } + } } } From 74440dcfdcc35fa847ecafe1d577f618a7d2b34b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Sep 2019 13:01:40 +0900 Subject: [PATCH 1156/2815] Make the cursors click every so often --- .../TestSceneGameplayCursor.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs index f4bc172d9c..f50b935477 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Tests { SetContents(() => new MovingCursorInputManager { - Child = new OsuCursorContainer + Child = new ClickingCursorContainer { RelativeSizeAxes = Axes.Both, Masking = true, @@ -30,6 +30,21 @@ namespace osu.Game.Rulesets.Osu.Tests }); } + private class ClickingCursorContainer : OsuCursorContainer + { + protected override void Update() + { + base.Update(); + + double currentTime = Time.Current; + + if (((int)(currentTime / 1000)) % 2 == 0) + OnPressed(OsuAction.LeftButton); + else + OnReleased(OsuAction.LeftButton); + } + } + private class MovingCursorInputManager : ManualInputManager { public MovingCursorInputManager() From 07fce8397bdd3bb4cae902a65196ad5cd01301fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Sep 2019 14:24:17 +0900 Subject: [PATCH 1157/2815] Move reset call to ctor --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 4ca9ddd183..18c2a2ca01 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -233,6 +233,8 @@ namespace osu.Game.Rulesets.Scoring drawableRuleset.OnRevertResult += revertResult; ApplyBeatmap(drawableRuleset.Beatmap); + + Reset(false); SimulateAutoplay(drawableRuleset.Beatmap); Reset(true); @@ -269,8 +271,6 @@ namespace osu.Game.Rulesets.Scoring /// The to simulate. protected virtual void SimulateAutoplay(Beatmap beatmap) { - Reset(false); - foreach (var obj in beatmap.HitObjects) simulate(obj); From c2353cbdfa7193941784799fd8d9bd473d542c27 Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Mon, 9 Sep 2019 13:30:48 +0800 Subject: [PATCH 1158/2815] move logic to logo action --- osu.Game/Screens/Menu/MainMenu.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 79a3993874..e85d59fc72 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -39,6 +39,8 @@ namespace osu.Game.Screens.Menu private ButtonSystem buttons; + private bool loginPrompted = false; + [Resolved] private GameHost host { get; set; } @@ -137,9 +139,6 @@ namespace osu.Game.Screens.Menu } } - if (!api.IsLoggedIn) - login?.ToggleVisibility(); - Beatmap.ValueChanged += beatmap_ValueChanged; } @@ -152,6 +151,16 @@ namespace osu.Game.Screens.Menu logo.FadeColour(Color4.White, 100, Easing.OutQuint); logo.FadeIn(100, Easing.OutQuint); + logo.Action += () => + { + if (!api.IsLoggedIn && !loginPrompted) + login?.ToggleVisibility(); + + loginPrompted = true; + + return true; + }; + if (resuming) { buttons.State = ButtonSystemState.TopLevel; From ff49c4ae98622e5a05d8d8e3b122a674d1b28ccb Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Mon, 9 Sep 2019 13:50:14 +0800 Subject: [PATCH 1159/2815] remove redundancies --- 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 e85d59fc72..4c3566b3e9 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Menu private ButtonSystem buttons; - private bool loginPrompted = false; + private bool loginPrompted; [Resolved] private GameHost host { get; set; } From 5b692915be98de0c3d17399f96b2dddcb4b472bf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Sep 2019 17:03:14 +0900 Subject: [PATCH 1160/2815] Add required type --- osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs index f50b935477..aa170eae1e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs @@ -15,7 +15,11 @@ namespace osu.Game.Rulesets.Osu.Tests [TestFixture] public class TestSceneGameplayCursor : SkinnableTestScene { - public override IReadOnlyList RequiredTypes => new[] { typeof(CursorTrail) }; + public override IReadOnlyList RequiredTypes => new[] + { + typeof(OsuCursorContainer), + typeof(CursorTrail) + }; [BackgroundDependencyLoader] private void load() From 81bb8d9bc4d2f606bc2f4b4211a33b0a0345c906 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Sep 2019 17:05:03 +0900 Subject: [PATCH 1161/2815] Make SkinnabbleTestScene use stored classic skin --- .../default-skin/approachcircle@2x.png | Bin 18164 -> 0 bytes .../Resources/default-skin/cursor@2x.png | Bin 28063 -> 0 bytes .../Resources/default-skin/cursormiddle@2x.png | Bin 7676 -> 0 bytes .../Resources/default-skin/hit0@2x.png | Bin 16112 -> 0 bytes .../Resources/default-skin/hit100@2x.png | Bin 31228 -> 0 bytes .../Resources/default-skin/hit100k@2x.png | Bin 21318 -> 0 bytes .../Resources/default-skin/hit300@2x.png | Bin 36873 -> 0 bytes .../Resources/default-skin/hit300g@2x.png | Bin 39840 -> 0 bytes .../Resources/default-skin/hit300k@2x.png | Bin 29098 -> 0 bytes .../Resources/default-skin/hit50@2x.png | Bin 26015 -> 0 bytes .../Resources/default-skin/hitcircle@2x.png | Bin 7768 -> 0 bytes .../default-skin/hitcircleoverlay@2x.png | Bin 45901 -> 0 bytes .../Resources/default-skin/sliderb-nd@2x.png | Bin 14258 -> 0 bytes .../Resources/default-skin/sliderb-spec@2x.png | Bin 13141 -> 0 bytes .../Resources/default-skin/sliderb0@2x.png | Bin 17053 -> 0 bytes .../Resources/default-skin/sliderb1@2x.png | Bin 17792 -> 0 bytes .../Resources/default-skin/sliderb2@2x.png | Bin 18268 -> 0 bytes .../Resources/default-skin/sliderb3@2x.png | Bin 18182 -> 0 bytes .../Resources/default-skin/sliderb4@2x.png | Bin 18062 -> 0 bytes .../Resources/default-skin/sliderb5@2x.png | Bin 16895 -> 0 bytes .../Resources/default-skin/sliderb6@2x.png | Bin 16702 -> 0 bytes .../Resources/default-skin/sliderb7@2x.png | Bin 17139 -> 0 bytes .../Resources/default-skin/sliderb8@2x.png | Bin 17084 -> 0 bytes .../Resources/default-skin/sliderb9@2x.png | Bin 17067 -> 0 bytes .../SkinnableTestScene.cs | 4 ++-- 25 files changed, 2 insertions(+), 2 deletions(-) delete mode 100755 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/approachcircle@2x.png delete mode 100755 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursor@2x.png delete mode 100755 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursormiddle@2x.png delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit0@2x.png delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit100@2x.png delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit100k@2x.png delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300@2x.png delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300g@2x.png delete mode 100755 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300k@2x.png delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit50@2x.png delete mode 100755 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hitcircle@2x.png delete mode 100755 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hitcircleoverlay@2x.png delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb-nd@2x.png delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb-spec@2x.png delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb0@2x.png delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb1@2x.png delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb2@2x.png delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb3@2x.png delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb4@2x.png delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb5@2x.png delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb6@2x.png delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb7@2x.png delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb8@2x.png delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb9@2x.png diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/approachcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/approachcircle@2x.png deleted file mode 100755 index db2f4a5730b80c3488c618fe825e322742e4007c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18164 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
xUBq)wpYuSo%L4;iD|W&d3&2irtb$J#-rmG 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 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursormiddle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursormiddle@2x.png deleted file mode 100755 index ebf59c18ba9f62390f49be4bed696a997a51f05c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7676 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 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit100@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit100@2x.png deleted file mode 100644 index 7db8eb312403e739d8dc61b280f930443ad0d81c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31228 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 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300@2x.png deleted file mode 100644 index 2c7c07852ff6e84edc2164b4487aaaa6cb4d8fe4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36873 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%0DE1j{G{fQ$Fey0U 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 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300g@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300g@2x.png deleted file mode 100644 index 1ce746e3a494f4446cc451f5924240f4c8eecb4b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39840 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%0DP1I3KX^ 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 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb-spec@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb-spec@2x.png deleted file mode 100644 index 76fd9ab16807c2ef61dd2aa0db47c78262db03b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13141 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 O@*I3?M71L9&Hn(Rv4%VV diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb1@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb1@2x.png deleted file mode 100644 index e99f076947aa7ca1614fe825839dd3d7b8dcc303..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17792 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 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb2@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb2@2x.png deleted file mode 100644 index cd36a0ae169e1d876012402b2b7aa34a13a179d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18268 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 SqgKcO0000l1{&5@md}*`-!Dp9 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 Y0Z!VEjc(%0RR91007*qoM6N<$f_m)to&W#< diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb4@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb4@2x.png deleted file mode 100644 index a5b19887d64f0040da387029545dea15d85669c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18062 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 Hu0mjf{m45K diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb5@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb5@2x.png deleted file mode 100644 index 4bb01f0e88229c6433c6363d7f7c725776372683..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16895 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

{E0 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 E0O3MVi~s-t diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb6@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb6@2x.png deleted file mode 100644 index 859e0aa4c1f0d548eda3fe4c6c02f7fc4af1831f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16702 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 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb8@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb8@2x.png deleted file mode 100644 index fcdf4ed4a4a76808dbfe41e54a45d4433dc1320b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17084 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 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb9@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb9@2x.png deleted file mode 100644 index c990cf0fe6784265e2419070a27459fa6f7b2fa0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17067 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, "Resources/metrics_skin"), audio, true); - defaultSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore(dllStore, "Resources/default_skin"), audio, false); + defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info); specialSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore(dllStore, "Resources/special_skin"), audio, true); } From 55afcc1e04ef4d48569cf7247195a32a15d0ec72 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Sep 2019 17:53:51 +0900 Subject: [PATCH 1162/2815] Add skin component for the legacy cursor trail --- osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 1 + .../Skinning/LegacyCursorTrail.cs | 24 +++++++++++++++++++ .../Skinning/OsuLegacySkinTransformer.cs | 6 +++++ .../UI/Cursor/CursorTrail.cs | 22 +++++++++++++---- .../UI/Cursor/OsuCursorContainer.cs | 20 ++++++++++++---- 5 files changed, 63 insertions(+), 10 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs index 5971f053c2..8dd48eace0 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs @@ -8,6 +8,7 @@ namespace osu.Game.Rulesets.Osu HitCircle, FollowPoint, Cursor, + CursorTrail, SliderScorePoint, ApproachCircle, ReverseArrow, diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs new file mode 100644 index 0000000000..74746faa44 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.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.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Osu.UI.Cursor; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Osu.Skinning +{ + public class LegacyCursorTrail : CursorTrail + { + public LegacyCursorTrail() + { + Blending = BlendingParameters.Additive; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + Texture = skin.GetTexture("cursortrail"); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 5957b81d7e..b5475f50ef 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -78,6 +78,12 @@ namespace osu.Game.Rulesets.Osu.Skinning return null; + case OsuSkinComponents.CursorTrail: + if (source.GetTexture("cursortrail") != null) + return new LegacyCursorTrail(); + + return null; + case OsuSkinComponents.HitCircleText: var font = GetConfig(OsuSkinConfiguration.HitCircleFont)?.Value ?? "default"; var overlap = GetConfig(OsuSkinConfiguration.HitCircleOverlap)?.Value ?? 0; diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index a50c3a2fea..acb7fb8251 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -20,14 +20,28 @@ using osuTK.Graphics.ES30; namespace osu.Game.Rulesets.Osu.UI.Cursor { - internal class CursorTrail : Drawable, IRequireHighFrequencyMousePosition + public class CursorTrail : Drawable, IRequireHighFrequencyMousePosition { private const int max_sprites = 2048; + private Texture texture = Texture.WhitePixel; + + public Texture Texture + { + get => texture; + set + { + if (texture == value) + return; + + texture = value; + Invalidate(Invalidation.DrawNode); + } + } + private readonly TrailPart[] parts = new TrailPart[max_sprites]; private int currentIndex; private IShader shader; - private Texture texture; private double timeOffset; private float time; @@ -47,11 +61,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor } [BackgroundDependencyLoader] - private void load(ShaderManager shaders, TextureStore textures) + private void load(ShaderManager shaders) { shader = shaders.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE); - texture = textures.Get(@"Cursor/cursortrail"); - Scale = new Vector2(1 / texture.ScaleAdjust); } protected override void LoadComplete() diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index 893c7875fa..5d68d200ff 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -6,9 +6,12 @@ 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.Input.Bindings; using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.UI; +using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Osu.UI.Cursor { @@ -22,17 +25,14 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private readonly Bindable showTrail = new Bindable(true); - private readonly CursorTrail cursorTrail; + private readonly Drawable cursorTrail; public OsuCursorContainer() { InternalChild = fadeContainer = new Container { RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - cursorTrail = new CursorTrail { Depth = 1 } - } + Child = cursorTrail = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.CursorTrail), _ => new DefaultCursorTrail()) }; } @@ -98,5 +98,15 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint); ActiveCursor.ScaleTo(0.8f, 450, Easing.OutQuint); } + + private class DefaultCursorTrail : CursorTrail + { + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + Texture = textures.Get(@"Cursor/cursortrail"); + Scale = new Vector2(1 / Texture.ScaleAdjust); + } + } } } From 2d636ce1472a9e19fa15698a012ac1b60b775f59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Sep 2019 17:54:53 +0900 Subject: [PATCH 1163/2815] 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 896b10133d..f76297c197 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -63,6 +63,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 5f2aad24dc..791d2fe285 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 5027a4ef8c..0560c45edf 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,8 +118,8 @@ - - + + From 195f101799d4ef63af86fdda28df71b6a5d7b52d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Sep 2019 19:00:42 +0900 Subject: [PATCH 1164/2815] Move complex property below ctor --- .../UI/Cursor/CursorTrail.cs | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index acb7fb8251..8164816f9f 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -24,21 +24,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { private const int max_sprites = 2048; - private Texture texture = Texture.WhitePixel; - - public Texture Texture - { - get => texture; - set - { - if (texture == value) - return; - - texture = value; - Invalidate(Invalidation.DrawNode); - } - } - private readonly TrailPart[] parts = new TrailPart[max_sprites]; private int currentIndex; private IShader shader; @@ -72,6 +57,21 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor resetTime(); } + private Texture texture = Texture.WhitePixel; + + public Texture Texture + { + get => texture; + set + { + if (texture == value) + return; + + texture = value; + Invalidate(Invalidation.DrawNode); + } + } + public override bool IsPresent => true; protected override void Update() From 1d225ba81e627128d14e16eb2aef9c34304d6f3d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Sep 2019 19:02:10 +0900 Subject: [PATCH 1165/2815] Add FadeDuration to control cursor trail fade --- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 8164816f9f..91bc3278bf 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -72,6 +72,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor } } + ///

+ /// The amount of time to fade the cursor trail pieces. + /// + protected virtual double FadeDuration => 300; + public override bool IsPresent => true; protected override void Update() @@ -82,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor const int fade_clock_reset_threshold = 1000000; - time = (float)(Time.Current - timeOffset) / 300f; + time = (float)((Time.Current - timeOffset) / FadeDuration); if (time > fade_clock_reset_threshold) resetTime(); } From 3b1b7910bbf50447b82b8d5ba7bce555255ad0a1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Sep 2019 19:22:27 +0900 Subject: [PATCH 1166/2815] Add toggle for cursor trail interpolation --- .../UI/Cursor/CursorTrail.cs | 45 +++++++++++++------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 91bc3278bf..0998b5d604 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -106,6 +106,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private Vector2 size => texture.Size * Scale; + /// + /// Whether to interpolate mouse movements and add trail pieces at intermediate points. + /// + protected virtual bool InterpolateMovements => true; + private Vector2? lastPosition; private readonly InputResampler resampler = new InputResampler(); @@ -126,29 +131,41 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { Trace.Assert(lastPosition.HasValue); - // ReSharper disable once PossibleInvalidOperationException - Vector2 pos1 = lastPosition.Value; - Vector2 diff = pos2 - pos1; - float distance = diff.Length; - Vector2 direction = diff / distance; - - float interval = size.X / 2 * 0.9f; - - for (float d = interval; d < distance; d += interval) + if (InterpolateMovements) { - lastPosition = pos1 + direction * d; + // ReSharper disable once PossibleInvalidOperationException + Vector2 pos1 = lastPosition.Value; + Vector2 diff = pos2 - pos1; + float distance = diff.Length; + Vector2 direction = diff / distance; - parts[currentIndex].Position = lastPosition.Value; - parts[currentIndex].Time = time; - ++parts[currentIndex].InvalidationID; + float interval = size.X / 2 * 0.9f; - currentIndex = (currentIndex + 1) % max_sprites; + for (float d = interval; d < distance; d += interval) + { + lastPosition = pos1 + direction * d; + addPart(lastPosition.Value); + } + } + else + { + lastPosition = pos2; + addPart(lastPosition.Value); } } return base.OnMouseMove(e); } + private void addPart(Vector2 screenSpacePosition) + { + parts[currentIndex].Position = screenSpacePosition; + parts[currentIndex].Time = time; + ++parts[currentIndex].InvalidationID; + + currentIndex = (currentIndex + 1) % max_sprites; + } + protected override DrawNode CreateDrawNode() => new TrailDrawNode(this); private struct TrailPart From 292d50aacfed96eb19e83b6775515ef74b933c74 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Sep 2019 19:22:44 +0900 Subject: [PATCH 1167/2815] Don't confine the cursor trail --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index 5d68d200ff..a944ff88c6 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor InternalChild = fadeContainer = new Container { RelativeSizeAxes = Axes.Both, - Child = cursorTrail = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.CursorTrail), _ => new DefaultCursorTrail()) + Child = cursorTrail = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.CursorTrail), _ => new DefaultCursorTrail(), confineMode: ConfineMode.NoScaling) }; } From a200485fbd0db1b2a4b429bb15620c8174cc4a9f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Sep 2019 19:23:02 +0900 Subject: [PATCH 1168/2815] Implement disjoint (old style) cursor trails --- .../Skinning/LegacyCursorTrail.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs index 74746faa44..d2018ea720 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Skinning; @@ -10,6 +11,11 @@ namespace osu.Game.Rulesets.Osu.Skinning { public class LegacyCursorTrail : CursorTrail { + private const double disjoint_trail_time_separation = 1000 / 60.0; + + private bool disjointTrail; + private double lastTrailTime; + public LegacyCursorTrail() { Blending = BlendingParameters.Additive; @@ -19,6 +25,31 @@ namespace osu.Game.Rulesets.Osu.Skinning private void load(ISkinSource skin) { Texture = skin.GetTexture("cursortrail"); + disjointTrail = skin.GetTexture("cursormiddle") == null; + + if (disjointTrail && Texture != null) + { + // stable "magic ratio". see OsuPlayfieldAdjustmentContainer for full explanation. + Texture.ScaleAdjust *= 1.6f; + } + } + + protected override double FadeDuration => disjointTrail ? 150 : 500; + + protected override bool InterpolateMovements => !disjointTrail; + + protected override bool OnMouseMove(MouseMoveEvent e) + { + if (!disjointTrail) + return base.OnMouseMove(e); + + if (Time.Current - lastTrailTime >= disjoint_trail_time_separation) + { + lastTrailTime = Time.Current; + return base.OnMouseMove(e); + } + + return false; } } } From e3b972187e7bfa966b0efc1d29adabcdb9875088 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Sep 2019 19:30:31 +0900 Subject: [PATCH 1169/2815] Fix incorrect cursor trail size + scale --- .../UI/Cursor/CursorTrail.cs | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 0998b5d604..7975982aec 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.Runtime.InteropServices; using osu.Framework.Allocation; +using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Batches; using osu.Framework.Graphics.OpenGL.Vertices; @@ -72,6 +73,20 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor } } + private readonly Cached partSizeCache = new Cached(); + + private Vector2 partSize => partSizeCache.IsValid + ? partSizeCache.Value + : (partSizeCache.Value = new Vector2(Texture.DisplayWidth, Texture.DisplayHeight) * DrawInfo.Matrix.ExtractScale().Xy); + + public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) + { + if ((invalidation & (Invalidation.DrawInfo | Invalidation.RequiredParentSizeToFit | Invalidation.Presence)) > 0) + partSizeCache.Invalidate(); + + return base.Invalidate(invalidation, source, shallPropagate); + } + /// /// The amount of time to fade the cursor trail pieces. /// @@ -104,8 +119,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor timeOffset = Time.Current; } - private Vector2 size => texture.Size * Scale; - /// /// Whether to interpolate mouse movements and add trail pieces at intermediate points. /// @@ -139,7 +152,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor float distance = diff.Length; Vector2 direction = diff / distance; - float interval = size.X / 2 * 0.9f; + float interval = partSize.X / 2 * 0.9f; for (float d = interval; d < distance; d += interval) { @@ -202,7 +215,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor shader = Source.shader; texture = Source.texture; - size = Source.size; + size = Source.partSize; time = Source.time; for (int i = 0; i < Source.parts.Length; ++i) From 0790e9e377582117b70688865efa2abcfba2859b Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2019 11:35:26 +0000 Subject: [PATCH 1170/2815] Bump ppy.osu.Framework.iOS from 2019.905.0 to 2019.909.0 Bumps [ppy.osu.Framework.iOS](https://github.com/ppy/osu-framework) from 2019.905.0 to 2019.909.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.905.0...2019.909.0) Signed-off-by: dependabot-preview[bot] --- osu.iOS.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.iOS.props b/osu.iOS.props index 5027a4ef8c..5645862bf7 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -119,7 +119,7 @@ - + From 7a7c3d21a1a158691daaf39005268e5acdd3b26d Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2019 11:36:58 +0000 Subject: [PATCH 1171/2815] Bump ppy.osu.Framework from 2019.905.0 to 2019.909.0 Bumps [ppy.osu.Framework](https://github.com/ppy/osu-framework) from 2019.905.0 to 2019.909.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.905.0...2019.909.0) 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 5f2aad24dc..791d2fe285 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 5027a4ef8c..77756dfd87 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,7 +118,7 @@ - + From 3b4750ab9e54033fed4a358041e9b15124feb643 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2019 11:37:09 +0000 Subject: [PATCH 1172/2815] Bump ppy.osu.Framework.Android from 2019.905.0 to 2019.909.0 Bumps [ppy.osu.Framework.Android](https://github.com/ppy/osu-framework) from 2019.905.0 to 2019.909.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.905.0...2019.909.0) Signed-off-by: dependabot-preview[bot] --- osu.Android.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Android.props b/osu.Android.props index 896b10133d..f76297c197 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -63,6 +63,6 @@ - + From c4aee11fe08c60db192388be63cad4287cb70f65 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 9 Sep 2019 15:35:18 +0300 Subject: [PATCH 1173/2815] Revert renaming changes --- osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 4 ++-- .../Objects/Drawables/Pieces/SpinnerDisc.cs | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs index 5d7acb77bb..3ed3f3e981 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Tests if (auto && !userTriggered && Time.Current > Spinner.StartTime + Spinner.Duration / 2 && Progress < 1) { // force completion only once to not break human interaction - Disc.BidirectionalRotation = Spinner.SpinsRequired * 360; + Disc.RotationAbsolute = Spinner.SpinsRequired * 360; auto = false; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index a8dc275fb5..49aaa2aaea 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables positionBindable.BindTo(HitObject.PositionBindable); } - public float Progress => MathHelper.Clamp(Disc.BidirectionalRotation / 360 / Spinner.SpinsRequired, 0, 1); + public float Progress => MathHelper.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1); protected override void CheckForResult(bool userTriggered, double timeOffset) { @@ -188,7 +188,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables circle.Rotation = Disc.Rotation; Ticks.Rotation = Disc.Rotation; - spmCounter.SetRotation(Disc.BidirectionalRotation); + spmCounter.SetRotation(Disc.RotationAbsolute); float relativeCircleScale = Spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight; Disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index 5b2b3bcd42..2ae420a5e2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -82,12 +82,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private float lastAngle; private float currentRotation; - public float BidirectionalRotation; + public float RotationAbsolute; private int completeTick; private double lastTime; - private bool updateCompleteTick() => completeTick != (completeTick = (int)(BidirectionalRotation / 360)); + private bool updateCompleteTick() => completeTick != (completeTick = (int)(RotationAbsolute / 360)); private bool rotationTransferred; @@ -113,8 +113,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces lastAngle -= 360; currentRotation += thisAngle - lastAngle; - BidirectionalRotation += Math.Abs(thisAngle - lastAngle) * Math.Sign(Time.Current - lastTime); lastTime = Time.Current; + RotationAbsolute += Math.Abs(thisAngle - lastAngle) * Math.Sign(Clock.ElapsedFrameTime); } lastAngle = thisAngle; From aec04dcf904cc1a8ba2f80b6298f82ef3e419b57 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 9 Sep 2019 15:36:20 +0300 Subject: [PATCH 1174/2815] Use Clock.ElapsedFrameTime instead --- osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index 2ae420a5e2..c45e98cc76 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -85,8 +85,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces public float RotationAbsolute; private int completeTick; - private double lastTime; - private bool updateCompleteTick() => completeTick != (completeTick = (int)(RotationAbsolute / 360)); private bool rotationTransferred; @@ -113,7 +111,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces lastAngle -= 360; currentRotation += thisAngle - lastAngle; - lastTime = Time.Current; RotationAbsolute += Math.Abs(thisAngle - lastAngle) * Math.Sign(Clock.ElapsedFrameTime); } From 0ec642d8261d347ab5b4520f44259747185fb400 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Sep 2019 01:06:37 +0900 Subject: [PATCH 1175/2815] Show instead of toggle --- 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 4c3566b3e9..65df98551d 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -154,7 +154,7 @@ namespace osu.Game.Screens.Menu logo.Action += () => { if (!api.IsLoggedIn && !loginPrompted) - login?.ToggleVisibility(); + login?.Show(); loginPrompted = true; From f398f134e171e8284c6b8b3f5b66b10df813ffbe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Sep 2019 01:12:30 +0900 Subject: [PATCH 1176/2815] Remove unnecessary bool storage Also delay show slightly for better user experience. --- osu.Game/Screens/Menu/MainMenu.cs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 65df98551d..978a1bffcd 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -39,8 +39,6 @@ namespace osu.Game.Screens.Menu private ButtonSystem buttons; - private bool loginPrompted; - [Resolved] private GameHost host { get; set; } @@ -151,16 +149,6 @@ namespace osu.Game.Screens.Menu logo.FadeColour(Color4.White, 100, Easing.OutQuint); logo.FadeIn(100, Easing.OutQuint); - logo.Action += () => - { - if (!api.IsLoggedIn && !loginPrompted) - login?.Show(); - - loginPrompted = true; - - return true; - }; - if (resuming) { buttons.State = ButtonSystemState.TopLevel; @@ -170,6 +158,14 @@ namespace osu.Game.Screens.Menu sideFlashes.Delay(FADE_IN_DURATION).FadeIn(64, Easing.InQuint); } + else if (!api.IsLoggedIn) + { + logo.Action += () => + { + Scheduler.AddDelayed(() => login?.Show(), 500); + return true; + }; + } } protected override void LogoSuspending(OsuLogo logo) From 7eb20da820128defcfd3c9a3738f5ee5b3f3b861 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Sep 2019 01:17:58 +0900 Subject: [PATCH 1177/2815] Add back local bool (required due to action limitations) --- osu.Game/Screens/Menu/MainMenu.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 978a1bffcd..18a3de7f37 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -140,6 +140,8 @@ namespace osu.Game.Screens.Menu Beatmap.ValueChanged += beatmap_ValueChanged; } + private bool loginDisplayed = false; + protected override void LogoArriving(OsuLogo logo, bool resuming) { base.LogoArriving(logo, resuming); @@ -160,11 +162,18 @@ namespace osu.Game.Screens.Menu } else if (!api.IsLoggedIn) { - logo.Action += () => + logo.Action += displayLogin; + } + + bool displayLogin() + { + if (!loginDisplayed) { Scheduler.AddDelayed(() => login?.Show(), 500); - return true; - }; + loginDisplayed = true; + } + + return true; } } From 98e384129c578742fb88408d7b00c2a69cd223a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Sep 2019 01:34:48 +0900 Subject: [PATCH 1178/2815] Remove redundant initialisation --- 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 18a3de7f37..a006877082 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -140,7 +140,7 @@ namespace osu.Game.Screens.Menu Beatmap.ValueChanged += beatmap_ValueChanged; } - private bool loginDisplayed = false; + private bool loginDisplayed; protected override void LogoArriving(OsuLogo logo, bool resuming) { From 22fabef344c41920820a662bfc37009675c12207 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 9 Sep 2019 19:52:31 +0300 Subject: [PATCH 1179/2815] Use TestWorkingBeatmap in BeatmapDetailsArea tests --- .../SongSelect/TestSceneBeatmapDetailArea.cs | 80 +++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs index 7b97a27732..ed9e01a67e 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Screens.Select; +using osu.Game.Tests.Beatmaps; using osuTK; namespace osu.Game.Tests.Visual.SongSelect @@ -30,45 +31,44 @@ namespace osu.Game.Tests.Visual.SongSelect Size = new Vector2(550f, 450f), }); - AddStep("all metrics", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null) + AddStep("all metrics", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap + { + BeatmapInfo = { - BeatmapSetInfo = + BeatmapSet = new BeatmapSetInfo { Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() } }, - BeatmapInfo = + Version = "All Metrics", + Metadata = new BeatmapMetadata { - Version = "All Metrics", - Metadata = new BeatmapMetadata - { - Source = "osu!lazer", - Tags = "this beatmap has all the metrics", - }, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 7, - DrainRate = 1, - OverallDifficulty = 5.7f, - ApproachRate = 3.5f, - }, - StarDifficulty = 5.3f, - Metrics = new BeatmapMetrics - { - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), - }, - } + Source = "osu!lazer", + Tags = "this beatmap has all the metrics", + }, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 7, + DrainRate = 1, + OverallDifficulty = 5.7f, + ApproachRate = 3.5f, + }, + StarDifficulty = 5.3f, + Metrics = new BeatmapMetrics + { + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), + }, } - ); + })); - AddStep("all except source", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null) + AddStep("all except source", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap { - BeatmapSetInfo = - { - Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() } - }, BeatmapInfo = { + BeatmapSet = new BeatmapSetInfo + { + Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() } + }, Version = "All Metrics", Metadata = new BeatmapMetadata { @@ -88,16 +88,16 @@ namespace osu.Game.Tests.Visual.SongSelect Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), }, } - }); + })); - AddStep("ratings", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null) + AddStep("ratings", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap { - BeatmapSetInfo = - { - Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() } - }, BeatmapInfo = { + BeatmapSet = new BeatmapSetInfo + { + Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() } + }, Version = "Only Ratings", Metadata = new BeatmapMetadata { @@ -113,9 +113,9 @@ namespace osu.Game.Tests.Visual.SongSelect }, StarDifficulty = 4.8f } - }); + })); - AddStep("fails+retries", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null) + AddStep("fails+retries", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap { BeatmapInfo = { @@ -139,9 +139,9 @@ namespace osu.Game.Tests.Visual.SongSelect Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), }, } - }); + })); - AddStep("null metrics", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null) + AddStep("null metrics", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap { BeatmapInfo = { @@ -160,7 +160,7 @@ namespace osu.Game.Tests.Visual.SongSelect }, StarDifficulty = 1.97f, } - }); + })); AddStep("null beatmap", () => detailsArea.Beatmap = null); } From b77550625c86360b5c98618eb61dec642c4824dd Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 9 Sep 2019 20:04:04 +0300 Subject: [PATCH 1180/2815] Check if DummyWorkingBeatmap is selected instead --- osu.Game/Screens/Select/BeatmapDetailArea.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs index bf8fc8cf07..5348de68d6 100644 --- a/osu.Game/Screens/Select/BeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Select { beatmap = value; Details.Beatmap = beatmap?.BeatmapInfo; - Leaderboard.Beatmap = beatmap is NoBeatmapsAvailableWorkingBeatmap ? null : beatmap?.BeatmapInfo; + Leaderboard.Beatmap = beatmap is DummyWorkingBeatmap ? null : beatmap?.BeatmapInfo; } } From 65869c7ebba728b9727d53fcf39caf11c68c9d67 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 10 Sep 2019 04:04:37 +0300 Subject: [PATCH 1181/2815] Refactor LeaderboardScopeSelector for more extensibility --- .../UserInterface/GradientLineTabControl.cs | 129 ++++++++++++++++++ .../BeatmapSet/LeaderboardScopeSelector.cs | 93 +------------ 2 files changed, 131 insertions(+), 91 deletions(-) create mode 100644 osu.Game/Graphics/UserInterface/GradientLineTabControl.cs diff --git a/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs b/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs new file mode 100644 index 0000000000..f4c43b0222 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs @@ -0,0 +1,129 @@ +// 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.UserInterface; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osuTK; +using osu.Framework.Graphics.Shapes; +using osuTK.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Input.Events; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; + +namespace osu.Game.Graphics.UserInterface +{ + public class GradientLineTabControl : PageTabControl + { + protected override Dropdown CreateDropdown() => null; + + protected override TabItem CreateTabItem(TModel value) => new ScopeSelectorTabItem(value); + + protected Color4 LineColour + { + get => line.MainColour.Value; + set => line.MainColour.Value = value; + } + + private readonly GradientLine line; + + public GradientLineTabControl() + { + RelativeSizeAxes = Axes.X; + + AddInternal(line = new GradientLine + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + }); + } + + protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(20, 0), + }; + + private class ScopeSelectorTabItem : PageTabItem + { + public ScopeSelectorTabItem(TModel value) + : base(value) + { + Text.Font = OsuFont.GetFont(size: 16); + } + + protected override bool OnHover(HoverEvent e) + { + Text.FadeColour(AccentColour); + + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + + Text.FadeColour(Color4.White); + } + } + + private class GradientLine : GridContainer + { + public readonly Bindable MainColour = new Bindable(); + + private readonly Box left; + private readonly Box middle; + private readonly Box right; + + public GradientLine() + { + RelativeSizeAxes = Axes.X; + Size = new Vector2(0.8f, 1.5f); + + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(mode: GridSizeMode.Relative, size: 0.4f), + new Dimension(), + }; + + Content = new[] + { + new Drawable[] + { + left = new Box + { + RelativeSizeAxes = Axes.Both, + }, + middle = new Box + { + RelativeSizeAxes = Axes.Both, + }, + right = new Box + { + RelativeSizeAxes = Axes.Both, + }, + } + }; + } + + protected override void LoadComplete() + { + MainColour.BindValueChanged(onColourChanged, true); + base.LoadComplete(); + } + + private void onColourChanged(ValueChangedEvent colour) + { + left.Colour = ColourInfo.GradientHorizontal(colour.NewValue.Opacity(0), colour.NewValue); + middle.Colour = colour.NewValue; + right.Colour = ColourInfo.GradientHorizontal(colour.NewValue, colour.NewValue.Opacity(0)); + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs index dcd58db427..bdcd5c21b9 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs @@ -1,119 +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 osu.Framework.Graphics.UserInterface; using osu.Game.Screens.Select.Leaderboards; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osuTK; using osu.Game.Graphics.UserInterface; -using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Framework.Allocation; using osuTK.Graphics; -using osu.Framework.Graphics.Colour; -using osu.Framework.Input.Events; namespace osu.Game.Overlays.BeatmapSet { - public class LeaderboardScopeSelector : PageTabControl + public class LeaderboardScopeSelector : GradientLineTabControl { protected override bool AddEnumEntriesAutomatically => false; - protected override Dropdown CreateDropdown() => null; - - protected override TabItem CreateTabItem(BeatmapLeaderboardScope value) => new ScopeSelectorTabItem(value); - public LeaderboardScopeSelector() { - RelativeSizeAxes = Axes.X; - AddItem(BeatmapLeaderboardScope.Global); AddItem(BeatmapLeaderboardScope.Country); AddItem(BeatmapLeaderboardScope.Friend); - - AddInternal(new GradientLine - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - }); } [BackgroundDependencyLoader] private void load(OsuColour colours) { AccentColour = colours.Blue; - } - - protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(20, 0), - }; - - private class ScopeSelectorTabItem : PageTabItem - { - public ScopeSelectorTabItem(BeatmapLeaderboardScope value) - : base(value) - { - Text.Font = OsuFont.GetFont(size: 16); - } - - protected override bool OnHover(HoverEvent e) - { - Text.FadeColour(AccentColour); - - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - base.OnHoverLost(e); - - Text.FadeColour(Color4.White); - } - } - - private class GradientLine : GridContainer - { - public GradientLine() - { - RelativeSizeAxes = Axes.X; - Size = new Vector2(0.8f, 1.5f); - - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(mode: GridSizeMode.Relative, size: 0.4f), - new Dimension(), - }; - - Content = new[] - { - new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(Color4.Transparent, Color4.Gray), - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Gray, - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(Color4.Gray, Color4.Transparent), - }, - } - }; - } + LineColour = Color4.Gray; } } } From 03bd7ca8e72a88d2551887b742281374bec2ee14 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 10 Sep 2019 04:20:32 +0300 Subject: [PATCH 1182/2815] Implement RankingsScopeSelector --- .../Online/TestSceneRankingsScopeSelector.cs | 53 +++++++++++++++++++ .../UserInterface/GradientLineTabControl.cs | 26 --------- .../BeatmapSet/LeaderboardScopeSelector.cs | 28 ++++++++++ osu.Game/Overlays/RankingsScopeSelector.cs | 26 +++++++++ 4 files changed, 107 insertions(+), 26 deletions(-) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs create mode 100644 osu.Game/Overlays/RankingsScopeSelector.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs new file mode 100644 index 0000000000..1488addb09 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs @@ -0,0 +1,53 @@ +// 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.Bindables; +using osu.Game.Overlays; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Framework.Extensions.Color4Extensions; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneRankingsScopeSelector : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(RankingsScopeSelector), + }; + + private readonly Box background; + + public TestSceneRankingsScopeSelector() + { + Bindable scope = new Bindable(); + + Add(background = new Box + { + RelativeSizeAxes = Axes.Both + }); + + Add(new RankingsScopeSelector + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Current = { BindTarget = scope } + }); + + AddStep(@"Select country", () => scope.Value = RankingsScope.Country); + AddStep(@"Select performance", () => scope.Value = RankingsScope.Performance); + AddStep(@"Select score", () => scope.Value = RankingsScope.Score); + AddStep(@"Select spotlights", () => scope.Value = RankingsScope.Spotlights); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Yellow.Opacity(50); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs b/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs index f4c43b0222..7cd8d2c5bd 100644 --- a/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs +++ b/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs @@ -8,7 +8,6 @@ using osuTK; using osu.Framework.Graphics.Shapes; using osuTK.Graphics; using osu.Framework.Graphics.Colour; -using osu.Framework.Input.Events; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -18,8 +17,6 @@ namespace osu.Game.Graphics.UserInterface { protected override Dropdown CreateDropdown() => null; - protected override TabItem CreateTabItem(TModel value) => new ScopeSelectorTabItem(value); - protected Color4 LineColour { get => line.MainColour.Value; @@ -49,29 +46,6 @@ namespace osu.Game.Graphics.UserInterface Spacing = new Vector2(20, 0), }; - private class ScopeSelectorTabItem : PageTabItem - { - public ScopeSelectorTabItem(TModel value) - : base(value) - { - Text.Font = OsuFont.GetFont(size: 16); - } - - protected override bool OnHover(HoverEvent e) - { - Text.FadeColour(AccentColour); - - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - base.OnHoverLost(e); - - Text.FadeColour(Color4.White); - } - } - private class GradientLine : GridContainer { public readonly Bindable MainColour = new Bindable(); diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs index bdcd5c21b9..e2a725ec46 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs @@ -6,6 +6,9 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Graphics; using osu.Framework.Allocation; using osuTK.Graphics; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Framework.Graphics; namespace osu.Game.Overlays.BeatmapSet { @@ -13,6 +16,8 @@ namespace osu.Game.Overlays.BeatmapSet { protected override bool AddEnumEntriesAutomatically => false; + protected override TabItem CreateTabItem(BeatmapLeaderboardScope value) => new ScopeSelectorTabItem(value); + public LeaderboardScopeSelector() { AddItem(BeatmapLeaderboardScope.Global); @@ -26,5 +31,28 @@ namespace osu.Game.Overlays.BeatmapSet AccentColour = colours.Blue; LineColour = Color4.Gray; } + + private class ScopeSelectorTabItem : PageTabItem + { + public ScopeSelectorTabItem(BeatmapLeaderboardScope value) + : base(value) + { + Text.Font = OsuFont.GetFont(size: 16); + } + + protected override bool OnHover(HoverEvent e) + { + Text.FadeColour(AccentColour); + + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + + Text.FadeColour(Color4.White); + } + } } } diff --git a/osu.Game/Overlays/RankingsScopeSelector.cs b/osu.Game/Overlays/RankingsScopeSelector.cs new file mode 100644 index 0000000000..5935876ec9 --- /dev/null +++ b/osu.Game/Overlays/RankingsScopeSelector.cs @@ -0,0 +1,26 @@ +// 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.Graphics.UserInterface; +using osu.Framework.Allocation; +using osuTK.Graphics; + +namespace osu.Game.Overlays +{ + public class RankingsScopeSelector : GradientLineTabControl + { + [BackgroundDependencyLoader] + private void load() + { + AccentColour = LineColour = Color4.Black; + } + } + + public enum RankingsScope + { + Performance, + Spotlights, + Score, + Country + } +} From f505a3ff1d48efea15967e3c322f6203fb8bf1cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Sep 2019 11:44:11 +0900 Subject: [PATCH 1183/2815] Mark AutoGeneration tests as headless --- osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs index 20ac5eaa39..f260357df5 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs @@ -3,6 +3,7 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Testing; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Replays; @@ -12,6 +13,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests { [TestFixture] + [HeadlessTest] public class TestSceneAutoGeneration : OsuTestScene { [Test] From af3bb5a2cdf7084cc14a988a0610e636438ad4fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Sep 2019 13:29:50 +0900 Subject: [PATCH 1184/2815] Centralise and share bar line generation code between rulesets --- .../TestSceneStage.cs | 4 +- osu.Game.Rulesets.Mania/Objects/BarLine.cs | 21 ------- .../Objects/Drawables/DrawableBarLine.cs | 9 ++- .../UI/DrawableManiaRuleset.cs | 33 +---------- osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 1 + osu.Game.Rulesets.Mania/UI/ManiaStage.cs | 1 + osu.Game.Rulesets.Taiko/Objects/BarLine.cs | 9 --- .../Objects/Drawables/DrawableBarLine.cs | 3 +- .../Objects/Drawables/DrawableBarLineMajor.cs | 1 + .../UI/DrawableTaikoRuleset.cs | 47 +-------------- osu.Game/Rulesets/Objects/BarLine.cs | 16 +++++ osu.Game/Rulesets/Objects/BarLineGenerator.cs | 58 +++++++++++++++++++ 12 files changed, 89 insertions(+), 114 deletions(-) delete mode 100644 osu.Game.Rulesets.Mania/Objects/BarLine.cs delete mode 100644 osu.Game.Rulesets.Taiko/Objects/BarLine.cs create mode 100644 osu.Game/Rulesets/Objects/BarLine.cs create mode 100644 osu.Game/Rulesets/Objects/BarLineGenerator.cs diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs index 395e6daf0a..e7fd601abe 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; using osuTK; @@ -114,8 +115,7 @@ namespace osu.Game.Rulesets.Mania.Tests var obj = new BarLine { StartTime = Time.Current + 2000, - ControlPoint = new TimingControlPoint(), - BeatIndex = major ? 0 : 1 + Major = major, }; obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); diff --git a/osu.Game.Rulesets.Mania/Objects/BarLine.cs b/osu.Game.Rulesets.Mania/Objects/BarLine.cs deleted file mode 100644 index 4c644a8f09..0000000000 --- a/osu.Game.Rulesets.Mania/Objects/BarLine.cs +++ /dev/null @@ -1,21 +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.Game.Beatmaps.ControlPoints; - -namespace osu.Game.Rulesets.Mania.Objects -{ - public class BarLine : ManiaHitObject - { - /// - /// The control point which this bar line is part of. - /// - public TimingControlPoint ControlPoint; - - /// - /// The index of the beat which this bar line represents within the control point. - /// This is a "major" bar line if % == 0. - /// - public int BeatIndex; - } -} diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs index e9c352c97e..862af8d15b 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs @@ -4,6 +4,7 @@ using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osuTK.Graphics; @@ -13,7 +14,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// Visualises a . Although this derives DrawableManiaHitObject, /// this does not handle input/sound like a normal hit object. ///
- public class DrawableBarLine : DrawableManiaHitObject + public class DrawableBarLine : DrawableHitObject { /// /// Height of major bar line triangles. @@ -40,9 +41,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Colour = new Color4(255, 204, 33, 255), }); - bool isMajor = barLine.BeatIndex % (int)barLine.ControlPoint.TimeSignature == 0; - - if (isMajor) + if (barLine.Major) { AddInternal(new EquilateralTriangle { @@ -65,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables }); } - if (!isMajor && barLine.BeatIndex % 2 == 1) + if (!barLine.Major) Alpha = 0.2f; } diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index f26526fe70..29863fba2e 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -2,14 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Input; -using osu.Framework.MathUtils; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Input.Handlers; using osu.Game.Replays; using osu.Game.Rulesets.Mania.Beatmaps; @@ -19,8 +16,8 @@ using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; @@ -45,33 +42,7 @@ namespace osu.Game.Rulesets.Mania.UI public DrawableManiaRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { - // Generate the bar lines - double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; - - var timingPoints = Beatmap.ControlPointInfo.TimingPoints; - var barLines = new List(); - - for (int i = 0; i < timingPoints.Count; i++) - { - TimingControlPoint point = timingPoints[i]; - - // Stop on the beat before the next timing point, or if there is no next timing point stop slightly past the last object - double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time - point.BeatLength : lastObjectTime + point.BeatLength * (int)point.TimeSignature; - - int index = 0; - - for (double t = timingPoints[i].Time; Precision.DefinitelyBigger(endTime, t); t += point.BeatLength, index++) - { - barLines.Add(new BarLine - { - StartTime = t, - ControlPoint = point, - BeatIndex = index - }); - } - } - - BarLines = barLines; + BarLines = new BarLineGenerator(Beatmap).BarLines; } [BackgroundDependencyLoader] diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 5ab07416a6..12faa499ad 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osuTK; diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs index a28de7ea58..98a4b7d0b6 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs @@ -12,6 +12,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.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; diff --git a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs b/osu.Game.Rulesets.Taiko/Objects/BarLine.cs deleted file mode 100644 index a07012fd71..0000000000 --- a/osu.Game.Rulesets.Taiko/Objects/BarLine.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.Taiko.Objects -{ - public class BarLine : TaikoHitObject - { - } -} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs index bf89f7e15b..1a5a797f28 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Objects; using osuTK; using osu.Game.Rulesets.Objects.Drawables; @@ -11,7 +12,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables /// /// A line that scrolls alongside hit objects in the playfield and visualises control points. /// - public class DrawableBarLine : DrawableHitObject + public class DrawableBarLine : DrawableHitObject { /// /// The width of the line tracker. diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs index 4d3a1a3f8a..f5b75a781b 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osuTK; using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index b03bea578e..5caa9e4626 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -5,19 +5,18 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.Taiko.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Taiko.Replays; -using System.Linq; using osu.Framework.Input; using osu.Game.Configuration; using osu.Game.Input.Handlers; using osu.Game.Replays; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Taiko.UI @@ -38,49 +37,7 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load() { - loadBarLines(); - } - - private void loadBarLines() - { - TaikoHitObject lastObject = Beatmap.HitObjects[Beatmap.HitObjects.Count - 1]; - double lastHitTime = 1 + ((lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime); - - var timingPoints = Beatmap.ControlPointInfo.TimingPoints.ToList(); - - if (timingPoints.Count == 0) - return; - - int currentIndex = 0; - int currentBeat = 0; - double time = timingPoints[currentIndex].Time; - - while (time <= lastHitTime) - { - int nextIndex = currentIndex + 1; - - if (nextIndex < timingPoints.Count && time > timingPoints[nextIndex].Time) - { - currentIndex = nextIndex; - time = timingPoints[currentIndex].Time; - currentBeat = 0; - } - - var currentPoint = timingPoints[currentIndex]; - - var barLine = new BarLine - { - StartTime = time, - }; - - barLine.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty); - - bool isMajor = currentBeat % (int)currentPoint.TimeSignature == 0; - Playfield.Add(isMajor ? new DrawableBarLineMajor(barLine) : new DrawableBarLine(barLine)); - - time += currentPoint.BeatLength * (int)currentPoint.TimeSignature; - currentBeat++; - } + new BarLineGenerator(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar.Major ? new DrawableBarLineMajor(bar) : new DrawableBarLine(bar))); } public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this); diff --git a/osu.Game/Rulesets/Objects/BarLine.cs b/osu.Game/Rulesets/Objects/BarLine.cs new file mode 100644 index 0000000000..a5c716e127 --- /dev/null +++ b/osu.Game/Rulesets/Objects/BarLine.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Objects +{ + /// + /// A hit object representing the end of a bar. + /// + public class BarLine : HitObject + { + /// + /// Whether this barline is a prominent beat (based on time signature of beatmap). + /// + public bool Major; + } +} diff --git a/osu.Game/Rulesets/Objects/BarLineGenerator.cs b/osu.Game/Rulesets/Objects/BarLineGenerator.cs new file mode 100644 index 0000000000..ce571d7b17 --- /dev/null +++ b/osu.Game/Rulesets/Objects/BarLineGenerator.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.Collections.Generic; +using System.Linq; +using osu.Framework.MathUtils; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Objects +{ + public class BarLineGenerator + { + /// + /// The generated bar lines. + /// + public readonly List BarLines = new List(); + + /// + /// Constructs and generates bar lines for provided beatmap. + /// + /// The beatmap to generate bar lines for. + public BarLineGenerator(IBeatmap beatmap) + { + if (beatmap.HitObjects.Count == 0) + return; + + HitObject lastObject = beatmap.HitObjects.Last(); + double lastHitTime = 1 + ((lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime); + + var timingPoints = beatmap.ControlPointInfo.TimingPoints; + + if (timingPoints.Count == 0) + return; + + for (int i = 0; i < timingPoints.Count; i++) + { + TimingControlPoint currentTimingPoint = timingPoints[i]; + int currentBeat = 0; + + // Stop on the beat before the next timing point, or if there is no next timing point stop slightly past the last object + double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time - currentTimingPoint.BeatLength : lastHitTime + currentTimingPoint.BeatLength * (int)currentTimingPoint.TimeSignature; + + double barLength = currentTimingPoint.BeatLength * (int)currentTimingPoint.TimeSignature; + + for (double t = currentTimingPoint.Time; Precision.DefinitelyBigger(endTime, t); t += barLength, currentBeat++) + { + BarLines.Add(new BarLine + { + StartTime = t, + Major = currentBeat % (int)currentTimingPoint.TimeSignature == 0 + }); + } + } + } + } +} From ef90914f581311f17bc689dea4cf83b7f96e01de Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Sep 2019 15:27:40 +0900 Subject: [PATCH 1185/2815] Fix mania notes test scene not visually displaying --- osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs index 031abb08e2..8dae5e6d84 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.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; @@ -11,6 +11,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; @@ -40,6 +41,7 @@ namespace osu.Game.Rulesets.Mania.Tests { Child = new FillFlowContainer { + Clock = new FramedClock(new ManualClock()), Anchor = Anchor.Centre, Origin = Anchor.Centre, AutoSizeAxes = Axes.Both, @@ -62,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.Tests private Drawable createNoteDisplay(ScrollingDirection direction, int identifier, out DrawableNote hitObject) { - var note = new Note { StartTime = 999999999 }; + var note = new Note { StartTime = 0 }; note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); return new ScrollingTestContainer(direction) @@ -77,7 +79,7 @@ namespace osu.Game.Rulesets.Mania.Tests private Drawable createHoldNoteDisplay(ScrollingDirection direction, int identifier, out DrawableHoldNote hitObject) { - var note = new HoldNote { StartTime = 999999999, Duration = 5000 }; + var note = new HoldNote { StartTime = 0, Duration = 5000 }; note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); return new ScrollingTestContainer(direction) @@ -133,7 +135,7 @@ namespace osu.Game.Rulesets.Mania.Tests Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.Both, Width = 1.25f, - Colour = Color4.Black.Opacity(0.5f) + Colour = Color4.Green.Opacity(0.5f) }, content = new Container { RelativeSizeAxes = Axes.Both } } From 01fd08cba92f1e7c96878147259b1c2b6411abab Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 10 Sep 2019 17:11:16 +0900 Subject: [PATCH 1186/2815] Fix broken positioning of effected usernames --- osu.Game/Overlays/Chat/ChatLine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 4c37d626c0..d125da8e92 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -148,8 +148,8 @@ namespace osu.Game.Overlays.Chat }, new MessageSender(message.Sender) { + AutoSizeAxes = Axes.Both, Padding = new MarginPadding { Left = timestamp_padding }, - RelativeSizeAxes = Axes.Both, Origin = Anchor.TopRight, Anchor = Anchor.TopRight, Child = effectedUsername, From 717a287d692e207e08231eec334f29076ad82b0c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 10 Sep 2019 17:11:26 +0900 Subject: [PATCH 1187/2815] Use real ellipsis character --- osu.Game/Overlays/Chat/ChatLine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index d125da8e92..7596231a3d 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -90,7 +90,7 @@ namespace osu.Game.Overlays.Chat Shadow = false, Colour = hasBackground ? customUsernameColour : username_colours[message.Sender.Id % username_colours.Length], Truncate = true, - EllipsisString = ".. :", + EllipsisString = "… :", Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Bold, italics: true), Anchor = Anchor.TopRight, Origin = Anchor.TopRight, From 36d3736e1dde496c2a1446f31a176dcf7d5a90f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Sep 2019 18:06:24 +0900 Subject: [PATCH 1188/2815] Fix hitcircle font prefix not being read for legacy skins --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 2 +- osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 5957b81d7e..6b6a08ed21 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.Skinning return null; case OsuSkinComponents.HitCircleText: - var font = GetConfig(OsuSkinConfiguration.HitCircleFont)?.Value ?? "default"; + var font = GetConfig(OsuSkinConfiguration.HitCirclePrefix)?.Value ?? "default"; var overlap = GetConfig(OsuSkinConfiguration.HitCircleOverlap)?.Value ?? 0; return !hasFont(font) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs index a6b87150ae..e7b686d27d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs @@ -5,7 +5,7 @@ namespace osu.Game.Rulesets.Osu.Skinning { public enum OsuSkinConfiguration { - HitCircleFont, + HitCirclePrefix, HitCircleOverlap, SliderBorderSize, SliderPathRadius, From 1969c5b89bccc3788a295d14ecc8e83412b58da7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 10 Sep 2019 16:36:05 +0300 Subject: [PATCH 1189/2815] Apply suggetsted changes --- .../Online/TestSceneRankingsScopeSelector.cs | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs index 1488addb09..93a00e1d06 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs @@ -9,7 +9,6 @@ using osu.Game.Overlays; using osu.Framework.Graphics.Shapes; using osu.Framework.Allocation; using osu.Game.Graphics; -using osu.Framework.Extensions.Color4Extensions; namespace osu.Game.Tests.Visual.Online { @@ -24,18 +23,20 @@ namespace osu.Game.Tests.Visual.Online public TestSceneRankingsScopeSelector() { - Bindable scope = new Bindable(); + var scope = new Bindable(); - Add(background = new Box + AddRange(new Drawable[] { - RelativeSizeAxes = Axes.Both - }); - - Add(new RankingsScopeSelector - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Current = { BindTarget = scope } + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + new RankingsScopeSelector + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Current = { BindTarget = scope } + } }); AddStep(@"Select country", () => scope.Value = RankingsScope.Country); @@ -47,7 +48,7 @@ namespace osu.Game.Tests.Visual.Online [BackgroundDependencyLoader] private void load(OsuColour colours) { - background.Colour = colours.Yellow.Opacity(50); + background.Colour = colours.GreySeafoam; } } } From e682ca4fd9a17cf90ac083ec5ff8faf317606b59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Sep 2019 12:51:54 +0900 Subject: [PATCH 1190/2815] Adjust osu!mania scroll speed defaults to be more sane --- .../Configuration/ManiaRulesetConfigManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs index b591f9da22..f5412dcfc5 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Configuration { base.InitialiseDefaults(); - Set(ManiaRulesetSetting.ScrollTime, 2250.0, 50.0, 10000.0, 50.0); + Set(ManiaRulesetSetting.ScrollTime, 1500.0, 50.0, 5000.0, 50.0); Set(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down); } From 70d39e9be47d7fae0250c0acc3c9fdf27e0f8ced Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 11 Sep 2019 13:28:36 +0900 Subject: [PATCH 1191/2815] Always apply stable's magic ratio --- osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs index d2018ea720..1885c76fcc 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Skinning Texture = skin.GetTexture("cursortrail"); disjointTrail = skin.GetTexture("cursormiddle") == null; - if (disjointTrail && Texture != null) + if (Texture != null) { // stable "magic ratio". see OsuPlayfieldAdjustmentContainer for full explanation. Texture.ScaleAdjust *= 1.6f; From 6c00d3936ae556af3184f587fffdf85afab58b61 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 11 Sep 2019 13:28:46 +0900 Subject: [PATCH 1192/2815] Reduce interval between cursor trail parts --- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 7975982aec..b32dfd483f 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -152,7 +152,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor float distance = diff.Length; Vector2 direction = diff / distance; - float interval = partSize.X / 2 * 0.9f; + float interval = partSize.X / 2.5f; for (float d = interval; d < distance; d += interval) { From 562280ced02ed9c570a7b56a8a6e4cd9dbd95507 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 11 Sep 2019 13:30:07 +0900 Subject: [PATCH 1193/2815] Add cursor trail test scene --- .../TestSceneCursorTrail.cs | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs new file mode 100644 index 0000000000..dae75e5a9d --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs @@ -0,0 +1,120 @@ +// 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Textures; +using osu.Framework.Testing.Input; +using osu.Game.Audio; +using osu.Game.Rulesets.Osu.Skinning; +using osu.Game.Rulesets.Osu.UI.Cursor; +using osu.Game.Skinning; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneCursorTrail : OsuTestScene + { + [Test] + public void TestSmoothCursorTrail() + { + createTest(() => new CursorTrail()); + } + + [Test] + public void TestLegacySmoothCursorTrail() + { + createTest(() => new LegacySkinContainer(false) + { + Child = new LegacyCursorTrail() + }); + } + + [Test] + public void TestLegacyDisjointCursorTrail() + { + createTest(() => new LegacySkinContainer(true) + { + Child = new LegacyCursorTrail() + }); + } + + private void createTest(Func createContent) => AddStep("create trail", () => + { + Clear(); + + Add(new Container + { + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.8f), + Child = new MovingCursorInputManager { Child = createContent?.Invoke() } + }); + }); + + [Cached(typeof(ISkinSource))] + private class LegacySkinContainer : Container, ISkinSource + { + private readonly bool disjoint; + + public LegacySkinContainer(bool disjoint) + { + this.disjoint = disjoint; + + RelativeSizeAxes = Axes.Both; + } + + public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotImplementedException(); + + public Texture GetTexture(string componentName) + { + switch (componentName) + { + case "cursortrail": + var tex = new Texture(Texture.WhitePixel.TextureGL); + + if (disjoint) + tex.ScaleAdjust = 1 / 25f; + return tex; + + case "cursormiddle": + return disjoint ? null : Texture.WhitePixel; + } + + return null; + } + + public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); + + public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); + + public event Action SourceChanged; + } + + private class MovingCursorInputManager : ManualInputManager + { + public MovingCursorInputManager() + { + UseParentInput = false; + } + + protected override void Update() + { + base.Update(); + + const double spin_duration = 1000; + double currentTime = Time.Current; + + double angle = (currentTime % spin_duration) / spin_duration * 2 * Math.PI; + Vector2 rPos = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle)); + + MoveMouseTo(ToScreenSpace(DrawSize / 2 + DrawSize / 3 * rPos)); + } + } + } +} From 6760e239a119b74661a9efc0b6bce63615d6fdcd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Sep 2019 13:39:21 +0900 Subject: [PATCH 1194/2815] Fix osu! hitcircle font textures being incorrectly sized --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 6 +++--- osu.Game/Skinning/LegacySpriteText.cs | 7 +------ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 6b6a08ed21..c978d95302 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -86,9 +86,9 @@ namespace osu.Game.Rulesets.Osu.Skinning ? null : new LegacySpriteText(source, font) { - // Spacing value was reverse-engineered from the ratio of the rendered sprite size in the visual inspector vs the actual texture size - Scale = new Vector2(0.96f), - Spacing = new Vector2(-overlap * 0.89f, 0) + // stable applies a blanket 0.8x scale to hitcircle fonts + Scale = new Vector2(0.8f), + Spacing = new Vector2(-overlap, 0) }; } diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs index dbcec019d6..773a9dc5c6 100644 --- a/osu.Game/Skinning/LegacySpriteText.cs +++ b/osu.Game/Skinning/LegacySpriteText.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using osu.Framework.Graphics.Sprites; using osu.Framework.Text; -using osu.Game.Graphics; using osu.Game.Graphics.Sprites; namespace osu.Game.Skinning @@ -18,7 +17,7 @@ namespace osu.Game.Skinning Shadow = false; UseFullGlyphHeight = false; - Font = new FontUsage(font, OsuFont.DEFAULT_FONT_SIZE); + Font = new FontUsage(font, 1); glyphStore = new LegacyGlyphStore(skin); } @@ -37,10 +36,6 @@ namespace osu.Game.Skinning { var texture = skin.GetTexture($"{fontName}-{character}"); - if (texture != null) - // Approximate value that brings character sizing roughly in-line with stable - texture.ScaleAdjust *= 18; - if (texture == null) return null; From e408efff4984be7d737c86a76221d9c5771c229d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 11 Sep 2019 13:40:53 +0900 Subject: [PATCH 1195/2815] Add scaling to the test --- osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs index dae75e5a9d..685a51d208 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs @@ -24,7 +24,15 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestSmoothCursorTrail() { - createTest(() => new CursorTrail()); + Container scalingContainer = null; + + createTest(() => scalingContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Child = new CursorTrail() + }); + + AddStep("set large scale", () => scalingContainer.Scale = new Vector2(10)); } [Test] From 96efc91b51a9b46a0045d246e7c2cc26e86fc6b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Sep 2019 14:57:42 +0900 Subject: [PATCH 1196/2815] Fix follow points not displaying on some skins --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 82181945a4..479c250eab 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -45,6 +45,9 @@ namespace osu.Game.Rulesets.Osu.Skinning switch (osuComponent.Component) { + case OsuSkinComponents.FollowPoint: + return this.GetAnimation(component.LookupName, true, false); + case OsuSkinComponents.SliderFollowCircle: return this.GetAnimation("sliderfollowcircle", true, true); From 95828b07efecaf83ef25127043f71ebb77e6c92f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 11 Sep 2019 10:40:58 +0300 Subject: [PATCH 1197/2815] Implement HeaderFlag component for rankings overlay --- .../Online/TestSceneRankingsHeaderFlag.cs | 55 +++++++++++++++ osu.Game/Overlays/Rankings/HeaderFlag.cs | 68 +++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderFlag.cs create mode 100644 osu.Game/Overlays/Rankings/HeaderFlag.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderFlag.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderFlag.cs new file mode 100644 index 0000000000..d31e3011b8 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderFlag.cs @@ -0,0 +1,55 @@ +// 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.Sprites; +using osu.Game.Graphics; +using osu.Game.Overlays.Rankings; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneRankingsHeaderFlag : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(HeaderFlag), + }; + + public TestSceneRankingsHeaderFlag() + { + HeaderFlag flag; + SpriteText text; + + AddRange(new Drawable[] + { + flag = new HeaderFlag + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(30, 20), + Country = new Country + { + FlagName = "BY", + FullName = "Belarus" + } + }, + text = new SpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = "Invoked", + Font = OsuFont.GetFont(size: 30), + Alpha = 0, + } + }); + + flag.Action += () => text.FadeIn().Then().FadeOut(1000, Easing.OutQuint); + + AddStep("Trigger click", () => flag.Click()); + } + } +} diff --git a/osu.Game/Overlays/Rankings/HeaderFlag.cs b/osu.Game/Overlays/Rankings/HeaderFlag.cs new file mode 100644 index 0000000000..5b00e3e487 --- /dev/null +++ b/osu.Game/Overlays/Rankings/HeaderFlag.cs @@ -0,0 +1,68 @@ +// 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.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Users.Drawables; +using osuTK.Graphics; +using osuTK; +using osu.Framework.Input.Events; +using osu.Framework.Extensions.Color4Extensions; +using System; + +namespace osu.Game.Overlays.Rankings +{ + public class HeaderFlag : UpdateableFlag + { + private const int duration = 200; + + public Action Action; + + private readonly Container hoverContainer; + + public HeaderFlag() + { + AddInternal(hoverContainer = new Container + { + Alpha = 0, + Depth = -1, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(15), + Icon = FontAwesome.Solid.Times, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(100), + } + } + }); + } + + protected override bool OnHover(HoverEvent e) + { + hoverContainer.FadeIn(duration, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + hoverContainer.FadeOut(duration, Easing.OutQuint); + } + + protected override bool OnClick(ClickEvent e) + { + Action?.Invoke(); + return base.OnClick(e); + } + } +} From d610c903716356593f9b774c5e768412583e13c5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 11 Sep 2019 10:43:51 +0300 Subject: [PATCH 1198/2815] Add more tests --- .../Online/TestSceneRankingsHeaderFlag.cs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderFlag.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderFlag.cs index d31e3011b8..c9531e1016 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderFlag.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderFlag.cs @@ -24,6 +24,18 @@ namespace osu.Game.Tests.Visual.Online HeaderFlag flag; SpriteText text; + var countryA = new Country + { + FlagName = "BY", + FullName = "Belarus" + }; + + var countryB = new Country + { + FlagName = "US", + FullName = "United States" + }; + AddRange(new Drawable[] { flag = new HeaderFlag @@ -31,11 +43,7 @@ namespace osu.Game.Tests.Visual.Online Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(30, 20), - Country = new Country - { - FlagName = "BY", - FullName = "Belarus" - } + Country = countryA, }, text = new SpriteText { @@ -50,6 +58,8 @@ namespace osu.Game.Tests.Visual.Online flag.Action += () => text.FadeIn().Then().FadeOut(1000, Easing.OutQuint); AddStep("Trigger click", () => flag.Click()); + AddStep("Change to country 2", () => flag.Country = countryB); + AddStep("Change to country 1", () => flag.Country = countryA); } } } From 1d1da1bc13c7f1c613aa8bba35c8f5b90a819b15 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 11 Sep 2019 11:26:09 +0300 Subject: [PATCH 1199/2815] Visual improvements --- osu.Game/Overlays/Rankings/HeaderFlag.cs | 34 +++++++++--------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/osu.Game/Overlays/Rankings/HeaderFlag.cs b/osu.Game/Overlays/Rankings/HeaderFlag.cs index 5b00e3e487..8bd4bdf13f 100644 --- a/osu.Game/Overlays/Rankings/HeaderFlag.cs +++ b/osu.Game/Overlays/Rankings/HeaderFlag.cs @@ -20,49 +20,39 @@ namespace osu.Game.Overlays.Rankings public Action Action; - private readonly Container hoverContainer; + private readonly SpriteIcon hoverIcon; public HeaderFlag() { - AddInternal(hoverContainer = new Container + AddInternal(hoverIcon = new SpriteIcon { - Alpha = 0, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Depth = -1, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(15), - Icon = FontAwesome.Solid.Times, - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(100), - } - } + Alpha = 0, + Size = new Vector2(10), + Icon = FontAwesome.Solid.Times, }); } protected override bool OnHover(HoverEvent e) { - hoverContainer.FadeIn(duration, Easing.OutQuint); + hoverIcon.FadeIn(duration, Easing.OutQuint); + this.FadeColour(Color4.Gray, duration, Easing.OutQuint); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { base.OnHoverLost(e); - hoverContainer.FadeOut(duration, Easing.OutQuint); + hoverIcon.FadeOut(duration, Easing.OutQuint); + this.FadeColour(Color4.White, duration, Easing.OutQuint); } protected override bool OnClick(ClickEvent e) { Action?.Invoke(); - return base.OnClick(e); + return true; } } } From 825a34ecd3180d421aa6f6f8827df3a8269f5f70 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Sep 2019 17:34:03 +0900 Subject: [PATCH 1200/2815] Early return to avoid other potential fail cases --- .../Containers/OsuFocusedOverlayContainer.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 5c2efbc354..08164dbf3e 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.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 osu.Framework.Allocation; @@ -106,26 +106,26 @@ namespace osu.Game.Graphics.Containers protected override void UpdateState(ValueChangedEvent state) { - base.UpdateState(state); - switch (state.NewValue) { case Visibility.Visible: - if (OverlayActivationMode.Value != OverlayActivation.Disabled) + if (OverlayActivationMode.Value == OverlayActivation.Disabled) { - if (PlaySamplesOnStateChange) samplePopIn?.Play(); - if (BlockScreenWideMouse && DimMainContent) osuGame?.AddBlockingOverlay(this); + State.Value = Visibility.Hidden; + return; } - else - Hide(); + if (PlaySamplesOnStateChange) samplePopIn?.Play(); + if (BlockScreenWideMouse && DimMainContent) game?.AddBlockingOverlay(this); break; case Visibility.Hidden: if (PlaySamplesOnStateChange) samplePopOut?.Play(); - if (BlockScreenWideMouse) osuGame?.RemoveBlockingOverlay(this); + if (BlockScreenWideMouse) game?.RemoveBlockingOverlay(this); break; } + + base.UpdateState(state); } protected override void PopOut() From 2c09efa23b577bc5e15c3a4efea6558a490f7d62 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Sep 2019 17:34:28 +0900 Subject: [PATCH 1201/2815] Handle changes to OverlayActivationMode --- .../Containers/OsuFocusedOverlayContainer.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 08164dbf3e..0ce095d44f 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.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 osu.Framework.Allocation; @@ -32,7 +32,7 @@ namespace osu.Game.Graphics.Containers protected virtual bool DimMainContent => true; [Resolved(CanBeNull = true)] - private OsuGame osuGame { get; set; } + private OsuGame game { get; set; } [Resolved] private PreviewTrackManager previewTrackManager { get; set; } @@ -42,8 +42,14 @@ namespace osu.Game.Graphics.Containers [BackgroundDependencyLoader(true)] private void load(AudioManager audio) { - if (osuGame != null) - OverlayActivationMode.BindTo(osuGame.OverlayActivationMode); + OverlayActivationMode.ValueChanged += mode => + { + if (mode.NewValue == OverlayActivation.Disabled) + State.Value = Visibility.Hidden; + }; + + if (game != null) + OverlayActivationMode.BindTo(game.OverlayActivationMode); samplePopIn = audio.Samples.Get(@"UI/overlay-pop-in"); samplePopOut = audio.Samples.Get(@"UI/overlay-pop-out"); @@ -137,7 +143,7 @@ namespace osu.Game.Graphics.Containers protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - osuGame?.RemoveBlockingOverlay(this); + game?.RemoveBlockingOverlay(this); } } } From 660c678cdcbe74b8a6b6fe55555a60b2b778f820 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 11 Sep 2019 11:40:51 +0300 Subject: [PATCH 1202/2815] Remove unused using directives --- osu.Game/Overlays/Rankings/HeaderFlag.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Overlays/Rankings/HeaderFlag.cs b/osu.Game/Overlays/Rankings/HeaderFlag.cs index 8bd4bdf13f..6f641c74a5 100644 --- a/osu.Game/Overlays/Rankings/HeaderFlag.cs +++ b/osu.Game/Overlays/Rankings/HeaderFlag.cs @@ -2,14 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Users.Drawables; using osuTK.Graphics; using osuTK; using osu.Framework.Input.Events; -using osu.Framework.Extensions.Color4Extensions; using System; namespace osu.Game.Overlays.Rankings From 41ad44791bfc2918af739f05273e244e180b7f20 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 11 Sep 2019 11:58:18 +0300 Subject: [PATCH 1203/2815] Move RankingsScopeSelector to another namespace --- osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs | 2 +- osu.Game/Overlays/{ => Rankings}/RankingsScopeSelector.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename osu.Game/Overlays/{ => Rankings}/RankingsScopeSelector.cs (94%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs index 93a00e1d06..2081a6c0cb 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs @@ -5,10 +5,10 @@ using System; using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Bindables; -using osu.Game.Overlays; using osu.Framework.Graphics.Shapes; using osu.Framework.Allocation; using osu.Game.Graphics; +using osu.Game.Overlays.Rankings; namespace osu.Game.Tests.Visual.Online { diff --git a/osu.Game/Overlays/RankingsScopeSelector.cs b/osu.Game/Overlays/Rankings/RankingsScopeSelector.cs similarity index 94% rename from osu.Game/Overlays/RankingsScopeSelector.cs rename to osu.Game/Overlays/Rankings/RankingsScopeSelector.cs index 5935876ec9..2095bcc61c 100644 --- a/osu.Game/Overlays/RankingsScopeSelector.cs +++ b/osu.Game/Overlays/Rankings/RankingsScopeSelector.cs @@ -5,7 +5,7 @@ using osu.Game.Graphics.UserInterface; using osu.Framework.Allocation; using osuTK.Graphics; -namespace osu.Game.Overlays +namespace osu.Game.Overlays.Rankings { public class RankingsScopeSelector : GradientLineTabControl { From da6ba20fc80a7cb25badd75fcc0dfd23a2b4f0bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Sep 2019 18:15:03 +0900 Subject: [PATCH 1204/2815] Reduce glow on notes --- osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 2cd81104a3..5aeaba717c 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.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.Diagnostics; @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, - Colour = colour.NewValue.Lighten(1f).Opacity(0.6f), + Colour = colour.NewValue.Lighten(1f).Opacity(0.2f), Radius = 10, }; }, true); From 44d90a4e860fecb7891d295fe929fdc8dbc8d6f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Sep 2019 18:16:14 +0900 Subject: [PATCH 1205/2815] Increase note height --- osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs | 4 +++- .../Objects/Drawables/Pieces/NotePiece.cs | 7 +++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 5aeaba717c..31221c05ee 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.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.Diagnostics; @@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// public class DrawableNote : DrawableManiaHitObject, IKeyBindingHandler { + public const float CORNER_RADIUS = NotePiece.NOTE_HEIGHT / 2; + private readonly NotePiece headPiece; public DrawableNote(Note hitObject) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs index bb33693783..4521af7dfb 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs @@ -18,8 +18,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces /// internal class NotePiece : Container, IHasAccentColour { - public const float NOTE_HEIGHT = 10; - private const float head_colour_height = 6; + public const float NOTE_HEIGHT = 12; private readonly IBindable direction = new Bindable(); @@ -39,8 +38,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces colouredBox = new Box { RelativeSizeAxes = Axes.X, - Height = head_colour_height, - Alpha = 0.2f + Height = NOTE_HEIGHT / 2, + Alpha = 0.1f } }; } From 8f6bc6fd5c8171152f308c937c2e3dff48894b97 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Sep 2019 18:12:43 +0900 Subject: [PATCH 1206/2815] Make osu!mania hit explosions more explodey --- .../TestSceneHitExplosion.cs | 84 ++++++++++++ osu.Game.Rulesets.Mania/UI/Column.cs | 9 +- osu.Game.Rulesets.Mania/UI/HitExplosion.cs | 123 +++++++++++++----- 3 files changed, 183 insertions(+), 33 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/TestSceneHitExplosion.cs diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHitExplosion.cs new file mode 100644 index 0000000000..12159ca239 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHitExplosion.cs @@ -0,0 +1,84 @@ +// 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.Graphics; +using osu.Game.Rulesets.Mania.Objects; +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.Scoring; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Tests +{ + [TestFixture] + public class TestSceneHitExplosion : OsuTestScene + { + private ScrollingTestContainer scrolling; + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(DrawableNote), + typeof(DrawableManiaHitObject), + }; + + protected override void LoadComplete() + { + 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, NotePiece.NOTE_HEIGHT), + }; + + int runcount = 0; + + AddRepeatStep("explode", () => + { + runcount++; + + if (runcount % 15 > 12) + return; + + scrolling.AddRange(new Drawable[] + { + 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, + } + }); + }, 100); + } + + private class TestNote : DrawableNote + { + protected override void CheckForResult(bool userTriggered, double timeOffset) + { + if (!userTriggered) + { + // force success + ApplyResult(r => r.Type = HitResult.Great); + } + else + base.CheckForResult(userTriggered, timeOffset); + } + + public TestNote(Note hitObject) + : base(hitObject) + { + AccentColour.Value = Color4.Pink; + } + } + } +} diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 91dd236ab1..fa14a0a293 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.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.Linq; @@ -90,6 +90,8 @@ namespace osu.Game.Rulesets.Mania.UI Bottom = dir.NewValue == ScrollingDirection.Down ? ManiaStage.HIT_TARGET_POSITION : 0, }; + explosionContainer.Y = dir.NewValue == ScrollingDirection.Down ? -NotePiece.NOTE_HEIGHT : 0; + keyArea.Anchor = keyArea.Origin = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; }, true); } @@ -163,9 +165,10 @@ namespace osu.Game.Rulesets.Mania.UI if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value) return; - explosionContainer.Add(new HitExplosion(judgedObject) + explosionContainer.Add(new HitExplosion(judgedObject.AccentColour.Value, judgedObject is DrawableHoldNoteTick) { - Anchor = Direction.Value == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre + Anchor = Direction.Value == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre, + Origin = Direction.Value == ScrollingDirection.Up ? Anchor.BottomCentre : Anchor.TopCentre, }); } diff --git a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs index 48470add8b..21726206f1 100644 --- a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs @@ -1,16 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -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.Framework.MathUtils; -using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; -using osu.Game.Rulesets.Objects.Drawables; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.UI { @@ -18,51 +16,116 @@ namespace osu.Game.Rulesets.Mania.UI { public override bool RemoveWhenNotAlive => true; - private readonly CircularContainer circle; + private readonly CircularContainer largeFaint; + private readonly CircularContainer mainGlow1; + private readonly CircularContainer mainGlow2; + private readonly CircularContainer mainGlow3; - public HitExplosion(DrawableHitObject judgedObject) + public HitExplosion(Color4 objectColour, bool isSmall = false) { - bool isTick = judgedObject is DrawableHoldNoteTick; - - Origin = Anchor.Centre; - RelativeSizeAxes = Axes.X; - Y = NotePiece.NOTE_HEIGHT / 2; Height = NotePiece.NOTE_HEIGHT; // scale roughly in-line with visual appearance of notes - Scale = new Vector2(isTick ? 0.4f : 0.8f); + Scale = new Vector2(1f, 0.6f); - InternalChild = circle = new CircularContainer + if (isSmall) + Scale *= 0.5f; + + const float angle_variangle = 15; // should be less than 45 + + const float roundness = 80; + + const float opacity = 1; + + const float initial_height = 10; + + var colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1); + + InternalChildren = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Masking = true, - // we want our size to be very small so the glow dominates it. - Size = new Vector2(0.1f), - EdgeEffect = new EdgeEffectParameters + largeFaint = new CircularContainer { - Type = EdgeEffectType.Glow, - Colour = Interpolation.ValueAt(0.1f, judgedObject.AccentColour.Value, Color4.White, 0, 1), - Radius = 100, - }, - Child = new Box - { - Alpha = 0, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - AlwaysPresent = true + Masking = true, + // we want our size to be very small so the glow dominates it. + Size = new Vector2(0.8f), + Blending = BlendingParameters.Additive, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f), + Roundness = 160, + Radius = 200, + }, + }, + mainGlow1 = new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Masking = true, + Blending = BlendingParameters.Additive, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1), + Roundness = 20, + Radius = 50, + }, + }, + mainGlow2 = new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Masking = true, + Size = new Vector2(0.01f, initial_height), + Blending = BlendingParameters.Additive, + Rotation = RNG.NextSingle(-angle_variangle, angle_variangle), + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = colour, + Roundness = roundness, + Radius = 40, + }, + }, + mainGlow3 = new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Masking = true, + Size = new Vector2(0.01f, initial_height), + Blending = BlendingParameters.Additive, + Rotation = RNG.NextSingle(-angle_variangle, angle_variangle), + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = colour, + Roundness = roundness, + Radius = 40, + }, } }; } protected override void LoadComplete() { + const double duration = 200; + base.LoadComplete(); - circle.ResizeTo(circle.Size * new Vector2(4, 20), 1000, Easing.OutQuint); - this.FadeIn(16).Then().FadeOut(500, Easing.OutQuint); + largeFaint + .ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint) + .FadeOut(duration * 2); + mainGlow1.ScaleTo(1.4f, duration, Easing.OutQuint); + + this.FadeOut(duration, Easing.Out); Expire(true); } } From 6bfdadb22fb1dcaaf172e5694a955c812c4bd030 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Sep 2019 18:20:41 +0900 Subject: [PATCH 1207/2815] Increase column width --- osu.Game.Rulesets.Mania/UI/Column.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 91dd236ab1..0caee025b6 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -11,6 +11,8 @@ using osu.Framework.Allocation; 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; @@ -19,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.UI { public class Column : ScrollingPlayfield, IKeyBindingHandler, IHasAccentColour { - private const float column_width = 45; + public const float COLUMN_WIDTH = 80; private const float special_column_width = 70; /// @@ -41,10 +43,7 @@ namespace osu.Game.Rulesets.Mania.UI Index = index; RelativeSizeAxes = Axes.Y; - Width = column_width; - - Masking = true; - CornerRadius = 5; + Width = COLUMN_WIDTH; background = new ColumnBackground { RelativeSizeAxes = Axes.Both }; @@ -67,7 +66,7 @@ namespace osu.Game.Rulesets.Mania.UI explosionContainer = new Container { Name = "Hit explosions", - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, } } }, @@ -108,7 +107,7 @@ namespace osu.Game.Rulesets.Mania.UI isSpecial = value; - Width = isSpecial ? special_column_width : column_width; + Width = isSpecial ? special_column_width : COLUMN_WIDTH; } } From c7186efd5339a954eaf67682c01fb184e669b455 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Sep 2019 18:21:29 +0900 Subject: [PATCH 1208/2815] Reduce opacity of judgement area --- osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs index a0d713067d..386bcbb724 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs @@ -9,6 +9,7 @@ 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; @@ -17,7 +18,6 @@ namespace osu.Game.Rulesets.Mania.UI.Components { public class ColumnHitObjectArea : CompositeDrawable, IHasAccentColour { - private const float hit_target_height = 10; private const float hit_target_bar_height = 2; private readonly IBindable direction = new Bindable(); @@ -32,7 +32,8 @@ namespace osu.Game.Rulesets.Mania.UI.Components hitTargetBar = new Box { RelativeSizeAxes = Axes.X, - Height = hit_target_height, + Height = NotePiece.NOTE_HEIGHT, + Alpha = 0.6f, Colour = Color4.Black }, hitTargetLine = new Container From b9e71d26b285922d7c0d0a7e3f2d0bcc60628220 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Sep 2019 18:21:39 +0900 Subject: [PATCH 1209/2815] Dim column backgrounds further --- osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs index 5ee78aa496..57241da564 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs @@ -35,7 +35,6 @@ namespace osu.Game.Rulesets.Mania.UI.Components { Name = "Background", RelativeSizeAxes = Axes.Both, - Alpha = 0.3f }, backgroundOverlay = new Box { @@ -82,7 +81,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components if (!IsLoaded) return; - background.Colour = AccentColour; + background.Colour = AccentColour.Darken(5); var brightPoint = AccentColour.Opacity(0.6f); var dimPoint = AccentColour.Opacity(0); From 06618b6d02e7f358d1929cf5bf19b1e6f22c269a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Sep 2019 18:45:47 +0900 Subject: [PATCH 1210/2815] Fix osu!mania minor barline alpha not being respected --- osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs index 862af8d15b..be21610525 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs @@ -68,6 +68,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Alpha = 0.2f; } + protected override void UpdateInitialTransforms() + { + } + protected override void UpdateStateTransforms(ArmedState state) { } From 039b5ec958fcac03296e31791721b2e786c17de7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Sep 2019 18:47:25 +0900 Subject: [PATCH 1211/2815] 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 f76297c197..45c162a30e 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -63,6 +63,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 791d2fe285..df8b11e653 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 0560c45edf..7c31744a14 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,8 +118,8 @@ - - + + From be66c0e9127982166e393e697daa9d4c2d5db938 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Sep 2019 19:06:31 +0900 Subject: [PATCH 1212/2815] Fix potential of toggle between load and LoadComplete --- .../Graphics/Containers/OsuFocusedOverlayContainer.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 0ce095d44f..a1df973b60 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -41,6 +41,12 @@ namespace osu.Game.Graphics.Containers [BackgroundDependencyLoader(true)] private void load(AudioManager audio) + { + samplePopIn = audio.Samples.Get(@"UI/overlay-pop-in"); + samplePopOut = audio.Samples.Get(@"UI/overlay-pop-out"); + } + + protected override void LoadComplete() { OverlayActivationMode.ValueChanged += mode => { @@ -51,8 +57,7 @@ namespace osu.Game.Graphics.Containers if (game != null) OverlayActivationMode.BindTo(game.OverlayActivationMode); - samplePopIn = audio.Samples.Get(@"UI/overlay-pop-in"); - samplePopOut = audio.Samples.Get(@"UI/overlay-pop-out"); + base.LoadComplete(); } /// From 55a071e8ba9bfe68ba237daa81a1024a43366d92 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Sep 2019 19:12:55 +0900 Subject: [PATCH 1213/2815] Use BindValueChanged --- .../Graphics/Containers/OsuFocusedOverlayContainer.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index a1df973b60..2e8910213b 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -48,14 +48,14 @@ namespace osu.Game.Graphics.Containers protected override void LoadComplete() { - OverlayActivationMode.ValueChanged += mode => + if (game != null) + OverlayActivationMode.BindTo(game.OverlayActivationMode); + + OverlayActivationMode.BindValueChanged(mode => { if (mode.NewValue == OverlayActivation.Disabled) State.Value = Visibility.Hidden; - }; - - if (game != null) - OverlayActivationMode.BindTo(game.OverlayActivationMode); + }, true); base.LoadComplete(); } From dbfbd1262fd1caffd5d657d1bbf6ef24d2b66997 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 11 Sep 2019 23:39:22 +0300 Subject: [PATCH 1214/2815] Implement HeaderTitle component for RankingsOverlay --- .../Online/TestSceneRankingsHeaderTitle.cs | 60 ++++++++++ osu.Game/Overlays/Rankings/HeaderTitle.cs | 105 ++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs create mode 100644 osu.Game/Overlays/Rankings/HeaderTitle.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs new file mode 100644 index 0000000000..0f16b2592f --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs @@ -0,0 +1,60 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Game.Overlays.Rankings; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneRankingsHeaderTitle : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(HeaderFlag), + typeof(HeaderTitle), + }; + + public TestSceneRankingsHeaderTitle() + { + var countryBindable = new Bindable(); + var scope = new Bindable(); + + Add(new HeaderTitle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Country = { BindTarget = countryBindable }, + Scope = { BindTarget = scope }, + }); + + var countryA = new Country + { + FlagName = "BY", + FullName = "Belarus" + }; + + var countryB = new Country + { + FlagName = "US", + FullName = "United States" + }; + + AddStep("Set country", () => countryBindable.Value = countryA); + AddAssert("Check scope is Performance", () => scope.Value == RankingsScope.Performance); + AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score); + AddAssert("Check country is Null", () => countryBindable.Value == null); + + AddStep("Set country 1", () => countryBindable.Value = countryA); + AddStep("Set country 2", () => countryBindable.Value = countryB); + AddStep("Set null country", () => countryBindable.Value = null); + AddStep("Set scope to Performance", () => scope.Value = RankingsScope.Performance); + AddStep("Set scope to Spotlights", () => scope.Value = RankingsScope.Spotlights); + AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score); + AddStep("Set scope to Country", () => scope.Value = RankingsScope.Country); + } + } +} diff --git a/osu.Game/Overlays/Rankings/HeaderTitle.cs b/osu.Game/Overlays/Rankings/HeaderTitle.cs new file mode 100644 index 0000000000..3f1feb10b8 --- /dev/null +++ b/osu.Game/Overlays/Rankings/HeaderTitle.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 osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Users; +using osu.Framework.Graphics; +using osuTK; +using osu.Game.Graphics; +using osu.Framework.Allocation; + +namespace osu.Game.Overlays.Rankings +{ + public class HeaderTitle : CompositeDrawable + { + private const int spacing = 10; + private const int flag_margin = 5; + private const int text_size = 40; + + public readonly Bindable Scope = new Bindable(); + public readonly Bindable Country = new Bindable(); + + private readonly SpriteText scopeText; + private readonly Container flagPlaceholder; + private readonly HeaderFlag flag; + + public HeaderTitle() + { + AutoSizeAxes = Axes.Both; + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(spacing, 0), + Children = new Drawable[] + { + flagPlaceholder = new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Bottom = flag_margin }, + Child = flag = new HeaderFlag + { + Size = new Vector2(30, 20), + }, + }, + scopeText = new SpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Light) + }, + new SpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Light), + Text = @"Ranking" + } + } + }; + + flag.Action += () => Country.Value = null; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + scopeText.Colour = colours.Lime; + } + + protected override void LoadComplete() + { + Scope.BindValueChanged(scope => onScopeChanged(scope.NewValue), true); + Country.BindValueChanged(onCountryChanged, true); + base.LoadComplete(); + } + + private void onScopeChanged(RankingsScope scope) + { + scopeText.Text = scope.ToString(); + + if (scope != RankingsScope.Performance) + Country.Value = null; + } + + private void onCountryChanged(ValueChangedEvent country) + { + if (country.NewValue == null) + { + flagPlaceholder.Hide(); + return; + } + + Scope.Value = RankingsScope.Performance; + + if (country.OldValue == null) + flagPlaceholder.Show(); + + flag.Country = country.NewValue; + } + } +} From e0bf579b18eaddd62a85c9333a2375403b2d2e14 Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 11 Sep 2019 15:35:47 -0700 Subject: [PATCH 1215/2815] Properly fix dialog overlay playing double samples on show/hide --- .../Containers/OsuFocusedOverlayContainer.cs | 6 ++---- osu.Game/Overlays/Dialog/PopupDialog.cs | 20 +------------------ osu.Game/Overlays/DialogOverlay.cs | 16 +++++++++++++-- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 2e8910213b..b117d71006 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -21,8 +21,6 @@ namespace osu.Game.Graphics.Containers private SampleChannel samplePopIn; private SampleChannel samplePopOut; - protected virtual bool PlaySamplesOnStateChange => true; - protected override bool BlockNonPositionalInput => true; /// @@ -126,12 +124,12 @@ namespace osu.Game.Graphics.Containers return; } - if (PlaySamplesOnStateChange) samplePopIn?.Play(); + samplePopIn?.Play(); if (BlockScreenWideMouse && DimMainContent) game?.AddBlockingOverlay(this); break; case Visibility.Hidden: - if (PlaySamplesOnStateChange) samplePopOut?.Play(); + samplePopOut?.Play(); if (BlockScreenWideMouse) game?.RemoveBlockingOverlay(this); break; } diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 1022edfe81..5c0ddb47b1 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -13,20 +13,17 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; -using osu.Game.Input.Bindings; using osuTK; using osuTK.Graphics; using osuTK.Input; namespace osu.Game.Overlays.Dialog { - public class PopupDialog : OsuFocusedOverlayContainer + public class PopupDialog : VisibilityContainer { public static readonly float ENTER_DURATION = 500; public static readonly float EXIT_DURATION = 200; - protected override bool BlockPositionalInput => false; - private readonly Vector2 ringSize = new Vector2(100f); private readonly Vector2 ringMinifiedSize = new Vector2(20f); private readonly Vector2 buttonsEnterSpacing = new Vector2(0f, 50f); @@ -202,18 +199,6 @@ namespace osu.Game.Overlays.Dialog }; } - public override bool OnPressed(GlobalAction action) - { - switch (action) - { - case GlobalAction.Select: - Buttons.OfType().FirstOrDefault()?.Click(); - return true; - } - - return base.OnPressed(action); - } - protected override bool OnKeyDown(KeyDownEvent e) { if (e.Repeat) return false; @@ -238,8 +223,6 @@ namespace osu.Game.Overlays.Dialog protected override void PopIn() { - base.PopIn(); - actionInvoked = false; // Reset various animations but only if the dialog animation fully completed @@ -263,7 +246,6 @@ namespace osu.Game.Overlays.Dialog // This is presumed to always be a sane default "cancel" action. buttonsContainer.Last().Click(); - base.PopOut(); content.FadeOut(EXIT_DURATION, Easing.InSine); } diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index aaae7bcf5c..0d3c96c984 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -5,6 +5,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Overlays.Dialog; using osu.Game.Graphics.Containers; +using osu.Game.Input.Bindings; +using System.Linq; namespace osu.Game.Overlays { @@ -41,8 +43,6 @@ namespace osu.Game.Overlays Show(); } - protected override bool PlaySamplesOnStateChange => false; - protected override bool BlockNonPositionalInput => true; private void onDialogOnStateChanged(VisibilityContainer dialog, Visibility v) @@ -74,5 +74,17 @@ namespace osu.Game.Overlays this.FadeOut(PopupDialog.EXIT_DURATION, Easing.InSine); } + + public override bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.Select: + currentDialog.Buttons.OfType().FirstOrDefault()?.Click(); + return true; + } + + return base.OnPressed(action); + } } } From 77ac186cf803e02861197032cfda700383933667 Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 11 Sep 2019 16:08:01 -0700 Subject: [PATCH 1216/2815] Add spacing to mod icons on leaderboards --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 1 + osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 1 + .../Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 008f8208eb..0b84cfc28a 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -215,6 +215,7 @@ namespace osu.Game.Online.Leaderboards Origin = Anchor.BottomRight, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, + Spacing = new Vector2(1), ChildrenEnumerable = score.Mods.Select(mod => new ModIcon(mod) { Scale = new Vector2(0.375f) }) }, }, diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 347522fb48..58f5f02956 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -171,6 +171,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { Direction = FillDirection.Horizontal, AutoSizeAxes = Axes.Both, + Spacing = new Vector2(1), ChildrenEnumerable = score.Mods.Select(m => new ModIcon(m) { AutoSizeAxes = Axes.Both, diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 6761d0f710..b9664d7c2f 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -172,7 +172,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores : this(new FillFlowContainer { AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal + Direction = FillDirection.Horizontal, + Spacing = new Vector2(1), }) { } From c3c2efe35ca966df9cbde9925417fedbe0000637 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 12 Sep 2019 05:03:59 +0300 Subject: [PATCH 1217/2815] Add ability to override text in PageTabItem --- osu.Game/Graphics/UserInterface/PageTabControl.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/PageTabControl.cs b/osu.Game/Graphics/UserInterface/PageTabControl.cs index a0d3745180..ddcb626701 100644 --- a/osu.Game/Graphics/UserInterface/PageTabControl.cs +++ b/osu.Game/Graphics/UserInterface/PageTabControl.cs @@ -63,7 +63,7 @@ namespace osu.Game.Graphics.UserInterface Margin = new MarginPadding { Top = 8, Bottom = 8 }, Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, - Text = (value as Enum)?.GetDescription() ?? value.ToString(), + Text = CreateText(), Font = OsuFont.GetFont(size: 14) }, box = new Box @@ -81,6 +81,8 @@ namespace osu.Game.Graphics.UserInterface Active.BindValueChanged(active => Text.Font = Text.Font.With(Typeface.Exo, weight: active.NewValue ? FontWeight.Bold : FontWeight.Medium), true); } + protected virtual string CreateText() => (Value as Enum)?.GetDescription() ?? Value.ToString(); + protected override bool OnHover(HoverEvent e) { if (!Active.Value) From 581508b8e75f51560398b06095d27e860afd89c1 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 12 Sep 2019 05:06:51 +0300 Subject: [PATCH 1218/2815] Implement RankingsRulesetSelector --- .../TestSceneRankingsRulesetSelector.cs | 42 ++++++++++++++ .../Rankings/RankingsRulesetSelector.cs | 56 +++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneRankingsRulesetSelector.cs create mode 100644 osu.Game/Overlays/Rankings/RankingsRulesetSelector.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsRulesetSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsRulesetSelector.cs new file mode 100644 index 0000000000..8ad10c6a63 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsRulesetSelector.cs @@ -0,0 +1,42 @@ +// 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.Overlays.Rankings; +using osu.Framework.Graphics; +using osu.Game.Rulesets; +using osu.Framework.Bindables; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Taiko; +using osu.Game.Rulesets.Catch; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneRankingsRulesetSelector : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(RankingsRulesetSelector), + }; + + public TestSceneRankingsRulesetSelector() + { + RankingsRulesetSelector selector; + var current = new Bindable(); + + Add(selector = new RankingsRulesetSelector + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Current = { BindTarget = current } + }); + + AddStep("Select osu!", () => current.Value = new OsuRuleset().RulesetInfo); + AddStep("Select mania", () => current.Value = new ManiaRuleset().RulesetInfo); + AddStep("Select taiko", () => current.Value = new TaikoRuleset().RulesetInfo); + AddStep("Select catch", () => current.Value = new CatchRuleset().RulesetInfo); + } + } +} diff --git a/osu.Game/Overlays/Rankings/RankingsRulesetSelector.cs b/osu.Game/Overlays/Rankings/RankingsRulesetSelector.cs new file mode 100644 index 0000000000..f1666507d1 --- /dev/null +++ b/osu.Game/Overlays/Rankings/RankingsRulesetSelector.cs @@ -0,0 +1,56 @@ +// 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.UserInterface; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; +using osuTK; +using System.Linq; + +namespace osu.Game.Overlays.Rankings +{ + public class RankingsRulesetSelector : PageTabControl + { + protected override TabItem CreateTabItem(RulesetInfo value) => new RankingsTabItem(value); + + protected override Dropdown CreateDropdown() => null; + + public RankingsRulesetSelector() + { + AutoSizeAxes = Axes.X; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, RulesetStore Rulesets) + { + foreach (var r in Rulesets.AvailableRulesets) + AddItem(r); + + AccentColour = colours.Lime; + + SelectTab(TabContainer.FirstOrDefault()); + } + + protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(20, 0), + }; + + private class RankingsTabItem : PageTabItem + { + public RankingsTabItem(RulesetInfo value) + : base(value) + { + } + + protected override string CreateText() => $"{Value.Name}"; + } + } +} From 4bfb681db6f816fd623e0ef407e86c41980c1c5a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 12 Sep 2019 05:16:56 +0300 Subject: [PATCH 1219/2815] CI fixes --- .../Visual/Online/TestSceneRankingsRulesetSelector.cs | 3 +-- osu.Game/Overlays/Rankings/RankingsRulesetSelector.cs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsRulesetSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsRulesetSelector.cs index 8ad10c6a63..84515bd3a4 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsRulesetSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsRulesetSelector.cs @@ -23,10 +23,9 @@ namespace osu.Game.Tests.Visual.Online public TestSceneRankingsRulesetSelector() { - RankingsRulesetSelector selector; var current = new Bindable(); - Add(selector = new RankingsRulesetSelector + Add(new RankingsRulesetSelector { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Overlays/Rankings/RankingsRulesetSelector.cs b/osu.Game/Overlays/Rankings/RankingsRulesetSelector.cs index f1666507d1..3d25e3995a 100644 --- a/osu.Game/Overlays/Rankings/RankingsRulesetSelector.cs +++ b/osu.Game/Overlays/Rankings/RankingsRulesetSelector.cs @@ -25,9 +25,9 @@ namespace osu.Game.Overlays.Rankings } [BackgroundDependencyLoader] - private void load(OsuColour colours, RulesetStore Rulesets) + private void load(OsuColour colours, RulesetStore rulesets) { - foreach (var r in Rulesets.AvailableRulesets) + foreach (var r in rulesets.AvailableRulesets) AddItem(r); AccentColour = colours.Lime; From b657e31f93b2b9fce626d0f151f6d36a565fd809 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 12 Sep 2019 05:26:10 +0300 Subject: [PATCH 1220/2815] Merge dependent changes --- .../Online/TestSceneRankingsHeaderFlag.cs | 65 +++++++++++ .../Online/TestSceneRankingsHeaderTitle.cs | 60 ++++++++++ .../TestSceneRankingsRulesetSelector.cs | 41 +++++++ .../Online/TestSceneRankingsScopeSelector.cs | 54 +++++++++ .../UserInterface/GradientLineTabControl.cs | 103 +++++++++++++++++ .../Graphics/UserInterface/PageTabControl.cs | 4 +- .../BeatmapSet/LeaderboardScopeSelector.cs | 69 +----------- osu.Game/Overlays/Rankings/HeaderFlag.cs | 55 +++++++++ osu.Game/Overlays/Rankings/HeaderTitle.cs | 105 ++++++++++++++++++ .../Rankings/RankingsRulesetSelector.cs | 56 ++++++++++ .../Rankings/RankingsScopeSelector.cs | 26 +++++ 11 files changed, 572 insertions(+), 66 deletions(-) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderFlag.cs create mode 100644 osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs create mode 100644 osu.Game.Tests/Visual/Online/TestSceneRankingsRulesetSelector.cs create mode 100644 osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs create mode 100644 osu.Game/Graphics/UserInterface/GradientLineTabControl.cs create mode 100644 osu.Game/Overlays/Rankings/HeaderFlag.cs create mode 100644 osu.Game/Overlays/Rankings/HeaderTitle.cs create mode 100644 osu.Game/Overlays/Rankings/RankingsRulesetSelector.cs create mode 100644 osu.Game/Overlays/Rankings/RankingsScopeSelector.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderFlag.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderFlag.cs new file mode 100644 index 0000000000..c9531e1016 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderFlag.cs @@ -0,0 +1,65 @@ +// 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.Sprites; +using osu.Game.Graphics; +using osu.Game.Overlays.Rankings; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneRankingsHeaderFlag : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(HeaderFlag), + }; + + public TestSceneRankingsHeaderFlag() + { + HeaderFlag flag; + SpriteText text; + + var countryA = new Country + { + FlagName = "BY", + FullName = "Belarus" + }; + + var countryB = new Country + { + FlagName = "US", + FullName = "United States" + }; + + AddRange(new Drawable[] + { + flag = new HeaderFlag + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(30, 20), + Country = countryA, + }, + text = new SpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = "Invoked", + Font = OsuFont.GetFont(size: 30), + Alpha = 0, + } + }); + + flag.Action += () => text.FadeIn().Then().FadeOut(1000, Easing.OutQuint); + + AddStep("Trigger click", () => flag.Click()); + AddStep("Change to country 2", () => flag.Country = countryB); + AddStep("Change to country 1", () => flag.Country = countryA); + } + } +} diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs new file mode 100644 index 0000000000..0f16b2592f --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs @@ -0,0 +1,60 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Game.Overlays.Rankings; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneRankingsHeaderTitle : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(HeaderFlag), + typeof(HeaderTitle), + }; + + public TestSceneRankingsHeaderTitle() + { + var countryBindable = new Bindable(); + var scope = new Bindable(); + + Add(new HeaderTitle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Country = { BindTarget = countryBindable }, + Scope = { BindTarget = scope }, + }); + + var countryA = new Country + { + FlagName = "BY", + FullName = "Belarus" + }; + + var countryB = new Country + { + FlagName = "US", + FullName = "United States" + }; + + AddStep("Set country", () => countryBindable.Value = countryA); + AddAssert("Check scope is Performance", () => scope.Value == RankingsScope.Performance); + AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score); + AddAssert("Check country is Null", () => countryBindable.Value == null); + + AddStep("Set country 1", () => countryBindable.Value = countryA); + AddStep("Set country 2", () => countryBindable.Value = countryB); + AddStep("Set null country", () => countryBindable.Value = null); + AddStep("Set scope to Performance", () => scope.Value = RankingsScope.Performance); + AddStep("Set scope to Spotlights", () => scope.Value = RankingsScope.Spotlights); + AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score); + AddStep("Set scope to Country", () => scope.Value = RankingsScope.Country); + } + } +} diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsRulesetSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsRulesetSelector.cs new file mode 100644 index 0000000000..84515bd3a4 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsRulesetSelector.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 System.Collections.Generic; +using osu.Game.Overlays.Rankings; +using osu.Framework.Graphics; +using osu.Game.Rulesets; +using osu.Framework.Bindables; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Taiko; +using osu.Game.Rulesets.Catch; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneRankingsRulesetSelector : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(RankingsRulesetSelector), + }; + + public TestSceneRankingsRulesetSelector() + { + var current = new Bindable(); + + Add(new RankingsRulesetSelector + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Current = { BindTarget = current } + }); + + AddStep("Select osu!", () => current.Value = new OsuRuleset().RulesetInfo); + AddStep("Select mania", () => current.Value = new ManiaRuleset().RulesetInfo); + AddStep("Select taiko", () => current.Value = new TaikoRuleset().RulesetInfo); + AddStep("Select catch", () => current.Value = new CatchRuleset().RulesetInfo); + } + } +} diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs new file mode 100644 index 0000000000..2081a6c0cb --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs @@ -0,0 +1,54 @@ +// 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.Bindables; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Overlays.Rankings; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneRankingsScopeSelector : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(RankingsScopeSelector), + }; + + private readonly Box background; + + public TestSceneRankingsScopeSelector() + { + var scope = new Bindable(); + + AddRange(new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + new RankingsScopeSelector + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Current = { BindTarget = scope } + } + }); + + AddStep(@"Select country", () => scope.Value = RankingsScope.Country); + AddStep(@"Select performance", () => scope.Value = RankingsScope.Performance); + AddStep(@"Select score", () => scope.Value = RankingsScope.Score); + AddStep(@"Select spotlights", () => scope.Value = RankingsScope.Spotlights); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.GreySeafoam; + } + } +} diff --git a/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs b/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs new file mode 100644 index 0000000000..7cd8d2c5bd --- /dev/null +++ b/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs @@ -0,0 +1,103 @@ +// 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.UserInterface; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osuTK; +using osu.Framework.Graphics.Shapes; +using osuTK.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; + +namespace osu.Game.Graphics.UserInterface +{ + public class GradientLineTabControl : PageTabControl + { + protected override Dropdown CreateDropdown() => null; + + protected Color4 LineColour + { + get => line.MainColour.Value; + set => line.MainColour.Value = value; + } + + private readonly GradientLine line; + + public GradientLineTabControl() + { + RelativeSizeAxes = Axes.X; + + AddInternal(line = new GradientLine + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + }); + } + + protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(20, 0), + }; + + private class GradientLine : GridContainer + { + public readonly Bindable MainColour = new Bindable(); + + private readonly Box left; + private readonly Box middle; + private readonly Box right; + + public GradientLine() + { + RelativeSizeAxes = Axes.X; + Size = new Vector2(0.8f, 1.5f); + + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(mode: GridSizeMode.Relative, size: 0.4f), + new Dimension(), + }; + + Content = new[] + { + new Drawable[] + { + left = new Box + { + RelativeSizeAxes = Axes.Both, + }, + middle = new Box + { + RelativeSizeAxes = Axes.Both, + }, + right = new Box + { + RelativeSizeAxes = Axes.Both, + }, + } + }; + } + + protected override void LoadComplete() + { + MainColour.BindValueChanged(onColourChanged, true); + base.LoadComplete(); + } + + private void onColourChanged(ValueChangedEvent colour) + { + left.Colour = ColourInfo.GradientHorizontal(colour.NewValue.Opacity(0), colour.NewValue); + middle.Colour = colour.NewValue; + right.Colour = ColourInfo.GradientHorizontal(colour.NewValue, colour.NewValue.Opacity(0)); + } + } + } +} diff --git a/osu.Game/Graphics/UserInterface/PageTabControl.cs b/osu.Game/Graphics/UserInterface/PageTabControl.cs index a0d3745180..ddcb626701 100644 --- a/osu.Game/Graphics/UserInterface/PageTabControl.cs +++ b/osu.Game/Graphics/UserInterface/PageTabControl.cs @@ -63,7 +63,7 @@ namespace osu.Game.Graphics.UserInterface Margin = new MarginPadding { Top = 8, Bottom = 8 }, Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, - Text = (value as Enum)?.GetDescription() ?? value.ToString(), + Text = CreateText(), Font = OsuFont.GetFont(size: 14) }, box = new Box @@ -81,6 +81,8 @@ namespace osu.Game.Graphics.UserInterface Active.BindValueChanged(active => Text.Font = Text.Font.With(Typeface.Exo, weight: active.NewValue ? FontWeight.Bold : FontWeight.Medium), true); } + protected virtual string CreateText() => (Value as Enum)?.GetDescription() ?? Value.ToString(); + protected override bool OnHover(HoverEvent e) { if (!Active.Value) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs index dcd58db427..e2a725ec46 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs @@ -1,60 +1,37 @@ // 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.UserInterface; using osu.Game.Screens.Select.Leaderboards; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osuTK; using osu.Game.Graphics.UserInterface; -using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Framework.Allocation; using osuTK.Graphics; -using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Graphics; namespace osu.Game.Overlays.BeatmapSet { - public class LeaderboardScopeSelector : PageTabControl + public class LeaderboardScopeSelector : GradientLineTabControl { protected override bool AddEnumEntriesAutomatically => false; - protected override Dropdown CreateDropdown() => null; - protected override TabItem CreateTabItem(BeatmapLeaderboardScope value) => new ScopeSelectorTabItem(value); public LeaderboardScopeSelector() { - RelativeSizeAxes = Axes.X; - AddItem(BeatmapLeaderboardScope.Global); AddItem(BeatmapLeaderboardScope.Country); AddItem(BeatmapLeaderboardScope.Friend); - - AddInternal(new GradientLine - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - }); } [BackgroundDependencyLoader] private void load(OsuColour colours) { AccentColour = colours.Blue; + LineColour = Color4.Gray; } - protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(20, 0), - }; - private class ScopeSelectorTabItem : PageTabItem { public ScopeSelectorTabItem(BeatmapLeaderboardScope value) @@ -77,43 +54,5 @@ namespace osu.Game.Overlays.BeatmapSet Text.FadeColour(Color4.White); } } - - private class GradientLine : GridContainer - { - public GradientLine() - { - RelativeSizeAxes = Axes.X; - Size = new Vector2(0.8f, 1.5f); - - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(mode: GridSizeMode.Relative, size: 0.4f), - new Dimension(), - }; - - Content = new[] - { - new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(Color4.Transparent, Color4.Gray), - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Gray, - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(Color4.Gray, Color4.Transparent), - }, - } - }; - } - } } } diff --git a/osu.Game/Overlays/Rankings/HeaderFlag.cs b/osu.Game/Overlays/Rankings/HeaderFlag.cs new file mode 100644 index 0000000000..6f641c74a5 --- /dev/null +++ b/osu.Game/Overlays/Rankings/HeaderFlag.cs @@ -0,0 +1,55 @@ +// 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.Game.Users.Drawables; +using osuTK.Graphics; +using osuTK; +using osu.Framework.Input.Events; +using System; + +namespace osu.Game.Overlays.Rankings +{ + public class HeaderFlag : UpdateableFlag + { + private const int duration = 200; + + public Action Action; + + private readonly SpriteIcon hoverIcon; + + public HeaderFlag() + { + AddInternal(hoverIcon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Depth = -1, + Alpha = 0, + Size = new Vector2(10), + Icon = FontAwesome.Solid.Times, + }); + } + + protected override bool OnHover(HoverEvent e) + { + hoverIcon.FadeIn(duration, Easing.OutQuint); + this.FadeColour(Color4.Gray, duration, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + hoverIcon.FadeOut(duration, Easing.OutQuint); + this.FadeColour(Color4.White, duration, Easing.OutQuint); + } + + protected override bool OnClick(ClickEvent e) + { + Action?.Invoke(); + return true; + } + } +} diff --git a/osu.Game/Overlays/Rankings/HeaderTitle.cs b/osu.Game/Overlays/Rankings/HeaderTitle.cs new file mode 100644 index 0000000000..3f1feb10b8 --- /dev/null +++ b/osu.Game/Overlays/Rankings/HeaderTitle.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 osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Users; +using osu.Framework.Graphics; +using osuTK; +using osu.Game.Graphics; +using osu.Framework.Allocation; + +namespace osu.Game.Overlays.Rankings +{ + public class HeaderTitle : CompositeDrawable + { + private const int spacing = 10; + private const int flag_margin = 5; + private const int text_size = 40; + + public readonly Bindable Scope = new Bindable(); + public readonly Bindable Country = new Bindable(); + + private readonly SpriteText scopeText; + private readonly Container flagPlaceholder; + private readonly HeaderFlag flag; + + public HeaderTitle() + { + AutoSizeAxes = Axes.Both; + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(spacing, 0), + Children = new Drawable[] + { + flagPlaceholder = new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Bottom = flag_margin }, + Child = flag = new HeaderFlag + { + Size = new Vector2(30, 20), + }, + }, + scopeText = new SpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Light) + }, + new SpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Light), + Text = @"Ranking" + } + } + }; + + flag.Action += () => Country.Value = null; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + scopeText.Colour = colours.Lime; + } + + protected override void LoadComplete() + { + Scope.BindValueChanged(scope => onScopeChanged(scope.NewValue), true); + Country.BindValueChanged(onCountryChanged, true); + base.LoadComplete(); + } + + private void onScopeChanged(RankingsScope scope) + { + scopeText.Text = scope.ToString(); + + if (scope != RankingsScope.Performance) + Country.Value = null; + } + + private void onCountryChanged(ValueChangedEvent country) + { + if (country.NewValue == null) + { + flagPlaceholder.Hide(); + return; + } + + Scope.Value = RankingsScope.Performance; + + if (country.OldValue == null) + flagPlaceholder.Show(); + + flag.Country = country.NewValue; + } + } +} diff --git a/osu.Game/Overlays/Rankings/RankingsRulesetSelector.cs b/osu.Game/Overlays/Rankings/RankingsRulesetSelector.cs new file mode 100644 index 0000000000..3d25e3995a --- /dev/null +++ b/osu.Game/Overlays/Rankings/RankingsRulesetSelector.cs @@ -0,0 +1,56 @@ +// 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.UserInterface; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; +using osuTK; +using System.Linq; + +namespace osu.Game.Overlays.Rankings +{ + public class RankingsRulesetSelector : PageTabControl + { + protected override TabItem CreateTabItem(RulesetInfo value) => new RankingsTabItem(value); + + protected override Dropdown CreateDropdown() => null; + + public RankingsRulesetSelector() + { + AutoSizeAxes = Axes.X; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, RulesetStore rulesets) + { + foreach (var r in rulesets.AvailableRulesets) + AddItem(r); + + AccentColour = colours.Lime; + + SelectTab(TabContainer.FirstOrDefault()); + } + + protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(20, 0), + }; + + private class RankingsTabItem : PageTabItem + { + public RankingsTabItem(RulesetInfo value) + : base(value) + { + } + + protected override string CreateText() => $"{Value.Name}"; + } + } +} diff --git a/osu.Game/Overlays/Rankings/RankingsScopeSelector.cs b/osu.Game/Overlays/Rankings/RankingsScopeSelector.cs new file mode 100644 index 0000000000..2095bcc61c --- /dev/null +++ b/osu.Game/Overlays/Rankings/RankingsScopeSelector.cs @@ -0,0 +1,26 @@ +// 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.Graphics.UserInterface; +using osu.Framework.Allocation; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Rankings +{ + public class RankingsScopeSelector : GradientLineTabControl + { + [BackgroundDependencyLoader] + private void load() + { + AccentColour = LineColour = Color4.Black; + } + } + + public enum RankingsScope + { + Performance, + Spotlights, + Score, + Country + } +} From 0c6c8fdcd0e97e74979ae9d248241050f0bf5149 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 12 Sep 2019 05:53:18 +0300 Subject: [PATCH 1221/2815] Implement RankingsHeader component --- .../Visual/Online/TestSceneRankingsHeader.cs | 52 ++++++++++++ osu.Game/Overlays/Rankings/RankingsHeader.cs | 84 +++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs create mode 100644 osu.Game/Overlays/Rankings/RankingsHeader.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs new file mode 100644 index 0000000000..81534e7d44 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.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; +using System.Collections.Generic; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Overlays.Rankings; +using osu.Game.Rulesets; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneRankingsHeader : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(HeaderFlag), + typeof(HeaderTitle), + typeof(RankingsRulesetSelector), + typeof(RankingsScopeSelector), + typeof(RankingsHeader), + }; + + public TestSceneRankingsHeader() + { + var countryBindable = new Bindable(); + var ruleset = new Bindable(); + var scope = new Bindable(); + + Add(new RankingsHeader + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scope = { BindTarget = scope }, + Country = { BindTarget = countryBindable }, + Ruleset = { BindTarget = ruleset }, + }); + + var country = new Country + { + FlagName = "BY", + FullName = "Belarus" + }; + + AddStep("Set country", () => countryBindable.Value = country); + AddAssert("Check scope is Performance", () => scope.Value == RankingsScope.Performance); + AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score); + AddAssert("Check country is Null", () => countryBindable.Value == null); + } + } +} diff --git a/osu.Game/Overlays/Rankings/RankingsHeader.cs b/osu.Game/Overlays/Rankings/RankingsHeader.cs new file mode 100644 index 0000000000..7fdc2ab9c8 --- /dev/null +++ b/osu.Game/Overlays/Rankings/RankingsHeader.cs @@ -0,0 +1,84 @@ +// 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.Containers; +using osu.Framework.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Framework.Bindables; +using osu.Game.Rulesets; +using osu.Game.Users; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; + +namespace osu.Game.Overlays.Rankings +{ + public class RankingsHeader : CompositeDrawable + { + private const int content_height = 250; + + public readonly Bindable Scope = new Bindable(); + public readonly Bindable Ruleset = new Bindable(); + public readonly Bindable Country = new Bindable(); + + public RankingsHeader() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + AddInternal(new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new RankingsRulesetSelector + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Current = { BindTarget = Ruleset } + }, + new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + Height = content_height, + Children = new Drawable[] + { + new HeaderBackground(), + new RankingsScopeSelector + { + Margin = new MarginPadding { Top = 10 }, + Current = { BindTarget = Scope } + }, + new HeaderTitle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scope = { BindTarget = Scope }, + Country = { BindTarget = Country }, + } + } + } + } + }); + } + + public class HeaderBackground : Sprite + { + public HeaderBackground() + { + RelativeSizeAxes = Axes.Both; + FillMode = FillMode.Fill; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + Texture = textures.Get(@"Headers/rankings"); + } + } + } +} From acdd26422dc4c006f88b2eb0028113622b23a4ad Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 12 Sep 2019 06:36:17 +0300 Subject: [PATCH 1222/2815] Implement Spotlights logic --- .../Visual/Online/TestSceneRankingsHeader.cs | 18 +++++ osu.Game/Overlays/Rankings/RankingsHeader.cs | 74 +++++++++++++++++-- osu.Game/Overlays/Rankings/Spotlight.cs | 18 +++++ 3 files changed, 102 insertions(+), 8 deletions(-) create mode 100644 osu.Game/Overlays/Rankings/Spotlight.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs index 81534e7d44..e8ed94b59c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs @@ -35,6 +35,24 @@ namespace osu.Game.Tests.Visual.Online Scope = { BindTarget = scope }, Country = { BindTarget = countryBindable }, Ruleset = { BindTarget = ruleset }, + Spotlights = new[] + { + new Spotlight + { + Id = 1, + Text = "Spotlight 1" + }, + new Spotlight + { + Id = 2, + Text = "Spotlight 2" + }, + new Spotlight + { + Id = 3, + Text = "Spotlight 4" + } + } }); var country = new Country diff --git a/osu.Game/Overlays/Rankings/RankingsHeader.cs b/osu.Game/Overlays/Rankings/RankingsHeader.cs index 7fdc2ab9c8..6d55e92502 100644 --- a/osu.Game/Overlays/Rankings/RankingsHeader.cs +++ b/osu.Game/Overlays/Rankings/RankingsHeader.cs @@ -4,23 +4,38 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Framework.Allocation; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; using osu.Framework.Bindables; using osu.Game.Rulesets; using osu.Game.Users; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osuTK; +using osu.Game.Graphics.UserInterface; +using System.Collections.Generic; namespace osu.Game.Overlays.Rankings { public class RankingsHeader : CompositeDrawable { private const int content_height = 250; + private const int dropdown_height = 50; + private const int spacing = 20; + private const int title_offset = 30; + private const int duration = 200; + + public IEnumerable Spotlights + { + get => dropdown.Items; + set => dropdown.Items = value; + } public readonly Bindable Scope = new Bindable(); public readonly Bindable Ruleset = new Bindable(); public readonly Bindable Country = new Bindable(); + public readonly Bindable Spotlight = new Bindable(); + + private readonly Container dropdownPlaceholder; + private readonly OsuDropdown dropdown; public RankingsHeader() { @@ -47,29 +62,72 @@ namespace osu.Game.Overlays.Rankings Height = content_height, Children = new Drawable[] { - new HeaderBackground(), + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = new HeaderBackground(), + }, new RankingsScopeSelector { Margin = new MarginPadding { Top = 10 }, Current = { BindTarget = Scope } }, - new HeaderTitle + new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Scope = { BindTarget = Scope }, - Country = { BindTarget = Country }, - } + Y = title_offset, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, spacing), + Children = new Drawable[] + { + new HeaderTitle + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Scope = { BindTarget = Scope }, + Country = { BindTarget = Country }, + }, + dropdownPlaceholder = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + Height = dropdown_height, + Width = 0.8f, + AlwaysPresent = true, + Child = dropdown = new OsuDropdown + { + RelativeSizeAxes = Axes.X, + Current = { BindTarget = Spotlight }, + } + } + } + }, } } } }); } - public class HeaderBackground : Sprite + protected override void LoadComplete() + { + Scope.BindValueChanged(scope => onScopeChanged(scope.NewValue), true); + base.LoadComplete(); + } + + private void onScopeChanged(RankingsScope scope) => + dropdownPlaceholder.FadeTo(scope == RankingsScope.Spotlights ? 1 : 0, duration, Easing.OutQuint); + + private class HeaderBackground : Sprite { public HeaderBackground() { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; RelativeSizeAxes = Axes.Both; FillMode = FillMode.Fill; } diff --git a/osu.Game/Overlays/Rankings/Spotlight.cs b/osu.Game/Overlays/Rankings/Spotlight.cs new file mode 100644 index 0000000000..e956b4f449 --- /dev/null +++ b/osu.Game/Overlays/Rankings/Spotlight.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; + +namespace osu.Game.Overlays.Rankings +{ + public class Spotlight + { + [JsonProperty("id")] + public int Id; + + [JsonProperty("text")] + public string Text; + + public override string ToString() => Text; + } +} From b1c0b080ecced55aecae5aa4f28078418d8ecbd7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 12 Sep 2019 13:52:27 +0900 Subject: [PATCH 1223/2815] Fix bad hit explosion anchoring --- osu.Game.Rulesets.Mania/UI/Column.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index fa14a0a293..8021660f77 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -11,6 +11,8 @@ using osu.Framework.Allocation; 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; @@ -90,7 +92,11 @@ namespace osu.Game.Rulesets.Mania.UI Bottom = dir.NewValue == ScrollingDirection.Down ? ManiaStage.HIT_TARGET_POSITION : 0, }; - explosionContainer.Y = dir.NewValue == ScrollingDirection.Down ? -NotePiece.NOTE_HEIGHT : 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); @@ -168,7 +174,7 @@ namespace osu.Game.Rulesets.Mania.UI explosionContainer.Add(new HitExplosion(judgedObject.AccentColour.Value, judgedObject is DrawableHoldNoteTick) { Anchor = Direction.Value == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre, - Origin = Direction.Value == ScrollingDirection.Up ? Anchor.BottomCentre : Anchor.TopCentre, + Origin = Anchor.Centre }); } From bbf80f63aa920b3a86b51142cd2b54d782fe5182 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 12 Sep 2019 13:53:05 +0900 Subject: [PATCH 1224/2815] Publicly expose column width constant --- osu.Game.Rulesets.Mania/UI/Column.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 8021660f77..910342a3b0 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.UI { public class Column : ScrollingPlayfield, IKeyBindingHandler, IHasAccentColour { - private const float column_width = 45; + public const float COLUMN_WIDTH = 45; private const float special_column_width = 70; /// @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Mania.UI Index = index; RelativeSizeAxes = Axes.Y; - Width = column_width; + Width = COLUMN_WIDTH; Masking = true; CornerRadius = 5; @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Mania.UI isSpecial = value; - Width = isSpecial ? special_column_width : column_width; + Width = isSpecial ? special_column_width : COLUMN_WIDTH; } } From f9c969788a758f913001047dca99aff00a853de8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Sep 2019 13:56:23 +0900 Subject: [PATCH 1225/2815] Fix keys not reaching full brightness as soon as they should --- osu.Game/Screens/Play/KeyCounter.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/KeyCounter.cs b/osu.Game/Screens/Play/KeyCounter.cs index 88a62ac8d4..ad5fcfcf24 100644 --- a/osu.Game/Screens/Play/KeyCounter.cs +++ b/osu.Game/Screens/Play/KeyCounter.cs @@ -130,15 +130,17 @@ namespace osu.Game.Screens.Play private void updateGlowSprite(bool show) { + double remainingFadeTime = FadeTime * (1 - glowSprite.Alpha); + if (show) { - glowSprite.FadeIn(FadeTime); - textLayer.FadeColour(KeyDownTextColor, FadeTime); + glowSprite.FadeIn(remainingFadeTime); + textLayer.FadeColour(KeyDownTextColor, remainingFadeTime); } else { - glowSprite.FadeOut(FadeTime); - textLayer.FadeColour(KeyUpTextColor, FadeTime); + glowSprite.FadeOut(remainingFadeTime); + textLayer.FadeColour(KeyUpTextColor, remainingFadeTime); } } From b941f12688eb2bb309e8d37cd3fc5f29beb582a1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 12 Sep 2019 14:09:21 +0900 Subject: [PATCH 1226/2815] Cleanup --- .../TestSceneHitExplosion.cs | 22 ------------------- osu.Game.Rulesets.Mania/UI/HitExplosion.cs | 8 ++----- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHitExplosion.cs index 12159ca239..26a1b1b1ec 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHitExplosion.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHitExplosion.cs @@ -5,11 +5,9 @@ using System; using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Graphics; -using osu.Game.Rulesets.Mania.Objects; 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.Scoring; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; using osuTK; @@ -60,25 +58,5 @@ namespace osu.Game.Rulesets.Mania.Tests }); }, 100); } - - private class TestNote : DrawableNote - { - protected override void CheckForResult(bool userTriggered, double timeOffset) - { - if (!userTriggered) - { - // force success - ApplyResult(r => r.Type = HitResult.Great); - } - else - base.CheckForResult(userTriggered, timeOffset); - } - - public TestNote(Note hitObject) - : base(hitObject) - { - AccentColour.Value = Color4.Pink; - } - } } } diff --git a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs index 21726206f1..ccbff226a9 100644 --- a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs @@ -18,8 +18,6 @@ namespace osu.Game.Rulesets.Mania.UI private readonly CircularContainer largeFaint; private readonly CircularContainer mainGlow1; - private readonly CircularContainer mainGlow2; - private readonly CircularContainer mainGlow3; public HitExplosion(Color4 objectColour, bool isSmall = false) { @@ -36,8 +34,6 @@ namespace osu.Game.Rulesets.Mania.UI const float roundness = 80; - const float opacity = 1; - const float initial_height = 10; var colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1); @@ -76,7 +72,7 @@ namespace osu.Game.Rulesets.Mania.UI Radius = 50, }, }, - mainGlow2 = new CircularContainer + new CircularContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -93,7 +89,7 @@ namespace osu.Game.Rulesets.Mania.UI Radius = 40, }, }, - mainGlow3 = new CircularContainer + new CircularContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, From 158737e001e46bdfd4f8ed85a48ab53ba81f70a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Sep 2019 14:27:29 +0900 Subject: [PATCH 1227/2815] Remove FadeTime customisation Also adjusts fade transitions to feel better, especially in fast forward scenarios. --- .../Visual/Gameplay/TestSceneKeyCounter.cs | 1 - osu.Game/Screens/Play/HUDOverlay.cs | 1 - osu.Game/Screens/Play/KeyCounter.cs | 14 +++++------ osu.Game/Screens/Play/KeyCounterDisplay.cs | 25 ++----------------- 4 files changed, 9 insertions(+), 32 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs index 18088a9a5b..4643b82792 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs @@ -44,7 +44,6 @@ namespace osu.Game.Tests.Visual.Gameplay Key key = (Key)((int)Key.A + RNG.Next(26)); kc.Add(new KeyCounterKeyboard(key)); }); - AddSliderStep("Fade time", 0, 200, 50, v => kc.FadeTime = v); Key testKey = ((KeyCounterKeyboard)kc.Children.First()).Key; double time1 = 0; diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index eee7235a6e..0f9edf5606 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -231,7 +231,6 @@ namespace osu.Game.Screens.Play protected virtual KeyCounterDisplay CreateKeyCounter() => new KeyCounterDisplay { - FadeTime = 50, Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, Margin = new MarginPadding(10), diff --git a/osu.Game/Screens/Play/KeyCounter.cs b/osu.Game/Screens/Play/KeyCounter.cs index ad5fcfcf24..1930369299 100644 --- a/osu.Game/Screens/Play/KeyCounter.cs +++ b/osu.Game/Screens/Play/KeyCounter.cs @@ -65,7 +65,7 @@ namespace osu.Game.Screens.Play //further: change default values here and in KeyCounterCollection if needed, instead of passing them in every constructor public Color4 KeyDownTextColor { get; set; } = Color4.DarkGray; public Color4 KeyUpTextColor { get; set; } = Color4.White; - public int FadeTime { get; set; } + public double FadeTime { get; set; } protected KeyCounter(string name) { @@ -130,17 +130,17 @@ namespace osu.Game.Screens.Play private void updateGlowSprite(bool show) { - double remainingFadeTime = FadeTime * (1 - glowSprite.Alpha); - if (show) { - glowSprite.FadeIn(remainingFadeTime); - textLayer.FadeColour(KeyDownTextColor, remainingFadeTime); + double remainingFadeTime = FadeTime * (1 - glowSprite.Alpha); + glowSprite.FadeIn(remainingFadeTime, Easing.OutQuint); + textLayer.FadeColour(KeyDownTextColor, remainingFadeTime, Easing.OutQuint); } else { - glowSprite.FadeOut(remainingFadeTime); - textLayer.FadeColour(KeyUpTextColor, remainingFadeTime); + double remainingFadeTime = 8 * FadeTime * glowSprite.Alpha; + glowSprite.FadeOut(remainingFadeTime, Easing.OutQuint); + textLayer.FadeColour(KeyUpTextColor, remainingFadeTime, Easing.OutQuint); } } diff --git a/osu.Game/Screens/Play/KeyCounterDisplay.cs b/osu.Game/Screens/Play/KeyCounterDisplay.cs index d5967f5899..6b8fa5c75b 100644 --- a/osu.Game/Screens/Play/KeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/KeyCounterDisplay.cs @@ -17,6 +17,7 @@ namespace osu.Game.Screens.Play public class KeyCounterDisplay : FillFlowContainer { private const int duration = 100; + private const double key_fade_time = 80; public readonly Bindable Visible = new Bindable(true); private readonly Bindable configVisibility = new Bindable(); @@ -33,17 +34,11 @@ namespace osu.Game.Screens.Play base.Add(key); key.IsCounting = IsCounting; - key.FadeTime = FadeTime; + key.FadeTime = key_fade_time; key.KeyDownTextColor = KeyDownTextColor; key.KeyUpTextColor = KeyUpTextColor; } - public void ResetCount() - { - foreach (var counter in Children) - counter.ResetCount(); - } - [BackgroundDependencyLoader] private void load(OsuConfigManager config) { @@ -68,22 +63,6 @@ namespace osu.Game.Screens.Play } } - private int fadeTime; - - public int FadeTime - { - get => fadeTime; - set - { - if (value != fadeTime) - { - fadeTime = value; - foreach (var child in Children) - child.FadeTime = value; - } - } - } - private Color4 keyDownTextColor = Color4.DarkGray; public Color4 KeyDownTextColor From cb0cf6e2c5e14e37d37716a79cec9b02b89d2aae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Sep 2019 14:27:52 +0900 Subject: [PATCH 1228/2815] Remove reset functions --- osu.Game/Screens/Play/KeyCounter.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Screens/Play/KeyCounter.cs b/osu.Game/Screens/Play/KeyCounter.cs index 1930369299..ad0858184e 100644 --- a/osu.Game/Screens/Play/KeyCounter.cs +++ b/osu.Game/Screens/Play/KeyCounter.cs @@ -144,12 +144,6 @@ namespace osu.Game.Screens.Play } } - public void ResetCount() - { - CountPresses = 0; - states.Clear(); - } - protected override void Update() { base.Update(); From 0cdf125c1e8f64fc5397fc1701e42cb21d1adaf2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Sep 2019 15:41:53 +0900 Subject: [PATCH 1229/2815] Handle key counter rewinding in a better way Use ElapsedFrameTime rather than storing state data --- .../Visual/Gameplay/TestSceneKeyCounter.cs | 40 ++------------- osu.Game/Rulesets/UI/RulesetInputManager.cs | 4 +- osu.Game/Screens/Play/KeyCounter.cs | 49 ++++++------------- osu.Game/Screens/Play/KeyCounterAction.cs | 22 ++++++--- osu.Game/Screens/Play/KeyCounterDisplay.cs | 5 -- osu.Game/Screens/Play/KeyCounterKeyboard.cs | 7 ++- osu.Game/Screens/Play/KeyCounterMouse.cs | 7 ++- 7 files changed, 50 insertions(+), 84 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs index 4643b82792..6783a36ac3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs @@ -7,7 +7,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.MathUtils; -using osu.Framework.Timing; using osu.Game.Screens.Play; using osuTK.Input; @@ -25,14 +24,15 @@ namespace osu.Game.Tests.Visual.Gameplay public TestSceneKeyCounter() { - KeyCounterKeyboard rewindTestKeyCounterKeyboard; + KeyCounterKeyboard testCounter; + KeyCounterDisplay kc = new KeyCounterDisplay { Origin = Anchor.Centre, Anchor = Anchor.Centre, Children = new KeyCounter[] { - rewindTestKeyCounterKeyboard = new KeyCounterKeyboard(Key.X), + testCounter = new KeyCounterKeyboard(Key.X), new KeyCounterKeyboard(Key.X), new KeyCounterMouse(MouseButton.Left), new KeyCounterMouse(MouseButton.Right), @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.Gameplay InputManager.ReleaseKey(testKey); }); - AddAssert($"Check {testKey} counter after keypress", () => rewindTestKeyCounterKeyboard.CountPresses == 1); + AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 1); AddStep($"Press {testKey} key", () => { @@ -63,39 +63,9 @@ namespace osu.Game.Tests.Visual.Gameplay time1 = Clock.CurrentTime; }); - AddAssert($"Check {testKey} counter after keypress", () => rewindTestKeyCounterKeyboard.CountPresses == 2); - - IFrameBasedClock oldClock = null; - - AddStep($"Rewind {testKey} counter once", () => - { - oldClock = rewindTestKeyCounterKeyboard.Clock; - rewindTestKeyCounterKeyboard.Clock = new FramedOffsetClock(new FixedClock(time1 - 10)); - }); - - AddAssert($"Check {testKey} counter after rewind", () => rewindTestKeyCounterKeyboard.CountPresses == 1); - - AddStep($"Rewind {testKey} counter to zero", () => rewindTestKeyCounterKeyboard.Clock = new FramedOffsetClock(new FixedClock(0))); - - AddAssert($"Check {testKey} counter after rewind", () => rewindTestKeyCounterKeyboard.CountPresses == 0); - - AddStep("Restore clock", () => rewindTestKeyCounterKeyboard.Clock = oldClock); + AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 2); Add(kc); } - - private class FixedClock : IClock - { - private readonly double time; - - public FixedClock(double time) - { - this.time = time; - } - - public double CurrentTime => time; - public double Rate => 1; - public bool IsRunning => false; - } } } diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index e25c3bd0e7..98e27240d3 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -137,9 +137,9 @@ namespace osu.Game.Rulesets.UI { } - public bool OnPressed(T action) => Target.Children.OfType>().Any(c => c.OnPressed(action)); + public bool OnPressed(T action) => Target.Children.OfType>().Any(c => c.OnPressed(action, Clock.ElapsedFrameTime > 0)); - public bool OnReleased(T action) => Target.Children.OfType>().Any(c => c.OnReleased(action)); + public bool OnReleased(T action) => Target.Children.OfType>().Any(c => c.OnReleased(action, Clock.ElapsedFrameTime > 0)); } #endregion diff --git a/osu.Game/Screens/Play/KeyCounter.cs b/osu.Game/Screens/Play/KeyCounter.cs index ad0858184e..1daf89d8f9 100644 --- a/osu.Game/Screens/Play/KeyCounter.cs +++ b/osu.Game/Screens/Play/KeyCounter.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 System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -22,9 +20,6 @@ namespace osu.Game.Screens.Play private Container textLayer; private SpriteText countSpriteText; - private readonly List states = new List(); - private KeyCounterState currentState; - public bool IsCounting { get; set; } = true; private int countPresses; @@ -52,16 +47,26 @@ namespace osu.Game.Screens.Play { isLit = value; updateGlowSprite(value); - - if (value && IsCounting) - { - CountPresses++; - saveState(); - } } } } + public void Increment() + { + if (!IsCounting) + return; + + CountPresses++; + } + + public void Decrement() + { + if (!IsCounting) + return; + + CountPresses--; + } + //further: change default values here and in KeyCounterCollection if needed, instead of passing them in every constructor public Color4 KeyDownTextColor { get; set; } = Color4.DarkGray; public Color4 KeyUpTextColor { get; set; } = Color4.White; @@ -143,27 +148,5 @@ namespace osu.Game.Screens.Play textLayer.FadeColour(KeyUpTextColor, remainingFadeTime, Easing.OutQuint); } } - - protected override void Update() - { - base.Update(); - - if (currentState?.Time > Clock.CurrentTime) - restoreStateTo(Clock.CurrentTime); - } - - private void saveState() - { - if (currentState == null || currentState.Time < Clock.CurrentTime) - states.Add(currentState = new KeyCounterState(Clock.CurrentTime, CountPresses)); - } - - private void restoreStateTo(double time) - { - states.RemoveAll(state => state.Time > time); - - currentState = states.LastOrDefault(); - CountPresses = currentState?.Count ?? 0; - } } } diff --git a/osu.Game/Screens/Play/KeyCounterAction.cs b/osu.Game/Screens/Play/KeyCounterAction.cs index 8deac653ad..f60ad7aa5a 100644 --- a/osu.Game/Screens/Play/KeyCounterAction.cs +++ b/osu.Game/Screens/Play/KeyCounterAction.cs @@ -1,11 +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 osu.Framework.Input.Bindings; - namespace osu.Game.Screens.Play { - public class KeyCounterAction : KeyCounter, IKeyBindingHandler + public class KeyCounterAction : KeyCounter where T : struct { public T Action { get; } @@ -16,15 +14,25 @@ namespace osu.Game.Screens.Play Action = action; } - public bool OnPressed(T action) + public bool OnPressed(T action, bool forwards) { - if (action.Equals(Action)) IsLit = true; + if (!action.Equals(Action)) + return false; + + IsLit = true; + if (forwards) + Increment(); return false; } - public bool OnReleased(T action) + public bool OnReleased(T action, bool forwards) { - if (action.Equals(Action)) IsLit = false; + if (!action.Equals(Action)) + return false; + + IsLit = false; + if (!forwards) + Decrement(); return false; } } diff --git a/osu.Game/Screens/Play/KeyCounterDisplay.cs b/osu.Game/Screens/Play/KeyCounterDisplay.cs index 6b8fa5c75b..1edb95ca46 100644 --- a/osu.Game/Screens/Play/KeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/KeyCounterDisplay.cs @@ -102,11 +102,6 @@ namespace osu.Game.Screens.Play private Receptor receptor; - public Receptor GetReceptor() - { - return receptor ?? (receptor = new Receptor(this)); - } - public void SetReceptor(Receptor receptor) { if (this.receptor != null) diff --git a/osu.Game/Screens/Play/KeyCounterKeyboard.cs b/osu.Game/Screens/Play/KeyCounterKeyboard.cs index d9b6dca79d..29188b6b59 100644 --- a/osu.Game/Screens/Play/KeyCounterKeyboard.cs +++ b/osu.Game/Screens/Play/KeyCounterKeyboard.cs @@ -18,7 +18,12 @@ namespace osu.Game.Screens.Play protected override bool OnKeyDown(KeyDownEvent e) { - if (e.Key == Key) IsLit = true; + if (e.Key == Key) + { + IsLit = true; + Increment(); + } + return base.OnKeyDown(e); } diff --git a/osu.Game/Screens/Play/KeyCounterMouse.cs b/osu.Game/Screens/Play/KeyCounterMouse.cs index 95fa58e5c0..828441de6e 100644 --- a/osu.Game/Screens/Play/KeyCounterMouse.cs +++ b/osu.Game/Screens/Play/KeyCounterMouse.cs @@ -36,7 +36,12 @@ namespace osu.Game.Screens.Play protected override bool OnMouseDown(MouseDownEvent e) { - if (e.Button == Button) IsLit = true; + if (e.Button == Button) + { + IsLit = true; + Increment(); + } + return base.OnMouseDown(e); } From 831d04f339402020b0467bd38a7b940ab8031638 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Sep 2019 15:48:07 +0900 Subject: [PATCH 1230/2815] Don't use gameplay clock in KeyCounter --- osu.Game/Screens/Play/KeyCounter.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/KeyCounter.cs b/osu.Game/Screens/Play/KeyCounter.cs index 1daf89d8f9..f4109a63d0 100644 --- a/osu.Game/Screens/Play/KeyCounter.cs +++ b/osu.Game/Screens/Play/KeyCounter.cs @@ -78,11 +78,8 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader(true)] - private void load(TextureStore textures, GameplayClock clock) + private void load(TextureStore textures) { - if (clock != null) - Clock = clock; - Children = new Drawable[] { buttonSprite = new Sprite From 09a0c9f4d2fa8224651ff91d05881fe31f118ac1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Sep 2019 18:10:50 +0900 Subject: [PATCH 1231/2815] Add key counter rewind tests --- osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs | 9 ++++++++- .../Visual/Gameplay/TestSceneGameplayRewinding.cs | 7 +++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index 452ac859de..e2b620ea98 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -21,7 +21,12 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void AddCheckSteps() { AddUntilStep("score above zero", () => ((ScoreAccessiblePlayer)Player).ScoreProcessor.TotalScore.Value > 0); - AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0)); + AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 5)); + AddStep("rewind", () => + { + ((ScoreAccessiblePlayer)Player).GameplayClockContainer.Seek(0); + }); + AddUntilStep("key counter counted no", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); } private class ScoreAccessiblePlayer : TestPlayer @@ -29,6 +34,8 @@ namespace osu.Game.Tests.Visual.Gameplay public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; public new HUDOverlay HUDOverlay => base.HUDOverlay; + public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; + public ScoreAccessiblePlayer() : base(false, false) { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs index 237fee1594..ffc025a942 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs @@ -14,6 +14,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osuTK; @@ -47,9 +48,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for track to start running", () => track.IsRunning); addSeekStep(3000); AddAssert("all judged", () => player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged)); + AddUntilStep("key counter counted keys", () => player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses >= 7)); AddStep("clear results", () => player.AppliedResults.Clear()); addSeekStep(0); AddAssert("none judged", () => player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged)); + AddUntilStep("key counters reset", () => player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); AddAssert("no results triggered", () => player.AppliedResults.Count == 0); } @@ -90,6 +93,10 @@ namespace osu.Game.Tests.Visual.Gameplay { public readonly List AppliedResults = new List(); + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + public new HUDOverlay HUDOverlay => base.HUDOverlay; + public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; public new DrawableRuleset DrawableRuleset => base.DrawableRuleset; From acdfeef1dc44fcacad192926163e71ced4adc1e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Sep 2019 18:33:46 +0900 Subject: [PATCH 1232/2815] Improve how osu!catch stores and replays key actions --- .../Replays/CatchAutoGenerator.cs | 29 ++++++++++------ .../Replays/CatchFramedReplayInputHandler.cs | 15 ++------- .../Replays/CatchReplayFrame.cs | 33 ++++++++++++++++--- .../Replays/ManiaReplayFrame.cs | 2 +- .../Replays/OsuReplayFrame.cs | 8 ++--- .../Replays/TaikoReplayFrame.cs | 10 +++--- .../Replays/Types/IConvertibleReplayFrame.cs | 5 +-- osu.Game/Scoring/Legacy/LegacyScoreParser.cs | 15 ++++++--- 8 files changed, 74 insertions(+), 43 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index 8dd00756f2..4ea1f22006 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -27,6 +27,8 @@ namespace osu.Game.Rulesets.Catch.Replays protected Replay Replay; + private CatchReplayFrame currentFrame; + public override Replay Generate() { // todo: add support for HT DT @@ -36,7 +38,7 @@ namespace osu.Game.Rulesets.Catch.Replays double lastTime = 0; // Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled - Replay.Frames.Add(new CatchReplayFrame(-100000, lastPosition)); + addFrame(-100000, lastPosition); void moveToNext(CatchHitObject h) { @@ -58,18 +60,18 @@ namespace osu.Game.Rulesets.Catch.Replays { //we are already in the correct range. lastTime = h.StartTime; - Replay.Frames.Add(new CatchReplayFrame(h.StartTime, lastPosition)); + addFrame(h.StartTime, lastPosition); return; } if (impossibleJump) { - Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X)); + addFrame(h.StartTime, h.X); } else if (h.HyperDash) { - Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable, lastPosition)); - Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X)); + addFrame(h.StartTime - timeAvailable, lastPosition); + addFrame(h.StartTime, h.X); } else if (dashRequired) { @@ -81,16 +83,16 @@ namespace osu.Game.Rulesets.Catch.Replays float midPosition = (float)Interpolation.Lerp(lastPosition, h.X, (float)timeAtDashSpeed / timeAvailable); //dash movement - Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable + 1, lastPosition, true)); - Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition)); - Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X)); + addFrame(h.StartTime - timeAvailable + 1, lastPosition, true); + addFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition); + addFrame(h.StartTime, h.X); } else { double timeBefore = positionChange / movement_speed; - Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeBefore, lastPosition)); - Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X)); + addFrame(h.StartTime - timeBefore, lastPosition); + addFrame(h.StartTime, h.X); } lastTime = h.StartTime; @@ -122,5 +124,12 @@ namespace osu.Game.Rulesets.Catch.Replays return Replay; } + + private void addFrame(double time, float? position = null, bool dashing = false) + { + var last = currentFrame; + currentFrame = new CatchReplayFrame(time, position, dashing, last); + Replay.Frames.Add(currentFrame); + } } } diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs index 103aa6c3f1..22532bc9ec 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using osu.Framework.Input.StateChanges; using osu.Framework.MathUtils; using osu.Game.Replays; @@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Replays { } - protected override bool IsImportant(CatchReplayFrame frame) => frame.Position > 0; + protected override bool IsImportant(CatchReplayFrame frame) => frame.Actions.Any(); protected float? Position { @@ -38,21 +39,11 @@ namespace osu.Game.Rulesets.Catch.Replays { if (!Position.HasValue) return new List(); - var actions = new List(); - - if (CurrentFrame.Dashing) - actions.Add(CatchAction.Dash); - - if (Position.Value > CurrentFrame.Position) - actions.Add(CatchAction.MoveRight); - else if (Position.Value < CurrentFrame.Position) - actions.Add(CatchAction.MoveLeft); - return new List { new CatchReplayState { - PressedActions = actions, + PressedActions = CurrentFrame?.Actions ?? new List(), CatcherX = Position.Value }, }; diff --git a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs index 1e88b35c3b..19637f321b 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.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.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Catch.UI; @@ -11,6 +12,8 @@ namespace osu.Game.Rulesets.Catch.Replays { public class CatchReplayFrame : ReplayFrame, IConvertibleReplayFrame { + public List Actions = new List(); + public float Position; public bool Dashing; @@ -18,17 +21,39 @@ namespace osu.Game.Rulesets.Catch.Replays { } - public CatchReplayFrame(double time, float? position = null, bool dashing = false) + public CatchReplayFrame(double time, float? position = null, bool dashing = false, CatchReplayFrame lastFrame = null) : base(time) { Position = position ?? -1; Dashing = dashing; + + if (Dashing) + Actions.Add(CatchAction.Dash); + + if (lastFrame != null) + { + if (Position > lastFrame.Position) + Actions.Add(CatchAction.MoveRight); + else if (Position < lastFrame.Position) + Actions.Add(CatchAction.MoveLeft); + } } - public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap) + public void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, LegacyReplayFrame lastFrame = null) { - Position = legacyFrame.Position.X / CatchPlayfield.BASE_WIDTH; - Dashing = legacyFrame.ButtonState == ReplayButtonState.Left1; + Position = currentFrame.Position.X / CatchPlayfield.BASE_WIDTH; + Dashing = currentFrame.ButtonState == ReplayButtonState.Left1; + + if (Dashing) + Actions.Add(CatchAction.Dash); + + if (lastFrame != null) + { + if (currentFrame.Position.X > lastFrame.Position.X) + Actions.Add(CatchAction.MoveRight); + else if (currentFrame.Position.X < lastFrame.Position.X) + Actions.Add(CatchAction.MoveLeft); + } } } } diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs index f7277d3669..b2901f46c0 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) + public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap, LegacyReplayFrame lastFrame = null) { // We don't need to fully convert, just create the converter var converter = new ManiaBeatmapConverter(beatmap); diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs index 4d90fcadd5..441b69ef2d 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs @@ -26,11 +26,11 @@ namespace osu.Game.Rulesets.Osu.Replays Actions.AddRange(actions); } - public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap) + public void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, LegacyReplayFrame lastFrame = null) { - Position = legacyFrame.Position; - if (legacyFrame.MouseLeft) Actions.Add(OsuAction.LeftButton); - if (legacyFrame.MouseRight) Actions.Add(OsuAction.RightButton); + Position = currentFrame.Position; + if (currentFrame.MouseLeft) Actions.Add(OsuAction.LeftButton); + if (currentFrame.MouseRight) Actions.Add(OsuAction.RightButton); } } } diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs index 5203415e90..6e43892777 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs @@ -23,12 +23,12 @@ namespace osu.Game.Rulesets.Taiko.Replays Actions.AddRange(actions); } - public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap) + public void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, LegacyReplayFrame lastFrame = null) { - if (legacyFrame.MouseRight1) Actions.Add(TaikoAction.LeftRim); - if (legacyFrame.MouseRight2) Actions.Add(TaikoAction.RightRim); - if (legacyFrame.MouseLeft1) Actions.Add(TaikoAction.LeftCentre); - if (legacyFrame.MouseLeft2) Actions.Add(TaikoAction.RightCentre); + if (currentFrame.MouseRight1) Actions.Add(TaikoAction.LeftRim); + if (currentFrame.MouseRight2) Actions.Add(TaikoAction.RightRim); + if (currentFrame.MouseLeft1) Actions.Add(TaikoAction.LeftCentre); + if (currentFrame.MouseLeft2) Actions.Add(TaikoAction.RightCentre); } } } diff --git a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs index 7ecdc0715b..8fcdec6630 100644 --- a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs +++ b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs @@ -14,8 +14,9 @@ namespace osu.Game.Rulesets.Replays.Types /// /// Populates this using values from a . /// - /// The to extract values from. + /// The to extract values from. /// The beatmap. - void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap); + /// The last , used to fill in missing delta information. May be null. + void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, LegacyReplayFrame lastFrame = null); } } diff --git a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs index 2e4b4b3a9a..5a90daa045 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs @@ -218,6 +218,7 @@ namespace osu.Game.Scoring.Legacy private void readLegacyReplay(Replay replay, StreamReader reader) { float lastTime = 0; + LegacyReplayFrame currentFrame = null; foreach (var l in reader.ReadToEnd().Split(',')) { @@ -240,23 +241,27 @@ namespace osu.Game.Scoring.Legacy if (diff < 0) continue; - replay.Frames.Add(convertFrame(new LegacyReplayFrame(lastTime, + var lastFrame = currentFrame; + + currentFrame = new LegacyReplayFrame(lastTime, Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE), Parsing.ParseFloat(split[2], Parsing.MAX_COORDINATE_VALUE), - (ReplayButtonState)Parsing.ParseInt(split[3])))); + (ReplayButtonState)Parsing.ParseInt(split[3])); + + replay.Frames.Add(convertFrame(currentFrame, lastFrame)); } } - private ReplayFrame convertFrame(LegacyReplayFrame legacyFrame) + private ReplayFrame convertFrame(LegacyReplayFrame currentFrame, LegacyReplayFrame lastFrame) { var convertible = currentRuleset.CreateConvertibleReplayFrame(); if (convertible == null) throw new InvalidOperationException($"Legacy replay cannot be converted for the ruleset: {currentRuleset.Description}"); - convertible.ConvertFrom(legacyFrame, currentBeatmap); + convertible.ConvertFrom(currentFrame, currentBeatmap, lastFrame); var frame = (ReplayFrame)convertible; - frame.Time = legacyFrame.Time; + frame.Time = currentFrame.Time; return frame; } From 68feedbd159b677879e77260b7f2b1f40e789185 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Sep 2019 18:46:42 +0900 Subject: [PATCH 1233/2815] Fix unreported CI issue --- osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs index 6783a36ac3..ad747e88e1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs @@ -46,7 +46,6 @@ namespace osu.Game.Tests.Visual.Gameplay }); Key testKey = ((KeyCounterKeyboard)kc.Children.First()).Key; - double time1 = 0; AddStep($"Press {testKey} key", () => { @@ -60,7 +59,6 @@ namespace osu.Game.Tests.Visual.Gameplay { InputManager.PressKey(testKey); InputManager.ReleaseKey(testKey); - time1 = Clock.CurrentTime; }); AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 2); From f21e47d6d2a513451dff67eeaa0bf1f78426f69a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Sep 2019 19:29:08 +0900 Subject: [PATCH 1234/2815] Move expire to DrawableHitObject --- .../Drawable/DrawableCatchHitObject.cs | 4 +-- .../Drawables/DrawableManiaHitObject.cs | 4 +-- .../Objects/Drawables/DrawableHitCircle.cs | 26 +++++++++++++++---- .../Objects/Drawables/DrawableOsuHitObject.cs | 8 ++++++ .../Objects/Drawables/DrawableSlider.cs | 4 +-- .../Objects/Drawables/DrawableSpinner.cs | 6 ----- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 8 +----- .../Objects/Drawables/DrawableDrumRoll.cs | 2 +- .../Objects/Drawables/DrawableDrumRollTick.cs | 2 +- .../Objects/Drawables/DrawableHit.cs | 8 ++---- .../Objects/Drawables/DrawableSwell.cs | 2 -- .../Objects/Drawables/DrawableHitObject.cs | 5 +++- 12 files changed, 43 insertions(+), 36 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs index 00734810b3..51deae6e85 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs @@ -71,11 +71,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable switch (state) { case ArmedState.Miss: - this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out).Expire(); + this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out); break; case ArmedState.Hit: - this.FadeOut().Expire(); + this.FadeOut(); break; } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index e5b114ca81..5bfa07bd14 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -51,11 +51,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables switch (state) { case ArmedState.Miss: - this.FadeOut(150, Easing.In).Expire(); + this.FadeOut(150, Easing.In); break; case ArmedState.Hit: - this.FadeOut(150, Easing.OutQuint).Expire(); + this.FadeOut(150, Easing.OutQuint); break; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 985dcbca86..85fd68efdd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -86,6 +86,26 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables AccentColour.BindValueChanged(accent => ApproachCircle.Colour = accent.NewValue, true); } + public override double LifetimeStart + { + get => base.LifetimeStart; + set + { + base.LifetimeStart = value; + ApproachCircle.LifetimeStart = value; + } + } + + public override double LifetimeEnd + { + get => base.LifetimeEnd; + set + { + base.LifetimeEnd = value; + ApproachCircle.LifetimeEnd = value; + } + } + protected override void CheckForResult(bool userTriggered, double timeOffset) { Debug.Assert(HitObject.HitWindows != null); @@ -132,22 +152,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Expire(true); hitArea.HitAction = null; - - // override lifetime end as FadeIn may have been changed externally, causing out expiration to be too early. - LifetimeEnd = HitObject.StartTime + HitObject.HitWindows.WindowFor(HitResult.Miss); break; case ArmedState.Miss: ApproachCircle.FadeOut(50); this.FadeOut(100); - Expire(); break; case ArmedState.Hit: ApproachCircle.FadeOut(50); // todo: temporary / arbitrary - this.Delay(800).Expire(); + this.Delay(800).FadeOut(); break; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index fcd42314fc..8a7e5117f9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -41,6 +41,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength); + protected override void LoadComplete() + { + base.LoadComplete(); + + // Manually set to reduce the number of future alive objects to a bare minimum. + LifetimeStart = HitObject.StartTime - HitObject.TimePreempt; + } + protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(HitObject, judgement); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 00c953c393..65f1d5e15f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -219,10 +219,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables break; } - this.FadeOut(fade_out_time, Easing.OutQuint).Expire(); + this.FadeOut(fade_out_time, Easing.OutQuint); } - - Expire(true); } public Drawable ProxiedLayer => HeadCircle.ApproachCircle; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 49aaa2aaea..b1185ddba8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -219,10 +219,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables switch (state) { - case ArmedState.Idle: - Expire(true); - break; - case ArmedState.Hit: sequence.ScaleTo(Scale * 1.2f, 320, Easing.Out); break; @@ -231,8 +227,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables sequence.ScaleTo(Scale * 0.8f, 320, Easing.In); break; } - - Expire(); } } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index ea7eee8bb8..df12ebc514 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -70,13 +70,7 @@ namespace osu.Game.Rulesets.Osu.UI base.Add(h); } - private void addApproachCircleProxy(Drawable d) - { - var proxy = d.CreateProxy(); - proxy.LifetimeStart = d.LifetimeStart; - proxy.LifetimeEnd = d.LifetimeEnd; - approachCircles.Add(proxy); - } + private void addApproachCircleProxy(Drawable d) => approachCircles.Add(d.CreateProxy()); public override void PostProcess() { diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index f4407a7b54..8e16a21199 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { case ArmedState.Hit: case ArmedState.Miss: - this.Delay(HitObject.Duration).FadeOut(100).Expire(); + this.Delay(HitObject.Duration).FadeOut(100); break; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index cef9a53deb..25b6141a0e 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables switch (state) { case ArmedState.Hit: - this.ScaleTo(0, 100, Easing.OutQuint).Expire(); + this.ScaleTo(0, 100, Easing.OutQuint); break; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 676ecd5a0b..4b25ff0ecc 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -105,12 +105,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables validActionPressed = false; UnproxyContent(); - this.Delay(HitObject.HitWindows.WindowFor(HitResult.Miss)).Expire(); break; case ArmedState.Miss: - this.FadeOut(100) - .Expire(); + this.FadeOut(100); break; case ArmedState.Hit: @@ -129,9 +127,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables .Then() .MoveToY(gravity_travel_height * 2, gravity_time * 2, Easing.In); - this.FadeOut(800) - .Expire(); - + this.FadeOut(800); break; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 094ad1230f..07af7fe7e0 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -208,8 +208,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { this.FadeOut(transition_duration, Easing.Out); bodyContainer.ScaleTo(1.4f, transition_duration); - - Expire(); } break; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index e3390c8cf0..90c49a0144 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -153,7 +153,6 @@ namespace osu.Game.Rulesets.Objects.Drawables if (UseTransformStateManagement) { - lifetimeStart = null; LifetimeEnd = double.MaxValue; double transformTime = HitObject.StartTime - InitialLifetimeOffset; @@ -173,6 +172,9 @@ namespace osu.Game.Rulesets.Objects.Drawables state.Value = newState; } } + + if (state.Value != ArmedState.Idle && LifetimeEnd == double.MaxValue) + Expire(); } else state.Value = newState; @@ -203,6 +205,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Apply transforms based on the current . Previous states are automatically cleared. + /// In the case of a non-idle , and if was not set during this call, will be invoked. /// /// The new armed state. protected virtual void UpdateStateTransforms(ArmedState state) From b81b162ee12a96b0668222ee57ac477567851a0a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Sep 2019 19:30:27 +0900 Subject: [PATCH 1235/2815] Update InitialLifetimeOffset comment --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 90c49a0144..00b57f7249 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -314,7 +314,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// This is only used as an optimisation to delay the initial update of this and may be tuned more aggressively if required. /// It is indirectly used to decide the automatic transform offset provided to . - /// A more accurate should be set inside for an state. + /// A more accurate should be set for further optimisation (in , for example). /// protected virtual double InitialLifetimeOffset => 10000; From cafb5105bc4b4997bc0c75259c9c1ce587d0c768 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 12 Sep 2019 16:44:15 +0300 Subject: [PATCH 1236/2815] Rename HeaderFlag to DismissableFlag --- ...aderFlag.cs => TestSceneRankingsDismissableFlag.cs} | 10 +++++----- .../Rankings/{HeaderFlag.cs => DismissableFlag.cs} | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) rename osu.Game.Tests/Visual/Online/{TestSceneRankingsHeaderFlag.cs => TestSceneRankingsDismissableFlag.cs} (88%) rename osu.Game/Overlays/Rankings/{HeaderFlag.cs => DismissableFlag.cs} (94%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderFlag.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsDismissableFlag.cs similarity index 88% rename from osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderFlag.cs rename to osu.Game.Tests/Visual/Online/TestSceneRankingsDismissableFlag.cs index c9531e1016..db6afa9bf3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderFlag.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsDismissableFlag.cs @@ -12,16 +12,16 @@ using osuTK; namespace osu.Game.Tests.Visual.Online { - public class TestSceneRankingsHeaderFlag : OsuTestScene + public class TestSceneRankingsDismissableFlag : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { - typeof(HeaderFlag), + typeof(DismissableFlag), }; - public TestSceneRankingsHeaderFlag() + public TestSceneRankingsDismissableFlag() { - HeaderFlag flag; + DismissableFlag flag; SpriteText text; var countryA = new Country @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Online AddRange(new Drawable[] { - flag = new HeaderFlag + flag = new DismissableFlag { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Overlays/Rankings/HeaderFlag.cs b/osu.Game/Overlays/Rankings/DismissableFlag.cs similarity index 94% rename from osu.Game/Overlays/Rankings/HeaderFlag.cs rename to osu.Game/Overlays/Rankings/DismissableFlag.cs index 6f641c74a5..7a55b0bba6 100644 --- a/osu.Game/Overlays/Rankings/HeaderFlag.cs +++ b/osu.Game/Overlays/Rankings/DismissableFlag.cs @@ -11,7 +11,7 @@ using System; namespace osu.Game.Overlays.Rankings { - public class HeaderFlag : UpdateableFlag + public class DismissableFlag : UpdateableFlag { private const int duration = 200; @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Rankings private readonly SpriteIcon hoverIcon; - public HeaderFlag() + public DismissableFlag() { AddInternal(hoverIcon = new SpriteIcon { From b17d097a39d7edd1d15f6d2744029d47c7f2b08d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 12 Sep 2019 17:17:57 +0300 Subject: [PATCH 1237/2815] Simplify colour usage in GradientLine --- .../UserInterface/GradientLineTabControl.cs | 38 +++++-------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs b/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs index 7cd8d2c5bd..3523876fca 100644 --- a/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs +++ b/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs @@ -8,7 +8,6 @@ using osuTK; using osu.Framework.Graphics.Shapes; using osuTK.Graphics; using osu.Framework.Graphics.Colour; -using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; namespace osu.Game.Graphics.UserInterface @@ -19,8 +18,8 @@ namespace osu.Game.Graphics.UserInterface protected Color4 LineColour { - get => line.MainColour.Value; - set => line.MainColour.Value = value; + get => line.Colour; + set => line.Colour = value; } private readonly GradientLine line; @@ -48,12 +47,6 @@ namespace osu.Game.Graphics.UserInterface private class GradientLine : GridContainer { - public readonly Bindable MainColour = new Bindable(); - - private readonly Box left; - private readonly Box middle; - private readonly Box right; - public GradientLine() { RelativeSizeAxes = Axes.X; @@ -70,34 +63,23 @@ namespace osu.Game.Graphics.UserInterface { new Drawable[] { - left = new Box + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0), Color4.White) + }, + new Box { RelativeSizeAxes = Axes.Both, }, - middle = new Box - { - RelativeSizeAxes = Axes.Both, - }, - right = new Box + new Box { RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientHorizontal(Color4.White, Color4.White.Opacity(0)) }, } }; } - - protected override void LoadComplete() - { - MainColour.BindValueChanged(onColourChanged, true); - base.LoadComplete(); - } - - private void onColourChanged(ValueChangedEvent colour) - { - left.Colour = ColourInfo.GradientHorizontal(colour.NewValue.Opacity(0), colour.NewValue); - middle.Colour = colour.NewValue; - right.Colour = ColourInfo.GradientHorizontal(colour.NewValue, colour.NewValue.Opacity(0)); - } } } } From 7ee01ee3233a5a3d7eee3b1f41a018d923567e35 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 12 Sep 2019 18:11:48 +0300 Subject: [PATCH 1238/2815] Use assignment instead of binding --- osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs index 2081a6c0cb..178016c648 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Online { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Current = { BindTarget = scope } + Current = scope } }); From 99fc13b4d85a17e4dcf5c65012c857892674e36c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 12 Sep 2019 19:34:58 +0300 Subject: [PATCH 1239/2815] Update usage of the DismissableFlag --- osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs | 2 +- osu.Game/Overlays/Rankings/HeaderTitle.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs index 0f16b2592f..849ca2defc 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Online { public override IReadOnlyList RequiredTypes => new[] { - typeof(HeaderFlag), + typeof(DismissableFlag), typeof(HeaderTitle), }; diff --git a/osu.Game/Overlays/Rankings/HeaderTitle.cs b/osu.Game/Overlays/Rankings/HeaderTitle.cs index 3f1feb10b8..a00c6c6dcd 100644 --- a/osu.Game/Overlays/Rankings/HeaderTitle.cs +++ b/osu.Game/Overlays/Rankings/HeaderTitle.cs @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.Rankings private readonly SpriteText scopeText; private readonly Container flagPlaceholder; - private readonly HeaderFlag flag; + private readonly DismissableFlag flag; public HeaderTitle() { @@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Rankings Origin = Anchor.BottomLeft, AutoSizeAxes = Axes.Both, Margin = new MarginPadding { Bottom = flag_margin }, - Child = flag = new HeaderFlag + Child = flag = new DismissableFlag { Size = new Vector2(30, 20), }, From 2a8fa2f5936543aa1c77baed96836a26d9c8ef98 Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 12 Sep 2019 14:01:12 -0700 Subject: [PATCH 1240/2815] Refactor modsContainer on profile scores --- .../Sections/Ranks/DrawableProfileScore.cs | 9 ++++---- .../Sections/Ranks/ScoreModsContainer.cs | 21 ------------------- 2 files changed, 5 insertions(+), 25 deletions(-) delete mode 100644 osu.Game/Overlays/Profile/Sections/Ranks/ScoreModsContainer.cs diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index e54ce44ca2..6362d3dfb0 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -12,12 +12,13 @@ using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Beatmaps; using osu.Framework.Localisation; +using osu.Framework.Graphics.Containers; namespace osu.Game.Overlays.Profile.Sections.Ranks { public abstract class DrawableProfileScore : DrawableProfileRow { - private readonly ScoreModsContainer modsContainer; + private readonly FillFlowContainer modsContainer; protected readonly ScoreInfo Score; protected DrawableProfileScore(ScoreInfo score) @@ -28,12 +29,12 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Height = 60; Children = new Drawable[] { - modsContainer = new ScoreModsContainer + modsContainer = new FillFlowContainer { - AutoSizeAxes = Axes.Y, + AutoSizeAxes = Axes.Both, Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Width = 60, + Spacing = new Vector2(1), Margin = new MarginPadding { Right = 160 } } }; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/ScoreModsContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/ScoreModsContainer.cs deleted file mode 100644 index 1ce04effa8..0000000000 --- a/osu.Game/Overlays/Profile/Sections/Ranks/ScoreModsContainer.cs +++ /dev/null @@ -1,21 +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 osuTK; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.UI; -using System.Collections.Generic; -using System.Linq; - -namespace osu.Game.Overlays.Profile.Sections.Ranks -{ - public class ScoreModsContainer : FlowContainer - { - protected override IEnumerable ComputeLayoutPositions() - { - int count = FlowingChildren.Count(); - for (int i = 0; i < count; i++) - yield return new Vector2(DrawWidth * i * (count == 1 ? 0 : 1f / (count - 1)), 0); - } - } -} From b917f29cfe6cccf9158332edc694572cbb7c302b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Sep 2019 13:59:06 +0900 Subject: [PATCH 1241/2815] Make GradientLineTabControl abstract --- osu.Game/Graphics/UserInterface/GradientLineTabControl.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs b/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs index 3523876fca..a9bbda4194 100644 --- a/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs +++ b/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs @@ -12,10 +12,8 @@ using osu.Framework.Extensions.Color4Extensions; namespace osu.Game.Graphics.UserInterface { - public class GradientLineTabControl : PageTabControl + public abstract class GradientLineTabControl : PageTabControl { - protected override Dropdown CreateDropdown() => null; - protected Color4 LineColour { get => line.Colour; @@ -24,7 +22,7 @@ namespace osu.Game.Graphics.UserInterface private readonly GradientLine line; - public GradientLineTabControl() + protected GradientLineTabControl() { RelativeSizeAxes = Axes.X; @@ -35,6 +33,8 @@ namespace osu.Game.Graphics.UserInterface }); } + protected override Dropdown CreateDropdown() => null; + protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer { Anchor = Anchor.BottomCentre, From 0e679fb468a4ee27dc0b57bd6aefbcfbce67ccfc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Sep 2019 14:06:19 +0900 Subject: [PATCH 1242/2815] Use colour constant rather than opacity helper function --- osu.Game/Graphics/UserInterface/GradientLineTabControl.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs b/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs index a9bbda4194..4fd4a2adbd 100644 --- a/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs +++ b/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs @@ -8,7 +8,6 @@ using osuTK; using osu.Framework.Graphics.Shapes; using osuTK.Graphics; using osu.Framework.Graphics.Colour; -using osu.Framework.Extensions.Color4Extensions; namespace osu.Game.Graphics.UserInterface { @@ -66,7 +65,7 @@ namespace osu.Game.Graphics.UserInterface new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0), Color4.White) + Colour = ColourInfo.GradientHorizontal(Color4.Transparent, Color4.White) }, new Box { @@ -75,7 +74,7 @@ namespace osu.Game.Graphics.UserInterface new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(Color4.White, Color4.White.Opacity(0)) + Colour = ColourInfo.GradientHorizontal(Color4.White, Color4.Transparent) }, } }; From 44947aa9edece7d0c140aa8660aa6c342a28b54f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Sep 2019 15:27:29 +0900 Subject: [PATCH 1243/2815] Make PopupDialog abstract --- .../UserInterface/TestSceneDialogOverlay.cs | 8 +++++-- .../UserInterface/TestScenePopupDialog.cs | 23 ++++++++++++------- osu.Game/Overlays/Dialog/PopupDialog.cs | 2 +- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs index a6ff3462d4..cc4a57fb83 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs @@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.UserInterface Add(overlay = new DialogOverlay()); - AddStep("dialog #1", () => overlay.Push(new PopupDialog + AddStep("dialog #1", () => overlay.Push(new TestPopupDialog { Icon = FontAwesome.Regular.TrashAlt, HeaderText = @"Confirm deletion of", @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.UserInterface }, })); - AddStep("dialog #2", () => overlay.Push(new PopupDialog + AddStep("dialog #2", () => overlay.Push(new TestPopupDialog { Icon = FontAwesome.Solid.Cog, HeaderText = @"What do you want to do with", @@ -71,5 +71,9 @@ namespace osu.Game.Tests.Visual.UserInterface }, })); } + + private class TestPopupDialog : PopupDialog + { + } } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs index 9ddd8f4038..3d39bb7003 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs @@ -13,13 +13,22 @@ namespace osu.Game.Tests.Visual.UserInterface { public TestScenePopupDialog() { - var popup = new PopupDialog + Add(new TestPopupDialog { RelativeSizeAxes = Axes.Both, State = { Value = Framework.Graphics.Containers.Visibility.Visible }, - Icon = FontAwesome.Solid.AssistiveListeningSystems, - HeaderText = @"This is a test popup", - BodyText = "I can say lots of stuff and even wrap my words!", + }); + } + + private class TestPopupDialog : PopupDialog + { + public TestPopupDialog() + { + Icon = FontAwesome.Solid.AssistiveListeningSystems; + + HeaderText = @"This is a test popup"; + BodyText = "I can say lots of stuff and even wrap my words!"; + Buttons = new PopupDialogButton[] { new PopupDialogCancelButton @@ -30,10 +39,8 @@ namespace osu.Game.Tests.Visual.UserInterface { Text = @"You're a fake!", }, - } - }; - - Add(popup); + }; + } } } } diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 5c0ddb47b1..37674a5dcb 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -19,7 +19,7 @@ using osuTK.Input; namespace osu.Game.Overlays.Dialog { - public class PopupDialog : VisibilityContainer + public abstract class PopupDialog : VisibilityContainer { public static readonly float ENTER_DURATION = 500; public static readonly float EXIT_DURATION = 200; From dc8c7a50414caa25b9304f7cc99a1a9d45ebea44 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Sep 2019 15:27:42 +0900 Subject: [PATCH 1244/2815] Add null check for safety --- osu.Game/Overlays/DialogOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 0d3c96c984..6aaeff8554 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -80,7 +80,7 @@ namespace osu.Game.Overlays switch (action) { case GlobalAction.Select: - currentDialog.Buttons.OfType().FirstOrDefault()?.Click(); + currentDialog?.Buttons.OfType().FirstOrDefault()?.Click(); return true; } From c66e96370531ae6baf9c1779cbfa20eecb045ba4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Sep 2019 15:42:36 +0900 Subject: [PATCH 1245/2815] Make constructor private --- osu.Game/Overlays/Dialog/PopupDialog.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 37674a5dcb..cff887865a 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -87,7 +87,7 @@ namespace osu.Game.Overlays.Dialog } } - public PopupDialog() + protected PopupDialog() { RelativeSizeAxes = Axes.Both; From cf2f841b4d00da43aa1c4dd496d34df0a80a0746 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 13 Sep 2019 15:41:53 +0900 Subject: [PATCH 1246/2815] Fix player not correctly exiting after an unpause --- .../Visual/Gameplay/TestScenePause.cs | 10 ++++++++++ osu.Game/Screens/Play/Player.cs | 19 +++++++++++-------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 5808a78056..50583e43c4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -160,6 +160,15 @@ namespace osu.Game.Tests.Visual.Gameplay exitAndConfirm(); } + [Test] + public void TestRestartAfterResume() + { + pauseAndConfirm(); + resumeAndConfirm(); + restart(); + confirmExited(); + } + private void pauseAndConfirm() { pause(); @@ -198,6 +207,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("player exited", () => !Player.IsCurrentScreen()); } + private void restart() => AddStep("restart", () => Player.Restart()); private void pause() => AddStep("pause", () => Player.Pause()); private void resume() => AddStep("resume", () => Player.Resume()); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 3f1603eabe..3fd0f0260c 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -502,15 +502,18 @@ namespace osu.Game.Screens.Play return true; } - if (pauseCooldownActive && !GameplayClockContainer.IsPaused.Value) - // still want to block if we are within the cooldown period and not already paused. - return true; - - if (HasFailed && ValidForResume && !FailOverlay.IsPresent) - // ValidForResume is false when restarting + // ValidForResume is false when restarting + if (ValidForResume) { - failAnimation.FinishTransforms(true); - return true; + if (pauseCooldownActive && !GameplayClockContainer.IsPaused.Value) + // still want to block if we are within the cooldown period and not already paused. + return true; + + if (HasFailed && !FailOverlay.IsPresent) + { + failAnimation.FinishTransforms(true); + return true; + } } GameplayClockContainer.ResetLocalAdjustments(); From 7818ecd71c1eab60b1c0f7f17b4dcc79f5357ad9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Sep 2019 16:03:44 +0900 Subject: [PATCH 1247/2815] Forward ValueChangedEvent instead --- osu.Game/Overlays/Rankings/HeaderTitle.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Rankings/HeaderTitle.cs b/osu.Game/Overlays/Rankings/HeaderTitle.cs index a00c6c6dcd..ed9dc99a79 100644 --- a/osu.Game/Overlays/Rankings/HeaderTitle.cs +++ b/osu.Game/Overlays/Rankings/HeaderTitle.cs @@ -73,16 +73,16 @@ namespace osu.Game.Overlays.Rankings protected override void LoadComplete() { - Scope.BindValueChanged(scope => onScopeChanged(scope.NewValue), true); + Scope.BindValueChanged(onScopeChanged, true); Country.BindValueChanged(onCountryChanged, true); base.LoadComplete(); } - private void onScopeChanged(RankingsScope scope) + private void onScopeChanged(ValueChangedEvent scope) { - scopeText.Text = scope.ToString(); + scopeText.Text = scope.NewValue.ToString(); - if (scope != RankingsScope.Performance) + if (scope.NewValue != RankingsScope.Performance) Country.Value = null; } From 78e7be919f7dc05f40b806a07d2e8847cb2275f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Sep 2019 16:25:25 +0900 Subject: [PATCH 1248/2815] Remove unnecessary container --- osu.Game/Overlays/Rankings/HeaderTitle.cs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Rankings/HeaderTitle.cs b/osu.Game/Overlays/Rankings/HeaderTitle.cs index ed9dc99a79..efaf4225d5 100644 --- a/osu.Game/Overlays/Rankings/HeaderTitle.cs +++ b/osu.Game/Overlays/Rankings/HeaderTitle.cs @@ -22,7 +22,6 @@ namespace osu.Game.Overlays.Rankings public readonly Bindable Country = new Bindable(); private readonly SpriteText scopeText; - private readonly Container flagPlaceholder; private readonly DismissableFlag flag; public HeaderTitle() @@ -35,16 +34,12 @@ namespace osu.Game.Overlays.Rankings Spacing = new Vector2(spacing, 0), Children = new Drawable[] { - flagPlaceholder = new Container + flag = new DismissableFlag { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - AutoSizeAxes = Axes.Both, Margin = new MarginPadding { Bottom = flag_margin }, - Child = flag = new DismissableFlag - { - Size = new Vector2(30, 20), - }, + Size = new Vector2(30, 20), }, scopeText = new SpriteText { @@ -90,15 +85,13 @@ namespace osu.Game.Overlays.Rankings { if (country.NewValue == null) { - flagPlaceholder.Hide(); + flag.Hide(); return; } Scope.Value = RankingsScope.Performance; - if (country.OldValue == null) - flagPlaceholder.Show(); - + flag.Show(); flag.Country = country.NewValue; } } From 51f17ccb1b8caec176e2b712066cd76caf453c98 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 13 Sep 2019 10:48:02 +0300 Subject: [PATCH 1249/2815] Remove test duplicate --- .../Online/TestSceneRankingsHeaderFlag.cs | 65 ------------------- 1 file changed, 65 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderFlag.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderFlag.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderFlag.cs deleted file mode 100644 index 17f6f8417b..0000000000 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderFlag.cs +++ /dev/null @@ -1,65 +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 System; -using System.Collections.Generic; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; -using osu.Game.Overlays.Rankings; -using osu.Game.Users; -using osuTK; - -namespace osu.Game.Tests.Visual.Online -{ - public class TestSceneRankingsHeaderFlag : OsuTestScene - { - public override IReadOnlyList RequiredTypes => new[] - { - typeof(DismissableFlag), - }; - - public TestSceneRankingsHeaderFlag() - { - DismissableFlag flag; - SpriteText text; - - var countryA = new Country - { - FlagName = "BY", - FullName = "Belarus" - }; - - var countryB = new Country - { - FlagName = "US", - FullName = "United States" - }; - - AddRange(new Drawable[] - { - flag = new DismissableFlag - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(30, 20), - Country = countryA, - }, - text = new SpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = "Invoked", - Font = OsuFont.GetFont(size: 30), - Alpha = 0, - } - }); - - flag.Action += () => text.FadeIn().Then().FadeOut(1000, Easing.OutQuint); - - AddStep("Trigger click", () => flag.Click()); - AddStep("Change to country 2", () => flag.Country = countryB); - AddStep("Change to country 1", () => flag.Country = countryA); - } - } -} From c9ae4336f944b779becfcf7f880cef8293a19815 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 13 Sep 2019 10:50:26 +0300 Subject: [PATCH 1250/2815] Fix RankingsScope test --- osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs index fef9c3a7b1..3693d6b5b4 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Online { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Current = { BindTarget = scope } + Current = scope, } }); From 6867b3c23214a8a6f47d547cf6377cdf4d13919b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 13 Sep 2019 10:56:21 +0300 Subject: [PATCH 1251/2815] 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 45c162a30e..4167d07698 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -62,7 +62,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index df8b11e653..5703293caf 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 7c31744a14..683dccf3ae 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -117,7 +117,7 @@ - + From 9a9654dbd1021614d95969231a4d09523699b663 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 13 Sep 2019 10:59:09 +0300 Subject: [PATCH 1252/2815] Fix the Test Scene --- osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs index e8ed94b59c..0ceb5f21d3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tests.Visual.Online { public override IReadOnlyList RequiredTypes => new[] { - typeof(HeaderFlag), + typeof(DismissableFlag), typeof(HeaderTitle), typeof(RankingsRulesetSelector), typeof(RankingsScopeSelector), @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Online new Spotlight { Id = 3, - Text = "Spotlight 4" + Text = "Spotlight 3" } } }); From 7cb79dd7605f37e9c5fdd67ec98cda660e4a46b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Sep 2019 17:15:33 +0900 Subject: [PATCH 1253/2815] Fix incorrect DI usage of IAPIProvider in many tests --- .../Visual/Menus/TestSceneDisclaimer.cs | 11 ++++---- .../Multiplayer/TestSceneMatchLeaderboard.cs | 2 +- .../Multiplayer/TestSceneMultiScreen.cs | 2 +- .../Online/TestSceneAccountCreationOverlay.cs | 14 +++++++---- .../Online/TestSceneBeatmapSetOverlay.cs | 2 +- .../Online/TestSceneChangelogOverlay.cs | 2 +- .../Visual/Online/TestSceneDirectOverlay.cs | 2 +- .../Online/TestSceneHistoricalSection.cs | 2 +- .../Visual/Online/TestSceneSocialOverlay.cs | 2 +- .../Online/TestSceneUserProfileHeader.cs | 2 +- .../Online/TestSceneUserProfileOverlay.cs | 2 +- .../Visual/Online/TestSceneUserRanks.cs | 2 +- ...tSceneUpdateableBeatmapBackgroundSprite.cs | 3 +-- osu.Game/Tests/Visual/OsuTestScene.cs | 25 ++++++++++++++----- 14 files changed, 44 insertions(+), 29 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs b/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs index 13116de320..681bf1b40b 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Game.Online.API; using osu.Game.Screens.Menu; using osu.Game.Users; @@ -11,17 +10,17 @@ namespace osu.Game.Tests.Visual.Menus public class TestSceneDisclaimer : ScreenTestScene { [BackgroundDependencyLoader] - private void load(IAPIProvider api) + private void load() { AddStep("load disclaimer", () => LoadScreen(new Disclaimer())); AddStep("toggle support", () => { - api.LocalUser.Value = new User + API.LocalUser.Value = new User { - Username = api.LocalUser.Value.Username, - Id = api.LocalUser.Value.Id, - IsSupporter = !api.LocalUser.Value.IsSupporter, + Username = API.LocalUser.Value.Username, + Id = API.LocalUser.Value.Id, + IsSupporter = !API.LocalUser.Value.IsSupporter, }; }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs index 723e5fc03d..7ba1782a28 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMatchLeaderboard : MultiplayerTestScene { - protected override bool RequiresAPIAccess => true; + protected override bool UseOnlineAPI => true; public TestSceneMatchLeaderboard() { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs index b646433846..dfe61a4dda 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [TestFixture] public class TestSceneMultiScreen : ScreenTestScene { - protected override bool RequiresAPIAccess => true; + protected override bool UseOnlineAPI => true; public override IReadOnlyList RequiredTypes => new[] { diff --git a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs index 66ab1fe18a..31eab7f74e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs @@ -4,9 +4,9 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.AccountCreation; using osu.Game.Users; @@ -27,6 +27,8 @@ namespace osu.Game.Tests.Visual.Online private readonly Container userPanelArea; + private Bindable localUser; + public TestSceneAccountCreationOverlay() { AccountCreationOverlay accountCreation; @@ -47,12 +49,14 @@ namespace osu.Game.Tests.Visual.Online } [BackgroundDependencyLoader] - private void load(IAPIProvider api) + private void load() { - api.Logout(); - api.LocalUser.BindValueChanged(user => { userPanelArea.Child = new UserPanel(user.NewValue) { Width = 200 }; }, true); + API.Logout(); - AddStep("logout", api.Logout); + localUser = API.LocalUser.GetBoundCopy(); + localUser.BindValueChanged(user => { userPanelArea.Child = new UserPanel(user.NewValue) { Width = 200 }; }, true); + + AddStep("logout", API.Logout); } } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index 5068064a1f..9f03d947b9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Online typeof(BeatmapAvailability), }; - protected override bool RequiresAPIAccess => true; + protected override bool UseOnlineAPI => true; private RulesetInfo taikoRuleset; private RulesetInfo maniaRuleset; diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs index 324291c9d7..f555c276f4 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Online typeof(Comments), }; - protected override bool RequiresAPIAccess => true; + protected override bool UseOnlineAPI => true; protected override void LoadComplete() { diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs index 14ae975806..d9873ea243 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs @@ -13,7 +13,7 @@ namespace osu.Game.Tests.Visual.Online { private DirectOverlay direct; - protected override bool RequiresAPIAccess => true; + protected override bool UseOnlineAPI => true; protected override void LoadComplete() { diff --git a/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs b/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs index c98f98c23d..d3b037f499 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs @@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.Online [TestFixture] public class TestSceneHistoricalSection : OsuTestScene { - protected override bool RequiresAPIAccess => true; + protected override bool UseOnlineAPI => true; public override IReadOnlyList RequiredTypes => new[] { diff --git a/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs index 806b36e855..dbd7544b38 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs @@ -13,7 +13,7 @@ namespace osu.Game.Tests.Visual.Online [TestFixture] public class TestSceneSocialOverlay : OsuTestScene { - protected override bool RequiresAPIAccess => true; + protected override bool UseOnlineAPI => true; public override IReadOnlyList RequiredTypes => new[] { diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs index 555d5334d8..63b8acb234 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs @@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.Online { public class TestSceneUserProfileHeader : OsuTestScene { - protected override bool RequiresAPIAccess => true; + protected override bool UseOnlineAPI => true; public override IReadOnlyList RequiredTypes => new[] { diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index 42c8ffbf0a..93e6607ac5 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Online [TestFixture] public class TestSceneUserProfileOverlay : OsuTestScene { - protected override bool RequiresAPIAccess => true; + protected override bool UseOnlineAPI => true; private readonly TestUserProfileOverlay profile; diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs b/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs index d777f9766a..2951f6b63e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.Online [TestFixture] public class TestSceneUserRanks : OsuTestScene { - protected override bool RequiresAPIAccess => true; + protected override bool UseOnlineAPI => true; public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableProfileScore), typeof(RanksSection) }; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs index fdc50be3fa..d3359fd824 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestSceneUpdateableBeatmapBackgroundSprite : OsuTestScene { - protected override bool RequiresAPIAccess => true; + protected override bool UseOnlineAPI => true; private BeatmapSetInfo testBeatmap; private IAPIProvider api; @@ -32,7 +32,6 @@ namespace osu.Game.Tests.Visual.UserInterface [BackgroundDependencyLoader] private void load(OsuGameBase osu, IAPIProvider api, RulesetStore rulesets) { - this.api = api; this.rulesets = rulesets; testBeatmap = ImportBeatmapTest.LoadOszIntoOsu(osu).Result; diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index dd68ed93e6..5a7fbd31e2 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -46,13 +46,27 @@ namespace osu.Game.Tests.Visual protected Storage LocalStorage => localStorage.Value; private readonly Lazy contextFactory; + + protected IAPIProvider API + { + get + { + if (UseOnlineAPI) + throw new Exception("Using the OsuTestScene dummy API is not supported when UseOnlineAPI is true"); + + return dummyAPI; + } + } + + private DummyAPIAccess dummyAPI; + protected DatabaseContextFactory ContextFactory => contextFactory.Value; /// - /// Whether this test scene requires API access - /// Setting this will cache an actual . + /// Whether this test scene requires real-world API access. + /// If true, this will bypass the local and use the provided one. /// - protected virtual bool RequiresAPIAccess => false; + protected virtual bool UseOnlineAPI => false; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { @@ -66,10 +80,9 @@ namespace osu.Game.Tests.Visual Dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - if (!RequiresAPIAccess) + if (!UseOnlineAPI) { - var dummyAPI = new DummyAPIAccess(); - + dummyAPI = new DummyAPIAccess(); Dependencies.CacheAs(dummyAPI); Add(dummyAPI); } From 0cc21c9c747979052603576c25be1ec0bd502034 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Sep 2019 17:21:47 +0900 Subject: [PATCH 1254/2815] Fix changelog overlay potentially adding children after disposal --- osu.Game/Overlays/ChangelogOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index 7755c8a6a6..dfe3669813 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -170,7 +170,7 @@ namespace osu.Game.Overlays var tcs = new TaskCompletionSource(); var req = new GetChangelogRequest(); - req.Success += res => + req.Success += res => Schedule(() => { // remap streams to builds to ensure model equality res.Builds.ForEach(b => b.UpdateStream = res.Streams.Find(s => s.Id == b.UpdateStream.Id)); @@ -182,7 +182,7 @@ namespace osu.Game.Overlays header.Streams.Populate(res.Streams); tcs.SetResult(true); - }; + }); req.Failure += _ => initialFetchTask = null; req.Perform(API); From d681f43e29c2c45fa50c762c47955668e9872e21 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 13 Sep 2019 08:38:02 +0000 Subject: [PATCH 1255/2815] Bump ppy.osu.Game.Resources from 2019.904.0 to 2019.913.0 Bumps [ppy.osu.Game.Resources](https://github.com/ppy/osu-resources) from 2019.904.0 to 2019.913.0. - [Release notes](https://github.com/ppy/osu-resources/releases) - [Commits](https://github.com/ppy/osu-resources/compare/2019.904.0...2019.913.0) Signed-off-by: dependabot-preview[bot] --- 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 45c162a30e..4167d07698 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -62,7 +62,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index df8b11e653..5703293caf 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 7c31744a14..683dccf3ae 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -117,7 +117,7 @@ - + From a7c59098ce51be6eb3828d9029f57bfe51e02688 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Sep 2019 17:38:04 +0900 Subject: [PATCH 1256/2815] Fix missing assignment --- .../UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs index d3359fd824..198cc70e01 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs @@ -32,6 +32,7 @@ namespace osu.Game.Tests.Visual.UserInterface [BackgroundDependencyLoader] private void load(OsuGameBase osu, IAPIProvider api, RulesetStore rulesets) { + this.api = api; this.rulesets = rulesets; testBeatmap = ImportBeatmapTest.LoadOszIntoOsu(osu).Result; From 1e4f3507ed9c0529eda45ab530fe2430aa85cf07 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Sep 2019 18:07:52 +0900 Subject: [PATCH 1257/2815] Fix layout not matching web --- osu.Game/Overlays/Rankings/RankingsHeader.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Rankings/RankingsHeader.cs b/osu.Game/Overlays/Rankings/RankingsHeader.cs index 6d55e92502..18a0599036 100644 --- a/osu.Game/Overlays/Rankings/RankingsHeader.cs +++ b/osu.Game/Overlays/Rankings/RankingsHeader.cs @@ -20,7 +20,6 @@ namespace osu.Game.Overlays.Rankings private const int content_height = 250; private const int dropdown_height = 50; private const int spacing = 20; - private const int title_offset = 30; private const int duration = 200; public IEnumerable Spotlights @@ -68,27 +67,25 @@ namespace osu.Game.Overlays.Rankings Masking = true, Child = new HeaderBackground(), }, - new RankingsScopeSelector - { - Margin = new MarginPadding { Top = 10 }, - Current = { BindTarget = Scope } - }, new FillFlowContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Y = title_offset, AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Direction = FillDirection.Vertical, Spacing = new Vector2(0, spacing), Children = new Drawable[] { + new RankingsScopeSelector + { + Margin = new MarginPadding { Top = 10 }, + Current = { BindTarget = Scope } + }, new HeaderTitle { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Scope = { BindTarget = Scope }, + Margin = new MarginPadding { Top = 10 }, Country = { BindTarget = Country }, }, dropdownPlaceholder = new Container From 031f0ee1e7251308e1830ad86dcca94f69ee7656 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Sep 2019 18:09:15 +0900 Subject: [PATCH 1258/2815] Consume ValueChanged and inline some pointless constants --- osu.Game/Overlays/Rankings/RankingsHeader.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Rankings/RankingsHeader.cs b/osu.Game/Overlays/Rankings/RankingsHeader.cs index 18a0599036..4e502fb7fe 100644 --- a/osu.Game/Overlays/Rankings/RankingsHeader.cs +++ b/osu.Game/Overlays/Rankings/RankingsHeader.cs @@ -19,8 +19,6 @@ namespace osu.Game.Overlays.Rankings { private const int content_height = 250; private const int dropdown_height = 50; - private const int spacing = 20; - private const int duration = 200; public IEnumerable Spotlights { @@ -72,7 +70,7 @@ namespace osu.Game.Overlays.Rankings AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, spacing), + Spacing = new Vector2(0, 20), Children = new Drawable[] { new RankingsScopeSelector @@ -112,12 +110,12 @@ namespace osu.Game.Overlays.Rankings protected override void LoadComplete() { - Scope.BindValueChanged(scope => onScopeChanged(scope.NewValue), true); + Scope.BindValueChanged(onScopeChanged, true); base.LoadComplete(); } - private void onScopeChanged(RankingsScope scope) => - dropdownPlaceholder.FadeTo(scope == RankingsScope.Spotlights ? 1 : 0, duration, Easing.OutQuint); + private void onScopeChanged(ValueChangedEvent scope) => + dropdownPlaceholder.FadeTo(scope.NewValue == RankingsScope.Spotlights ? 1 : 0, 200, Easing.OutQuint); private class HeaderBackground : Sprite { From 614e68cdf960cd15667f4b2597012320239750a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Sep 2019 18:10:52 +0900 Subject: [PATCH 1259/2815] Remove redundant BindTarget usage --- osu.Game/Overlays/Rankings/RankingsHeader.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Rankings/RankingsHeader.cs b/osu.Game/Overlays/Rankings/RankingsHeader.cs index 4e502fb7fe..fbf3097f4f 100644 --- a/osu.Game/Overlays/Rankings/RankingsHeader.cs +++ b/osu.Game/Overlays/Rankings/RankingsHeader.cs @@ -49,7 +49,7 @@ namespace osu.Game.Overlays.Rankings { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Current = { BindTarget = Ruleset } + Current = Ruleset }, new Container { @@ -76,14 +76,14 @@ namespace osu.Game.Overlays.Rankings new RankingsScopeSelector { Margin = new MarginPadding { Top = 10 }, - Current = { BindTarget = Scope } + Current = Scope }, new HeaderTitle { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Scope = { BindTarget = Scope }, Margin = new MarginPadding { Top = 10 }, + Scope = { BindTarget = Scope }, Country = { BindTarget = Country }, }, dropdownPlaceholder = new Container @@ -97,7 +97,7 @@ namespace osu.Game.Overlays.Rankings Child = dropdown = new OsuDropdown { RelativeSizeAxes = Axes.X, - Current = { BindTarget = Spotlight }, + Current = Spotlight, } } } From 5c2c055614d943f1cc46d8b8f1faf8f0bb17daa9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 13 Sep 2019 18:49:21 +0900 Subject: [PATCH 1260/2815] Set lifetime on initial state update --- .../Objects/Drawables/DrawableHitCircle.cs | 2 ++ .../Objects/Drawables/DrawableOsuHitObject.cs | 13 +++++++++---- .../Objects/Drawables/DrawableRepeatPoint.cs | 2 ++ .../Objects/Drawables/DrawableSlider.cs | 2 ++ .../Objects/Drawables/DrawableSliderTick.cs | 2 ++ .../Objects/Drawables/DrawableSpinner.cs | 2 ++ 6 files changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 85fd68efdd..83646c561d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -142,6 +142,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void UpdateStateTransforms(ArmedState state) { + base.UpdateStateTransforms(state); + Debug.Assert(HitObject.HitWindows != null); switch (state) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 8a7e5117f9..c46343c73c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -41,12 +41,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength); - protected override void LoadComplete() + protected override void UpdateStateTransforms(ArmedState state) { - base.LoadComplete(); + base.UpdateStateTransforms(state); - // Manually set to reduce the number of future alive objects to a bare minimum. - LifetimeStart = HitObject.StartTime - HitObject.TimePreempt; + switch (state) + { + case ArmedState.Idle: + // Manually set to reduce the number of future alive objects to a bare minimum. + LifetimeStart = HitObject.StartTime - HitObject.TimePreempt; + break; + } } protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(HitObject, judgement); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index 00a943a67f..84d2a4af9b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -74,6 +74,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void UpdateStateTransforms(ArmedState state) { + base.UpdateStateTransforms(state); + switch (state) { case ArmedState.Idle: diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 65f1d5e15f..08b43b0345 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -202,6 +202,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void UpdateStateTransforms(ArmedState state) { + base.UpdateStateTransforms(state); + Ball.FadeIn(); Ball.ScaleTo(HitObject.Scale); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index ba931976a8..9d4d9958a1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -75,6 +75,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void UpdateStateTransforms(ArmedState state) { + base.UpdateStateTransforms(state); + switch (state) { case ArmedState.Idle: diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index b1185ddba8..d1b9ee6cb4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -215,6 +215,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void UpdateStateTransforms(ArmedState state) { + base.UpdateStateTransforms(state); + var sequence = this.Delay(Spinner.Duration).FadeOut(160); switch (state) From a6420def9909b6e375c57f6f43e1b9f05edb401f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Sep 2019 19:29:49 +0900 Subject: [PATCH 1261/2815] Make hyperdash test automatic --- osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs index a603d96201..7b8c699f2c 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs @@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Catch.Tests { } + protected override bool Autoplay => true; + [Test] public void TestHyperDash() { From c13950fbbf801d92a8c62aa4075ef51e2dadc323 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 13 Sep 2019 13:43:21 +0300 Subject: [PATCH 1262/2815] Remove custom db additions --- osu.Game/Migrations/20171019041408_InitialCreate.cs | 1 - osu.Game/Migrations/20181007180454_StandardizePaths.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game/Migrations/20171019041408_InitialCreate.cs b/osu.Game/Migrations/20171019041408_InitialCreate.cs index 3349998873..9b6881f98c 100644 --- a/osu.Game/Migrations/20171019041408_InitialCreate.cs +++ b/osu.Game/Migrations/20171019041408_InitialCreate.cs @@ -35,7 +35,6 @@ namespace osu.Game.Migrations AudioFile = table.Column(type: "TEXT", nullable: true), Author = table.Column(type: "TEXT", nullable: true), BackgroundFile = table.Column(type: "TEXT", nullable: true), - VideoFile = table.Column(type: "TEXT", nullable: true), PreviewTime = table.Column(type: "INTEGER", nullable: false), Source = table.Column(type: "TEXT", nullable: true), Tags = table.Column(type: "TEXT", nullable: true), diff --git a/osu.Game/Migrations/20181007180454_StandardizePaths.cs b/osu.Game/Migrations/20181007180454_StandardizePaths.cs index c106b839e2..274b8030a9 100644 --- a/osu.Game/Migrations/20181007180454_StandardizePaths.cs +++ b/osu.Game/Migrations/20181007180454_StandardizePaths.cs @@ -15,7 +15,6 @@ namespace osu.Game.Migrations migrationBuilder.Sql($"UPDATE `BeatmapInfo` SET `Path` = REPLACE(`Path`, '{windowsStyle}', '{standardized}')"); migrationBuilder.Sql($"UPDATE `BeatmapMetadata` SET `AudioFile` = REPLACE(`AudioFile`, '{windowsStyle}', '{standardized}')"); migrationBuilder.Sql($"UPDATE `BeatmapMetadata` SET `BackgroundFile` = REPLACE(`BackgroundFile`, '{windowsStyle}', '{standardized}')"); - migrationBuilder.Sql($"UPDATE `BeatmapMetadata` SET `VideoFile` = REPLACE(`VideoFile`, '{windowsStyle}', '{standardized}')"); migrationBuilder.Sql($"UPDATE `BeatmapSetFileInfo` SET `Filename` = REPLACE(`Filename`, '{windowsStyle}', '{standardized}')"); migrationBuilder.Sql($"UPDATE `SkinFileInfo` SET `Filename` = REPLACE(`Filename`, '{windowsStyle}', '{standardized}')"); } From 9e742839ac360f81f45c3db28c7587b1d4cd4002 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 13 Sep 2019 13:57:55 +0300 Subject: [PATCH 1263/2815] Use correct database migration --- ...20190913104727_AddBeatmapVideo.Designer.cs | 506 ++++++++++++++++++ .../20190913104727_AddBeatmapVideo.cs | 22 + .../Migrations/OsuDbContextModelSnapshot.cs | 4 +- 3 files changed, 531 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs create mode 100644 osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs diff --git a/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs new file mode 100644 index 0000000000..826233a2b0 --- /dev/null +++ b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs @@ -0,0 +1,506 @@ +// +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("20190913104727_AddBeatmapVideo")] + partial class AddBeatmapVideo + { + 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.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/20190913104727_AddBeatmapVideo.cs b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs new file mode 100644 index 0000000000..9ed0943acd --- /dev/null +++ b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddBeatmapVideo : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "VideoFile", + table: "BeatmapMetadata", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "VideoFile", + table: "BeatmapMetadata"); + } + } +} diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index 761dca2801..a6d9d1f3cb 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -14,7 +14,7 @@ namespace osu.Game.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => { @@ -139,6 +139,8 @@ namespace osu.Game.Migrations b.Property("TitleUnicode"); + b.Property("VideoFile"); + b.HasKey("ID"); b.ToTable("BeatmapMetadata"); From 744085fa549766b74d2bcb9f78e8a841225fe936 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Sep 2019 20:25:08 +0900 Subject: [PATCH 1264/2815] Fix exploding fruit not getting correct lifetime --- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 330f6e6b24..40d2f64f6a 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -74,6 +74,7 @@ namespace osu.Game.Rulesets.Catch.UI caughtFruit.Anchor = Anchor.TopCentre; caughtFruit.Origin = Anchor.Centre; caughtFruit.Scale *= 0.7f; + caughtFruit.LifetimeStart = caughtFruit.HitObject.StartTime; caughtFruit.LifetimeEnd = double.MaxValue; MovableCatcher.Add(caughtFruit); @@ -414,7 +415,10 @@ namespace osu.Game.Rulesets.Catch.UI f.MoveToY(f.Y + 75, 750, Easing.InSine); f.FadeOut(750); - f.Expire(true); + + // todo: this shouldn't exist once DrawableHitObject's ClearTransformsAfter overrides are repaired. + f.LifetimeStart = Time.Current; + f.Expire(); } } @@ -448,7 +452,10 @@ namespace osu.Game.Rulesets.Catch.UI fruit.MoveToY(fruit.Y - 50, 250, Easing.OutSine).Then().MoveToY(fruit.Y + 50, 500, Easing.InSine); fruit.MoveToX(fruit.X + originalX * 6, 1000); fruit.FadeOut(750); - fruit.Expire(true); + + // todo: this shouldn't exist once DrawableHitObject's ClearTransformsAfter overrides are repaired. + fruit.LifetimeStart = Time.Current; + fruit.Expire(); } } } From d385c35955aa89f604c606e8d7051761d5808374 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Sep 2019 21:55:45 +0900 Subject: [PATCH 1265/2815] Apply suggestions from code review Co-Authored-By: Salman Ahmed --- osu.Game/Tests/Visual/OsuTestScene.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 5a7fbd31e2..b382fdb3c0 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual get { if (UseOnlineAPI) - throw new Exception("Using the OsuTestScene dummy API is not supported when UseOnlineAPI is true"); + throw new Exception($"Using the {nameof(OsuTestScene)} dummy API is not supported when {nameof(UseOnlineAPI)} is true"); return dummyAPI; } @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual /// /// Whether this test scene requires real-world API access. - /// If true, this will bypass the local and use the provided one. + /// If true, this will bypass the local and use the provided one. /// protected virtual bool UseOnlineAPI => false; From 2379b665e3e81cee3eb91bef8bcebc1a0a66c61b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Sep 2019 22:15:11 +0900 Subject: [PATCH 1266/2815] Use InvalidOperationException --- osu.Game/Tests/Visual/OsuTestScene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index b382fdb3c0..2b8baab57c 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual get { if (UseOnlineAPI) - throw new Exception($"Using the {nameof(OsuTestScene)} dummy API is not supported when {nameof(UseOnlineAPI)} is true"); + throw new InvalidOperationException($"Using the {nameof(OsuTestScene)} dummy API is not supported when {nameof(UseOnlineAPI)} is true"); return dummyAPI; } From 82561aa44a30dd0986e79b402168c709218a6527 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Sep 2019 22:44:40 +0900 Subject: [PATCH 1267/2815] Fix catcher additive sprite rewinding and remove unnecessary update code --- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 40d2f64f6a..56c8b33e02 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.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; @@ -199,7 +199,6 @@ namespace osu.Game.Rulesets.Catch.UI additive.Anchor = Anchor; additive.OriginPosition = additive.OriginPosition + new Vector2(DrawWidth / 2, 0); // also temporary to align sprite correctly. - additive.LifetimeStart = Clock.CurrentTime; additive.Position = Position; additive.Scale = Scale; additive.Colour = HyperDashing ? Color4.Red : Color4.White; @@ -208,7 +207,8 @@ namespace osu.Game.Rulesets.Catch.UI AdditiveTarget.Add(additive); - additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint).Expire(); + additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint); + additive.Expire(true); Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50); } @@ -303,6 +303,7 @@ namespace osu.Game.Rulesets.Catch.UI { this.FadeColour(Color4.White, hyper_dash_transition_length, Easing.OutQuint); this.FadeTo(1, hyper_dash_transition_length, Easing.OutQuint); + Trail &= Dashing; } } else @@ -386,12 +387,6 @@ namespace osu.Game.Rulesets.Catch.UI X = hyperDashTargetPosition; SetHyperDashState(); } - - if (Clock.ElapsedFrameTime < 0) - { - AdditiveTarget.RemoveAll(d => Clock.CurrentTime < d.LifetimeStart); - caughtFruit.RemoveAll(d => d.HitObject.StartTime > Clock.CurrentTime); - } } /// From 624e5644a4f582d66df2b9ecdc7c2216f5a29394 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Sep 2019 23:05:47 +0900 Subject: [PATCH 1268/2815] Change osu!catch key trigger to occur on frame before positional change --- .../Replays/CatchReplayFrame.cs | 15 ++++++++------- .../Replays/ManiaReplayFrame.cs | 2 +- osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs | 2 +- .../Replays/TaikoReplayFrame.cs | 2 +- .../Replays/Types/IConvertibleReplayFrame.cs | 4 ++-- osu.Game/Scoring/Legacy/LegacyScoreParser.cs | 12 +++++------- 6 files changed, 18 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs index 19637f321b..b41a5e0612 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs @@ -33,13 +33,13 @@ namespace osu.Game.Rulesets.Catch.Replays if (lastFrame != null) { if (Position > lastFrame.Position) - Actions.Add(CatchAction.MoveRight); + lastFrame.Actions.Add(CatchAction.MoveRight); else if (Position < lastFrame.Position) - Actions.Add(CatchAction.MoveLeft); + lastFrame.Actions.Add(CatchAction.MoveLeft); } } - public void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, LegacyReplayFrame lastFrame = null) + public void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) { Position = currentFrame.Position.X / CatchPlayfield.BASE_WIDTH; Dashing = currentFrame.ButtonState == ReplayButtonState.Left1; @@ -47,11 +47,12 @@ namespace osu.Game.Rulesets.Catch.Replays if (Dashing) Actions.Add(CatchAction.Dash); - if (lastFrame != null) + // this probably needs some cross-checking with osu-stable to ensure it is actually correct. + if (lastFrame is CatchReplayFrame lastCatchFrame) { - if (currentFrame.Position.X > lastFrame.Position.X) - Actions.Add(CatchAction.MoveRight); - else if (currentFrame.Position.X < lastFrame.Position.X) + if (Position > lastCatchFrame.Position) + lastCatchFrame.Actions.Add(CatchAction.MoveRight); + else if (Position < lastCatchFrame.Position) Actions.Add(CatchAction.MoveLeft); } } diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs index b2901f46c0..70ba5cd938 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, LegacyReplayFrame lastFrame = null) + public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) { // We don't need to fully convert, just create the converter var converter = new ManiaBeatmapConverter(beatmap); diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs index 441b69ef2d..e6c6db5e61 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Replays Actions.AddRange(actions); } - public void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, LegacyReplayFrame lastFrame = null) + public void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) { Position = currentFrame.Position; if (currentFrame.MouseLeft) Actions.Add(OsuAction.LeftButton); diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs index 6e43892777..c5ebefc397 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, LegacyReplayFrame lastFrame = null) + public void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) { if (currentFrame.MouseRight1) Actions.Add(TaikoAction.LeftRim); if (currentFrame.MouseRight2) Actions.Add(TaikoAction.RightRim); diff --git a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs index 8fcdec6630..c2947c0aca 100644 --- a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs +++ b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Replays.Types /// /// The to extract values from. /// The beatmap. - /// The last , used to fill in missing delta information. May be null. - void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, LegacyReplayFrame lastFrame = null); + /// The last post-conversion , used to fill in missing delta information. May be null. + void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null); } } diff --git a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs index 5a90daa045..2cdd0c4b5e 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs @@ -218,7 +218,7 @@ namespace osu.Game.Scoring.Legacy private void readLegacyReplay(Replay replay, StreamReader reader) { float lastTime = 0; - LegacyReplayFrame currentFrame = null; + ReplayFrame currentFrame = null; foreach (var l in reader.ReadToEnd().Split(',')) { @@ -241,18 +241,16 @@ namespace osu.Game.Scoring.Legacy if (diff < 0) continue; - var lastFrame = currentFrame; - - currentFrame = new LegacyReplayFrame(lastTime, + currentFrame = convertFrame(new LegacyReplayFrame(lastTime, Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE), Parsing.ParseFloat(split[2], Parsing.MAX_COORDINATE_VALUE), - (ReplayButtonState)Parsing.ParseInt(split[3])); + (ReplayButtonState)Parsing.ParseInt(split[3])), currentFrame); - replay.Frames.Add(convertFrame(currentFrame, lastFrame)); + replay.Frames.Add(currentFrame); } } - private ReplayFrame convertFrame(LegacyReplayFrame currentFrame, LegacyReplayFrame lastFrame) + private ReplayFrame convertFrame(LegacyReplayFrame currentFrame, ReplayFrame lastFrame) { var convertible = currentRuleset.CreateConvertibleReplayFrame(); if (convertible == null) From 65aa7b20169e6a92444713fa010ce65582a591c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 14 Sep 2019 00:07:06 +0900 Subject: [PATCH 1269/2815] Recreate beatmap video on each consumption Should not be shared over multiple usages --- osu.Game/Beatmaps/WorkingBeatmap.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index bf3fa90d7b..3fc33e9f52 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -44,7 +44,6 @@ namespace osu.Game.Beatmaps track = new RecyclableLazy(() => GetTrack() ?? GetVirtualTrack()); background = new RecyclableLazy(GetBackground, BackgroundStillValid); - video = new RecyclableLazy(GetVideo); waveform = new RecyclableLazy(GetWaveform); storyboard = new RecyclableLazy(GetStoryboard); skin = new RecyclableLazy(GetSkin); @@ -188,11 +187,9 @@ namespace osu.Game.Beatmaps protected abstract Texture GetBackground(); private readonly RecyclableLazy background; - public bool VideoLoaded => video.IsResultAvailable; - public VideoSprite Video => video.Value; + public VideoSprite Video => GetVideo(); protected abstract VideoSprite GetVideo(); - private readonly RecyclableLazy video; public bool TrackLoaded => track.IsResultAvailable; public Track Track => track.Value; From 9a94405b3a195d18ddcfaf2e1a01a683bff7bd52 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 14 Sep 2019 06:05:09 +0300 Subject: [PATCH 1270/2815] Fix video playback is stretched on client resize --- osu.Game/Screens/Play/DimmableVideo.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/DimmableVideo.cs b/osu.Game/Screens/Play/DimmableVideo.cs index 3e6b95d2cc..4981c3457f 100644 --- a/osu.Game/Screens/Play/DimmableVideo.cs +++ b/osu.Game/Screens/Play/DimmableVideo.cs @@ -59,8 +59,12 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both; Masking = true; - AddInternal(video); video.RelativeSizeAxes = Axes.Both; + video.FillMode = FillMode.Fill; + video.Anchor = Anchor.Centre; + video.Origin = Anchor.Centre; + + AddInternal(video); } [BackgroundDependencyLoader] From 8ad782a82d71b03d6f17bec85b35bbacf6e30102 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 14 Sep 2019 06:16:25 +0300 Subject: [PATCH 1271/2815] Fix RankingsHeader dropdown can be clickable when not visible --- osu.Game/Overlays/Rankings/RankingsHeader.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Rankings/RankingsHeader.cs b/osu.Game/Overlays/Rankings/RankingsHeader.cs index fbf3097f4f..13c1f0fe40 100644 --- a/osu.Game/Overlays/Rankings/RankingsHeader.cs +++ b/osu.Game/Overlays/Rankings/RankingsHeader.cs @@ -31,7 +31,6 @@ namespace osu.Game.Overlays.Rankings public readonly Bindable Country = new Bindable(); public readonly Bindable Spotlight = new Bindable(); - private readonly Container dropdownPlaceholder; private readonly OsuDropdown dropdown; public RankingsHeader() @@ -86,14 +85,13 @@ namespace osu.Game.Overlays.Rankings Scope = { BindTarget = Scope }, Country = { BindTarget = Country }, }, - dropdownPlaceholder = new Container + new Container { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X, Height = dropdown_height, Width = 0.8f, - AlwaysPresent = true, Child = dropdown = new OsuDropdown { RelativeSizeAxes = Axes.X, @@ -115,7 +113,7 @@ namespace osu.Game.Overlays.Rankings } private void onScopeChanged(ValueChangedEvent scope) => - dropdownPlaceholder.FadeTo(scope.NewValue == RankingsScope.Spotlights ? 1 : 0, 200, Easing.OutQuint); + dropdown.FadeTo(scope.NewValue == RankingsScope.Spotlights ? 1 : 0, 200, Easing.OutQuint); private class HeaderBackground : Sprite { From a36c80868247ff5a7fe1c180378e34e95732a905 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 14 Sep 2019 06:28:59 +0300 Subject: [PATCH 1272/2815] Use Fit FillFode --- osu.Game/Screens/Play/DimmableVideo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/DimmableVideo.cs b/osu.Game/Screens/Play/DimmableVideo.cs index 4981c3457f..628871b164 100644 --- a/osu.Game/Screens/Play/DimmableVideo.cs +++ b/osu.Game/Screens/Play/DimmableVideo.cs @@ -60,7 +60,7 @@ namespace osu.Game.Screens.Play Masking = true; video.RelativeSizeAxes = Axes.Both; - video.FillMode = FillMode.Fill; + video.FillMode = FillMode.Fit; video.Anchor = Anchor.Centre; video.Origin = Anchor.Centre; From 9febeb1f3def1c30e07c825785f5c2557bbdcb26 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 14 Sep 2019 06:32:00 +0300 Subject: [PATCH 1273/2815] Add black background --- osu.Game/Screens/Play/DimmableVideo.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/DimmableVideo.cs b/osu.Game/Screens/Play/DimmableVideo.cs index 628871b164..4d6c10d69d 100644 --- a/osu.Game/Screens/Play/DimmableVideo.cs +++ b/osu.Game/Screens/Play/DimmableVideo.cs @@ -4,8 +4,10 @@ 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.Game.Graphics.Containers; +using osuTK.Graphics; namespace osu.Game.Screens.Play { @@ -64,7 +66,15 @@ namespace osu.Game.Screens.Play video.Anchor = Anchor.Centre; video.Origin = Anchor.Centre; - AddInternal(video); + AddRangeInternal(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + video, + }); } [BackgroundDependencyLoader] From 2783ae62ef7b409b5b6a5d9f4f3867d1553d2bf6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 14 Sep 2019 06:34:57 +0300 Subject: [PATCH 1274/2815] Remove useless container --- osu.Game/Overlays/Rankings/RankingsHeader.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Rankings/RankingsHeader.cs b/osu.Game/Overlays/Rankings/RankingsHeader.cs index 13c1f0fe40..6aa3e75df9 100644 --- a/osu.Game/Overlays/Rankings/RankingsHeader.cs +++ b/osu.Game/Overlays/Rankings/RankingsHeader.cs @@ -18,7 +18,6 @@ namespace osu.Game.Overlays.Rankings public class RankingsHeader : CompositeDrawable { private const int content_height = 250; - private const int dropdown_height = 50; public IEnumerable Spotlights { @@ -85,18 +84,13 @@ namespace osu.Game.Overlays.Rankings Scope = { BindTarget = Scope }, Country = { BindTarget = Country }, }, - new Container + dropdown = new OsuDropdown { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X, - Height = dropdown_height, Width = 0.8f, - Child = dropdown = new OsuDropdown - { - RelativeSizeAxes = Axes.X, - Current = Spotlight, - } + Current = Spotlight, } } }, From 8456861b8d58f5b4bb7695d81aaee02a952717d8 Mon Sep 17 00:00:00 2001 From: Roman Kapustin Date: Sat, 14 Sep 2019 17:08:56 +0300 Subject: [PATCH 1275/2815] Wait for cursor hiding using ManualResetEventSlim --- osu.Game/Graphics/ScreenshotManager.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index f532302de2..02d928ec66 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -83,11 +83,19 @@ namespace osu.Game.Graphics const int frames_to_wait = 3; int framesWaited = 0; - ScheduledDelegate waitDelegate = host.DrawThread.Scheduler.AddDelayed(() => framesWaited++, 0, true); - while (framesWaited < frames_to_wait) - Thread.Sleep(10); - waitDelegate.Cancel(); + using (var framesWaitedEvent = new ManualResetEventSlim(false)) + { + ScheduledDelegate waitDelegate = host.DrawThread.Scheduler.AddDelayed(() => + { + if (framesWaited++ < frames_to_wait) + // ReSharper disable once AccessToDisposedClosure + framesWaitedEvent.Set(); + }, 10, true); + + framesWaitedEvent.Wait(); + waitDelegate.Cancel(); + } } using (var image = await host.TakeScreenshotAsync()) From 7b1ff38df7652b3eee6c159f50125f7809cefb4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 9 Sep 2019 23:41:51 +0200 Subject: [PATCH 1276/2815] Implement line-buffered reader Add a line-buffered reader decorator operating on StreamReader instances. The decorator has two main operations - PeekLine(), which allows to see the next line in the stream without consuming it, ReadLine(), which consumes and returns the next line in the stream, and ReadToEnd() which reads all the remaining text in the stream (including the unconsumed peeked line). Peeking line-per-line uses an internal queue of lines that have been read ahead from the underlying stream. The addition of the line-buffered reader is a workaround solution to a problem with decoding. At current selecting a decoder works by irreversibly reading the first line from the stream and looking for a magic string that indicates the type of decoder to use. It might however be possible for a file to be valid in format, just missing a header. In such a case a lack of a line-buffered reader makes it impossible to reparse the content of that first line. Introducing it will however allow to peek the first line for magic first. - If magic is found in the first line, GetDecoder() will peek it and use it to return the correct Decoder instance. Note that in the case of JsonBeatmapDecoder the magic is the opening JSON object brace, and therefore must not be consumed. - If magic is not found, the fallback decoder will be able to consume it using ReadLine() in Decode(). This commit additionally contains basic unit tests for the reader. Suggested-by: Aergwyn --- .../Beatmaps/IO/LineBufferedReaderTest.cs | 133 ++++++++++++++++++ osu.Game/IO/LineBufferedReader.cs | 70 +++++++++ 2 files changed, 203 insertions(+) create mode 100644 osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.cs create mode 100644 osu.Game/IO/LineBufferedReader.cs diff --git a/osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.cs new file mode 100644 index 0000000000..b582ca0a6f --- /dev/null +++ b/osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.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 System.IO; +using System.Text; +using NUnit.Framework; +using osu.Game.IO; + +namespace osu.Game.Tests.Beatmaps.IO +{ + [TestFixture] + public class LineBufferedReaderTest + { + [Test] + public void TestReadLineByLine() + { + const string contents = @"line 1 +line 2 +line 3"; + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents))) + using (var bufferedReader = new LineBufferedReader(stream)) + { + Assert.AreEqual("line 1", bufferedReader.ReadLine()); + Assert.AreEqual("line 2", bufferedReader.ReadLine()); + Assert.AreEqual("line 3", bufferedReader.ReadLine()); + Assert.IsNull(bufferedReader.ReadLine()); + } + } + + [Test] + public void TestPeekLineOnce() + { + const string contents = @"line 1 +peek this +line 3"; + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents))) + using (var bufferedReader = new LineBufferedReader(stream)) + { + Assert.AreEqual("line 1", bufferedReader.ReadLine()); + Assert.AreEqual("peek this", bufferedReader.PeekLine()); + Assert.AreEqual("peek this", bufferedReader.ReadLine()); + Assert.AreEqual("line 3", bufferedReader.ReadLine()); + Assert.IsNull(bufferedReader.ReadLine()); + } + } + + [Test] + public void TestPeekLineMultipleTimes() + { + const string contents = @"peek this once +line 2 +peek this a lot"; + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents))) + using (var bufferedReader = new LineBufferedReader(stream)) + { + Assert.AreEqual("peek this once", bufferedReader.PeekLine()); + Assert.AreEqual("peek this once", bufferedReader.ReadLine()); + Assert.AreEqual("line 2", bufferedReader.ReadLine()); + Assert.AreEqual("peek this a lot", bufferedReader.PeekLine()); + Assert.AreEqual("peek this a lot", bufferedReader.PeekLine()); + Assert.AreEqual("peek this a lot", bufferedReader.PeekLine()); + Assert.AreEqual("peek this a lot", bufferedReader.ReadLine()); + Assert.IsNull(bufferedReader.ReadLine()); + } + } + + [Test] + public void TestPeekLineAtEndOfStream() + { + const string contents = @"first line +second line"; + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents))) + using (var bufferedReader = new LineBufferedReader(stream)) + { + Assert.AreEqual("first line", bufferedReader.ReadLine()); + Assert.AreEqual("second line", bufferedReader.ReadLine()); + Assert.IsNull(bufferedReader.PeekLine()); + Assert.IsNull(bufferedReader.ReadLine()); + Assert.IsNull(bufferedReader.PeekLine()); + } + } + + [Test] + public void TestPeekReadLineOnEmptyStream() + { + using (var stream = new MemoryStream()) + using (var bufferedReader = new LineBufferedReader(stream)) + { + Assert.IsNull(bufferedReader.PeekLine()); + Assert.IsNull(bufferedReader.ReadLine()); + Assert.IsNull(bufferedReader.ReadLine()); + Assert.IsNull(bufferedReader.PeekLine()); + } + } + + [Test] + public void TestReadToEndNoPeeks() + { + const string contents = @"first line +second line"; + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents))) + using (var bufferedReader = new LineBufferedReader(stream)) + { + Assert.AreEqual(contents, bufferedReader.ReadToEnd()); + } + } + + [Test] + public void TestReadToEndAfterReadsAndPeeks() + { + const string contents = @"this line is gone +this one shouldn't be +these ones +definitely not"; + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents))) + using (var bufferedReader = new LineBufferedReader(stream)) + { + Assert.AreEqual("this line is gone", bufferedReader.ReadLine()); + Assert.AreEqual("this one shouldn't be", bufferedReader.PeekLine()); + const string ending = @"this one shouldn't be +these ones +definitely not"; + Assert.AreEqual(ending, bufferedReader.ReadToEnd()); + } + } + } +} diff --git a/osu.Game/IO/LineBufferedReader.cs b/osu.Game/IO/LineBufferedReader.cs new file mode 100644 index 0000000000..66dee1181f --- /dev/null +++ b/osu.Game/IO/LineBufferedReader.cs @@ -0,0 +1,70 @@ +// 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.IO; +using System.Text; + +namespace osu.Game.IO +{ + /// + /// A -like decorator (with more limited API) for s + /// that allows lines to be peeked without consuming. + /// + public class LineBufferedReader : IDisposable + { + private readonly StreamReader streamReader; + private readonly Queue lineBuffer; + + public LineBufferedReader(Stream stream) + { + streamReader = new StreamReader(stream); + lineBuffer = new Queue(); + } + + /// + /// Reads the next line from the stream without consuming it. + /// + public string PeekLine() + { + if (lineBuffer.Count > 0) + return lineBuffer.Peek(); + + var line = streamReader.ReadLine(); + if (line != null) + lineBuffer.Enqueue(line); + return line; + } + + /// + /// Reads the next line from the stream and consumes it. + /// + public string ReadLine() => lineBuffer.Count > 0 ? lineBuffer.Dequeue() : streamReader.ReadLine(); + + /// + /// Reads the stream to its end and returns the text read. + /// This includes any peeked but unconsumed lines. + /// + public string ReadToEnd() + { + var remainingText = streamReader.ReadToEnd(); + if (lineBuffer.Count == 0) + return remainingText; + + var builder = new StringBuilder(); + + // this might not be completely correct due to varying platform line endings + while (lineBuffer.Count > 0) + builder.AppendLine(lineBuffer.Dequeue()); + builder.Append(remainingText); + + return builder.ToString(); + } + + public void Dispose() + { + streamReader?.Dispose(); + } + } +} From 11eda44d3451c889469af4e8e30d522b04d2dd15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 10 Sep 2019 00:43:30 +0200 Subject: [PATCH 1277/2815] Migrate decoding to line-buffered reader Migrate all usages of StreamReader in the context of decoding beatmaps, storyboards or skins to the new LineBufferedReader. --- osu.Game.Rulesets.Osu.Tests/StackingTest.cs | 3 +- .../Formats/LegacyBeatmapDecoderTest.cs | 42 +++++++++---------- .../Beatmaps/Formats/LegacyDecoderTest.cs | 4 +- .../Formats/LegacyStoryboardDecoderTest.cs | 6 +-- .../Beatmaps/Formats/OsuJsonDecoderTest.cs | 5 ++- .../Beatmaps/IO/OszArchiveReaderTest.cs | 3 +- osu.Game.Tests/Skins/LegacySkinDecoderTest.cs | 6 +-- osu.Game.Tests/WaveformTestBeatmap.cs | 3 +- osu.Game/Beatmaps/BeatmapManager.cs | 5 ++- .../Beatmaps/BeatmapManager_WorkingBeatmap.cs | 8 ++-- osu.Game/Beatmaps/Formats/Decoder.cs | 9 ++-- .../Beatmaps/Formats/JsonBeatmapDecoder.cs | 7 +--- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 3 +- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 4 +- .../Formats/LegacyStoryboardDecoder.cs | 3 +- osu.Game/Skinning/LegacySkin.cs | 3 +- .../Tests/Beatmaps/BeatmapConversionTest.cs | 3 +- .../Beatmaps/DifficultyCalculatorTest.cs | 3 +- osu.Game/Tests/Beatmaps/TestBeatmap.cs | 3 +- 19 files changed, 64 insertions(+), 59 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/StackingTest.cs b/osu.Game.Rulesets.Osu.Tests/StackingTest.cs index e8b99e86f9..871afdb09d 100644 --- a/osu.Game.Rulesets.Osu.Tests/StackingTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/StackingTest.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Text; using NUnit.Framework; using osu.Game.Beatmaps; +using osu.Game.IO; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Tests.Beatmaps; @@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Tests public void TestStacking() { using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(beatmap_data))) - using (var reader = new StreamReader(stream)) + using (var reader = new LineBufferedReader(stream)) { var beatmap = Decoder.GetDecoder(reader).Decode(reader); var converted = new TestWorkingBeatmap(beatmap).GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty()); diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 535320530d..6a77c6ca96 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.IO; using NUnit.Framework; using osuTK; using osuTK.Graphics; @@ -13,6 +12,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Types; using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Timing; +using osu.Game.IO; using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; @@ -30,13 +30,9 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapVersion() { using (var resStream = TestResources.OpenResource("beatmap-version.osu")) - using (var stream = new StreamReader(resStream)) + using (var stream = new LineBufferedReader(resStream)) { var decoder = Decoder.GetDecoder(stream); - - stream.BaseStream.Position = 0; - stream.DiscardBufferedData(); - var working = new TestWorkingBeatmap(decoder.Decode(stream)); Assert.AreEqual(6, working.BeatmapInfo.BeatmapVersion); @@ -51,7 +47,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) - using (var stream = new StreamReader(resStream)) + using (var stream = new LineBufferedReader(resStream)) { var beatmap = decoder.Decode(stream); var beatmapInfo = beatmap.BeatmapInfo; @@ -75,7 +71,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var decoder = new LegacyBeatmapDecoder(); using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) - using (var stream = new StreamReader(resStream)) + using (var stream = new LineBufferedReader(resStream)) { var beatmapInfo = decoder.Decode(stream).BeatmapInfo; @@ -101,7 +97,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var decoder = new LegacyBeatmapDecoder(); using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) - using (var stream = new StreamReader(resStream)) + using (var stream = new LineBufferedReader(resStream)) { var beatmap = decoder.Decode(stream); var beatmapInfo = beatmap.BeatmapInfo; @@ -126,7 +122,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var decoder = new LegacyBeatmapDecoder(); using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) - using (var stream = new StreamReader(resStream)) + using (var stream = new LineBufferedReader(resStream)) { var difficulty = decoder.Decode(stream).BeatmapInfo.BaseDifficulty; @@ -145,7 +141,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) - using (var stream = new StreamReader(resStream)) + using (var stream = new LineBufferedReader(resStream)) { var beatmap = decoder.Decode(stream); var metadata = beatmap.Metadata; @@ -164,7 +160,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) - using (var stream = new StreamReader(resStream)) + using (var stream = new LineBufferedReader(resStream)) { var beatmap = decoder.Decode(stream); var controlPoints = beatmap.ControlPointInfo; @@ -239,7 +235,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; using (var resStream = TestResources.OpenResource("overlapping-control-points.osu")) - using (var stream = new StreamReader(resStream)) + using (var stream = new LineBufferedReader(resStream)) { var controlPoints = decoder.Decode(stream).ControlPointInfo; @@ -271,7 +267,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var decoder = new LegacySkinDecoder(); using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) - using (var stream = new StreamReader(resStream)) + using (var stream = new LineBufferedReader(resStream)) { var comboColors = decoder.Decode(stream).ComboColours; @@ -297,7 +293,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var decoder = new LegacyBeatmapDecoder(); using (var resStream = TestResources.OpenResource("hitobject-combo-offset.osu")) - using (var stream = new StreamReader(resStream)) + using (var stream = new LineBufferedReader(resStream)) { var beatmap = decoder.Decode(stream); @@ -320,7 +316,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var decoder = new LegacyBeatmapDecoder(); using (var resStream = TestResources.OpenResource("hitobject-combo-offset.osu")) - using (var stream = new StreamReader(resStream)) + using (var stream = new LineBufferedReader(resStream)) { var beatmap = decoder.Decode(stream); @@ -343,7 +339,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) - using (var stream = new StreamReader(resStream)) + using (var stream = new LineBufferedReader(resStream)) { var hitObjects = decoder.Decode(stream).HitObjects; @@ -371,7 +367,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; using (var resStream = TestResources.OpenResource("controlpoint-custom-samplebank.osu")) - using (var stream = new StreamReader(resStream)) + using (var stream = new LineBufferedReader(resStream)) { var hitObjects = decoder.Decode(stream).HitObjects; @@ -393,7 +389,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; using (var resStream = TestResources.OpenResource("hitobject-custom-samplebank.osu")) - using (var stream = new StreamReader(resStream)) + using (var stream = new LineBufferedReader(resStream)) { var hitObjects = decoder.Decode(stream).HitObjects; @@ -411,7 +407,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; using (var resStream = TestResources.OpenResource("hitobject-file-samples.osu")) - using (var stream = new StreamReader(resStream)) + using (var stream = new LineBufferedReader(resStream)) { var hitObjects = decoder.Decode(stream).HitObjects; @@ -431,7 +427,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; using (var resStream = TestResources.OpenResource("slider-samples.osu")) - using (var stream = new StreamReader(resStream)) + using (var stream = new LineBufferedReader(resStream)) { var hitObjects = decoder.Decode(stream).HitObjects; @@ -475,7 +471,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; using (var resStream = TestResources.OpenResource("hitobject-no-addition-bank.osu")) - using (var stream = new StreamReader(resStream)) + using (var stream = new LineBufferedReader(resStream)) { var hitObjects = decoder.Decode(stream).HitObjects; @@ -489,7 +485,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; using (var badResStream = TestResources.OpenResource("invalid-events.osu")) - using (var badStream = new StreamReader(badResStream)) + using (var badStream = new LineBufferedReader(badResStream)) { Assert.DoesNotThrow(() => decoder.Decode(badStream)); } diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyDecoderTest.cs index b4d219456c..335a6aeeb0 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyDecoderTest.cs @@ -2,9 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.IO; using NUnit.Framework; using osu.Game.Beatmaps.Formats; +using osu.Game.IO; using osu.Game.Tests.Resources; namespace osu.Game.Tests.Beatmaps.Formats @@ -18,7 +18,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var decoder = new LineLoggingDecoder(14); using (var resStream = TestResources.OpenResource("comments.osu")) - using (var stream = new StreamReader(resStream)) + using (var stream = new LineBufferedReader(resStream)) { decoder.Decode(stream); diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs index 953763c95d..66d53d7e7b 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -1,12 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.IO; using System.Linq; using NUnit.Framework; using osuTK; using osu.Framework.Graphics; using osu.Game.Beatmaps.Formats; +using osu.Game.IO; using osu.Game.Storyboards; using osu.Game.Tests.Resources; @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var decoder = new LegacyStoryboardDecoder(); using (var resStream = TestResources.OpenResource("Himeringo - Yotsuya-san ni Yoroshiku (RLC) [Winber1's Extreme].osu")) - using (var stream = new StreamReader(resStream)) + using (var stream = new LineBufferedReader(resStream)) { var storyboard = decoder.Decode(stream); @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var decoder = new LegacyStoryboardDecoder(); using (var resStream = TestResources.OpenResource("variable-with-suffix.osb")) - using (var stream = new StreamReader(resStream)) + using (var stream = new LineBufferedReader(resStream)) { var storyboard = decoder.Decode(stream); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 4859abbb8e..63346b8c9d 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; +using osu.Game.IO; using osu.Game.IO.Serialization; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; @@ -148,13 +149,13 @@ namespace osu.Game.Tests.Beatmaps.Formats private Beatmap decode(string filename, out Beatmap jsonDecoded) { using (var stream = TestResources.OpenResource(filename)) - using (var sr = new StreamReader(stream)) + 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 StreamReader(ms)) + using (var sr2 = new LineBufferedReader(ms)) { sw.Write(legacyDecoded.Serialize()); sw.Flush(); diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs index 37e0565df0..022b2c1a59 100644 --- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Tests.Resources; using osu.Game.Beatmaps.Formats; +using osu.Game.IO; using osu.Game.IO.Archives; namespace osu.Game.Tests.Beatmaps.IO @@ -50,7 +51,7 @@ namespace osu.Game.Tests.Beatmaps.IO Beatmap beatmap; - using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu"))) + using (var stream = new LineBufferedReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu"))) beatmap = Decoder.GetDecoder(stream).Decode(stream); var meta = beatmap.Metadata; diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs index 8bd846518b..0d96dd08da 100644 --- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs +++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs @@ -2,8 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.IO; using NUnit.Framework; +using osu.Game.IO; using osu.Game.Skinning; using osu.Game.Tests.Resources; using osuTK.Graphics; @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Skins var decoder = new LegacySkinDecoder(); using (var resStream = TestResources.OpenResource(hasColours ? "skin.ini" : "skin-empty.ini")) - using (var stream = new StreamReader(resStream)) + using (var stream = new LineBufferedReader(resStream)) { var comboColors = decoder.Decode(stream).ComboColours; @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Skins var decoder = new LegacySkinDecoder(); using (var resStream = TestResources.OpenResource("skin.ini")) - using (var stream = new StreamReader(resStream)) + using (var stream = new LineBufferedReader(resStream)) { var config = decoder.Decode(stream); diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs index db9576b5fa..0d16a78f75 100644 --- a/osu.Game.Tests/WaveformTestBeatmap.cs +++ b/osu.Game.Tests/WaveformTestBeatmap.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Video; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; +using osu.Game.IO; using osu.Game.IO.Archives; using osu.Game.Tests.Resources; @@ -56,7 +57,7 @@ namespace osu.Game.Tests private Beatmap createTestBeatmap() { using (var beatmapStream = getBeatmapStream()) - using (var beatmapReader = new StreamReader(beatmapStream)) + using (var beatmapReader = new LineBufferedReader(beatmapStream)) return Decoder.GetDecoder(beatmapReader).Decode(beatmapReader); } } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index b9ed3664ef..372eec50fd 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -20,6 +20,7 @@ using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Beatmaps.Formats; using osu.Game.Database; +using osu.Game.IO; using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -264,7 +265,7 @@ namespace osu.Game.Beatmaps } Beatmap beatmap; - using (var stream = new StreamReader(reader.GetStream(mapName))) + using (var stream = new LineBufferedReader(reader.GetStream(mapName))) beatmap = Decoder.GetDecoder(stream).Decode(stream); return new BeatmapSetInfo @@ -287,7 +288,7 @@ namespace osu.Game.Beatmaps { using (var raw = reader.GetStream(name)) using (var ms = new MemoryStream()) //we need a memory stream so we can seek - using (var sr = new StreamReader(ms)) + using (var sr = new LineBufferedReader(ms)) { raw.CopyTo(ms); ms.Position = 0; diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 1d00c94ef2..b879b92f01 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.IO; using System.Linq; using osu.Framework.Audio; using osu.Framework.Audio.Track; @@ -11,6 +10,7 @@ using osu.Framework.Graphics.Video; using osu.Framework.IO.Stores; using osu.Framework.Logging; using osu.Game.Beatmaps.Formats; +using osu.Game.IO; using osu.Game.Skinning; using osu.Game.Storyboards; @@ -33,7 +33,7 @@ namespace osu.Game.Beatmaps { try { - using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) + using (var stream = new LineBufferedReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) return Decoder.GetDecoder(stream).Decode(stream); } catch @@ -127,7 +127,7 @@ namespace osu.Game.Beatmaps try { - using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) + using (var stream = new LineBufferedReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) { var decoder = Decoder.GetDecoder(stream); @@ -136,7 +136,7 @@ namespace osu.Game.Beatmaps storyboard = decoder.Decode(stream); else { - using (var secondaryStream = new StreamReader(store.GetStream(getPathForFile(BeatmapSetInfo.StoryboardFile)))) + using (var secondaryStream = new LineBufferedReader(store.GetStream(getPathForFile(BeatmapSetInfo.StoryboardFile)))) storyboard = decoder.Decode(stream, secondaryStream); } } diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs index 953e50eadc..0f1b617809 100644 --- a/osu.Game/Beatmaps/Formats/Decoder.cs +++ b/osu.Game/Beatmaps/Formats/Decoder.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using osu.Game.IO; namespace osu.Game.Beatmaps.Formats { @@ -13,15 +14,15 @@ namespace osu.Game.Beatmaps.Formats { protected virtual TOutput CreateTemplateObject() => new TOutput(); - public TOutput Decode(StreamReader primaryStream, params StreamReader[] otherStreams) + public TOutput Decode(LineBufferedReader primaryStream, params LineBufferedReader[] otherStreams) { var output = CreateTemplateObject(); - foreach (StreamReader stream in otherStreams.Prepend(primaryStream)) + foreach (LineBufferedReader stream in otherStreams.Prepend(primaryStream)) ParseStreamInto(stream, output); return output; } - protected abstract void ParseStreamInto(StreamReader stream, TOutput output); + protected abstract void ParseStreamInto(LineBufferedReader stream, TOutput output); } public abstract class Decoder @@ -39,7 +40,7 @@ namespace osu.Game.Beatmaps.Formats /// Retrieves a to parse a . /// /// A stream pointing to the . - public static Decoder GetDecoder(StreamReader stream) + public static Decoder GetDecoder(LineBufferedReader stream) where T : new() { if (stream == null) diff --git a/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs index d8482b200f..988968fa42 100644 --- a/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs @@ -1,7 +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.IO; +using osu.Game.IO; using osu.Game.IO.Serialization; namespace osu.Game.Beatmaps.Formats @@ -13,11 +13,8 @@ namespace osu.Game.Beatmaps.Formats AddDecoder("{", m => new JsonBeatmapDecoder()); } - protected override void ParseStreamInto(StreamReader stream, Beatmap output) + protected override void ParseStreamInto(LineBufferedReader stream, Beatmap output) { - stream.BaseStream.Position = 0; - stream.DiscardBufferedData(); - stream.ReadToEnd().DeserializeInto(output); foreach (var hitObject in output.HitObjects) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 0532790f0a..6e34fd8e90 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -8,6 +8,7 @@ using osu.Framework.IO.File; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.IO; namespace osu.Game.Beatmaps.Formats { @@ -41,7 +42,7 @@ namespace osu.Game.Beatmaps.Formats offset = FormatVersion < 5 ? 24 : 0; } - protected override void ParseStreamInto(StreamReader stream, Beatmap beatmap) + protected override void ParseStreamInto(LineBufferedReader stream, Beatmap beatmap) { this.beatmap = beatmap; this.beatmap.BeatmapInfo.BeatmapVersion = FormatVersion; diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 9a8197ad82..83d20da458 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -3,10 +3,10 @@ using System; using System.Collections.Generic; -using System.IO; using osu.Framework.Logging; using osu.Game.Audio; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.IO; using osuTK.Graphics; namespace osu.Game.Beatmaps.Formats @@ -21,7 +21,7 @@ namespace osu.Game.Beatmaps.Formats FormatVersion = version; } - protected override void ParseStreamInto(StreamReader stream, T output) + protected override void ParseStreamInto(LineBufferedReader stream, T output) { Section section = Section.None; diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 14c6ea5c8e..a8f8dbf69d 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -10,6 +10,7 @@ using osuTK; using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.IO.File; +using osu.Game.IO; using osu.Game.Storyboards; namespace osu.Game.Beatmaps.Formats @@ -35,7 +36,7 @@ namespace osu.Game.Beatmaps.Formats AddDecoder(@"[Events]", m => new LegacyStoryboardDecoder()); } - protected override void ParseStreamInto(StreamReader stream, Storyboard storyboard) + protected override void ParseStreamInto(LineBufferedReader stream, Storyboard storyboard) { this.storyboard = storyboard; base.ParseStreamInto(stream, storyboard); diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 0b1076be01..fea15458e4 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Game.Audio; +using osu.Game.IO; using osu.Game.Rulesets.Scoring; using osuTK.Graphics; @@ -35,7 +36,7 @@ namespace osu.Game.Skinning { Stream stream = storage?.GetStream(filename); if (stream != null) - using (StreamReader reader = new StreamReader(stream)) + using (LineBufferedReader reader = new LineBufferedReader(stream)) Configuration = new LegacySkinDecoder().Decode(reader); else Configuration = new DefaultSkinConfiguration(); diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 3fc9662b17..e99b5fc5fb 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Video; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; +using osu.Game.IO; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; @@ -142,7 +143,7 @@ namespace osu.Game.Tests.Beatmaps private IBeatmap getBeatmap(string name) { using (var resStream = openResource($"{resource_namespace}.{name}.osu")) - using (var stream = new StreamReader(resStream)) + using (var stream = new LineBufferedReader(resStream)) { var decoder = Decoder.GetDecoder(stream); ((LegacyBeatmapDecoder)decoder).ApplyOffsets = false; diff --git a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs index 108fa8ff71..748a52d1c5 100644 --- a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs +++ b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs @@ -7,6 +7,7 @@ using System.Reflection; using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; +using osu.Game.IO; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; @@ -26,7 +27,7 @@ namespace osu.Game.Tests.Beatmaps private WorkingBeatmap getBeatmap(string name) { using (var resStream = openResource($"{resource_namespace}.{name}.osu")) - using (var stream = new StreamReader(resStream)) + using (var stream = new LineBufferedReader(resStream)) { var decoder = Decoder.GetDecoder(stream); ((LegacyBeatmapDecoder)decoder).ApplyOffsets = false; diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index b77a8508ad..d6f92ba086 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Text; using osu.Game.Beatmaps; +using osu.Game.IO; using osu.Game.Rulesets; using Decoder = osu.Game.Beatmaps.Formats.Decoder; @@ -39,7 +40,7 @@ namespace osu.Game.Tests.Beatmaps private static Beatmap createTestBeatmap() { using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(test_beatmap_data))) - using (var reader = new StreamReader(stream)) + using (var reader = new LineBufferedReader(stream)) return Decoder.GetDecoder(reader).Decode(reader); } From 86588778b170b363b2e7156a9ffa08ba32e15f67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 10 Sep 2019 22:06:10 +0200 Subject: [PATCH 1278/2815] Implement fallback decoder registration After the preparatory introduction of LineBufferedReader, it is now possible to introduce registration of fallback decoders that won't drop input supplied in the first line of the file. A fallback decoder is used when the magic in the first line of the file does not match any of the other known decoders. In such a case, the fallback decoder is constructed and provided a LineBufferedReader instance. The process of matching magic only peeks the first non-empty line, so it is available for re-reading in Decode() using ReadLine(). There can be only one fallback decoder per type; a second attempt of registering a fallback will result in an exception to avoid bugs. To address the issue of parsing failing on badly or non-headered files, set the legacy decoders for Beatmaps and Storyboards as the fallbacks. Due to non-trivial logic, several new, passing unit tests with possible edge cases also included. --- .../Formats/LegacyBeatmapDecoderTest.cs | 101 ++++++++++++++++++ .../Beatmaps/IO/ImportBeatmapTest.cs | 2 +- osu.Game.Tests/Resources/corrupted-header.osu | 5 + .../empty-line-instead-of-header.osu | 5 + .../Resources/empty-lines-at-start.osu | 8 ++ osu.Game.Tests/Resources/missing-header.osu | 4 + .../Resources/no-empty-line-after-header.osu | 4 + osu.Game.Tests/osu.Game.Tests.csproj | 7 ++ osu.Game/Beatmaps/Formats/Decoder.cs | 41 +++++-- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 1 + .../Formats/LegacyStoryboardDecoder.cs | 1 + osu.Game/IO/LineBufferedReader.cs | 2 + 12 files changed, 172 insertions(+), 9 deletions(-) create mode 100644 osu.Game.Tests/Resources/corrupted-header.osu create mode 100644 osu.Game.Tests/Resources/empty-line-instead-of-header.osu create mode 100644 osu.Game.Tests/Resources/empty-lines-at-start.osu create mode 100644 osu.Game.Tests/Resources/missing-header.osu create mode 100644 osu.Game.Tests/Resources/no-empty-line-after-header.osu diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 6a77c6ca96..f6c0dbbecf 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.IO; using NUnit.Framework; using osuTK; using osuTK.Graphics; @@ -490,5 +491,105 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.DoesNotThrow(() => decoder.Decode(badStream)); } } + + [Test] + public void TestFallbackDecoderForCorruptedHeader() + { + Decoder decoder = null; + Beatmap beatmap = null; + + using (var resStream = TestResources.OpenResource("corrupted-header.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder(stream)); + Assert.IsInstanceOf(decoder); + Assert.DoesNotThrow(() => beatmap = decoder.Decode(stream)); + Assert.IsNotNull(beatmap); + Assert.AreEqual("Beatmap with corrupted header", beatmap.Metadata.Title); + Assert.AreEqual("Evil Hacker", beatmap.Metadata.AuthorString); + } + } + + [Test] + public void TestFallbackDecoderForMissingHeader() + { + Decoder decoder = null; + Beatmap beatmap = null; + + using (var resStream = TestResources.OpenResource("missing-header.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder(stream)); + Assert.IsInstanceOf(decoder); + Assert.DoesNotThrow(() => beatmap = decoder.Decode(stream)); + Assert.IsNotNull(beatmap); + Assert.AreEqual("Beatmap with no header", beatmap.Metadata.Title); + Assert.AreEqual("Incredibly Evil Hacker", beatmap.Metadata.AuthorString); + } + } + + [Test] + public void TestDecodeFileWithEmptyLinesAtStart() + { + Decoder decoder = null; + Beatmap beatmap = null; + + using (var resStream = TestResources.OpenResource("empty-lines-at-start.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder(stream)); + Assert.IsInstanceOf(decoder); + Assert.DoesNotThrow(() => beatmap = decoder.Decode(stream)); + Assert.IsNotNull(beatmap); + Assert.AreEqual("Empty lines at start", beatmap.Metadata.Title); + Assert.AreEqual("Edge Case Hunter", beatmap.Metadata.AuthorString); + } + } + + [Test] + public void TestDecodeFileWithEmptyLinesAndNoHeader() + { + Decoder decoder = null; + Beatmap beatmap = null; + + using (var resStream = TestResources.OpenResource("empty-line-instead-of-header.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder(stream)); + Assert.IsInstanceOf(decoder); + Assert.DoesNotThrow(() => beatmap = decoder.Decode(stream)); + Assert.IsNotNull(beatmap); + Assert.AreEqual("The dog ate the file header", beatmap.Metadata.Title); + Assert.AreEqual("Why does this keep happening", beatmap.Metadata.AuthorString); + } + } + + [Test] + public void TestDecodeFileWithContentImmediatelyAfterHeader() + { + Decoder decoder = null; + Beatmap beatmap = null; + + using (var resStream = TestResources.OpenResource("no-empty-line-after-header.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder(stream)); + Assert.IsInstanceOf(decoder); + Assert.DoesNotThrow(() => beatmap = decoder.Decode(stream)); + Assert.IsNotNull(beatmap); + Assert.AreEqual("No empty line delimiting header from contents", beatmap.Metadata.Title); + Assert.AreEqual("Edge Case Hunter", beatmap.Metadata.AuthorString); + } + } + + [Test] + public void TestDecodeEmptyFile() + { + using (var resStream = new MemoryStream()) + using (var stream = new LineBufferedReader(resStream)) + { + Assert.Throws(() => Decoder.GetDecoder(stream)); + } + } } } diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index ad0ed00989..0686073292 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -126,7 +126,7 @@ namespace osu.Game.Tests.Beatmaps.IO var breakTemp = TestResources.GetTestBeatmapForImport(); - MemoryStream brokenOsu = new MemoryStream(new byte[] { 1, 3, 3, 7 }); + MemoryStream brokenOsu = new MemoryStream(); MemoryStream brokenOsz = new MemoryStream(File.ReadAllBytes(breakTemp)); File.Delete(breakTemp); diff --git a/osu.Game.Tests/Resources/corrupted-header.osu b/osu.Game.Tests/Resources/corrupted-header.osu new file mode 100644 index 0000000000..92701a4a7d --- /dev/null +++ b/osu.Game.Tests/Resources/corrupted-header.osu @@ -0,0 +1,5 @@ +ow computerosu file format v14 + +[Metadata] +Title: Beatmap with corrupted header +Creator: Evil Hacker diff --git a/osu.Game.Tests/Resources/empty-line-instead-of-header.osu b/osu.Game.Tests/Resources/empty-line-instead-of-header.osu new file mode 100644 index 0000000000..91ecf8d84a --- /dev/null +++ b/osu.Game.Tests/Resources/empty-line-instead-of-header.osu @@ -0,0 +1,5 @@ + + +[Metadata] +Title: The dog ate the file header +Creator: Why does this keep happening \ No newline at end of file diff --git a/osu.Game.Tests/Resources/empty-lines-at-start.osu b/osu.Game.Tests/Resources/empty-lines-at-start.osu new file mode 100644 index 0000000000..cb3b1761a2 --- /dev/null +++ b/osu.Game.Tests/Resources/empty-lines-at-start.osu @@ -0,0 +1,8 @@ + + + +osu file format v14 + +[Metadata] +Title: Empty lines at start +Creator: Edge Case Hunter \ No newline at end of file diff --git a/osu.Game.Tests/Resources/missing-header.osu b/osu.Game.Tests/Resources/missing-header.osu new file mode 100644 index 0000000000..95fac0d79b --- /dev/null +++ b/osu.Game.Tests/Resources/missing-header.osu @@ -0,0 +1,4 @@ +[Metadata] + +Title: Beatmap with no header +Creator: Incredibly Evil Hacker diff --git a/osu.Game.Tests/Resources/no-empty-line-after-header.osu b/osu.Game.Tests/Resources/no-empty-line-after-header.osu new file mode 100644 index 0000000000..9db2b7c01c --- /dev/null +++ b/osu.Game.Tests/Resources/no-empty-line-after-header.osu @@ -0,0 +1,4 @@ +osu file format v14 +[Metadata] +Title: No empty line delimiting header from contents +Creator: Edge Case Hunter \ No newline at end of file diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 84f67c9319..9dc3e85e1b 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -1,5 +1,12 @@  + + + + + + + diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs index 0f1b617809..4a539cc33f 100644 --- a/osu.Game/Beatmaps/Formats/Decoder.cs +++ b/osu.Game/Beatmaps/Formats/Decoder.cs @@ -28,6 +28,7 @@ namespace osu.Game.Beatmaps.Formats public abstract class Decoder { private static readonly Dictionary>> decoders = new Dictionary>>(); + private static readonly Dictionary> fallback_decoders = new Dictionary>(); static Decoder() { @@ -49,21 +50,31 @@ namespace osu.Game.Beatmaps.Formats if (!decoders.TryGetValue(typeof(T), out var typedDecoders)) throw new IOException(@"Unknown decoder type"); - string line; + // start off with the first line of the file + string line = stream.PeekLine()?.Trim(); - do + while (line != null && line.Length == 0) { - line = stream.ReadLine()?.Trim(); - } while (line != null && line.Length == 0); + // consume the previously peeked empty line and advance to the next one + stream.ReadLine(); + line = stream.PeekLine()?.Trim(); + } if (line == null) - throw new IOException(@"Unknown file format (null)"); + throw new IOException("Unknown file format (null)"); var decoder = typedDecoders.Select(d => line.StartsWith(d.Key, StringComparison.InvariantCulture) ? d.Value : null).FirstOrDefault(); - if (decoder == null) - throw new IOException($@"Unknown file format ({line})"); - return (Decoder)decoder.Invoke(line); + // 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) + // decoder implementations should therefore not die on receiving their own magic + if (decoder != null) + return (Decoder)decoder.Invoke(line); + + if (!fallback_decoders.TryGetValue(typeof(T), out var fallbackDecoder)) + throw new IOException($"Unknown file format ({line})"); + + return (Decoder)fallbackDecoder.Invoke(); } /// @@ -78,5 +89,19 @@ namespace osu.Game.Beatmaps.Formats typedDecoders[magic] = constructor; } + + /// + /// Registers a fallback decoder instantiation function. + /// The fallback will be returned if the first line of the decoded stream does not match any known magic. + /// + /// Type of object being decoded. + /// A function that constructs the fallback. + protected static void SetFallbackDecoder(Func constructor) + { + if (fallback_decoders.ContainsKey(typeof(T))) + throw new InvalidOperationException($"A fallback decoder was already added for type {typeof(T)}."); + + fallback_decoders[typeof(T)] = constructor; + } } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 6e34fd8e90..786b7611b5 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -26,6 +26,7 @@ namespace osu.Game.Beatmaps.Formats public static void Register() { AddDecoder(@"osu file format v", m => new LegacyBeatmapDecoder(Parsing.ParseInt(m.Split('v').Last()))); + SetFallbackDecoder(() => new LegacyBeatmapDecoder()); } /// diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index a8f8dbf69d..5dbd67d304 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -34,6 +34,7 @@ namespace osu.Game.Beatmaps.Formats // note that this isn't completely correct AddDecoder(@"osu file format v", m => new LegacyStoryboardDecoder()); AddDecoder(@"[Events]", m => new LegacyStoryboardDecoder()); + SetFallbackDecoder(() => new LegacyStoryboardDecoder()); } protected override void ParseStreamInto(LineBufferedReader stream, Storyboard storyboard) diff --git a/osu.Game/IO/LineBufferedReader.cs b/osu.Game/IO/LineBufferedReader.cs index 66dee1181f..aab761afd8 100644 --- a/osu.Game/IO/LineBufferedReader.cs +++ b/osu.Game/IO/LineBufferedReader.cs @@ -25,6 +25,7 @@ namespace osu.Game.IO /// /// Reads the next line from the stream without consuming it. + /// Subsequent calls to without a will return the same string. /// public string PeekLine() { @@ -39,6 +40,7 @@ namespace osu.Game.IO /// /// Reads the next line from the stream and consumes it. + /// If a line was peeked, that same line will then be consumed and returned. /// public string ReadLine() => lineBuffer.Count > 0 ? lineBuffer.Dequeue() : streamReader.ReadLine(); From 29fcab65f9a2e553dd9c9cd550173612f1b74303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 10 Sep 2019 22:40:52 +0200 Subject: [PATCH 1279/2815] Remove superfluous csproj entries --- osu.Game.Tests/osu.Game.Tests.csproj | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 9dc3e85e1b..84f67c9319 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -1,12 +1,5 @@  - - - - - - - From dd9f620c2364e35c1f1185f2f4b5a43893b5874f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 10 Sep 2019 22:45:34 +0200 Subject: [PATCH 1280/2815] Fix misleading xmldoc --- 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 4a539cc33f..045eb2d14d 100644 --- a/osu.Game/Beatmaps/Formats/Decoder.cs +++ b/osu.Game/Beatmaps/Formats/Decoder.cs @@ -92,7 +92,7 @@ namespace osu.Game.Beatmaps.Formats /// /// Registers a fallback decoder instantiation function. - /// The fallback will be returned if the first line of the decoded stream does not match any known magic. + /// The fallback will be returned if the first non-empty line of the decoded stream does not match any known magic. /// /// Type of object being decoded. /// A function that constructs the fallback. From babd34470ea38ca7b38cc80fbbb6a00f8ed7bfaf Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 15 Sep 2019 02:33:21 +0300 Subject: [PATCH 1281/2815] Fix DrawableFlag returns empty texture if there's no flag avaliable for needed country --- osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs | 7 +++++++ osu.Game/Users/Drawables/DrawableFlag.cs | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs index 0ceb5f21d3..c0da605cdb 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs @@ -61,10 +61,17 @@ namespace osu.Game.Tests.Visual.Online FullName = "Belarus" }; + var unknownCountry = new Country + { + FlagName = "CK", + FullName = "Cook Islands" + }; + AddStep("Set country", () => countryBindable.Value = country); AddAssert("Check scope is Performance", () => scope.Value == RankingsScope.Performance); AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score); AddAssert("Check country is Null", () => countryBindable.Value == null); + AddStep("Set country with no flag", () => countryBindable.Value = unknownCountry); } } } diff --git a/osu.Game/Users/Drawables/DrawableFlag.cs b/osu.Game/Users/Drawables/DrawableFlag.cs index 368354e48e..fab561c1af 100644 --- a/osu.Game/Users/Drawables/DrawableFlag.cs +++ b/osu.Game/Users/Drawables/DrawableFlag.cs @@ -26,7 +26,7 @@ namespace osu.Game.Users.Drawables if (ts == null) throw new ArgumentNullException(nameof(ts)); - Texture = ts.Get($@"Flags/{country?.FlagName ?? @"__"}"); + Texture = ts.Get($@"Flags/{country?.FlagName ?? @"__"}") ?? ts.Get($@"Flags/__"); } } } From 96453d8197be660721540348198819d551bf04ef Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 15 Sep 2019 02:46:28 +0300 Subject: [PATCH 1282/2815] Remove redundant string interpolation --- osu.Game/Users/Drawables/DrawableFlag.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Users/Drawables/DrawableFlag.cs b/osu.Game/Users/Drawables/DrawableFlag.cs index fab561c1af..1d648e46b6 100644 --- a/osu.Game/Users/Drawables/DrawableFlag.cs +++ b/osu.Game/Users/Drawables/DrawableFlag.cs @@ -26,7 +26,7 @@ namespace osu.Game.Users.Drawables if (ts == null) throw new ArgumentNullException(nameof(ts)); - Texture = ts.Get($@"Flags/{country?.FlagName ?? @"__"}") ?? ts.Get($@"Flags/__"); + Texture = ts.Get($@"Flags/{country?.FlagName ?? @"__"}") ?? ts.Get(@"Flags/__"); } } } From 78931c8e23f304e0b39387b6d8d355ff64c01555 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 15 Sep 2019 15:59:46 +0200 Subject: [PATCH 1283/2815] Implement notification when user has track or master volume muted This took around under a hour to implement, it has the same behavior and content from osu!stable. A notification will show up when the user has either its master or track volume set to the minimum, clicking it will set it to the default values. --- osu.Game/Screens/Play/PlayerLoader.cs | 46 ++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 5396321160..b8d5fc7bda 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -19,6 +20,8 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Input; +using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Menu; using osu.Game.Screens.Play.HUD; @@ -54,6 +57,8 @@ namespace osu.Game.Screens.Play private InputManager inputManager; + private NotificationOverlay notificationOverlay; + private IdleTracker idleTracker; public PlayerLoader(Func createPlayer) @@ -68,7 +73,7 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audioManager, NotificationOverlay notificationOverlay) { InternalChild = (content = new LogoTrackingContainer { @@ -101,6 +106,10 @@ namespace osu.Game.Screens.Play }); loadNewPlayer(); + + this.notificationOverlay = notificationOverlay; + + checkVolume(audioManager); } private void playerLoaded(Player player) => info.Loading = false; @@ -145,6 +154,12 @@ namespace osu.Game.Screens.Play content.FadeOut(250); } + private void checkVolume(AudioManager audio) + { + if (audio.Volume.Value <= audio.Volume.MinValue || audio.VolumeTrack.Value <= audio.VolumeTrack.MinValue) + notificationOverlay.Post(new MutedNotification()); + } + public override void OnEntering(IScreen last) { base.OnEntering(last); @@ -473,5 +488,34 @@ namespace osu.Game.Screens.Play Loading = true; } } + + private class MutedNotification : SimpleNotification + { + public MutedNotification() + { + this.Text = "Your music volume is set to 0%! Click here to restore it."; + } + + public override bool IsImportant => true; + + public override bool RequestsFocus => true; + + [BackgroundDependencyLoader] + private void load(OsuColour colours, AudioManager audioManager, NotificationOverlay notificationOverlay) + { + Icon = FontAwesome.Solid.VolumeMute; + IconBackgound.Colour = colours.RedDark; + + Activated = delegate + { + notificationOverlay.Hide(); + + audioManager.Volume.SetDefault(); + audioManager.VolumeTrack.SetDefault(); + + return true; + }; + } + } } } From 4a10e6c44e767012cc93266b5c0a9041e6a8cf82 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 15 Sep 2019 16:11:45 +0200 Subject: [PATCH 1284/2815] Use ResolvedAttribute for NotificationOverlay --- osu.Game/Screens/Play/PlayerLoader.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index b8d5fc7bda..aaf750b9f7 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -57,7 +57,8 @@ namespace osu.Game.Screens.Play private InputManager inputManager; - private NotificationOverlay notificationOverlay; + [Resolved] + private NotificationOverlay notificationOverlay { get; set; } private IdleTracker idleTracker; @@ -107,8 +108,6 @@ namespace osu.Game.Screens.Play loadNewPlayer(); - this.notificationOverlay = notificationOverlay; - checkVolume(audioManager); } From 72af640df7316e646fe0c80ffcf2b9fa7b73f2d3 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 15 Sep 2019 16:31:40 +0200 Subject: [PATCH 1285/2815] Expose VolumeOverlay as available dependencies --- osu.Game/OsuGame.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 8fa8ffaf9b..b07cd84bc3 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -485,7 +485,8 @@ namespace osu.Game toolbarElements.Add(d); }); - loadComponentSingleFile(volume = new VolumeOverlay(), leftFloatingOverlayContent.Add); + dependencies.Cache(loadComponentSingleFile(volume = new VolumeOverlay(), leftFloatingOverlayContent.Add)); + loadComponentSingleFile(new OnScreenDisplay(), Add, true); loadComponentSingleFile(musicController = new MusicController(), Add, true); From 0afb5c5bb05f4a9b9e54f43b6d97f4b166a10b3d Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 15 Sep 2019 16:31:57 +0200 Subject: [PATCH 1286/2815] Expose muted state from VolumeOverlay --- osu.Game/Overlays/VolumeOverlay.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index e6204a3179..ff3cbcd575 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -32,6 +32,12 @@ namespace osu.Game.Overlays private readonly BindableDouble muteAdjustment = new BindableDouble(); + public bool IsMuted + { + get => muteButton.Current.Value; + set => muteButton.Current.Value = value; + } + [BackgroundDependencyLoader] private void load(AudioManager audio, OsuColour colours) { From 00e46fdefed6be79e6542464342a0e8a345c32e0 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 15 Sep 2019 16:32:23 +0200 Subject: [PATCH 1287/2815] Add check if game is muted by MuteButton --- osu.Game/Screens/Play/PlayerLoader.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index aaf750b9f7..a00f005827 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -57,10 +58,13 @@ namespace osu.Game.Screens.Play private InputManager inputManager; + private IdleTracker idleTracker; + [Resolved] private NotificationOverlay notificationOverlay { get; set; } - private IdleTracker idleTracker; + [Resolved] + private VolumeOverlay volumeOverlay { get; set; } public PlayerLoader(Func createPlayer) { @@ -155,7 +159,7 @@ namespace osu.Game.Screens.Play private void checkVolume(AudioManager audio) { - if (audio.Volume.Value <= audio.Volume.MinValue || audio.VolumeTrack.Value <= audio.VolumeTrack.MinValue) + if (volumeOverlay.IsMuted || audio.Volume.Value <= audio.Volume.MinValue || audio.VolumeTrack.Value <= audio.VolumeTrack.MinValue) notificationOverlay.Post(new MutedNotification()); } @@ -500,7 +504,7 @@ namespace osu.Game.Screens.Play public override bool RequestsFocus => true; [BackgroundDependencyLoader] - private void load(OsuColour colours, AudioManager audioManager, NotificationOverlay notificationOverlay) + private void load(OsuColour colours, AudioManager audioManager, NotificationOverlay notificationOverlay, VolumeOverlay volumeOverlay) { Icon = FontAwesome.Solid.VolumeMute; IconBackgound.Colour = colours.RedDark; @@ -509,6 +513,7 @@ namespace osu.Game.Screens.Play { notificationOverlay.Hide(); + volumeOverlay.IsMuted = false; audioManager.Volume.SetDefault(); audioManager.VolumeTrack.SetDefault(); From 811a08d18ff2736b52382b95ba48b80544ff27f0 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 15 Sep 2019 16:50:01 +0200 Subject: [PATCH 1288/2815] Use Bindable instead of bool --- osu.Game/Overlays/VolumeOverlay.cs | 6 +----- osu.Game/Screens/Play/PlayerLoader.cs | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index ff3cbcd575..0c08e0eb6e 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -32,11 +32,7 @@ namespace osu.Game.Overlays private readonly BindableDouble muteAdjustment = new BindableDouble(); - public bool IsMuted - { - get => muteButton.Current.Value; - set => muteButton.Current.Value = value; - } + public Bindable IsMuted => muteButton.Current; [BackgroundDependencyLoader] private void load(AudioManager audio, OsuColour colours) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index a00f005827..baeec27412 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -159,7 +159,7 @@ namespace osu.Game.Screens.Play private void checkVolume(AudioManager audio) { - if (volumeOverlay.IsMuted || audio.Volume.Value <= audio.Volume.MinValue || audio.VolumeTrack.Value <= audio.VolumeTrack.MinValue) + if (volumeOverlay.IsMuted.Value || audio.Volume.Value <= audio.Volume.MinValue || audio.VolumeTrack.Value <= audio.VolumeTrack.MinValue) notificationOverlay.Post(new MutedNotification()); } @@ -513,7 +513,7 @@ namespace osu.Game.Screens.Play { notificationOverlay.Hide(); - volumeOverlay.IsMuted = false; + volumeOverlay.IsMuted.Value = false; audioManager.Volume.SetDefault(); audioManager.VolumeTrack.SetDefault(); From 08ab4e508f07b774f83c35063cb7d5216aecb421 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 15 Sep 2019 17:15:52 +0200 Subject: [PATCH 1289/2815] Use loadComponentSingleFile for caching --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b07cd84bc3..d539fe0f50 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -485,7 +485,7 @@ namespace osu.Game toolbarElements.Add(d); }); - dependencies.Cache(loadComponentSingleFile(volume = new VolumeOverlay(), leftFloatingOverlayContent.Add)); + loadComponentSingleFile(volume = new VolumeOverlay(), leftFloatingOverlayContent.Add, true); loadComponentSingleFile(new OnScreenDisplay(), Add, true); From ec788ac09d8ac8343e374187725fbb8a7c4cef24 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 15 Sep 2019 17:20:07 +0200 Subject: [PATCH 1290/2815] Make notification only show up once per session --- osu.Game/Screens/Play/PlayerLoader.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index baeec27412..3bce6fa1ed 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -66,6 +66,8 @@ namespace osu.Game.Screens.Play [Resolved] private VolumeOverlay volumeOverlay { get; set; } + private bool muteWarningShownOnce = false; + public PlayerLoader(Func createPlayer) { this.createPlayer = createPlayer; @@ -159,8 +161,12 @@ namespace osu.Game.Screens.Play private void checkVolume(AudioManager audio) { - if (volumeOverlay.IsMuted.Value || audio.Volume.Value <= audio.Volume.MinValue || audio.VolumeTrack.Value <= audio.VolumeTrack.MinValue) + //Checks if the notification has not been shown yet and also if master volume is muted, track/music volume is muted or if the whole game is muted. + if (!muteWarningShownOnce && (volumeOverlay.IsMuted.Value || audio.Volume.Value <= audio.Volume.MinValue || audio.VolumeTrack.Value <= audio.VolumeTrack.MinValue)) + { notificationOverlay.Post(new MutedNotification()); + muteWarningShownOnce = true; + } } public override void OnEntering(IScreen last) From 2381b4c00365e8d59003b9fc235f25e953618cb7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Sep 2019 00:20:56 +0900 Subject: [PATCH 1291/2815] Move video behind storyboard --- 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 5c7ac9e762..309f4837e5 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -143,8 +143,8 @@ namespace osu.Game.Screens.Play private void addUnderlayComponents(Container target) { - target.Add(DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }); target.Add(DimmableVideo = new DimmableVideo(Beatmap.Value.Video) { RelativeSizeAxes = Axes.Both }); + target.Add(DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }); } private void addGameplayComponents(Container target, WorkingBeatmap working) From e3884658af0be36a364e8637b70fea1f2b20ff08 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 15 Sep 2019 17:36:53 +0200 Subject: [PATCH 1292/2815] Resolve code inspection errors --- osu.Game/Screens/Play/PlayerLoader.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 3bce6fa1ed..d78f36f6de 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -66,7 +65,7 @@ namespace osu.Game.Screens.Play [Resolved] private VolumeOverlay volumeOverlay { get; set; } - private bool muteWarningShownOnce = false; + private bool muteWarningShownOnce; public PlayerLoader(Func createPlayer) { @@ -500,10 +499,7 @@ namespace osu.Game.Screens.Play private class MutedNotification : SimpleNotification { - public MutedNotification() - { - this.Text = "Your music volume is set to 0%! Click here to restore it."; - } + public MutedNotification() => Text = "Your music volume is set to 0%! Click here to restore it."; public override bool IsImportant => true; From ecce12981e7bea177c784912d6adc360805be57e Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Sun, 15 Sep 2019 17:47:44 +0200 Subject: [PATCH 1293/2815] Use block body for constructor to fix remaining code inspection issue --- osu.Game/Screens/Play/PlayerLoader.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index d78f36f6de..df55b738e1 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -499,7 +499,10 @@ namespace osu.Game.Screens.Play private class MutedNotification : SimpleNotification { - public MutedNotification() => Text = "Your music volume is set to 0%! Click here to restore it."; + public MutedNotification() + { + Text = "Your music volume is set to 0%! Click here to restore it."; + } public override bool IsImportant => true; From 43760bdfcdfffd6301a4cba30c2d86c131b8a2c2 Mon Sep 17 00:00:00 2001 From: HoLLy-HaCKeR Date: Sun, 15 Sep 2019 18:29:14 +0200 Subject: [PATCH 1294/2815] Specify Rider analysis rules explicitly --- osu.Android.sln.DotSettings | 21 ++++++++++++++++++++- osu.iOS.sln.DotSettings | 22 +++++++++++++++++++++- osu.sln.DotSettings | 12 ++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/osu.Android.sln.DotSettings b/osu.Android.sln.DotSettings index 3f5bd9d34d..5a97fc7518 100644 --- a/osu.Android.sln.DotSettings +++ b/osu.Android.sln.DotSettings @@ -1,4 +1,4 @@ - + True True True @@ -167,6 +167,14 @@ WARNING <?xml version="1.0" encoding="utf-16"?><Profile name="Code Cleanup (peppy)"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseVar><BehavourStyle>CAN_CHANGE_TO_EXPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_EXPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_EXPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" /><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers></Profile> Code Cleanup (peppy) + Required + Required + Required + Explicit + ExpressionBody + ExpressionBody + True + NEXT_LINE True True True @@ -176,12 +184,22 @@ True True NEXT_LINE + 1 + 1 + NEXT_LINE + MULTILINE NEXT_LINE + 1 + 1 True + NEXT_LINE NEVER NEVER + True False + True NEVER + False False True False @@ -189,6 +207,7 @@ True True False + False CHOP_IF_LONG True 200 diff --git a/osu.iOS.sln.DotSettings b/osu.iOS.sln.DotSettings index 3b2b851d45..752b817910 100644 --- a/osu.iOS.sln.DotSettings +++ b/osu.iOS.sln.DotSettings @@ -1,4 +1,4 @@ - + True True True @@ -165,8 +165,17 @@ HINT HINT WARNING + WARNING <?xml version="1.0" encoding="utf-16"?><Profile name="Code Cleanup (peppy)"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseVar><BehavourStyle>CAN_CHANGE_TO_EXPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_EXPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_EXPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" /><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers></Profile> Code Cleanup (peppy) + Required + Required + Required + Explicit + ExpressionBody + ExpressionBody + True + NEXT_LINE True True True @@ -176,12 +185,22 @@ True True NEXT_LINE + 1 + 1 + NEXT_LINE + MULTILINE NEXT_LINE + 1 + 1 True + NEXT_LINE NEVER NEVER + True False + True NEVER + False False True False @@ -189,6 +208,7 @@ True True False + False CHOP_IF_LONG True 200 diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index c3e274569d..ed162eed6e 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -218,9 +218,14 @@ WARNING <?xml version="1.0" encoding="utf-16"?><Profile name="Code Cleanup (peppy)"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseVar><BehavourStyle>CAN_CHANGE_TO_EXPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_EXPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_EXPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" /><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers></Profile> Code Cleanup (peppy) + Required + Required + Required + Explicit ExpressionBody ExpressionBody True + NEXT_LINE True True True @@ -232,14 +237,20 @@ NEXT_LINE 1 1 + NEXT_LINE + MULTILINE NEXT_LINE 1 1 True + NEXT_LINE NEVER NEVER + True False + True NEVER + False False True False @@ -247,6 +258,7 @@ True True False + False CHOP_IF_LONG True 200 From a407e267a238575d1418a94f3d912479a84f0bca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 15 Sep 2019 22:55:25 +0200 Subject: [PATCH 1295/2815] Fix PF/SD legacy mod conversion Upon investigating an user report in #6091 that indicated that viewing replays using the Perfect mod would also display the Sudden Death mod icon despite Perfect being the more restrictive of the two, it turned out that the logic of importing legacy scores was missing that corner case. A similar case of Double Time/Nightcore mutual exclusion was handled, but PF/SD was missed. Add analogous handling of PF/SD legacy mods for all four rulesets, and additionally cover a tiny fraction of all cases with unit tests. The most problematic cases (NC+HD and PF+SD) are covered in all four basic rulesets. --- .../CatchLegacyModConversionTest.cs | 29 +++++++++++++++ osu.Game.Rulesets.Catch/CatchRuleset.cs | 11 +++--- .../ManiaLegacyModConversionTest.cs | 30 ++++++++++++++++ osu.Game.Rulesets.Mania/ManiaRuleset.cs | 11 +++--- .../OsuLegacyModConversionTest.cs | 30 ++++++++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 11 +++--- .../TaikoLegacyModConversionTest.cs | 29 +++++++++++++++ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 11 +++--- .../Tests/Beatmaps/LegacyModConversionTest.cs | 35 +++++++++++++++++++ 9 files changed, 173 insertions(+), 24 deletions(-) create mode 100644 osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs create mode 100644 osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs create mode 100644 osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs create mode 100644 osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs create mode 100644 osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs diff --git a/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs new file mode 100644 index 0000000000..04e6dea376 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs @@ -0,0 +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 System; +using NUnit.Framework; +using osu.Game.Beatmaps.Legacy; +using osu.Game.Rulesets.Catch.Mods; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Catch.Tests +{ + [TestFixture] + public class CatchLegacyModConversionTest : LegacyModConversionTest + { + [TestCase(LegacyMods.Easy, new[] { typeof(CatchModEasy) })] + [TestCase(LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(CatchModHardRock), typeof(CatchModDoubleTime) })] + [TestCase(LegacyMods.DoubleTime, new[] { typeof(CatchModDoubleTime) })] + [TestCase(LegacyMods.Nightcore, new[] { typeof(CatchModNightcore) })] + [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(CatchModNightcore) })] + [TestCase(LegacyMods.Flashlight | LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(CatchModFlashlight), typeof(CatchModNightcore) })] + [TestCase(LegacyMods.Perfect, new[] { typeof(CatchModPerfect) })] + [TestCase(LegacyMods.SuddenDeath, new[] { typeof(CatchModSuddenDeath) })] + [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(CatchModPerfect) })] + [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(CatchModDoubleTime), typeof(CatchModPerfect) })] + public new void Test(LegacyMods legacyMods, Type[] expectedMods) => base.Test(legacyMods, expectedMods); + + protected override Ruleset CreateRuleset() => new CatchRuleset(); + } +} diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 5428b4eeb8..71d68ace94 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -46,6 +46,11 @@ namespace osu.Game.Rulesets.Catch else if (mods.HasFlag(LegacyMods.DoubleTime)) yield return new CatchModDoubleTime(); + if (mods.HasFlag(LegacyMods.Perfect)) + yield return new CatchModPerfect(); + else if (mods.HasFlag(LegacyMods.SuddenDeath)) + yield return new CatchModSuddenDeath(); + if (mods.HasFlag(LegacyMods.Autoplay)) yield return new CatchModAutoplay(); @@ -67,14 +72,8 @@ namespace osu.Game.Rulesets.Catch if (mods.HasFlag(LegacyMods.NoFail)) yield return new CatchModNoFail(); - if (mods.HasFlag(LegacyMods.Perfect)) - yield return new CatchModPerfect(); - if (mods.HasFlag(LegacyMods.Relax)) yield return new CatchModRelax(); - - if (mods.HasFlag(LegacyMods.SuddenDeath)) - yield return new CatchModSuddenDeath(); } public override IEnumerable GetModsFor(ModType type) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs new file mode 100644 index 0000000000..957743c5f1 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.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; +using NUnit.Framework; +using osu.Game.Beatmaps.Legacy; +using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Mania.Tests +{ + [TestFixture] + public class ManiaLegacyModConversionTest : LegacyModConversionTest + { + [TestCase(LegacyMods.Easy, new[] { typeof(ManiaModEasy) })] + [TestCase(LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(ManiaModHardRock), typeof(ManiaModDoubleTime) })] + [TestCase(LegacyMods.DoubleTime, new[] { typeof(ManiaModDoubleTime) })] + [TestCase(LegacyMods.Nightcore, new[] { typeof(ManiaModNightcore) })] + [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(ManiaModNightcore) })] + [TestCase(LegacyMods.Flashlight | LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(ManiaModFlashlight), typeof(ManiaModNightcore) })] + [TestCase(LegacyMods.Perfect, new[] { typeof(ManiaModPerfect) })] + [TestCase(LegacyMods.SuddenDeath, new[] { typeof(ManiaModSuddenDeath) })] + [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(ManiaModPerfect) })] + [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(ManiaModDoubleTime), typeof(ManiaModPerfect) })] + [TestCase(LegacyMods.Random | LegacyMods.SuddenDeath, new[] { typeof(ManiaModRandom), typeof(ManiaModSuddenDeath) })] + public new void Test(LegacyMods legacyMods, Type[] expectedMods) => base.Test(legacyMods, expectedMods); + + protected override Ruleset CreateRuleset() => new ManiaRuleset(); + } +} diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 0c4e7d4858..c74a292331 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -46,6 +46,11 @@ namespace osu.Game.Rulesets.Mania else if (mods.HasFlag(LegacyMods.DoubleTime)) yield return new ManiaModDoubleTime(); + if (mods.HasFlag(LegacyMods.Perfect)) + yield return new ManiaModPerfect(); + else if (mods.HasFlag(LegacyMods.SuddenDeath)) + yield return new ManiaModSuddenDeath(); + if (mods.HasFlag(LegacyMods.Autoplay)) yield return new ManiaModAutoplay(); @@ -97,14 +102,8 @@ namespace osu.Game.Rulesets.Mania if (mods.HasFlag(LegacyMods.NoFail)) yield return new ManiaModNoFail(); - if (mods.HasFlag(LegacyMods.Perfect)) - yield return new ManiaModPerfect(); - if (mods.HasFlag(LegacyMods.Random)) yield return new ManiaModRandom(); - - if (mods.HasFlag(LegacyMods.SuddenDeath)) - yield return new ManiaModSuddenDeath(); } public override IEnumerable GetModsFor(ModType type) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs new file mode 100644 index 0000000000..495f2738b5 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.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; +using NUnit.Framework; +using osu.Game.Beatmaps.Legacy; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class OsuLegacyModConversionTest : LegacyModConversionTest + { + [TestCase(LegacyMods.Easy, new[] { typeof(OsuModEasy) })] + [TestCase(LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(OsuModHardRock), typeof(OsuModDoubleTime) })] + [TestCase(LegacyMods.DoubleTime, new[] { typeof(OsuModDoubleTime) })] + [TestCase(LegacyMods.Nightcore, new[] { typeof(OsuModNightcore) })] + [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(OsuModNightcore) })] + [TestCase(LegacyMods.Flashlight | LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(OsuModFlashlight), typeof(OsuModFlashlight) })] + [TestCase(LegacyMods.Perfect, new[] { typeof(OsuModPerfect) })] + [TestCase(LegacyMods.SuddenDeath, new[] { typeof(OsuModSuddenDeath) })] + [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(OsuModPerfect) })] + [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(OsuModDoubleTime), typeof(OsuModPerfect) })] + [TestCase(LegacyMods.SpunOut | LegacyMods.Easy, new[] { typeof(OsuModSpunOut), typeof(OsuModEasy) })] + public new void Test(LegacyMods legacyMods, Type[] expectedMods) => base.Test(legacyMods, expectedMods); + + protected override Ruleset CreateRuleset() => new OsuRuleset(); + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index ceb9ed9343..df2ae81a5a 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -52,6 +52,11 @@ namespace osu.Game.Rulesets.Osu else if (mods.HasFlag(LegacyMods.DoubleTime)) yield return new OsuModDoubleTime(); + if (mods.HasFlag(LegacyMods.Perfect)) + yield return new OsuModPerfect(); + else if (mods.HasFlag(LegacyMods.SuddenDeath)) + yield return new OsuModSuddenDeath(); + if (mods.HasFlag(LegacyMods.Autopilot)) yield return new OsuModAutopilot(); @@ -76,18 +81,12 @@ namespace osu.Game.Rulesets.Osu if (mods.HasFlag(LegacyMods.NoFail)) yield return new OsuModNoFail(); - if (mods.HasFlag(LegacyMods.Perfect)) - yield return new OsuModPerfect(); - if (mods.HasFlag(LegacyMods.Relax)) yield return new OsuModRelax(); if (mods.HasFlag(LegacyMods.SpunOut)) yield return new OsuModSpunOut(); - if (mods.HasFlag(LegacyMods.SuddenDeath)) - yield return new OsuModSuddenDeath(); - if (mods.HasFlag(LegacyMods.Target)) yield return new OsuModTarget(); diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs new file mode 100644 index 0000000000..a59544386b --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs @@ -0,0 +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 System; +using NUnit.Framework; +using osu.Game.Beatmaps.Legacy; +using osu.Game.Rulesets.Taiko.Mods; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + [TestFixture] + public class TaikoLegacyModConversionTest : LegacyModConversionTest + { + [TestCase(LegacyMods.Easy, new[] { typeof(TaikoModEasy) })] + [TestCase(LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(TaikoModHardRock), typeof(TaikoModDoubleTime) })] + [TestCase(LegacyMods.DoubleTime, new[] { typeof(TaikoModDoubleTime) })] + [TestCase(LegacyMods.Nightcore, new[] { typeof(TaikoModNightcore) })] + [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(TaikoModNightcore) })] + [TestCase(LegacyMods.Flashlight | LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(TaikoModFlashlight), typeof(TaikoModNightcore) })] + [TestCase(LegacyMods.Perfect, new[] { typeof(TaikoModPerfect) })] + [TestCase(LegacyMods.SuddenDeath, new[] { typeof(TaikoModSuddenDeath) })] + [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(TaikoModPerfect) })] + [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(TaikoModDoubleTime), typeof(TaikoModPerfect) })] + public new void Test(LegacyMods legacyMods, Type[] expectedMods) => base.Test(legacyMods, expectedMods); + + protected override Ruleset CreateRuleset() => new TaikoRuleset(); + } +} diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 7fdb823388..b2655f592c 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -45,6 +45,11 @@ namespace osu.Game.Rulesets.Taiko else if (mods.HasFlag(LegacyMods.DoubleTime)) yield return new TaikoModDoubleTime(); + if (mods.HasFlag(LegacyMods.Perfect)) + yield return new TaikoModPerfect(); + else if (mods.HasFlag(LegacyMods.SuddenDeath)) + yield return new TaikoModSuddenDeath(); + if (mods.HasFlag(LegacyMods.Autoplay)) yield return new TaikoModAutoplay(); @@ -66,14 +71,8 @@ namespace osu.Game.Rulesets.Taiko if (mods.HasFlag(LegacyMods.NoFail)) yield return new TaikoModNoFail(); - if (mods.HasFlag(LegacyMods.Perfect)) - yield return new TaikoModPerfect(); - if (mods.HasFlag(LegacyMods.Relax)) yield return new TaikoModRelax(); - - if (mods.HasFlag(LegacyMods.SuddenDeath)) - yield return new TaikoModSuddenDeath(); } public override IEnumerable GetModsFor(ModType type) diff --git a/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs b/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs new file mode 100644 index 0000000000..e9251f8011 --- /dev/null +++ b/osu.Game/Tests/Beatmaps/LegacyModConversionTest.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.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps.Legacy; +using osu.Game.Rulesets; + +namespace osu.Game.Tests.Beatmaps +{ + /// + /// Base class for tests of converting enumeration flags to ruleset mod instances. + /// + public abstract class LegacyModConversionTest + { + /// + /// Creates the whose legacy mod conversion is to be tested. + /// + /// + protected abstract Ruleset CreateRuleset(); + + protected void Test(LegacyMods legacyMods, Type[] expectedMods) + { + var ruleset = CreateRuleset(); + var mods = ruleset.ConvertLegacyMods(legacyMods).ToList(); + Assert.AreEqual(expectedMods.Length, mods.Count); + + foreach (var modType in expectedMods) + { + Assert.IsNotNull(mods.SingleOrDefault(mod => mod.GetType() == modType)); + } + } + } +} From 220fdd0a044105e268cccfe9c113c1dce6fcf668 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 16 Sep 2019 06:56:52 +0200 Subject: [PATCH 1296/2815] Make muteWarningShownOnce static ... so it will actually get an instance per osu session. --- osu.Game/Screens/Play/PlayerLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index df55b738e1..dbb6c47943 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -65,7 +65,7 @@ namespace osu.Game.Screens.Play [Resolved] private VolumeOverlay volumeOverlay { get; set; } - private bool muteWarningShownOnce; + private static bool muteWarningShownOnce; public PlayerLoader(Func createPlayer) { From 78b6062100d83db9f6bc5c35f99a62c4038527d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Sep 2019 17:54:12 +0900 Subject: [PATCH 1297/2815] Update fastlane version --- Gemfile.lock | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index f7c19064b4..ac46fddb41 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,9 +1,9 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.0) - addressable (2.6.0) - public_suffix (>= 2.0.2, < 4.0) + CFPropertyList (3.0.1) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) atomos (0.1.3) babosa (1.0.2) claide (1.0.3) @@ -26,8 +26,8 @@ GEM http-cookie (~> 1.0.0) faraday_middleware (0.13.1) faraday (>= 0.7.4, < 1.0) - fastimage (2.1.5) - fastlane (2.129.0) + fastimage (2.1.7) + fastlane (2.131.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.3, < 3.0.0) babosa (>= 1.0.2, < 2.0.0) @@ -77,9 +77,9 @@ GEM representable (~> 3.0) retriable (>= 2.0, < 4.0) signet (~> 0.9) - google-cloud-core (1.3.0) + google-cloud-core (1.3.1) google-cloud-env (~> 1.0) - google-cloud-env (1.2.0) + google-cloud-env (1.2.1) faraday (~> 0.11) google-cloud-storage (1.16.0) digest-crc (~> 0.4) @@ -100,9 +100,9 @@ GEM json (2.2.0) jwt (2.1.0) memoist (0.16.0) - mime-types (3.2.2) + mime-types (3.3) mime-types-data (~> 3.2015) - mime-types-data (3.2019.0331) + mime-types-data (3.2019.0904) mini_magick (4.9.5) mini_portile2 (2.4.0) multi_json (1.13.1) @@ -121,14 +121,14 @@ GEM uber (< 0.2.0) retriable (3.1.2) rouge (2.0.7) - rubyzip (1.2.3) + rubyzip (1.2.4) security (0.1.3) signet (0.11.0) addressable (~> 2.3) faraday (~> 0.9) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - simctl (1.6.5) + simctl (1.6.6) CFPropertyList naturally slack-notifier (2.3.2) From 7bc4b4f98164bfa0d052c51b4540cb76afe10dc9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Sep 2019 18:08:11 +0900 Subject: [PATCH 1298/2815] Explicitly specify solution in beta lane --- fastlane/Fastfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index dd35fa0b46..e4a6bc4585 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -3,6 +3,9 @@ update_fastlane platform :android do desc 'Deploy to play store' lane :beta do |options| + + options[:solution_path] = 'osu.Android.sln' + # update csproj version update_version(options) From efedfefe635e63c8cad19c73ff56e29482741be9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Sep 2019 15:54:11 +0900 Subject: [PATCH 1299/2815] Fix disclaimer potentially pushing a null screen --- osu.Game/Screens/Menu/Disclaimer.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 073ab639e3..17f999d519 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -173,7 +173,11 @@ namespace osu.Game.Screens.Menu .Then(5500) .FadeOut(250) .ScaleTo(0.9f, 250, Easing.InQuint) - .Finally(d => this.Push(nextScreen)); + .Finally(d => + { + if (nextScreen != null) + this.Push(nextScreen); + }); } } } From f0bcb2b9337a94fa429b9aa609c5444f95dbab34 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Sep 2019 16:12:18 +0900 Subject: [PATCH 1300/2815] Debounce user-requested replay seeks --- osu.Game/Screens/Play/SongProgressBar.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SongProgressBar.cs b/osu.Game/Screens/Play/SongProgressBar.cs index dd7b5826d5..33c7595b37 100644 --- a/osu.Game/Screens/Play/SongProgressBar.cs +++ b/osu.Game/Screens/Play/SongProgressBar.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.MathUtils; +using osu.Framework.Threading; namespace osu.Game.Screens.Play { @@ -121,6 +122,12 @@ namespace osu.Game.Screens.Play handleBase.X = newX; } - protected override void OnUserChange(double value) => OnSeek?.Invoke(value); + private ScheduledDelegate scheduledSeek; + + protected override void OnUserChange(double value) + { + scheduledSeek?.Cancel(); + scheduledSeek = Schedule(() => OnSeek?.Invoke(value)); + } } } From 9543c277845bfeb1d1eaa282700c863a50ec0c21 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Sep 2019 19:10:41 +0900 Subject: [PATCH 1301/2815] Add full upload support --- fastlane/Fastfile | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index e4a6bc4585..906d284bc9 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -4,16 +4,21 @@ platform :android do desc 'Deploy to play store' lane :beta do |options| - options[:solution_path] = 'osu.Android.sln' - # update csproj version - update_version(options) + update_version( + solution_path: 'osu.Android.sln', + version: options[:version], + build: options[:build], + ) build() - client = HTTPClient.new - changelog = client.get_content 'https://gist.githubusercontent.com/peppy/ab89c29dcc0dce95f39eb218e8fad197/raw' - changelog.gsub!('$BUILD_ID', options[:build]) + supply( + apk: './osu.Android/bin/Release/sh.ppy.osulazer-Signed.apk', + package_name: 'sh.ppy.osulazer', + track: 'alpha', # upload to alpha, we can promote it later + json_key: options[:json_key], + ) end desc 'Compile the project' From 77947e83097164eb73adc4b380fd2b80fcf643e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Sep 2019 22:33:27 +0900 Subject: [PATCH 1302/2815] Fix rewind tests failing --- .../Replays/CatchAutoGenerator.cs | 1 + .../Replays/ManiaAutoGenerator.cs | 1 + .../Visual/Gameplay/TestSceneAutoplay.cs | 19 ++++++++++++++----- osu.Game/Screens/Play/GameplayClock.cs | 2 ++ osu.Game/Tests/Visual/OsuTestScene.cs | 4 ++-- 5 files changed, 20 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index 4ea1f22006..7f972a25ae 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -39,6 +39,7 @@ namespace osu.Game.Rulesets.Catch.Replays // Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled addFrame(-100000, lastPosition); + addFrame(0, lastPosition); void moveToNext(CatchHitObject h) { diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 7b8bbc2095..5d333e2047 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -49,6 +49,7 @@ namespace osu.Game.Rulesets.Mania.Replays { // Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled Replay.Frames.Add(new ManiaReplayFrame(-100000, 0)); + Replay.Frames.Add(new ManiaReplayFrame(0, 0)); var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index e2b620ea98..883aa9fc36 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.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; @@ -12,6 +13,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Description("Player instantiated with an autoplay mod.")] public class TestSceneAutoplay : AllPlayersTestScene { + private ClockBackedTestWorkingBeatmap.TrackVirtualManual track; + protected override Player CreatePlayer(Ruleset ruleset) { Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); @@ -22,11 +25,17 @@ namespace osu.Game.Tests.Visual.Gameplay { AddUntilStep("score above zero", () => ((ScoreAccessiblePlayer)Player).ScoreProcessor.TotalScore.Value > 0); AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 5)); - AddStep("rewind", () => - { - ((ScoreAccessiblePlayer)Player).GameplayClockContainer.Seek(0); - }); - AddUntilStep("key counter counted no", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); + AddStep("rewind", () => track.Seek(-10000)); + AddUntilStep("key counter reset", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); + } + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) + { + var working = base.CreateWorkingBeatmap(beatmap); + + track = (ClockBackedTestWorkingBeatmap.TrackVirtualManual)working.Track; + + return working; } private class ScoreAccessiblePlayer : TestPlayer diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index b1948d02d5..379c4c89ed 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -42,5 +42,7 @@ namespace osu.Game.Screens.Play public double FramesPerSecond => underlyingClock.FramesPerSecond; public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo; + + public IClock Source => underlyingClock; } } diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 2b8baab57c..32a32cfa43 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.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; @@ -239,7 +239,7 @@ namespace osu.Game.Tests.Visual public override bool Seek(double seek) { - offset = MathHelper.Clamp(seek, 0, Length); + offset = Math.Min(seek, Length); lastReferenceTime = null; return offset == seek; From 057c4aa795657654822ce73c086352e75a9677f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Sep 2019 22:42:20 +0900 Subject: [PATCH 1303/2815] Remove unused using statement --- osu.Game/Tests/Visual/OsuTestScene.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 32a32cfa43..2bc9273f69 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -20,7 +20,6 @@ using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Tests.Beatmaps; -using osuTK; namespace osu.Game.Tests.Visual { From 3ab352ffe51b0a37fef9af64569b91c9bbc0d2b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Sep 2019 23:08:37 +0900 Subject: [PATCH 1304/2815] Randomise beatmap playback order on startup Closes #6135. --- osu.Game/Overlays/Music/PlaylistList.cs | 18 ++++++++++-------- osu.Game/Overlays/MusicController.cs | 21 +++++++++++---------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index 539601c359..6ad0aa23ec 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; @@ -44,6 +45,8 @@ namespace osu.Game.Overlays.Music private class ItemsScrollContainer : OsuScrollContainer { + private BindableList beatmaps; + public Action Selected; public Action OrderChanged; @@ -73,20 +76,19 @@ namespace osu.Game.Overlays.Music } [BackgroundDependencyLoader] - private void load(BeatmapManager beatmaps, IBindable beatmap) + private void load(MusicController musicController, IBindable beatmap) { - beatmaps.GetAllUsableBeatmapSets().ForEach(addBeatmapSet); - beatmaps.ItemAdded += addBeatmapSet; - beatmaps.ItemRemoved += removeBeatmapSet; + beatmaps = musicController.BeatmapSets.GetBoundCopy(); + + beatmaps.ItemsAdded += i => i.ForEach(addBeatmapSet); + beatmaps.ItemsRemoved += i => i.ForEach(removeBeatmapSet); + beatmaps.ForEach(addBeatmapSet); beatmapBacking.BindTo(beatmap); beatmapBacking.ValueChanged += _ => updateSelectedSet(); } - private void addBeatmapSet(BeatmapSetInfo obj) => Schedule(() => - { - items.Insert(items.Count - 1, new PlaylistItem(obj) { OnSelect = set => Selected?.Invoke(set) }); - }); + private void addBeatmapSet(BeatmapSetInfo obj) => Schedule(() => { items.Insert(items.Count - 1, new PlaylistItem(obj) { OnSelect = set => Selected?.Invoke(set) }); }); private void removeBeatmapSet(BeatmapSetInfo obj) => Schedule(() => { diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 6ad147735b..92246dfc62 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; +using osu.Framework.MathUtils; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Input.Bindings; @@ -24,7 +25,7 @@ namespace osu.Game.Overlays [Resolved] private BeatmapManager beatmaps { get; set; } - private List beatmapSets; + public readonly BindableList BeatmapSets = new BindableList(); public bool IsUserPaused { get; private set; } @@ -46,7 +47,7 @@ namespace osu.Game.Overlays [BackgroundDependencyLoader] private void load() { - beatmapSets = beatmaps.GetAllUsableBeatmapSets(); + BeatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next())); beatmaps.ItemAdded += handleBeatmapAdded; beatmaps.ItemRemoved += handleBeatmapRemoved; } @@ -65,8 +66,8 @@ namespace osu.Game.Overlays /// The new position. public void ChangeBeatmapSetPosition(BeatmapSetInfo beatmapSetInfo, int index) { - beatmapSets.Remove(beatmapSetInfo); - beatmapSets.Insert(index, beatmapSetInfo); + BeatmapSets.Remove(beatmapSetInfo); + BeatmapSets.Insert(index, beatmapSetInfo); } /// @@ -75,10 +76,10 @@ namespace osu.Game.Overlays public bool IsPlaying => beatmap.Value.Track.IsRunning; private void handleBeatmapAdded(BeatmapSetInfo set) => - Schedule(() => beatmapSets.Add(set)); + Schedule(() => BeatmapSets.Add(set)); private void handleBeatmapRemoved(BeatmapSetInfo set) => - Schedule(() => beatmapSets.RemoveAll(s => s.ID == set.ID)); + Schedule(() => BeatmapSets.RemoveAll(s => s.ID == set.ID)); private ScheduledDelegate seekDelegate; @@ -140,7 +141,7 @@ namespace osu.Game.Overlays { queuedDirection = TrackChangeDirection.Prev; - var playable = beatmapSets.TakeWhile(i => i.ID != current.BeatmapSetInfo.ID).LastOrDefault() ?? beatmapSets.LastOrDefault(); + var playable = BeatmapSets.TakeWhile(i => i.ID != current.BeatmapSetInfo.ID).LastOrDefault() ?? BeatmapSets.LastOrDefault(); if (playable != null) { @@ -165,7 +166,7 @@ namespace osu.Game.Overlays if (!instant) queuedDirection = TrackChangeDirection.Next; - var playable = beatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).Skip(1).FirstOrDefault() ?? beatmapSets.FirstOrDefault(); + var playable = BeatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).Skip(1).FirstOrDefault() ?? BeatmapSets.FirstOrDefault(); if (playable != null) { @@ -200,8 +201,8 @@ namespace osu.Game.Overlays else { //figure out the best direction based on order in playlist. - var last = beatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count(); - var next = beatmap.NewValue == null ? -1 : beatmapSets.TakeWhile(b => b.ID != beatmap.NewValue.BeatmapSetInfo?.ID).Count(); + var last = BeatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count(); + var next = beatmap.NewValue == null ? -1 : BeatmapSets.TakeWhile(b => b.ID != beatmap.NewValue.BeatmapSetInfo?.ID).Count(); direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next; } From b5b29a21e783b93d21ea955d473fce33d59c8d47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2019 02:15:18 +0900 Subject: [PATCH 1305/2815] Move menu cursor rotation to more appropriate settings section --- .../Overlays/Settings/Sections/Graphics/DetailSettings.cs | 5 ----- .../{MainMenuSettings.cs => UserInterfaceSettings.cs} | 7 ++++++- osu.Game/Overlays/Settings/Sections/GraphicsSection.cs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) rename osu.Game/Overlays/Settings/Sections/Graphics/{MainMenuSettings.cs => UserInterfaceSettings.cs} (71%) diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs index 56e56f6ca8..9be34c3073 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs @@ -26,11 +26,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics LabelText = "Video", Bindable = config.GetBindable(OsuSetting.ShowVideoBackground) }, - new SettingsCheckbox - { - LabelText = "Rotate cursor when dragging", - Bindable = config.GetBindable(OsuSetting.CursorRotation) - }, new SettingsEnumDropdown { LabelText = "Screenshot format", diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs similarity index 71% rename from osu.Game/Overlays/Settings/Sections/Graphics/MainMenuSettings.cs rename to osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs index 92f64d0e14..dd822fedb6 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/MainMenuSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs @@ -6,7 +6,7 @@ using osu.Game.Configuration; namespace osu.Game.Overlays.Settings.Sections.Graphics { - public class MainMenuSettings : SettingsSubsection + public class UserInterfaceSettings : SettingsSubsection { protected override string Header => "User Interface"; @@ -15,6 +15,11 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { Children = new[] { + new SettingsCheckbox + { + LabelText = "Rotate cursor when dragging", + Bindable = config.GetBindable(OsuSetting.CursorRotation) + }, new SettingsCheckbox { LabelText = "Parallax", diff --git a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs index 3d6086d3ea..89caa3dc8f 100644 --- a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Settings.Sections new RendererSettings(), new LayoutSettings(), new DetailSettings(), - new MainMenuSettings(), + new UserInterfaceSettings(), }; } } From 63cc8d4f90b29a165f43eb704cc9c0a057766a5f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2019 02:16:57 +0900 Subject: [PATCH 1306/2815] Add hit lighting setting --- osu.Game/Configuration/OsuConfigManager.cs | 5 ++++- .../Overlays/Settings/Sections/Graphics/DetailSettings.cs | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index e26021d930..4cbf2b4d94 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -81,6 +81,8 @@ namespace osu.Game.Configuration Set(OsuSetting.DimLevel, 0.3, 0, 1, 0.01); Set(OsuSetting.BlurLevel, 0, 0, 1, 0.01); + Set(OsuSetting.HitLighting, true); + Set(OsuSetting.ShowInterface, true); Set(OsuSetting.ShowHealthDisplayWhenCantFail, true); Set(OsuSetting.KeyOverlay, false); @@ -180,6 +182,7 @@ namespace osu.Game.Configuration ScalingSizeX, ScalingSizeY, UIScale, - IntroSequence + IntroSequence, + HitLighting } } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs index 56e56f6ca8..5189d573cc 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs @@ -27,6 +27,11 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics Bindable = config.GetBindable(OsuSetting.ShowVideoBackground) }, new SettingsCheckbox + { + LabelText = "Hit Lighting", + Bindable = config.GetBindable(OsuSetting.HitLighting) + }, + new SettingsCheckbox { LabelText = "Rotate cursor when dragging", Bindable = config.GetBindable(OsuSetting.CursorRotation) From ba76f09c99df0c59c37c85c32ccbdc24875a9d30 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2019 02:49:54 +0900 Subject: [PATCH 1307/2815] Add initial implementation of hit lighting Requires a supporting skin, like osu!classic for now. --- .../Objects/Drawables/DrawableOsuJudgement.cs | 44 +++++++++++++++++++ .../Rulesets/Judgements/DrawableJudgement.cs | 8 +++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 938a2293ba..022e9ea12b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -1,22 +1,66 @@ // 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.Configuration; using osuTK; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; +using osu.Game.Skinning; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableOsuJudgement : DrawableJudgement { + private SkinnableSprite lighting; + private Bindable lightingColour; + public DrawableOsuJudgement(JudgementResult result, DrawableHitObject judgedObject) : base(result, judgedObject) { } + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + if (config.Get(OsuSetting.HitLighting) && Result.Type != HitResult.Miss) + { + AddInternal(lighting = new SkinnableSprite("lighting") + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Blending = BlendingParameters.Additive, + Depth = float.MaxValue + }); + + if (JudgedObject != null) + { + lightingColour = JudgedObject.AccentColour.GetBoundCopy(); + lightingColour.BindValueChanged(colour => lighting.Colour = colour.NewValue, true); + } + else + { + lighting.Colour = Color4.White; + } + } + } + + protected override double FadeOutDelay => lighting == null ? base.FadeOutDelay : 1400; + protected override void ApplyHitAnimations() { + if (lighting != null) + { + JudgementBody.Delay(FadeInDuration).FadeOut(400); + + lighting.ScaleTo(0.8f).ScaleTo(1.2f, 600, Easing.Out); + lighting.FadeIn(200).Then().Delay(200).FadeOut(1000); + } + JudgementText?.TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint); base.ApplyHitAnimations(); } diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 4f8cb7660b..4c503ea59c 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -34,10 +34,14 @@ namespace osu.Game.Rulesets.Judgements /// /// Duration of initial fade in. - /// Default fade out will start immediately after this duration. /// protected virtual double FadeInDuration => 100; + /// + /// Duration to wait until fade out begins. Defaults to . + /// + protected virtual double FadeOutDelay => FadeInDuration; + /// /// Creates a drawable which visualises a . /// @@ -76,7 +80,7 @@ namespace osu.Game.Rulesets.Judgements JudgementBody.ScaleTo(0.9f); JudgementBody.ScaleTo(1, 500, Easing.OutElastic); - this.Delay(FadeInDuration).FadeOut(400); + this.Delay(FadeOutDelay).FadeOut(400); } protected override void LoadComplete() From 26eca5b1f425403df404da0ab4b4157e9f5fce52 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2019 02:56:03 +0900 Subject: [PATCH 1308/2815] Fix judgement sizes not matching skins stable --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index df12ebc514..d1757de445 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.UI { Origin = Anchor.Centre, Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition, - Scale = new Vector2(((OsuHitObject)judgedObject.HitObject).Scale * 1.65f) + Scale = new Vector2(((OsuHitObject)judgedObject.HitObject).Scale) }; judgementLayer.Add(explosion); diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 4f8cb7660b..061c8cb3e1 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Judgements /// public class DrawableJudgement : CompositeDrawable { - private const float judgement_size = 80; + private const float judgement_size = 128; private OsuColour colours; @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Judgements Child = new SkinnableDrawable(new GameplaySkinComponent(Result.Type), _ => JudgementText = new OsuSpriteText { Text = Result.Type.GetDescription().ToUpperInvariant(), - Font = OsuFont.Numeric.With(size: 12), + Font = OsuFont.Numeric.With(size: 20), Colour = judgementColour(Result.Type), Scale = new Vector2(0.85f, 1), }) From adc2dfa6c6ffecf27ae299d0cecd1e2844d380f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2019 03:00:17 +0900 Subject: [PATCH 1309/2815] Fix HitCircleLongCombo test stacking off-screen --- osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs index 399cf22599..95c2810e94 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs @@ -29,7 +29,8 @@ namespace osu.Game.Rulesets.Osu.Tests }; for (int i = 0; i < 512; i++) - beatmap.HitObjects.Add(new HitCircle { Position = new Vector2(256, 192), StartTime = i * 100 }); + if (i % 32 < 20) + beatmap.HitObjects.Add(new HitCircle { Position = new Vector2(256, 192), StartTime = i * 100 }); return beatmap; } From 7e791f7cd78e2fed4e67f91a739e8a6411da891a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2019 13:14:33 +0900 Subject: [PATCH 1310/2815] Expose as IBindableList --- osu.Game/Overlays/MusicController.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 92246dfc62..db94b0278f 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -25,7 +25,9 @@ namespace osu.Game.Overlays [Resolved] private BeatmapManager beatmaps { get; set; } - public readonly BindableList BeatmapSets = new BindableList(); + public IBindableList BeatmapSets => beatmapSets; + + private readonly BindableList beatmapSets = new BindableList(); public bool IsUserPaused { get; private set; } @@ -47,7 +49,7 @@ namespace osu.Game.Overlays [BackgroundDependencyLoader] private void load() { - BeatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next())); + beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next())); beatmaps.ItemAdded += handleBeatmapAdded; beatmaps.ItemRemoved += handleBeatmapRemoved; } @@ -66,8 +68,8 @@ namespace osu.Game.Overlays /// The new position. public void ChangeBeatmapSetPosition(BeatmapSetInfo beatmapSetInfo, int index) { - BeatmapSets.Remove(beatmapSetInfo); - BeatmapSets.Insert(index, beatmapSetInfo); + beatmapSets.Remove(beatmapSetInfo); + beatmapSets.Insert(index, beatmapSetInfo); } /// @@ -76,10 +78,10 @@ namespace osu.Game.Overlays public bool IsPlaying => beatmap.Value.Track.IsRunning; private void handleBeatmapAdded(BeatmapSetInfo set) => - Schedule(() => BeatmapSets.Add(set)); + Schedule(() => beatmapSets.Add(set)); private void handleBeatmapRemoved(BeatmapSetInfo set) => - Schedule(() => BeatmapSets.RemoveAll(s => s.ID == set.ID)); + Schedule(() => beatmapSets.RemoveAll(s => s.ID == set.ID)); private ScheduledDelegate seekDelegate; From 91bdece9af42fe2848d9fef7b56432cdaaee279c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2019 13:15:39 +0900 Subject: [PATCH 1311/2815] Localise OrderChanged handling and fix callbacks The dragged item's position now only updates after the drag finishes. Local handling changes were required to ignore the bindable remove/add events that are fired as a result. --- osu.Game/Overlays/Music/PlaylistList.cs | 42 +++++++++++++++++----- osu.Game/Overlays/Music/PlaylistOverlay.cs | 8 ----- osu.Game/Overlays/NowPlayingOverlay.cs | 1 - 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index 6ad0aa23ec..636945edd2 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -19,7 +19,6 @@ namespace osu.Game.Overlays.Music public class PlaylistList : CompositeDrawable { public Action Selected; - public Action OrderChanged; private readonly ItemsScrollContainer items; @@ -29,7 +28,6 @@ namespace osu.Game.Overlays.Music { RelativeSizeAxes = Axes.Both, Selected = set => Selected?.Invoke(set), - OrderChanged = (s, i) => OrderChanged?.Invoke(s, i) }; } @@ -45,16 +43,17 @@ namespace osu.Game.Overlays.Music private class ItemsScrollContainer : OsuScrollContainer { - private BindableList beatmaps; + private IBindableList beatmaps; public Action Selected; - public Action OrderChanged; private readonly SearchContainer search; private readonly FillFlowContainer items; private readonly IBindable beatmapBacking = new Bindable(); + private MusicController musicController; + public ItemsScrollContainer() { Children = new Drawable[] @@ -78,6 +77,8 @@ namespace osu.Game.Overlays.Music [BackgroundDependencyLoader] private void load(MusicController musicController, IBindable beatmap) { + this.musicController = musicController; + beatmaps = musicController.BeatmapSets.GetBoundCopy(); beatmaps.ItemsAdded += i => i.ForEach(addBeatmapSet); @@ -88,10 +89,21 @@ namespace osu.Game.Overlays.Music beatmapBacking.ValueChanged += _ => updateSelectedSet(); } - private void addBeatmapSet(BeatmapSetInfo obj) => Schedule(() => { items.Insert(items.Count - 1, new PlaylistItem(obj) { OnSelect = set => Selected?.Invoke(set) }); }); + private void addBeatmapSet(BeatmapSetInfo obj) => Schedule(() => + { + if (obj == draggedItem?.BeatmapSetInfo) + { + draggedItem = null; + return; + } + + items.Insert(items.Count - 1, new PlaylistItem(obj) { OnSelect = set => Selected?.Invoke(set) }); + }); private void removeBeatmapSet(BeatmapSetInfo obj) => Schedule(() => { + if (obj == draggedItem?.BeatmapSetInfo) return; + var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == obj.ID); if (itemToRemove != null) items.Remove(itemToRemove); @@ -114,6 +126,8 @@ namespace osu.Game.Overlays.Music private Vector2 nativeDragPosition; private PlaylistItem draggedItem; + private int? dragDestination; + protected override bool OnDragStart(DragStartEvent e) { nativeDragPosition = e.ScreenSpaceMousePosition; @@ -133,10 +147,20 @@ namespace osu.Game.Overlays.Music protected override bool OnDragEnd(DragEndEvent e) { nativeDragPosition = e.ScreenSpaceMousePosition; - var handled = draggedItem != null || base.OnDragEnd(e); - draggedItem = null; - return handled; + if (draggedItem != null) + { + if (dragDestination != null) + { + // draggedItem is nulled when the BindableList's add event is received so we can quietly ignore the callbacks. + musicController.ChangeBeatmapSetPosition(draggedItem.BeatmapSetInfo, dragDestination.Value); + dragDestination = null; + } + + return true; + } + + return base.OnDragEnd(e); } protected override void Update() @@ -212,7 +236,7 @@ namespace osu.Game.Overlays.Music } items.SetLayoutPosition(draggedItem, dstIndex); - OrderChanged?.Invoke(draggedItem.BeatmapSetInfo, dstIndex); + dragDestination = dstIndex; } private class ItemSearchContainer : FillFlowContainer, IHasFilterableChildren diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index ec3d708645..ae81a6c117 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.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.Allocation; using osu.Framework.Bindables; @@ -22,12 +21,6 @@ namespace osu.Game.Overlays.Music private const float transition_duration = 600; private const float playlist_height = 510; - /// - /// Invoked when the order of an item in the list has changed. - /// The second parameter indicates the new index of the item. - /// - public Action OrderChanged; - private readonly Bindable beatmap = new Bindable(); private BeatmapManager beatmaps; @@ -65,7 +58,6 @@ namespace osu.Game.Overlays.Music RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Top = 95, Bottom = 10, Right = 10 }, Selected = itemSelected, - OrderChanged = (s, i) => OrderChanged?.Invoke(s, i) }, filter = new FilterControl { diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index cf42c8005a..6b79f2af07 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -81,7 +81,6 @@ namespace osu.Game.Overlays { RelativeSizeAxes = Axes.X, Y = player_height + 10, - OrderChanged = musicController.ChangeBeatmapSetPosition }, playerContainer = new Container { From 2db1e236a7180174ffa78318e29d92c0e9b6842f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2019 14:08:09 +0900 Subject: [PATCH 1312/2815] Fix frame count dependent tests regressing --- .../TestSceneAutoGeneration.cs | 101 ++++++++++-------- 1 file changed, 54 insertions(+), 47 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs index f260357df5..8206e33c7c 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs @@ -16,6 +16,11 @@ namespace osu.Game.Rulesets.Mania.Tests [HeadlessTest] public class TestSceneAutoGeneration : OsuTestScene { + /// + /// The number of frames which are generated at the start of a replay regardless of hitobject content. + /// + private const int frame_offset = 2; + [Test] public void TestSingleNote() { @@ -28,11 +33,11 @@ namespace osu.Game.Rulesets.Mania.Tests var generated = new ManiaAutoGenerator(beatmap).Generate(); - Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames"); - Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time"); - Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time"); - Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Special1), "Special1 has not been pressed"); - Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Special1), "Special1 has not been released"); + Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames"); + Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time"); + Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time"); + Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed"); + Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Special1), "Special1 has not been released"); } [Test] @@ -49,11 +54,11 @@ namespace osu.Game.Rulesets.Mania.Tests var generated = new ManiaAutoGenerator(beatmap).Generate(); - Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames"); - Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time"); - Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time"); - Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Special1), "Special1 has not been pressed"); - Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Special1), "Special1 has not been released"); + Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames"); + Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time"); + Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time"); + Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed"); + Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Special1), "Special1 has not been released"); } [Test] @@ -69,11 +74,11 @@ namespace osu.Game.Rulesets.Mania.Tests var generated = new ManiaAutoGenerator(beatmap).Generate(); - Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames"); - Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time"); - Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time"); - Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed"); - Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released"); + Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames"); + Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time"); + Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time"); + Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed"); + Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released"); } [Test] @@ -91,11 +96,13 @@ namespace osu.Game.Rulesets.Mania.Tests var generated = new ManiaAutoGenerator(beatmap).Generate(); - Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames"); - Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time"); - Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time"); - Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed"); - Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released"); + Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames"); + + Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time"); + Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time"); + + Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed"); + Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released"); } [Test] @@ -112,15 +119,15 @@ namespace osu.Game.Rulesets.Mania.Tests var generated = new ManiaAutoGenerator(beatmap).Generate(); - Assert.IsTrue(generated.Frames.Count == 5, "Replay must have 5 frames"); - Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time"); - Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect first note release time"); - Assert.AreEqual(2000, generated.Frames[3].Time, "Incorrect second note hit time"); - Assert.AreEqual(2000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time"); - Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed"); - Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1), "Key1 has not been released"); - Assert.IsTrue(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has not been pressed"); - Assert.IsFalse(checkContains(generated.Frames[4], ManiaAction.Key2), "Key2 has not been released"); + Assert.IsTrue(generated.Frames.Count == frame_offset + 4, "Replay must have 4 generated frames"); + Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time"); + Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect first note release time"); + Assert.AreEqual(2000, generated.Frames[frame_offset + 2].Time, "Incorrect second note hit time"); + Assert.AreEqual(2000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 3].Time, "Incorrect second note release time"); + Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed"); + Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released"); + Assert.IsTrue(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key2), "Key2 has not been pressed"); + Assert.IsFalse(checkContains(generated.Frames[frame_offset + 3], ManiaAction.Key2), "Key2 has not been released"); } [Test] @@ -139,16 +146,16 @@ namespace osu.Game.Rulesets.Mania.Tests var generated = new ManiaAutoGenerator(beatmap).Generate(); - Assert.IsTrue(generated.Frames.Count == 5, "Replay must have 5 frames"); - Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time"); - Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect first note release time"); - Assert.AreEqual(2000, generated.Frames[2].Time, "Incorrect second note hit time"); - Assert.AreEqual(4000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time"); - Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed"); - Assert.IsTrue(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed"); - Assert.IsFalse(checkContains(generated.Frames[3], ManiaAction.Key1), "Key1 has not been released"); - Assert.IsTrue(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has been released"); - Assert.IsFalse(checkContains(generated.Frames[4], ManiaAction.Key2), "Key2 has not been released"); + Assert.IsTrue(generated.Frames.Count == frame_offset + 4, "Replay must have 4 generated frames"); + Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time"); + Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 2].Time, "Incorrect first note release time"); + Assert.AreEqual(2000, generated.Frames[frame_offset + 1].Time, "Incorrect second note hit time"); + Assert.AreEqual(4000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 3].Time, "Incorrect second note release time"); + Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed"); + Assert.IsTrue(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed"); + Assert.IsFalse(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key1), "Key1 has not been released"); + Assert.IsTrue(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key2), "Key2 has been released"); + Assert.IsFalse(checkContains(generated.Frames[frame_offset + 3], ManiaAction.Key2), "Key2 has not been released"); } [Test] @@ -166,14 +173,14 @@ namespace osu.Game.Rulesets.Mania.Tests var generated = new ManiaAutoGenerator(beatmap).Generate(); - Assert.IsTrue(generated.Frames.Count == 4, "Replay must have 4 frames"); - Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time"); - Assert.AreEqual(3000, generated.Frames[2].Time, "Incorrect second note press time + first note release time"); - Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect second note release time"); - Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed"); - Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1), "Key1 has not been released"); - Assert.IsTrue(checkContains(generated.Frames[2], ManiaAction.Key2), "Key2 has not been pressed"); - Assert.IsFalse(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has not been released"); + Assert.IsTrue(generated.Frames.Count == frame_offset + 3, "Replay must have 3 generated frames"); + Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time"); + Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect second note press time + first note release time"); + Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 2].Time, "Incorrect second note release time"); + Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed"); + Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released"); + Assert.IsTrue(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key2), "Key2 has not been pressed"); + Assert.IsFalse(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key2), "Key2 has not been released"); } private bool checkContains(ReplayFrame frame, params ManiaAction[] actions) => actions.All(action => ((ManiaReplayFrame)frame).Actions.Contains(action)); From 2046f64b22b31c753390da30bc71d33b59381c96 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2019 16:07:02 +0900 Subject: [PATCH 1313/2815] Revert clamping logic --- osu.Game/Tests/Visual/OsuTestScene.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 2bc9273f69..8e98d51962 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -20,6 +20,7 @@ using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Tests.Beatmaps; +using osuTK; namespace osu.Game.Tests.Visual { @@ -238,7 +239,7 @@ namespace osu.Game.Tests.Visual public override bool Seek(double seek) { - offset = Math.Min(seek, Length); + offset = MathHelper.Clamp(seek, 0, Length); lastReferenceTime = null; return offset == seek; From 381daffe527afef49687cbd1aed9ba026f718ef2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2019 16:07:29 +0900 Subject: [PATCH 1314/2815] Generate better temporary frames to support framed handling flaws --- osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs | 8 ++++---- osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs | 2 +- osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs | 8 ++++---- osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs | 3 ++- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index 7f972a25ae..6c8515eb90 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -37,10 +37,6 @@ namespace osu.Game.Rulesets.Catch.Replays float lastPosition = 0.5f; double lastTime = 0; - // Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled - addFrame(-100000, lastPosition); - addFrame(0, lastPosition); - void moveToNext(CatchHitObject h) { float positionChange = Math.Abs(lastPosition - h.X); @@ -128,6 +124,10 @@ namespace osu.Game.Rulesets.Catch.Replays private void addFrame(double time, float? position = null, bool dashing = false) { + // todo: can be removed once FramedReplayInputHandler correctly handles rewinding before first frame. + if (Replay.Frames.Count == 0) + Replay.Frames.Add(new CatchReplayFrame(time - 1, position, false, null)); + var last = currentFrame; currentFrame = new CatchReplayFrame(time, position, dashing, last); Replay.Frames.Add(currentFrame); diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs index 8206e33c7c..a5248c7712 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Tests /// /// The number of frames which are generated at the start of a replay regardless of hitobject content. /// - private const int frame_offset = 2; + private const int frame_offset = 1; [Test] public void TestSingleNote() diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 5d333e2047..2b336ca16d 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -47,10 +47,6 @@ namespace osu.Game.Rulesets.Mania.Replays public override Replay Generate() { - // Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled - Replay.Frames.Add(new ManiaReplayFrame(-100000, 0)); - Replay.Frames.Add(new ManiaReplayFrame(0, 0)); - var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time); var actions = new List(); @@ -71,6 +67,10 @@ namespace osu.Game.Rulesets.Mania.Replays } } + // todo: can be removed once FramedReplayInputHandler correctly handles rewinding before first frame. + if (Replay.Frames.Count == 0) + Replay.Frames.Add(new ManiaReplayFrame(group.First().Time - 1)); + Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, actions.ToArray())); } diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs index 70ba5cd938..72c7eb60e0 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs @@ -21,7 +21,8 @@ namespace osu.Game.Rulesets.Mania.Replays public ManiaReplayFrame(double time, params ManiaAction[] actions) : base(time) { - Actions.AddRange(actions); + if (actions.Length > 0) + Actions.AddRange(actions); } public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) From e17cd9e964e5eaeded62b3b2a4584a813bec5470 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2019 16:14:31 +0900 Subject: [PATCH 1315/2815] Reduce length of tests --- osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index 883aa9fc36..f94071a7a9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void AddCheckSteps() { AddUntilStep("score above zero", () => ((ScoreAccessiblePlayer)Player).ScoreProcessor.TotalScore.Value > 0); - AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 5)); + AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2)); AddStep("rewind", () => track.Seek(-10000)); AddUntilStep("key counter reset", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); } From 61b396f235bedbd8306a26f84cbf5472df9f92d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2019 17:09:43 +0900 Subject: [PATCH 1316/2815] Remove redundant length check --- osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs index 72c7eb60e0..70ba5cd938 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs @@ -21,8 +21,7 @@ namespace osu.Game.Rulesets.Mania.Replays public ManiaReplayFrame(double time, params ManiaAction[] actions) : base(time) { - if (actions.Length > 0) - Actions.AddRange(actions); + Actions.AddRange(actions); } public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) From cfdac956c2b28acd95829b6cac682d803e9fea67 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2019 20:04:49 +0900 Subject: [PATCH 1317/2815] Fix issues with colour and skin application --- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 23 +++++++++++-------- .../Objects/Drawables/DrawableHitCircle.cs | 8 +++---- .../Objects/Drawables/DrawableSlider.cs | 4 ++-- .../Objects/Drawables/DrawableHitObject.cs | 15 +++++++++++- 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index 7c75bd608e..10a6334479 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -5,12 +5,12 @@ using System; using System.Linq; using osu.Framework.Bindables; using System.Collections.Generic; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Mods { @@ -22,7 +22,8 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override string Description => "Put your faith in the approach circles..."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModHidden), typeof(OsuModeObjectScaleTween) }; + + public override Type[] IncompatibleMods => new[] { typeof(OsuModHidden), typeof(OsuModSpinIn) }; private Bindable increaseFirstObjectVisibility = new Bindable(); public void ReadFromConfig(OsuConfigManager config) @@ -38,26 +39,28 @@ namespace osu.Game.Rulesets.Osu.Mods protected void ApplyTraceableState(DrawableHitObject drawable, ArmedState state) { - if (!(drawable is DrawableOsuHitObject d)) + if (!(drawable is DrawableOsuHitObject drawableOsu)) return; - var h = d.HitObject; + var h = drawableOsu.HitObject; switch (drawable) { case DrawableHitCircle circle: // we only want to see the approach circle using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) - { - circle.ApproachCircle.Show(); - } + circle.CirclePiece.Hide(); break; case DrawableSlider slider: - ApplyTraceableState(slider.HeadCircle, state); - slider.Body.AccentColour = Color4.Transparent; - slider.Body.BorderColour = slider.HeadCircle.AccentColour.Value; + slider.AccentColour.BindValueChanged(_ => + { + //will trigger on skin change. + slider.Body.AccentColour = slider.AccentColour.Value.Opacity(0); + slider.Body.BorderColour = slider.AccentColour.Value; + }, true); + break; case DrawableSpinner spinner: diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 83646c561d..c90f230f93 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableHitCircle : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach { - public ApproachCircle ApproachCircle; + public ApproachCircle ApproachCircle { get; } private readonly IBindable positionBindable = new Bindable(); private readonly IBindable stackHeightBindable = new Bindable(); @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly HitArea hitArea; - private readonly SkinnableDrawable mainContent; + public SkinnableDrawable CirclePiece { get; } public DrawableHitCircle(HitCircle h) : base(h) @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return true; }, }, - mainContent = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.HitCircle), _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)), + CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.HitCircle), _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)), ApproachCircle = new ApproachCircle { Alpha = 0, @@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdateInitialTransforms(); - mainContent.FadeInFromZero(HitObject.TimeFadeIn); + CirclePiece.FadeInFromZero(HitObject.TimeFadeIn); ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadeIn * 2, HitObject.TimePreempt)); ApproachCircle.ScaleTo(1f, HitObject.TimePreempt); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 08b43b0345..643a0f7336 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -163,9 +163,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private float sliderPathRadius; - protected override void SkinChanged(ISkinSource skin, bool allowFallback) + protected override void ApplySkin(ISkinSource skin, bool allowFallback) { - base.SkinChanged(skin, allowFallback); + base.ApplySkin(skin, allowFallback); Body.BorderSize = skin.GetConfig(OsuSkinConfiguration.SliderBorderSize)?.Value ?? SliderBody.DEFAULT_BORDER_SIZE; sliderPathRadius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 00b57f7249..9a7f1e8522 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -240,7 +240,7 @@ namespace osu.Game.Rulesets.Objects.Drawables #endregion - protected override void SkinChanged(ISkinSource skin, bool allowFallback) + protected sealed override void SkinChanged(ISkinSource skin, bool allowFallback) { base.SkinChanged(skin, allowFallback); @@ -250,6 +250,19 @@ namespace osu.Game.Rulesets.Objects.Drawables AccentColour.Value = comboColours?.Count > 0 ? comboColours[combo.ComboIndex % comboColours.Count] : Color4.White; } + + ApplySkin(skin, allowFallback); + + updateState(State.Value, true); + } + + /// + /// Called when a change is made to the skin. + /// + /// The new skin. + /// Whether fallback to default skin should be allowed if the custom skin is missing this resource. + protected virtual void ApplySkin(ISkinSource skin, bool allowFallback) + { } /// From aa1a6256431f0e6ac5a6cd793d3d57112e346faf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2019 20:07:44 +0900 Subject: [PATCH 1318/2815] Add back incompatibility marker --- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 3 +-- osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index 10a6334479..7e20feba02 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => "Put your faith in the approach circles..."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModHidden), typeof(OsuModSpinIn) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModHidden), typeof(OsuModSpinIn), typeof(OsuModeObjectScaleTween) }; private Bindable increaseFirstObjectVisibility = new Bindable(); public void ReadFromConfig(OsuConfigManager config) @@ -65,7 +65,6 @@ namespace osu.Game.Rulesets.Osu.Mods case DrawableSpinner spinner: spinner.Disc.Hide(); - //spinner.Ticks.Hide(); // do they contribute to the theme? debatable spinner.Background.Hide(); break; } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs index e926ade41b..923278f484 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Mods private Bindable increaseFirstObjectVisibility = new Bindable(); - public override Type[] IncompatibleMods => new[] { typeof(OsuModSpinIn) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModSpinIn), typeof(OsuModTraceable) }; public void ReadFromConfig(OsuConfigManager config) { From 5901a915e7850fb064cde851cbb5d5a4d8af193b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2019 20:19:57 +0900 Subject: [PATCH 1319/2815] Always update drawable hitobject state on skin change --- .../Objects/Drawables/DrawableSlider.cs | 4 ++-- .../Objects/Drawables/DrawableHitObject.cs | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 08b43b0345..643a0f7336 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -163,9 +163,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private float sliderPathRadius; - protected override void SkinChanged(ISkinSource skin, bool allowFallback) + protected override void ApplySkin(ISkinSource skin, bool allowFallback) { - base.SkinChanged(skin, allowFallback); + base.ApplySkin(skin, allowFallback); Body.BorderSize = skin.GetConfig(OsuSkinConfiguration.SliderBorderSize)?.Value ?? SliderBody.DEFAULT_BORDER_SIZE; sliderPathRadius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 00b57f7249..9a7f1e8522 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -240,7 +240,7 @@ namespace osu.Game.Rulesets.Objects.Drawables #endregion - protected override void SkinChanged(ISkinSource skin, bool allowFallback) + protected sealed override void SkinChanged(ISkinSource skin, bool allowFallback) { base.SkinChanged(skin, allowFallback); @@ -250,6 +250,19 @@ namespace osu.Game.Rulesets.Objects.Drawables AccentColour.Value = comboColours?.Count > 0 ? comboColours[combo.ComboIndex % comboColours.Count] : Color4.White; } + + ApplySkin(skin, allowFallback); + + updateState(State.Value, true); + } + + /// + /// Called when a change is made to the skin. + /// + /// The new skin. + /// Whether fallback to default skin should be allowed if the custom skin is missing this resource. + protected virtual void ApplySkin(ISkinSource skin, bool allowFallback) + { } /// From 646a64792a8a7be0f00b202b545cd4421d6ffce1 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 18 Sep 2019 14:36:31 +0000 Subject: [PATCH 1320/2815] Bump ppy.osu.Framework.Android from 2019.911.0 to 2019.918.0 Bumps [ppy.osu.Framework.Android](https://github.com/ppy/osu-framework) from 2019.911.0 to 2019.918.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.911.0...2019.918.0) Signed-off-by: dependabot-preview[bot] --- osu.Android.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Android.props b/osu.Android.props index 4167d07698..85e8cb9ceb 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -63,6 +63,6 @@ - + From 1d7377df65c29f90712bb9a97630acd05b9bab3a Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 18 Sep 2019 14:51:01 +0000 Subject: [PATCH 1321/2815] Bump ppy.osu.Framework from 2019.911.0 to 2019.918.0 Bumps [ppy.osu.Framework](https://github.com/ppy/osu-framework) from 2019.911.0 to 2019.918.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.911.0...2019.918.0) 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 5703293caf..a733a0e7f9 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 683dccf3ae..f70853d70b 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,7 +118,7 @@ - + From 5dc5181c9014f8f75ab32b6b17d16de5092e3e2c Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 18 Sep 2019 15:27:39 +0000 Subject: [PATCH 1322/2815] Bump ppy.osu.Framework.iOS from 2019.911.0 to 2019.918.0 Bumps [ppy.osu.Framework.iOS](https://github.com/ppy/osu-framework) from 2019.911.0 to 2019.918.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.911.0...2019.918.0) Signed-off-by: dependabot-preview[bot] --- osu.iOS.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.iOS.props b/osu.iOS.props index f70853d70b..4bfa1ebcd0 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -119,7 +119,7 @@ - + From 1150e9fdfbc14254816c5f26b978d6104decf147 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2019 01:45:42 +0900 Subject: [PATCH 1323/2815] Bring other mods up-to-date --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 1 + osu.Game/Rulesets/Mods/ModAutoplay.cs | 2 ++ osu.Game/Rulesets/Mods/ModBlockFail.cs | 2 ++ 3 files changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index ca72f18e9c..65d7acc911 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) }; public bool AllowFail => false; + public bool RestartOnFail => false; private OsuInputManager inputManager; diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index f0dffa60ce..070a10b1c8 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -26,8 +26,10 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.Automation; public override string Description => "Watch a perfect automated play through the song."; public override double ScoreMultiplier => 1; + public bool AllowFail => false; public bool RestartOnFail => false; + public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) }; public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0; diff --git a/osu.Game/Rulesets/Mods/ModBlockFail.cs b/osu.Game/Rulesets/Mods/ModBlockFail.cs index 26efc3932d..55c074bde2 100644 --- a/osu.Game/Rulesets/Mods/ModBlockFail.cs +++ b/osu.Game/Rulesets/Mods/ModBlockFail.cs @@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Mods /// public bool AllowFail => false; + public virtual bool RestartOnFail => false; + public void ReadFromConfig(OsuConfigManager config) { showHealthBar = config.GetBindable(OsuSetting.ShowHealthDisplayWhenCantFail); From 2fcc8c2d720d60bbd6431e720628f056f41594a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2019 01:45:59 +0900 Subject: [PATCH 1324/2815] Simplify implementation, play fail animation during restart --- osu.Game/Screens/Play/Player.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ca17e8a7bc..dac5561a45 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -360,7 +360,9 @@ namespace osu.Game.Screens.Play private bool onFail() { - if (Mods.Value.OfType().Any(m => !m.AllowFail)) + var failOverrideMods = Mods.Value.OfType(); + + if (failOverrideMods.Any(m => !m.AllowFail)) return false; HasFailed = true; @@ -371,13 +373,10 @@ namespace osu.Game.Screens.Play if (PauseOverlay.State.Value == Visibility.Visible) PauseOverlay.Hide(); - if (Beatmap.Value.Mods.Value.OfType().Any(m => m.RestartOnFail)) - { - Restart(); - return true; - } - failAnimation.Start(); + if (failOverrideMods.Any(m => m.RestartOnFail)) + Restart(); + return true; } From 2296ea75d7e64183b6b625810da21c3ef5b46458 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2019 02:03:30 +0900 Subject: [PATCH 1325/2815] Add reference to xmldoc Co-Authored-By: Salman Ahmed --- osu.Game/Rulesets/Mods/IApplicableFailOverride.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs b/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs index ae5903f085..120bfc9a23 100644 --- a/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs +++ b/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mods bool AllowFail { get; } /// - /// Whether we want to restart on fail. Only used if AllowFail is true. + /// Whether we want to restart on fail. Only used if is true. /// bool RestartOnFail { get; } } From 92556db9cd8bae5df028a295338d8ad1065e2a55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2019 02:37:35 +0900 Subject: [PATCH 1326/2815] Add query-based filter modes to song select search field --- osu.Game/Beatmaps/BeatmapManager.cs | 1 + .../Select/Carousel/CarouselBeatmap.cs | 24 +++- osu.Game/Screens/Select/FilterControl.cs | 118 ++++++++++++++++-- osu.Game/Screens/Select/FilterCriteria.cs | 33 ++++- 4 files changed, 163 insertions(+), 13 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index b9ed3664ef..02d7b2d98f 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -301,6 +301,7 @@ namespace osu.Game.Beatmaps var ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID); beatmap.BeatmapInfo.Ruleset = ruleset; + // TODO: this should be done in a better place once we actually need to dynamically update it. beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating ?? 0; beatmap.BeatmapInfo.Length = calculateLength(beatmap); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 712ab7b571..60e556a261 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -24,12 +24,26 @@ namespace osu.Game.Screens.Select.Carousel { base.Filter(criteria); - bool match = criteria.Ruleset == null || Beatmap.RulesetID == criteria.Ruleset.ID || (Beatmap.RulesetID == 0 && criteria.Ruleset.ID > 0 && criteria.AllowConvertedBeatmaps); + bool match = + criteria.Ruleset == null || + Beatmap.RulesetID == criteria.Ruleset.ID || + (Beatmap.RulesetID == 0 && criteria.Ruleset.ID > 0 && criteria.AllowConvertedBeatmaps); - foreach (var criteriaTerm in criteria.SearchTerms) - match &= - Beatmap.Metadata.SearchableTerms.Any(term => term.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0) || - Beatmap.Version.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0; + match &= criteria.StarDifficulty.IsInRange(Beatmap.StarDifficulty); + match &= criteria.ApproachRate.IsInRange(Beatmap.BaseDifficulty.ApproachRate); + match &= criteria.DrainRate.IsInRange(Beatmap.BaseDifficulty.DrainRate); + match &= criteria.CircleSize.IsInRange(Beatmap.BaseDifficulty.CircleSize); + match &= criteria.Length.IsInRange(Beatmap.Length); + match &= criteria.BPM.IsInRange(Beatmap.BPM); + + match &= !criteria.BeatDivisor.HasValue || criteria.BeatDivisor == Beatmap.BeatDivisor; + match &= !criteria.OnlineStatus.HasValue || criteria.OnlineStatus == Beatmap.Status; + + if (match) + foreach (var criteriaTerm in criteria.SearchTerms) + match &= + Beatmap.Metadata.SearchableTerms.Any(term => term.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0) || + Beatmap.Version.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0; Filtered.Value = !match; } diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index ed74b01fc9..01e7ed9383 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -16,6 +16,8 @@ using Container = osu.Framework.Graphics.Containers.Container; using osu.Framework.Graphics.Shapes; using osu.Game.Configuration; using osu.Game.Rulesets; +using System.Text.RegularExpressions; +using osu.Game.Beatmaps; namespace osu.Game.Screens.Select { @@ -33,14 +35,24 @@ namespace osu.Game.Screens.Select private Bindable groupMode; - public FilterCriteria CreateCriteria() => new FilterCriteria + public FilterCriteria CreateCriteria() { - Group = groupMode.Value, - Sort = sortMode.Value, - SearchText = searchTextBox.Text, - AllowConvertedBeatmaps = showConverted.Value, - Ruleset = ruleset.Value - }; + var query = searchTextBox.Text; + + var criteria = new FilterCriteria + { + Group = groupMode.Value, + Sort = sortMode.Value, + AllowConvertedBeatmaps = showConverted.Value, + Ruleset = ruleset.Value + }; + + applyQueries(criteria, ref query); + + criteria.SearchText = query; + + return criteria; + } public Action Exit; @@ -169,5 +181,97 @@ namespace osu.Game.Screens.Select } private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria()); + + private static readonly Regex query_syntax_regex = new Regex( + @"\b(?stars|ar|dr|cs|divisor|length|objects|bpm|status)(?[:><]+)(?\S*)", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private void applyQueries(FilterCriteria criteria, ref string query) + { + foreach (Match match in query_syntax_regex.Matches(query)) + { + var key = match.Groups["key"].Value.ToLower(); + var op = match.Groups["op"].Value; + var value = match.Groups["value"].Value; + + switch (key) + { + case "stars" when double.TryParse(value, out var stars): + updateCriteriaRange(ref criteria.StarDifficulty, op, stars, 0.5); + break; + + case "ar" when double.TryParse(value, out var ar): + updateCriteriaRange(ref criteria.ApproachRate, op, ar, 0.3); + break; + + case "dr" when double.TryParse(value, out var dr): + updateCriteriaRange(ref criteria.DrainRate, op, dr, 0.3); + break; + + case "cs" when double.TryParse(value, out var cs): + updateCriteriaRange(ref criteria.CircleSize, op, cs, 0.3); + break; + + case "bpm" when double.TryParse(value, out var bpm): + updateCriteriaRange(ref criteria.BPM, op, bpm, 0.3); + break; + + case "length" when double.TryParse(value.TrimEnd('m', 's', 'h'), out var length): + var scale = + value.EndsWith("ms") ? 1 : + value.EndsWith("s") ? 1000 : + value.EndsWith("m") ? 60000 : + value.EndsWith("h") ? 3600000 : 1000; + + updateCriteriaRange(ref criteria.Length, op, length * scale, scale / 2.0); + break; + + case "divisor" when op == ":" && int.TryParse(value, out var divisor): + criteria.BeatDivisor = divisor; + break; + + case "status" when op == ":" && Enum.TryParse(value, ignoreCase: true, out var statusValue): + criteria.OnlineStatus = statusValue; + break; + } + + query = query.Remove(match.Index, match.Length); + } + } + + private void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, double value, double equalityToleration = 0) + { + switch (op) + { + default: + return; + + case ":": + range.IsInclusive = true; + range.Min = value - equalityToleration; + range.Max = value + equalityToleration; + break; + + case ">": + range.IsInclusive = false; + range.Min = value; + break; + + case ">:": + range.IsInclusive = true; + range.Min = value; + break; + + case "<": + range.IsInclusive = false; + range.Max = value; + break; + + case "<:": + range.IsInclusive = true; + range.Max = value; + break; + } + } } } diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 140010ff54..84d63c16e0 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Screens.Select.Filter; @@ -13,6 +14,17 @@ namespace osu.Game.Screens.Select public GroupMode Group; public SortMode Sort; + public OptionalRange StarDifficulty; + public OptionalRange ApproachRate; + public OptionalRange DrainRate; + public OptionalRange CircleSize; + public OptionalRange Length; + public OptionalRange BPM; + + public int? BeatDivisor; + + public BeatmapSetOnlineStatus? OnlineStatus; + public string[] SearchTerms = Array.Empty(); public RulesetInfo Ruleset; @@ -26,8 +38,27 @@ namespace osu.Game.Screens.Select set { searchText = value; - SearchTerms = searchText.Split(',', ' ', '!').Where(s => !string.IsNullOrEmpty(s)).ToArray(); + SearchTerms = searchText.Split(new[] { ',', ' ', '!' }, StringSplitOptions.RemoveEmptyEntries).ToArray(); } } + + public struct OptionalRange : IEquatable + { + public bool IsInRange(double value) + { + if (Min.HasValue && (IsInclusive ? value < Min.Value : value <= Min.Value)) + return false; + if (Max.HasValue && (IsInclusive ? value > Max.Value : value >= Max.Value)) + return false; + + return true; + } + + public double? Min; + public double? Max; + public bool IsInclusive; + + public bool Equals(OptionalRange range) => Min == range.Min && Max == range.Max; + } } } From ecd721e8c5be287068bc1f51d2f85b4a44c225b3 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 18 Sep 2019 22:49:28 +0300 Subject: [PATCH 1327/2815] Add bypass fail property to Player --- osu.Game/Screens/Play/Player.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 309f4837e5..fbaab61b42 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -86,6 +86,11 @@ namespace osu.Game.Screens.Play [Cached(Type = typeof(IBindable>))] protected new readonly Bindable> Mods = new Bindable>(Array.Empty()); + /// + /// Whether to block the player from failing. + /// + protected virtual bool BypassFail => false; + private readonly bool allowPause; private readonly bool showResults; @@ -360,7 +365,7 @@ namespace osu.Game.Screens.Play private bool onFail() { - if (Mods.Value.OfType().Any(m => !m.AllowFail)) + if (Mods.Value.OfType().Any(m => !m.AllowFail) || BypassFail) return false; HasFailed = true; From 775b90e06643da66c6ebc72218e2163bbf15c8de Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 18 Sep 2019 22:49:48 +0300 Subject: [PATCH 1328/2815] Bypass fail on replays --- osu.Game/Screens/Play/ReplayPlayer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index a9c0ee3a15..da9f558c16 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -9,6 +9,9 @@ namespace osu.Game.Screens.Play { private readonly Score score; + // Block replays from failing. (see https://github.com/ppy/osu/issues/6108) + protected override bool BypassFail => true; + public ReplayPlayer(Score score, bool allowPause = true, bool showResults = true) : base(allowPause, showResults) { From 871adb16e0ba068e305ca79e342623bfff2b6e34 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 18 Sep 2019 22:51:03 +0300 Subject: [PATCH 1329/2815] Add asserts for fail bypassing --- osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs index 3fbce9d43c..d89304cf44 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs @@ -26,12 +26,14 @@ namespace osu.Game.Tests.Visual.Gameplay { AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0); AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0)); + AddAssert("cannot fail", () => ((ScoreAccessibleReplayPlayer)Player).BypassFail); } private class ScoreAccessibleReplayPlayer : ReplayPlayer { public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; public new HUDOverlay HUDOverlay => base.HUDOverlay; + public new bool BypassFail => base.BypassFail; protected override bool PauseOnFocusLost => false; From ea6318ed73c22607c2aace18d7fa9cc7a659ebd1 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 18 Sep 2019 23:17:24 +0300 Subject: [PATCH 1330/2815] Fix failing test --- .../Visual/Gameplay/TestSceneFailJudgement.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs index d57ec44f39..e6d302a5ca 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs @@ -17,9 +17,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override Player CreatePlayer(Ruleset ruleset) { Mods.Value = Array.Empty(); - - var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty()); - return new FailPlayer(ruleset.GetAutoplayMod().CreateReplayScore(beatmap)); + return new FailPlayer(); } protected override void AddCheckSteps() @@ -29,16 +27,12 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("total judgements == 1", () => ((FailPlayer)Player).ScoreProcessor.JudgedHits == 1); } - private class FailPlayer : ReplayPlayer + private class FailPlayer : TestPlayer { - public new DrawableRuleset DrawableRuleset => base.DrawableRuleset; - public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; - protected override bool PauseOnFocusLost => false; - - public FailPlayer(Score score) - : base(score, false, false) + public FailPlayer() + : base(false, false) { } From 3efcf0493c45c95b5a648bbcce99ab4af831ac7d Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 18 Sep 2019 23:28:48 +0300 Subject: [PATCH 1331/2815] Remove redundant using directive --- osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs index e6d302a5ca..cca6301b02 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs @@ -6,8 +6,6 @@ using System.Linq; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; -using osu.Game.Scoring; using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual.Gameplay From c3221e1a3623d3c0cc5134aef738fbed3acbd24a Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Wed, 18 Sep 2019 23:27:26 +0200 Subject: [PATCH 1332/2815] Prepare classes for player loader test scene --- .../Visual/Gameplay/TestScenePlayerLoader.cs | 90 +++++++++++++++++-- osu.Game/Screens/Play/PlayerLoader.cs | 16 +++- 2 files changed, 96 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index ab519360ac..dc099b14b9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -3,14 +3,20 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.MathUtils; using osu.Framework.Screens; +using osu.Game.Graphics.Containers; +using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; @@ -24,19 +30,35 @@ namespace osu.Game.Tests.Visual.Gameplay public class TestScenePlayerLoader : ManualInputManagerTestScene { private TestPlayerLoader loader; - private OsuScreenStack stack; + private TestPlayerLoaderContainer container; + private TestPlayer player; + + [Resolved] + private AudioManager audioManager { get; set; } [SetUp] public void Setup() => Schedule(() => { - InputManager.Child = stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }; + ResetPlayer(false); Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); }); + /// + /// Sets the input manager child to a new test player loader container instance. + /// + /// If the test player should behave like the production one. + public void ResetPlayer(bool interactive) + { + player = new TestPlayer(interactive, interactive); + loader = new TestPlayerLoader(() => player); + container = new TestPlayerLoaderContainer(loader); + InputManager.Child = container; + } + [Test] public void TestBlockLoadViaMouseMovement() { - AddStep("load dummy beatmap", () => stack.Push(loader = new TestPlayerLoader(() => new TestPlayer(false, false)))); + AddStep("load dummy beatmap", () => ResetPlayer(false)); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); AddRepeatStep("move mouse", () => InputManager.MoveMouseTo(loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft + (loader.VisualSettings.ScreenSpaceDrawQuad.BottomRight - loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft) * RNG.NextSingle()), 20); AddAssert("loader still active", () => loader.IsCurrentScreen()); @@ -49,13 +71,13 @@ namespace osu.Game.Tests.Visual.Gameplay Player player = null; SlowLoadPlayer slowPlayer = null; - AddStep("load dummy beatmap", () => stack.Push(loader = new TestPlayerLoader(() => player = new TestPlayer(false, false)))); + AddStep("load dummy beatmap", () => ResetPlayer(false)); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); AddUntilStep("wait for player to be current", () => player.IsCurrentScreen()); AddStep("load slow dummy beatmap", () => { - stack.Push(loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false))); + InputManager.Children = container = new TestPlayerLoaderContainer(loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false))); Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000); }); @@ -65,7 +87,6 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestModReinstantiation() { - TestPlayer player = null; TestMod gameMod = null; TestMod playerMod1 = null; TestMod playerMod2 = null; @@ -73,7 +94,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("load player", () => { Mods.Value = new[] { gameMod = new TestMod() }; - stack.Push(loader = new TestPlayerLoader(() => player = new TestPlayer())); + ResetPlayer(true); }); AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen()); @@ -97,6 +118,61 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("player mods applied", () => playerMod2.Applied); } + [Test] + public void TestMutedNotification() + { + AddStep("set master volume to 0%", () => audioManager.Volume.Value = 0); + AddStep("reset notification lock", () => PlayerLoader.ResetNotificationLock()); + //AddStep("reset notification overlay", () => notificationOverlay); + AddStep("load player", () => ResetPlayer(false)); + AddUntilStep("wait for player", () => player.IsLoaded); + + AddAssert("check for notification", () => container.NotificationOverlay.UnreadCount.Value == 1); + AddAssert("click notification", () => + { + var scrollContainer = container.NotificationOverlay.Children.Last() as OsuScrollContainer; + var flowContainer = scrollContainer.Children.First() as FillFlowContainer; + return flowContainer.Children.First().First().Click(); + }); + AddAssert("check master volume", () => audioManager.Volume.IsDefault); + + AddStep("restart player", () => + { + var lastPlayer = player; + player = null; + lastPlayer.Restart(); + }); + } + + private class TestPlayerLoaderContainer : Container + { + private TestPlayerLoader loader; + + [Cached] + public NotificationOverlay NotificationOverlay { get; } = new NotificationOverlay + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }; + + [Cached] + public VolumeOverlay VolumeOverlay { get; } = new VolumeOverlay + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + }; + + public TestPlayerLoaderContainer(TestPlayerLoader testPlayerLoader) + { + Children = new Drawable[] + { + loader = testPlayerLoader, + NotificationOverlay, + VolumeOverlay + }; + } + } + private class TestPlayerLoader : PlayerLoader { public new VisualSettings VisualSettings => base.VisualSettings; diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index dbb6c47943..8f2435d2f7 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -65,6 +65,9 @@ namespace osu.Game.Screens.Play [Resolved] private VolumeOverlay volumeOverlay { get; set; } + [Resolved] + private AudioManager audioManager { get; set; } + private static bool muteWarningShownOnce; public PlayerLoader(Func createPlayer) @@ -79,7 +82,7 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader] - private void load(AudioManager audioManager, NotificationOverlay notificationOverlay) + private void load() { InternalChild = (content = new LogoTrackingContainer { @@ -112,8 +115,6 @@ namespace osu.Game.Screens.Play }); loadNewPlayer(); - - checkVolume(audioManager); } private void playerLoaded(Player player) => info.Loading = false; @@ -212,6 +213,7 @@ namespace osu.Game.Screens.Play { inputManager = GetContainingInputManager(); base.LoadComplete(); + checkVolume(audioManager); } private ScheduledDelegate pushDebounce; @@ -526,5 +528,13 @@ namespace osu.Game.Screens.Play }; } } + + /// + /// Sets to , reserved for testing. + /// + public static void ResetNotificationLock() + { + muteWarningShownOnce = false; + } } } From 3fa1b53b2ae3d670bc7f1f2a6f8b55cb57659b93 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2019 12:39:15 +0900 Subject: [PATCH 1333/2815] Add back combo colours for osu!classic --- osu.Game/Skinning/DefaultLegacySkin.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs index 98f158c725..4b6eea6b6e 100644 --- a/osu.Game/Skinning/DefaultLegacySkin.cs +++ b/osu.Game/Skinning/DefaultLegacySkin.cs @@ -13,6 +13,13 @@ namespace osu.Game.Skinning : base(Info, storage, audioManager, string.Empty) { Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255); + Configuration.ComboColours.AddRange(new[] + { + new Color4(255, 192, 0, 255), + new Color4(0, 202, 0, 255), + new Color4(18, 124, 255, 255), + new Color4(242, 24, 57, 255), + }); } public static SkinInfo Info { get; } = new SkinInfo From e5509cd3908175a866809317576b0d896b53afe8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2019 13:19:48 +0900 Subject: [PATCH 1334/2815] Rename test --- ...stSceneLeaderboard.cs => TestSceneBeatmapLeaderboard.cs} | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) rename osu.Game.Tests/Visual/SongSelect/{TestSceneLeaderboard.cs => TestSceneBeatmapLeaderboard.cs} (98%) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs similarity index 98% rename from osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs rename to osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 186f27a8b2..cb4cd63266 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Online.Leaderboards; @@ -14,8 +13,7 @@ using osuTK; namespace osu.Game.Tests.Visual.SongSelect { - [Description("PlaySongSelect leaderboard")] - public class TestSceneLeaderboard : OsuTestScene + public class TestSceneBeatmapLeaderboard : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -26,7 +24,7 @@ namespace osu.Game.Tests.Visual.SongSelect private readonly FailableLeaderboard leaderboard; - public TestSceneLeaderboard() + public TestSceneBeatmapLeaderboard() { Add(leaderboard = new FailableLeaderboard { From 0644443979bde65a602504c34eeecbbfcff332f9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Sep 2019 13:51:50 +0900 Subject: [PATCH 1335/2815] Use resolved attribute for music controller --- osu.Game/Overlays/Music/PlaylistList.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index 636945edd2..cd9c91f3af 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -43,8 +43,6 @@ namespace osu.Game.Overlays.Music private class ItemsScrollContainer : OsuScrollContainer { - private IBindableList beatmaps; - public Action Selected; private readonly SearchContainer search; @@ -52,7 +50,10 @@ namespace osu.Game.Overlays.Music private readonly IBindable beatmapBacking = new Bindable(); - private MusicController musicController; + private IBindableList beatmaps; + + [Resolved] + private MusicController musicController { get; set; } public ItemsScrollContainer() { @@ -75,12 +76,9 @@ namespace osu.Game.Overlays.Music } [BackgroundDependencyLoader] - private void load(MusicController musicController, IBindable beatmap) + private void load(IBindable beatmap) { - this.musicController = musicController; - beatmaps = musicController.BeatmapSets.GetBoundCopy(); - beatmaps.ItemsAdded += i => i.ForEach(addBeatmapSet); beatmaps.ItemsRemoved += i => i.ForEach(removeBeatmapSet); beatmaps.ForEach(addBeatmapSet); From 4b97327b37ad910b93bb3a835a19201369dc6730 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Sep 2019 13:53:52 +0900 Subject: [PATCH 1336/2815] Cleanup draggedItem usages and make them more safe --- osu.Game/Overlays/Music/PlaylistList.cs | 50 ++++++++++++------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index cd9c91f3af..5b528c5ab2 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -87,25 +87,24 @@ namespace osu.Game.Overlays.Music beatmapBacking.ValueChanged += _ => updateSelectedSet(); } - private void addBeatmapSet(BeatmapSetInfo obj) => Schedule(() => - { - if (obj == draggedItem?.BeatmapSetInfo) - { - draggedItem = null; - return; - } - - items.Insert(items.Count - 1, new PlaylistItem(obj) { OnSelect = set => Selected?.Invoke(set) }); - }); - - private void removeBeatmapSet(BeatmapSetInfo obj) => Schedule(() => + private void addBeatmapSet(BeatmapSetInfo obj) { if (obj == draggedItem?.BeatmapSetInfo) return; - var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == obj.ID); - if (itemToRemove != null) - items.Remove(itemToRemove); - }); + Schedule(() => items.Insert(items.Count - 1, new PlaylistItem(obj) { OnSelect = set => Selected?.Invoke(set) })); + } + + private void removeBeatmapSet(BeatmapSetInfo obj) + { + if (obj == draggedItem?.BeatmapSetInfo) return; + + Schedule(() => + { + var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == obj.ID); + if (itemToRemove != null) + items.Remove(itemToRemove); + }); + } private void updateSelectedSet() { @@ -146,19 +145,16 @@ namespace osu.Game.Overlays.Music { nativeDragPosition = e.ScreenSpaceMousePosition; - if (draggedItem != null) - { - if (dragDestination != null) - { - // draggedItem is nulled when the BindableList's add event is received so we can quietly ignore the callbacks. - musicController.ChangeBeatmapSetPosition(draggedItem.BeatmapSetInfo, dragDestination.Value); - dragDestination = null; - } + if (draggedItem == null) + return base.OnDragEnd(e); - return true; - } + if (dragDestination != null) + musicController.ChangeBeatmapSetPosition(draggedItem.BeatmapSetInfo, dragDestination.Value); - return base.OnDragEnd(e); + draggedItem = null; + dragDestination = null; + + return true; } protected override void Update() From 9de0bcae1eef5ff0beaa2fb70f26d66a62cb725e Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 19 Sep 2019 07:58:54 +0300 Subject: [PATCH 1337/2815] Check for blocking fail mods by default --- osu.Game/Screens/Play/Player.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index fbaab61b42..e8134253f5 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -87,9 +87,11 @@ namespace osu.Game.Screens.Play protected new readonly Bindable> Mods = new Bindable>(Array.Empty()); /// - /// Whether to block the player from failing. + /// Whether failing should be allowed. + /// + /// By default, this checks whether any selected mod disallows failing. /// - protected virtual bool BypassFail => false; + protected virtual bool AllowFail => !Mods.Value.OfType().Any(m => !m.AllowFail); private readonly bool allowPause; private readonly bool showResults; @@ -365,7 +367,7 @@ namespace osu.Game.Screens.Play private bool onFail() { - if (Mods.Value.OfType().Any(m => !m.AllowFail) || BypassFail) + if (!AllowFail) return false; HasFailed = true; From e793854735a812acb0d4e01ddbff87c5d20922d3 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 19 Sep 2019 08:00:41 +0300 Subject: [PATCH 1338/2815] Invert BypassFail usage --- osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs | 4 ++-- osu.Game/Screens/Play/ReplayPlayer.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs index d89304cf44..36335bc54a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs @@ -26,14 +26,14 @@ namespace osu.Game.Tests.Visual.Gameplay { AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0); AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0)); - AddAssert("cannot fail", () => ((ScoreAccessibleReplayPlayer)Player).BypassFail); + AddAssert("cannot fail", () => !((ScoreAccessibleReplayPlayer)Player).AllowFail); } private class ScoreAccessibleReplayPlayer : ReplayPlayer { public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; public new HUDOverlay HUDOverlay => base.HUDOverlay; - public new bool BypassFail => base.BypassFail; + public new bool AllowFail => base.AllowFail; protected override bool PauseOnFocusLost => false; diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index da9f558c16..b040549efc 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -9,8 +9,8 @@ namespace osu.Game.Screens.Play { private readonly Score score; - // Block replays from failing. (see https://github.com/ppy/osu/issues/6108) - protected override bool BypassFail => true; + // Disallow replays from failing. (see https://github.com/ppy/osu/issues/6108) + protected override bool AllowFail => false; public ReplayPlayer(Score score, bool allowPause = true, bool showResults = true) : base(allowPause, showResults) From 177a789d792b55d69a232c56d82a2813ea6f0159 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2019 14:04:51 +0900 Subject: [PATCH 1339/2815] Add setting to adjust hold-to-confirm activation time --- osu.Game/Configuration/OsuConfigManager.cs | 5 ++++- .../Containers/HoldToConfirmContainer.cs | 18 ++++++++---------- osu.Game/Overlays/HoldToConfirmOverlay.cs | 2 +- .../Sections/Graphics/UserInterfaceSettings.cs | 15 ++++++++++++++- 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index e26021d930..62590a0a8f 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -112,6 +112,8 @@ namespace osu.Game.Configuration Set(OsuSetting.UIScale, 1f, 0.8f, 1.6f, 0.01f); + Set(OsuSetting.UIHoldActivationDelay, 200, 0, 500); + Set(OsuSetting.IntroSequence, IntroSequence.Triangles); } @@ -180,6 +182,7 @@ namespace osu.Game.Configuration ScalingSizeX, ScalingSizeY, UIScale, - IntroSequence + IntroSequence, + UIHoldActivationDelay } } diff --git a/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs b/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs index 773265d19b..a345fb554f 100644 --- a/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs +++ b/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs @@ -2,9 +2,11 @@ // 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.Game.Configuration; namespace osu.Game.Graphics.Containers { @@ -12,11 +14,8 @@ namespace osu.Game.Graphics.Containers { public Action Action; - private const int default_activation_delay = 200; private const int fadeout_delay = 200; - private readonly double activationDelay; - private bool fired; private bool confirming; @@ -27,13 +26,12 @@ namespace osu.Game.Graphics.Containers public Bindable Progress = new BindableDouble(); - /// - /// Create a new instance. - /// - /// The time requried before an action is confirmed. - protected HoldToConfirmContainer(double activationDelay = default_activation_delay) + private Bindable holdActivationDelay; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) { - this.activationDelay = activationDelay; + holdActivationDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); } protected void BeginConfirm() @@ -42,7 +40,7 @@ namespace osu.Game.Graphics.Containers confirming = true; - this.TransformBindableTo(Progress, 1, activationDelay * (1 - Progress.Value), Easing.Out).OnComplete(_ => Confirm()); + this.TransformBindableTo(Progress, 1, holdActivationDelay.Value * (1 - Progress.Value), Easing.Out).OnComplete(_ => Confirm()); } protected virtual void Confirm() diff --git a/osu.Game/Overlays/HoldToConfirmOverlay.cs b/osu.Game/Overlays/HoldToConfirmOverlay.cs index fdc6f096bc..eb325d8dd3 100644 --- a/osu.Game/Overlays/HoldToConfirmOverlay.cs +++ b/osu.Game/Overlays/HoldToConfirmOverlay.cs @@ -51,7 +51,7 @@ namespace osu.Game.Overlays protected override void Dispose(bool isDisposing) { - audio.Tracks.RemoveAdjustment(AdjustableProperty.Volume, audioVolume); + audio?.Tracks.RemoveAdjustment(AdjustableProperty.Volume, audioVolume); base.Dispose(isDisposing); } } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs index dd822fedb6..a6956b7d9a 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings.Sections.Graphics { @@ -13,7 +15,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - Children = new[] + Children = new Drawable[] { new SettingsCheckbox { @@ -25,7 +27,18 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics LabelText = "Parallax", Bindable = config.GetBindable(OsuSetting.MenuParallax) }, + new SettingsSlider + { + LabelText = "Hold-to-confirm activation time", + Bindable = config.GetBindable(OsuSetting.UIHoldActivationDelay), + KeyboardStep = 50 + }, }; } + + private class TimeSlider : OsuSliderBar + { + public override string TooltipText => Current.Value.ToString("N0") + "ms"; + } } } From 762adb783ae359a5d3f1d5bebeabd1ec3ef79be8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Sep 2019 14:15:06 +0900 Subject: [PATCH 1340/2815] Fix duplicate invocation of updateState on load complete --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 9a7f1e8522..b94de0df89 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -253,7 +253,8 @@ namespace osu.Game.Rulesets.Objects.Drawables ApplySkin(skin, allowFallback); - updateState(State.Value, true); + if (IsLoaded) + updateState(State.Value, true); } /// From a7b6895d4c9c79119922792b6d688faa48ee2166 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2019 14:26:15 +0900 Subject: [PATCH 1341/2815] Revert changes to BeatmapDetailArea --- .../SongSelect/TestSceneBeatmapDetailArea.cs | 1 - osu.Game/Screens/Select/BeatmapDetailArea.cs | 77 ++++++++----------- osu.Game/Screens/Select/SongSelect.cs | 1 - 3 files changed, 30 insertions(+), 49 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs index ee47888d99..ed9e01a67e 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs @@ -163,7 +163,6 @@ namespace osu.Game.Tests.Visual.SongSelect })); AddStep("null beatmap", () => detailsArea.Beatmap = null); - AddStep("Toggle top score visibility", () => detailsArea.TopScore.ToggleVisibility()); } } } diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs index 648dae68bc..5348de68d6 100644 --- a/osu.Game/Screens/Select/BeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs @@ -1,21 +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 osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; -using osu.Game.Screens.Select.Details; using osu.Game.Screens.Select.Leaderboards; namespace osu.Game.Screens.Select { public class BeatmapDetailArea : Container { - private const float padding = 10; + private const float details_padding = 10; + + private readonly Container content; + protected override Container Content => content; public readonly BeatmapDetails Details; public readonly BeatmapLeaderboard Leaderboard; - public readonly UserTopScoreContainer TopScore; private WorkingBeatmap beatmap; @@ -27,13 +29,12 @@ namespace osu.Game.Screens.Select beatmap = value; Details.Beatmap = beatmap?.BeatmapInfo; Leaderboard.Beatmap = beatmap is DummyWorkingBeatmap ? null : beatmap?.BeatmapInfo; - TopScore.Hide(); } } public BeatmapDetailArea() { - Children = new Drawable[] + AddRangeInternal(new Drawable[] { new BeatmapDetailAreaTabControl { @@ -57,51 +58,33 @@ namespace osu.Game.Screens.Select } }, }, - new Container + content = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = BeatmapDetailAreaTabControl.HEIGHT }, + }, + }); + + AddRange(new Drawable[] + { + Details = new BeatmapDetails + { + RelativeSizeAxes = Axes.X, + Alpha = 0, + Margin = new MarginPadding { Top = details_padding }, + }, + Leaderboard = new BeatmapLeaderboard { - Padding = new MarginPadding { Top = BeatmapDetailAreaTabControl.HEIGHT, Bottom = padding }, RelativeSizeAxes = Axes.Both, - Child = new GridContainer - { - RelativeSizeAxes = Axes.Both, - RowDimensions = new[] - { - new Dimension(GridSizeMode.Distributed), - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] - { - new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - Details = new BeatmapDetails - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Padding = new MarginPadding { Top = padding }, - }, - Leaderboard = new BeatmapLeaderboard - { - RelativeSizeAxes = Axes.Both, - } - } - } - }, - new Drawable[] - { - TopScore = new UserTopScoreContainer - { - Score = { BindTarget = Leaderboard.TopScore } - } - } - }, - }, } - }; + }); + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + Details.Height = Math.Min(DrawHeight - details_padding * 3 - BeatmapDetailAreaTabControl.HEIGHT, 450); } } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 173442384e..7f9804c6a3 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -226,7 +226,6 @@ namespace osu.Game.Screens.Select void displayScore(ScoreInfo score) => this.Push(new SoloResults(score)); BeatmapDetails.Leaderboard.ScoreSelected += displayScore; - BeatmapDetails.TopScore.ScoreSelected += displayScore; } [BackgroundDependencyLoader(true)] From da4d83063e73c4cda5a705fa0500f4153388a029 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 19 Sep 2019 08:31:11 +0300 Subject: [PATCH 1342/2815] Simplify LINQ expression --- osu.Game/Screens/Play/Player.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e8134253f5..30d6cfbf68 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -89,9 +89,9 @@ namespace osu.Game.Screens.Play /// /// Whether failing should be allowed. /// - /// By default, this checks whether any selected mod disallows failing. + /// By default, this checks whether all selected mods allow failing. /// - protected virtual bool AllowFail => !Mods.Value.OfType().Any(m => !m.AllowFail); + protected virtual bool AllowFail => Mods.Value.OfType().All(m => m.AllowFail); private readonly bool allowPause; private readonly bool showResults; From 4967ffd8e557291a261a6b678026a91a2901965f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2019 14:52:31 +0900 Subject: [PATCH 1343/2815] Move inside leaderboard --- osu.Game/Online/Leaderboards/Leaderboard.cs | 39 ++++++++++++++++--- .../Select/Leaderboards/BeatmapLeaderboard.cs | 12 ++++++ 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 147556b78b..d66a9a7535 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -35,6 +35,10 @@ namespace osu.Game.Online.Leaderboards private bool scoresLoadedOnce; + private readonly Container content; + + protected override Container Content => content; + private IEnumerable scores; public IEnumerable Scores @@ -60,13 +64,13 @@ namespace osu.Game.Online.Leaderboards // ensure placeholder is hidden when displaying scores PlaceholderState = PlaceholderState.Successful; - var sf = CreateScoreFlow(); - sf.ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)); + var scoreFlow = CreateScoreFlow(); + scoreFlow.ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)); // schedule because we may not be loaded yet (LoadComponentAsync complains). - showScoresDelegate = Schedule(() => LoadComponentAsync(sf, _ => + showScoresDelegate = Schedule(() => LoadComponentAsync(scoreFlow, _ => { - scrollContainer.Add(scrollFlow = sf); + scrollContainer.Add(scrollFlow = scoreFlow); int i = 0; @@ -164,10 +168,33 @@ namespace osu.Game.Online.Leaderboards { Children = new Drawable[] { - scrollContainer = new OsuScrollContainer + new GridContainer { RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] + { + scrollContainer = new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, + } + }, + new Drawable[] + { + content = new Container + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + }, + } + }, }, loading = new LoadingAnimation(), placeholderContainer = new Container diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 4e4def4911..0eef784279 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -80,6 +80,18 @@ namespace osu.Game.Screens.Select.Leaderboards if (filterMods) UpdateScores(); }; + + TopScore.BindValueChanged(newTopScore); + } + + private void newTopScore(ValueChangedEvent score) + { + Content.Clear(); + + if (score.NewValue != null) + { + + } } protected override bool IsOnlineScope => Scope != BeatmapLeaderboardScope.Local; From c76e27549a6db11e251376d5d15c2a3d710545b9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Sep 2019 14:56:52 +0900 Subject: [PATCH 1344/2815] Remove spacing --- osu.Game/Screens/Play/Player.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 30d6cfbf68..4b234ab296 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -88,7 +88,6 @@ namespace osu.Game.Screens.Play /// /// Whether failing should be allowed. - /// /// By default, this checks whether all selected mods allow failing. /// protected virtual bool AllowFail => Mods.Value.OfType().All(m => m.AllowFail); From 098e89cb66a2896d673c0cb924eecfc92147ff56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2019 15:23:33 +0900 Subject: [PATCH 1345/2815] Improve state reset flow --- osu.Game/Online/Leaderboards/Leaderboard.cs | 13 ++++--- .../Select/Details/UserTopScoreContainer.cs | 22 ++++++------ .../Select/Leaderboards/BeatmapLeaderboard.cs | 36 ++++++++++++------- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index d66a9a7535..83de0635fb 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -120,9 +120,7 @@ namespace osu.Game.Online.Leaderboards { if (value != PlaceholderState.Successful) { - getScoresRequest?.Cancel(); - getScoresRequest = null; - Scores = null; + Reset(); } if (value == placeholderState) @@ -166,7 +164,7 @@ namespace osu.Game.Online.Leaderboards protected Leaderboard() { - Children = new Drawable[] + InternalChildren = new Drawable[] { new GridContainer { @@ -204,6 +202,13 @@ namespace osu.Game.Online.Leaderboards }; } + protected virtual void Reset() + { + getScoresRequest?.Cancel(); + getScoresRequest = null; + Scores = null; + } + private IAPIProvider api; private ScheduledDelegate pendingUpdateScores; diff --git a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs b/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs index c10f4d4fd4..959fbb927a 100644 --- a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs +++ b/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs @@ -17,9 +17,8 @@ namespace osu.Game.Screens.Select.Details public class UserTopScoreContainer : VisibilityContainer { private const int height = 90; - private const int duration = 800; + private const int duration = 500; - private readonly Container contentContainer; private readonly Container scoreContainer; public Bindable Score = new Bindable(); @@ -38,7 +37,7 @@ namespace osu.Game.Screens.Select.Details Children = new Drawable[] { - contentContainer = new Container + new Container { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, @@ -74,15 +73,12 @@ namespace osu.Game.Screens.Select.Details { var newScore = score.NewValue; - if (newScore == null) - { - Hide(); - return; - } - scoreContainer.Clear(); loadScoreCancellation?.Cancel(); + if (newScore == null) + return; + LoadComponentAsync(new LeaderboardScore(newScore.Score, newScore.Position) { Action = () => ScoreSelected?.Invoke(newScore.Score) @@ -93,12 +89,14 @@ namespace osu.Game.Screens.Select.Details }, (loadScoreCancellation = new CancellationTokenSource()).Token); } - protected override void PopIn() => this.ResizeHeightTo(height, duration / 4f, Easing.OutQuint).OnComplete(_ => contentContainer.FadeIn(duration, Easing.OutQuint)); + protected override void PopIn() + { + this.FadeIn(duration, Easing.OutQuint); + } protected override void PopOut() { - this.ResizeHeightTo(0); - contentContainer.FadeOut(duration / 4f, Easing.OutQuint); + this.FadeOut(duration, Easing.OutQuint); } } } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 0eef784279..d038049504 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -14,13 +14,12 @@ using osu.Game.Online.Leaderboards; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; +using osu.Game.Screens.Select.Details; namespace osu.Game.Screens.Select.Leaderboards { public class BeatmapLeaderboard : Leaderboard { - public Bindable TopScore = new Bindable(); - public Action ScoreSelected; private BeatmapInfo beatmap; @@ -40,8 +39,25 @@ namespace osu.Game.Screens.Select.Leaderboards } } + public APILegacyUserTopScoreInfo TopScore + { + get => topScoreContainer.Score.Value; + set + { + if (value == null) + topScoreContainer.Hide(); + else + { + topScoreContainer.Show(); + topScoreContainer.Score.Value = value; + } + } + } + private bool filterMods; + private UserTopScoreContainer topScoreContainer; + /// /// Whether to apply the game's currently selected mods as a filter when retrieving scores. /// @@ -81,25 +97,19 @@ namespace osu.Game.Screens.Select.Leaderboards UpdateScores(); }; - TopScore.BindValueChanged(newTopScore); + Content.Add(topScoreContainer = new UserTopScoreContainer()); } - private void newTopScore(ValueChangedEvent score) + protected override void Reset() { - Content.Clear(); - - if (score.NewValue != null) - { - - } + base.Reset(); + TopScore = null; } protected override bool IsOnlineScope => Scope != BeatmapLeaderboardScope.Local; protected override APIRequest FetchScores(Action> scoresCallback) { - TopScore.Value = null; - if (Beatmap == null) { PlaceholderState = PlaceholderState.NoneSelected; @@ -161,7 +171,7 @@ namespace osu.Game.Screens.Select.Leaderboards req.Success += r => { scoresCallback?.Invoke(r.Scores); - TopScore.Value = r.UserScore; + TopScore = r.UserScore; }; return req; From 9b35de9ce17c1fbe85ccabbc4e58bba81bef805f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2019 15:23:37 +0900 Subject: [PATCH 1346/2815] Update tests --- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index cb4cd63266..bbd874fcd7 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -5,8 +5,12 @@ using System; using System.Collections.Generic; using osu.Framework.Graphics; using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Scoring; +using osu.Game.Screens.Select.Details; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; using osuTK; @@ -20,6 +24,8 @@ namespace osu.Game.Tests.Visual.SongSelect typeof(Placeholder), typeof(MessagePlaceholder), typeof(RetrievalFailurePlaceholder), + typeof(UserTopScoreContainer), + typeof(Leaderboard), }; private readonly FailableLeaderboard leaderboard; @@ -35,6 +41,7 @@ namespace osu.Game.Tests.Visual.SongSelect }); AddStep(@"New Scores", newScores); + AddStep(@"Show personal best", showPersonalBest); AddStep(@"Empty Scores", () => leaderboard.SetRetrievalState(PlaceholderState.NoScores)); AddStep(@"Network failure", () => leaderboard.SetRetrievalState(PlaceholderState.NetworkFailure)); AddStep(@"No supporter", () => leaderboard.SetRetrievalState(PlaceholderState.NotSupporter)); @@ -45,6 +52,32 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep($"{status} beatmap", () => showBeatmapWithStatus(status)); } + private void showPersonalBest() + { + leaderboard.TopScore = new APILegacyUserTopScoreInfo + { + Position = 999, + Score = new APILegacyScoreInfo + { + Rank = ScoreRank.XH, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + User = new User + { + Id = 6602580, + Username = @"waaiiru", + Country = new Country + { + FullName = @"Spain", + FlagName = @"ES", + }, + }, + } + }; + } + private void newScores() { var scores = new[] From 80f46e02d8578ed0c23db9bb68bd7806f637663c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Sep 2019 15:33:49 +0900 Subject: [PATCH 1347/2815] Add equals (=) query operator variants --- osu.Game/Screens/Select/FilterControl.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 01e7ed9383..b25e65092e 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -183,7 +183,7 @@ namespace osu.Game.Screens.Select private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria()); private static readonly Regex query_syntax_regex = new Regex( - @"\b(?stars|ar|dr|cs|divisor|length|objects|bpm|status)(?[:><]+)(?\S*)", + @"\b(?stars|ar|dr|cs|divisor|length|objects|bpm|status)(?[=:><]+)(?\S*)", RegexOptions.Compiled | RegexOptions.IgnoreCase); private void applyQueries(FilterCriteria criteria, ref string query) @@ -246,6 +246,7 @@ namespace osu.Game.Screens.Select default: return; + case "=": case ":": range.IsInclusive = true; range.Min = value - equalityToleration; @@ -257,6 +258,7 @@ namespace osu.Game.Screens.Select range.Min = value; break; + case ">=": case ">:": range.IsInclusive = true; range.Min = value; @@ -267,6 +269,7 @@ namespace osu.Game.Screens.Select range.Max = value; break; + case "<=": case "<:": range.IsInclusive = true; range.Max = value; From e0fd8609d1f96f5fd0169248a3e6b2d3611097f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2019 15:34:46 +0900 Subject: [PATCH 1348/2815] Fix margins and clean up implementation --- .../Select/Details/UserTopScoreContainer.cs | 28 ++++++------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs b/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs index 959fbb927a..9d03f8439a 100644 --- a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs +++ b/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs @@ -16,7 +16,6 @@ namespace osu.Game.Screens.Select.Details { public class UserTopScoreContainer : VisibilityContainer { - private const int height = 90; private const int duration = 500; private readonly Container scoreContainer; @@ -30,33 +29,30 @@ namespace osu.Game.Screens.Select.Details public UserTopScoreContainer() { RelativeSizeAxes = Axes.X; - Height = height; + AutoSizeAxes = Axes.Y; - Anchor = Anchor.BottomLeft; - Origin = Anchor.BottomLeft; + Margin = new MarginPadding { Vertical = 5 }; Children = new Drawable[] { - new Container + new FillFlowContainer { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, RelativeSizeAxes = Axes.X, - Height = height, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, Children = new Drawable[] { new OsuSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Margin = new MarginPadding { Top = 5 }, Text = @"your personal best".ToUpper(), Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold), }, scoreContainer = new Container { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, } @@ -89,14 +85,8 @@ namespace osu.Game.Screens.Select.Details }, (loadScoreCancellation = new CancellationTokenSource()).Token); } - protected override void PopIn() - { - this.FadeIn(duration, Easing.OutQuint); - } + protected override void PopIn() => this.FadeIn(duration, Easing.OutQuint); - protected override void PopOut() - { - this.FadeOut(duration, Easing.OutQuint); - } + protected override void PopOut() => this.FadeOut(duration, Easing.OutQuint); } } From 2b6c9aeb266e4f352fddebabb066314527ff658e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2019 15:38:40 +0900 Subject: [PATCH 1349/2815] Move top score container to more local namespace --- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 1 - .../Visual/SongSelect/TestSceneUserTopScoreContainer.cs | 2 +- osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 1 - .../{Details => Leaderboards}/UserTopScoreContainer.cs | 6 +++--- 4 files changed, 4 insertions(+), 6 deletions(-) rename osu.Game/Screens/Select/{Details => Leaderboards}/UserTopScoreContainer.cs (98%) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index bbd874fcd7..fb27ec7654 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -10,7 +10,6 @@ using osu.Game.Online.Leaderboards; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Scoring; -using osu.Game.Screens.Select.Details; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; using osuTK; diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs index 38ebb58e76..7fac45e0f1 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.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 osu.Game.Screens.Select.Details; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -10,6 +9,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Scoring; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; namespace osu.Game.Tests.Visual.SongSelect diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index d038049504..71aa8a8a31 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -14,7 +14,6 @@ using osu.Game.Online.Leaderboards; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; -using osu.Game.Screens.Select.Details; namespace osu.Game.Screens.Select.Leaderboards { diff --git a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs b/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs similarity index 98% rename from osu.Game/Screens/Select/Details/UserTopScoreContainer.cs rename to osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs index 9d03f8439a..301b3a6ae1 100644 --- a/osu.Game/Screens/Select/Details/UserTopScoreContainer.cs +++ b/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs @@ -1,6 +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.Threading; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -9,10 +11,8 @@ using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; using osu.Game.Scoring; -using System; -using System.Threading; -namespace osu.Game.Screens.Select.Details +namespace osu.Game.Screens.Select.Leaderboards { public class UserTopScoreContainer : VisibilityContainer { From 033c68a428bc74c126466d8b39b0bb119e8611dd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2019 15:44:00 +0900 Subject: [PATCH 1350/2815] Fade in score, not container --- osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs b/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs index 301b3a6ae1..c5872e271d 100644 --- a/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs +++ b/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.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; @@ -81,7 +81,7 @@ namespace osu.Game.Screens.Select.Leaderboards }, drawableScore => { scoreContainer.Child = drawableScore; - Show(); + drawableScore.FadeInFromZero(duration, Easing.OutQuint); }, (loadScoreCancellation = new CancellationTokenSource()).Token); } From 36d0695e5c243e2012b2f128364a52574665117b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2019 15:44:05 +0900 Subject: [PATCH 1351/2815] Add spacing --- osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs b/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs index c5872e271d..da8f676cd0 100644 --- a/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs +++ b/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.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; @@ -11,6 +11,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; using osu.Game.Scoring; +using osuTK; namespace osu.Game.Screens.Select.Leaderboards { @@ -40,6 +41,7 @@ namespace osu.Game.Screens.Select.Leaderboards RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, + Spacing = new Vector2(5), Children = new Drawable[] { new OsuSpriteText From c1daa187fe2aa68426341c6786b18bbee4c621df Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Sep 2019 15:44:14 +0900 Subject: [PATCH 1352/2815] Reduce default tolerance --- osu.Game/Screens/Select/FilterControl.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index b25e65092e..4c91208aec 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -197,23 +197,23 @@ namespace osu.Game.Screens.Select switch (key) { case "stars" when double.TryParse(value, out var stars): - updateCriteriaRange(ref criteria.StarDifficulty, op, stars, 0.5); + updateCriteriaRange(ref criteria.StarDifficulty, op, stars); break; case "ar" when double.TryParse(value, out var ar): - updateCriteriaRange(ref criteria.ApproachRate, op, ar, 0.3); + updateCriteriaRange(ref criteria.ApproachRate, op, ar); break; case "dr" when double.TryParse(value, out var dr): - updateCriteriaRange(ref criteria.DrainRate, op, dr, 0.3); + updateCriteriaRange(ref criteria.DrainRate, op, dr); break; case "cs" when double.TryParse(value, out var cs): - updateCriteriaRange(ref criteria.CircleSize, op, cs, 0.3); + updateCriteriaRange(ref criteria.CircleSize, op, cs); break; case "bpm" when double.TryParse(value, out var bpm): - updateCriteriaRange(ref criteria.BPM, op, bpm, 0.3); + updateCriteriaRange(ref criteria.BPM, op, bpm); break; case "length" when double.TryParse(value.TrimEnd('m', 's', 'h'), out var length): @@ -239,7 +239,7 @@ namespace osu.Game.Screens.Select } } - private void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, double value, double equalityToleration = 0) + private void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, double value, double tolerance = 0.05) { switch (op) { @@ -249,8 +249,8 @@ namespace osu.Game.Screens.Select case "=": case ":": range.IsInclusive = true; - range.Min = value - equalityToleration; - range.Max = value + equalityToleration; + range.Min = value - tolerance; + range.Max = value + tolerance; break; case ">": From 48ee95955b80ea7a385e55649fcd71e308d2b197 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2019 15:45:08 +0900 Subject: [PATCH 1353/2815] Remove unnecessary redirection --- osu.Game/Screens/Select/SongSelect.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 7f9804c6a3..fca801ce78 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -223,9 +223,7 @@ namespace osu.Game.Screens.Select }); } - void displayScore(ScoreInfo score) => this.Push(new SoloResults(score)); - - BeatmapDetails.Leaderboard.ScoreSelected += displayScore; + BeatmapDetails.Leaderboard.ScoreSelected += score => this.Push(new SoloResults(score)); } [BackgroundDependencyLoader(true)] From e2f7d4bc629defae3f03c64040be75857594c27f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2019 15:45:43 +0900 Subject: [PATCH 1354/2815] Remove unnecessary ToMetric avoidance --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index e21ad413b6..9387482f14 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -80,7 +80,7 @@ namespace osu.Game.Online.Leaderboards Anchor = Anchor.Centre, Origin = Anchor.Centre, Font = OsuFont.GetFont(size: 20, italics: true), - Text = rank <= 999 ? rank.ToString() : rank.ToMetric(decimals: rank < 100000 ? 1 : 0), + Text = rank.ToMetric(decimals: rank < 100000 ? 1 : 0), }, }, }, From a214e7e72fbf131c001b07b1f9288764bd5cdf85 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2019 16:26:22 +0900 Subject: [PATCH 1355/2815] Add confirmation dialog when exiting game --- osu.Game/Screens/Menu/MainMenu.cs | 49 ++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index a006877082..9393f785cd 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -1,18 +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 System; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Online.API; using osu.Game.Overlays; +using osu.Game.Overlays.Dialog; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Charts; using osu.Game.Screens.Edit; @@ -51,16 +55,23 @@ namespace osu.Game.Screens.Menu [Resolved] private IAPIProvider api { get; set; } + [Resolved] + private DialogOverlay dialogOverlay { get; set; } + private BackgroundScreenDefault background; protected override BackgroundScreen CreateBackground() => background; + private Bindable holdDelay; + [BackgroundDependencyLoader(true)] - private void load(DirectOverlay direct, SettingsOverlay settings) + private void load(DirectOverlay direct, SettingsOverlay settings, OsuConfigManager config) { if (host.CanExit) AddInternal(new ExitConfirmOverlay { Action = this.Exit }); + holdDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); + AddRangeInternal(new Drawable[] { new ParallaxContainer @@ -141,6 +152,7 @@ namespace osu.Game.Screens.Menu } private bool loginDisplayed; + private bool exitConfirmed; protected override void LogoArriving(OsuLogo logo, bool resuming) { @@ -221,9 +233,44 @@ namespace osu.Game.Screens.Menu public override bool OnExiting(IScreen next) { + if (holdDelay.Value == 0 && !exitConfirmed) + { + dialogOverlay?.Push(new ConfirmExitDialog(() => + { + exitConfirmed = true; + this.Exit(); + })); + + return true; + } + buttons.State = ButtonSystemState.Exit; this.FadeOut(3000); return base.OnExiting(next); } + + public class ConfirmExitDialog : PopupDialog + { + public ConfirmExitDialog(Action confirm) + { + HeaderText = "Are you sure you want to exit?"; + BodyText = "Last chance to back out."; + + Icon = FontAwesome.Solid.ExclamationTriangle; + + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = @"Good bye", + Action = confirm + }, + new PopupDialogCancelButton + { + Text = @"Just a little more" + }, + }; + } + } } } From 929f05884b8bf5cd4ce70c9481ffabf0486174e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2019 16:28:06 +0900 Subject: [PATCH 1356/2815] Always confirm exit when button is clicked --- osu.Game/Screens/Menu/MainMenu.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 9393f785cd..7645734859 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -85,7 +85,11 @@ namespace osu.Game.Screens.Menu OnEdit = delegate { this.Push(new Editor()); }, OnSolo = onSolo, OnMulti = delegate { this.Push(new Multiplayer()); }, - OnExit = this.Exit, + OnExit = delegate + { + exitConfirmed = true; + this.Exit(); + }, } } }, From 3c21b68b738af2d902341d934f4c34f920b5c5b8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Sep 2019 16:51:57 +0900 Subject: [PATCH 1357/2815] Make OptionalRange generic --- osu.Game/Screens/Select/FilterControl.cs | 27 ++++++++---- osu.Game/Screens/Select/FilterCriteria.cs | 51 ++++++++++++++++------- 2 files changed, 56 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 4c91208aec..8fda6c6a97 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -196,19 +196,19 @@ namespace osu.Game.Screens.Select switch (key) { - case "stars" when double.TryParse(value, out var stars): + case "stars" when float.TryParse(value, out var stars): updateCriteriaRange(ref criteria.StarDifficulty, op, stars); break; - case "ar" when double.TryParse(value, out var ar): + case "ar" when float.TryParse(value, out var ar): updateCriteriaRange(ref criteria.ApproachRate, op, ar); break; - case "dr" when double.TryParse(value, out var dr): + case "dr" when float.TryParse(value, out var dr): updateCriteriaRange(ref criteria.DrainRate, op, dr); break; - case "cs" when double.TryParse(value, out var cs): + case "cs" when float.TryParse(value, out var cs): updateCriteriaRange(ref criteria.CircleSize, op, cs); break; @@ -239,7 +239,8 @@ namespace osu.Game.Screens.Select } } - private void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, double value, double tolerance = 0.05) + private void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, T value, double tolerance = 0.05f) + where T : struct, IComparable { switch (op) { @@ -249,8 +250,20 @@ namespace osu.Game.Screens.Select case "=": case ":": range.IsInclusive = true; - range.Min = value - tolerance; - range.Max = value + tolerance; + + switch (value) + { + case float _: + range.Min = (T)(object)((float)(object)value - tolerance); + range.Max = (T)(object)((float)(object)value + tolerance); + break; + + case double _: + range.Min = (T)(object)((double)(object)value - tolerance); + range.Max = (T)(object)((double)(object)value + tolerance); + break; + } + break; case ">": diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 84d63c16e0..4867f27c0b 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -14,12 +14,12 @@ namespace osu.Game.Screens.Select public GroupMode Group; public SortMode Sort; - public OptionalRange StarDifficulty; - public OptionalRange ApproachRate; - public OptionalRange DrainRate; - public OptionalRange CircleSize; - public OptionalRange Length; - public OptionalRange BPM; + public OptionalRange StarDifficulty; + public OptionalRange ApproachRate; + public OptionalRange DrainRate; + public OptionalRange CircleSize; + public OptionalRange Length; + public OptionalRange BPM; public int? BeatDivisor; @@ -42,23 +42,44 @@ namespace osu.Game.Screens.Select } } - public struct OptionalRange : IEquatable + public struct OptionalRange : IEquatable> + where T : struct, IComparable { - public bool IsInRange(double value) + public bool IsInRange(T value) { - if (Min.HasValue && (IsInclusive ? value < Min.Value : value <= Min.Value)) - return false; - if (Max.HasValue && (IsInclusive ? value > Max.Value : value >= Max.Value)) - return false; + if (Min.HasValue) + { + int comparison = value.CompareTo(Min.Value); + + if (comparison < 0) + return false; + + if (!IsInclusive && comparison == 0) + return false; + } + + if (Max.HasValue) + { + int comparison = value.CompareTo(Max.Value); + + if (comparison > 0) + return false; + + if (!IsInclusive && comparison == 0) + return false; + } return true; } - public double? Min; - public double? Max; + public T? Min; + public T? Max; public bool IsInclusive; - public bool Equals(OptionalRange range) => Min == range.Min && Max == range.Max; + public bool Equals(OptionalRange other) + => Min.Equals(other.Min) + && Max.Equals(other.Max) + && IsInclusive.Equals(other.IsInclusive); } } } From 0915a94470bd67b3d1cdcabb21bd12c6d3e66373 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Sep 2019 16:53:27 +0900 Subject: [PATCH 1358/2815] Make BeatDivisor use OptionalRange --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 2 +- osu.Game/Screens/Select/FilterControl.cs | 4 ++-- osu.Game/Screens/Select/FilterCriteria.cs | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 60e556a261..7017310018 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Select.Carousel match &= criteria.Length.IsInRange(Beatmap.Length); match &= criteria.BPM.IsInRange(Beatmap.BPM); - match &= !criteria.BeatDivisor.HasValue || criteria.BeatDivisor == Beatmap.BeatDivisor; + match &= criteria.BeatDivisor.IsInRange(Beatmap.BeatDivisor); match &= !criteria.OnlineStatus.HasValue || criteria.OnlineStatus == Beatmap.Status; if (match) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 8fda6c6a97..829f16d622 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -226,8 +226,8 @@ namespace osu.Game.Screens.Select updateCriteriaRange(ref criteria.Length, op, length * scale, scale / 2.0); break; - case "divisor" when op == ":" && int.TryParse(value, out var divisor): - criteria.BeatDivisor = divisor; + case "divisor" when int.TryParse(value, out var divisor): + updateCriteriaRange(ref criteria.BeatDivisor, op, divisor); break; case "status" when op == ":" && Enum.TryParse(value, ignoreCase: true, out var statusValue): diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 4867f27c0b..4584c63c2e 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -20,8 +20,7 @@ namespace osu.Game.Screens.Select public OptionalRange CircleSize; public OptionalRange Length; public OptionalRange BPM; - - public int? BeatDivisor; + public OptionalRange BeatDivisor; public BeatmapSetOnlineStatus? OnlineStatus; From 167bb9fcc16751087fc687d3424e7ea7cfafa555 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Sep 2019 17:11:28 +0900 Subject: [PATCH 1359/2815] Fix ugly casts --- osu.Game/Screens/Select/FilterControl.cs | 46 +++++++++++++++--------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 829f16d622..6ada0e4980 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -239,8 +239,36 @@ namespace osu.Game.Screens.Select } } - private void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, T value, double tolerance = 0.05f) - where T : struct, IComparable + private void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, float value, float tolerance = 0.05f) + { + switch (op) + { + case "=": + case ":": + range.Min = value - tolerance; + range.Max = value + tolerance; + break; + } + + updateCriteriaRange(ref range, op, value); + } + + private void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, double value, double tolerance = 0.05f) + { + switch (op) + { + case "=": + case ":": + range.Min = value - tolerance; + range.Max = value + tolerance; + break; + } + + updateCriteriaRange(ref range, op, value); + } + + private void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, T value) + where T : struct, IComparable { switch (op) { @@ -250,20 +278,6 @@ namespace osu.Game.Screens.Select case "=": case ":": range.IsInclusive = true; - - switch (value) - { - case float _: - range.Min = (T)(object)((float)(object)value - tolerance); - range.Max = (T)(object)((float)(object)value + tolerance); - break; - - case double _: - range.Min = (T)(object)((double)(object)value - tolerance); - range.Max = (T)(object)((double)(object)value + tolerance); - break; - } - break; case ">": From d7831d8f5d6cdced76b9ff070b1d0d55c932818b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Sep 2019 17:11:43 +0900 Subject: [PATCH 1360/2815] Use non-generic IComparable interface --- osu.Game/Screens/Select/FilterCriteria.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 4584c63c2e..ff55ef5b12 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets; @@ -42,13 +43,13 @@ namespace osu.Game.Screens.Select } public struct OptionalRange : IEquatable> - where T : struct, IComparable + where T : struct, IComparable { public bool IsInRange(T value) { - if (Min.HasValue) + if (Min != null) { - int comparison = value.CompareTo(Min.Value); + int comparison = Comparer.Default.Compare(value, Min.Value); if (comparison < 0) return false; @@ -57,9 +58,9 @@ namespace osu.Game.Screens.Select return false; } - if (Max.HasValue) + if (Max != null) { - int comparison = value.CompareTo(Max.Value); + int comparison = Comparer.Default.Compare(value, Max.Value); if (comparison > 0) return false; From 7683f7ff23198fc782135b4959550abe13236c97 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Sep 2019 17:12:07 +0900 Subject: [PATCH 1361/2815] Make OnlineStatus use OptionalRange --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 2 +- osu.Game/Screens/Select/FilterControl.cs | 4 ++-- osu.Game/Screens/Select/FilterCriteria.cs | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 7017310018..9cc84c8bdd 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Select.Carousel match &= criteria.BPM.IsInRange(Beatmap.BPM); match &= criteria.BeatDivisor.IsInRange(Beatmap.BeatDivisor); - match &= !criteria.OnlineStatus.HasValue || criteria.OnlineStatus == Beatmap.Status; + match &= criteria.OnlineStatus.IsInRange(Beatmap.Status); if (match) foreach (var criteriaTerm in criteria.SearchTerms) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 6ada0e4980..f1c8bb3a4e 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -230,8 +230,8 @@ namespace osu.Game.Screens.Select updateCriteriaRange(ref criteria.BeatDivisor, op, divisor); break; - case "status" when op == ":" && Enum.TryParse(value, ignoreCase: true, out var statusValue): - criteria.OnlineStatus = statusValue; + case "status" when Enum.TryParse(value, true, out var statusValue): + updateCriteriaRange(ref criteria.OnlineStatus, op, statusValue); break; } diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index ff55ef5b12..674f64efb4 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -22,8 +22,7 @@ namespace osu.Game.Screens.Select public OptionalRange Length; public OptionalRange BPM; public OptionalRange BeatDivisor; - - public BeatmapSetOnlineStatus? OnlineStatus; + public OptionalRange OnlineStatus; public string[] SearchTerms = Array.Empty(); From e075dd7ea84258862fac010f95e4a56d62d6ea0c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Sep 2019 17:16:34 +0900 Subject: [PATCH 1362/2815] Fix equals operator not working --- osu.Game/Screens/Select/FilterControl.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index f1c8bb3a4e..cd958a9503 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -241,6 +241,8 @@ namespace osu.Game.Screens.Select private void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, float value, float tolerance = 0.05f) { + updateCriteriaRange(ref range, op, value); + switch (op) { case "=": @@ -249,12 +251,12 @@ namespace osu.Game.Screens.Select range.Max = value + tolerance; break; } - - updateCriteriaRange(ref range, op, value); } - private void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, double value, double tolerance = 0.05f) + private void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, double value, double tolerance = 0.05) { + updateCriteriaRange(ref range, op, value); + switch (op) { case "=": @@ -263,8 +265,6 @@ namespace osu.Game.Screens.Select range.Max = value + tolerance; break; } - - updateCriteriaRange(ref range, op, value); } private void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, T value) @@ -278,6 +278,8 @@ namespace osu.Game.Screens.Select case "=": case ":": range.IsInclusive = true; + range.Min = value; + range.Max = value; break; case ">": From 96ea507320f6b10a9b902f5c1cc6f2df17a9c07a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Sep 2019 17:21:22 +0900 Subject: [PATCH 1363/2815] Reorder comparison for readability --- osu.Game/Screens/Select/FilterCriteria.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 674f64efb4..a3fa1b10ca 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Select if (comparison < 0) return false; - if (!IsInclusive && comparison == 0) + if (comparison == 0 && !IsInclusive) return false; } @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Select if (comparison > 0) return false; - if (!IsInclusive && comparison == 0) + if (comparison == 0 && !IsInclusive) return false; } From e6c36a8bc7974e5c2b3f31c7e41467bc53809d7f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2019 17:27:54 +0900 Subject: [PATCH 1364/2815] Fix scaling mode being applied to judgements --- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 061c8cb3e1..b77fc1cfb0 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Judgements Font = OsuFont.Numeric.With(size: 20), Colour = judgementColour(Result.Type), Scale = new Vector2(0.85f, 1), - }) + }, confineMode: ConfineMode.NoScaling) }; } From 5120d82ef84cba05df1b775cd1689b1e7d60a562 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Sep 2019 17:36:42 +0900 Subject: [PATCH 1365/2815] Fix crash with multiple range criterias --- osu.Game/Screens/Select/FilterControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index cd958a9503..e3c23f7e22 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -235,7 +235,7 @@ namespace osu.Game.Screens.Select break; } - query = query.Remove(match.Index, match.Length); + query = query.Replace(match.ToString(), ""); } } From fa54a0bfd3954576adc0a07fef7619d6d6050f3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2019 17:40:46 +0900 Subject: [PATCH 1366/2815] Fix test failures --- osu.Game/Screens/Menu/MainMenu.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 7645734859..2d8c48873a 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Menu [Resolved] private IAPIProvider api { get; set; } - [Resolved] + [Resolved(canBeNull: true)] private DialogOverlay dialogOverlay { get; set; } private BackgroundScreenDefault background; @@ -237,9 +237,9 @@ namespace osu.Game.Screens.Menu public override bool OnExiting(IScreen next) { - if (holdDelay.Value == 0 && !exitConfirmed) + if (holdDelay.Value == 0 && !exitConfirmed && dialogOverlay != null) { - dialogOverlay?.Push(new ConfirmExitDialog(() => + dialogOverlay.Push(new ConfirmExitDialog(() => { exitConfirmed = true; this.Exit(); From da15b900f7d628f4790dfd263c045382cdcd4f51 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Sep 2019 17:44:24 +0900 Subject: [PATCH 1367/2815] Remove virtual member from ModBlockFail --- osu.Game/Rulesets/Mods/ModBlockFail.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModBlockFail.cs b/osu.Game/Rulesets/Mods/ModBlockFail.cs index 55c074bde2..7d7ecfa416 100644 --- a/osu.Game/Rulesets/Mods/ModBlockFail.cs +++ b/osu.Game/Rulesets/Mods/ModBlockFail.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mods /// public bool AllowFail => false; - public virtual bool RestartOnFail => false; + public bool RestartOnFail => false; public void ReadFromConfig(OsuConfigManager config) { From 65276cd235a0026170d4e06b2faffc515b045c06 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Sep 2019 17:58:10 +0900 Subject: [PATCH 1368/2815] Remove whitespace --- osu.Game/Rulesets/Mods/ModBlockFail.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModBlockFail.cs b/osu.Game/Rulesets/Mods/ModBlockFail.cs index 7d7ecfa416..957a8d3348 100644 --- a/osu.Game/Rulesets/Mods/ModBlockFail.cs +++ b/osu.Game/Rulesets/Mods/ModBlockFail.cs @@ -15,7 +15,6 @@ namespace osu.Game.Rulesets.Mods /// We never fail, 'yo. /// public bool AllowFail => false; - public bool RestartOnFail => false; public void ReadFromConfig(OsuConfigManager config) From bc9941a990ea7430f2f8f1d87714beb8517e1dab Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Sep 2019 18:00:11 +0900 Subject: [PATCH 1369/2815] Newline required when xmldocs are involved --- osu.Game/Rulesets/Mods/ModBlockFail.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Mods/ModBlockFail.cs b/osu.Game/Rulesets/Mods/ModBlockFail.cs index 957a8d3348..7d7ecfa416 100644 --- a/osu.Game/Rulesets/Mods/ModBlockFail.cs +++ b/osu.Game/Rulesets/Mods/ModBlockFail.cs @@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Mods /// We never fail, 'yo. /// public bool AllowFail => false; + public bool RestartOnFail => false; public void ReadFromConfig(OsuConfigManager config) From ddff9882cf25e447df3e5632b0bc65f65d125d73 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2019 17:35:45 +0900 Subject: [PATCH 1370/2815] Fix importing archives which are nested in a single folder within a zip --- .../Beatmaps/IO/ImportBeatmapTest.cs | 50 ++++++++++++++++++- osu.Game/Database/ArchiveModelManager.cs | 7 ++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index ad0ed00989..8b39946ab0 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -15,7 +15,10 @@ using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.IO; using osu.Game.Tests.Resources; +using SharpCompress.Archives; using SharpCompress.Archives.Zip; +using SharpCompress.Common; +using SharpCompress.Writers.Zip; namespace osu.Game.Tests.Beatmaps.IO { @@ -135,7 +138,7 @@ namespace osu.Game.Tests.Beatmaps.IO using (var zip = ZipArchive.Open(brokenOsz)) { zip.AddEntry("broken.osu", brokenOsu, false); - zip.SaveTo(outStream, SharpCompress.Common.CompressionType.Deflate); + zip.SaveTo(outStream, CompressionType.Deflate); } // this will trigger purging of the existing beatmap (online set id match) but should rollback due to broken osu. @@ -366,6 +369,51 @@ namespace osu.Game.Tests.Beatmaps.IO } } + [Test] + public async Task TestImportNestedStructure() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportNestedStructure")) + { + try + { + var osu = loadOsu(host); + + var temp = TestResources.GetTestBeatmapForImport(); + + string extractedFolder = $"{temp}_extracted"; + string subfolder = Path.Combine(extractedFolder, "subfolder"); + + Directory.CreateDirectory(subfolder); + + try + { + using (var zip = ZipArchive.Open(temp)) + zip.WriteToDirectory(subfolder); + + using (var zip = ZipArchive.Create()) + { + zip.AddAllFromDirectory(extractedFolder); + zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate)); + } + + var imported = await osu.Dependencies.Get().Import(temp); + + ensureLoaded(osu); + + Assert.IsFalse(imported.Files.Any(f => f.Filename.Contains("subfolder")), "Files contain common subfolder"); + } + finally + { + Directory.Delete(extractedFolder, true); + } + } + finally + { + host.Exit(); + } + } + } + public static async Task LoadOszIntoOsu(OsuGameBase osu, string path = null) { var temp = path ?? TestResources.GetTestBeatmapForImport(); diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 52d3f013ce..6c79b0d472 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -11,6 +11,7 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using osu.Framework; using osu.Framework.Extensions; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.IO.File; using osu.Framework.Logging; using osu.Framework.Platform; @@ -481,12 +482,16 @@ namespace osu.Game.Database { var fileInfos = new List(); + string prefix = reader.Filenames.GetCommonPrefix(); + if (!(prefix.EndsWith("/") || prefix.EndsWith("\\"))) + prefix = string.Empty; + // import files to manager foreach (string file in reader.Filenames) using (Stream s = reader.GetStream(file)) fileInfos.Add(new TFileModel { - Filename = FileSafety.PathStandardise(file), + Filename = FileSafety.PathStandardise(file.Substring(prefix.Length)), FileInfo = files.Add(s) }); From cffee1fd5e39c0857d877793be0a5184bce5cc3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2019 20:02:45 +0900 Subject: [PATCH 1371/2815] Fix imported beatmap paths not correctly matching files --- osu.Game/Beatmaps/BeatmapManager.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 02d7b2d98f..55b8b80e44 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -89,7 +89,7 @@ namespace osu.Game.Beatmaps protected override Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive, CancellationToken cancellationToken = default) { if (archive != null) - beatmapSet.Beatmaps = createBeatmapDifficulties(archive); + beatmapSet.Beatmaps = createBeatmapDifficulties(beatmapSet.Files); foreach (BeatmapInfo b in beatmapSet.Beatmaps) { @@ -279,13 +279,13 @@ namespace osu.Game.Beatmaps /// /// Create all required s for the provided archive. /// - private List createBeatmapDifficulties(ArchiveReader reader) + private List createBeatmapDifficulties(List files) { var beatmapInfos = new List(); - foreach (var name in reader.Filenames.Where(f => f.EndsWith(".osu"))) + foreach (var file in files.Where(f => f.Filename.EndsWith(".osu"))) { - using (var raw = reader.GetStream(name)) + using (var raw = Files.Store.GetStream(file.FileInfo.StoragePath)) using (var ms = new MemoryStream()) //we need a memory stream so we can seek using (var sr = new StreamReader(ms)) { @@ -295,7 +295,7 @@ namespace osu.Game.Beatmaps var decoder = Decoder.GetDecoder(sr); IBeatmap beatmap = decoder.Decode(sr); - beatmap.BeatmapInfo.Path = name; + beatmap.BeatmapInfo.Path = file.Filename; beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash(); beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash(); From 50d4206c4584950d20e8824585a613a5a6b5ada2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2019 20:17:58 +0900 Subject: [PATCH 1372/2815] Fix exit scenarios --- .../TestSceneHoldToConfirmOverlay.cs | 3 --- .../Containers/HoldToConfirmContainer.cs | 13 +++++++--- osu.Game/Overlays/DialogOverlay.cs | 26 +++++++++++-------- osu.Game/Screens/Menu/ExitConfirmOverlay.cs | 7 ++++- osu.Game/Screens/Menu/MainMenu.cs | 16 ++++++++---- 5 files changed, 41 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs index f787754aa4..d4143f3f8d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs @@ -61,10 +61,7 @@ namespace osu.Game.Tests.Visual.UserInterface private class TestHoldToConfirmOverlay : ExitConfirmOverlay { - protected override bool AllowMultipleFires => true; - public void Begin() => BeginConfirm(); - public void Abort() => AbortConfirm(); } } } diff --git a/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs b/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs index a345fb554f..5d549ba217 100644 --- a/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs +++ b/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs @@ -16,7 +16,11 @@ namespace osu.Game.Graphics.Containers private const int fadeout_delay = 200; - private bool fired; + /// + /// Whether currently in a fired state (and the confirm has been sent). + /// + public bool Fired { get; private set; } + private bool confirming; /// @@ -36,7 +40,7 @@ namespace osu.Game.Graphics.Containers protected void BeginConfirm() { - if (confirming || (!AllowMultipleFires && fired)) return; + if (confirming || (!AllowMultipleFires && Fired)) return; confirming = true; @@ -46,14 +50,15 @@ namespace osu.Game.Graphics.Containers protected virtual void Confirm() { Action?.Invoke(); - fired = true; + Fired = true; } protected void AbortConfirm() { - if (!AllowMultipleFires && fired) return; + if (!AllowMultipleFires && Fired) return; confirming = false; + Fired = false; this.TransformBindableTo(Progress, 0, fadeout_delay, Easing.Out); } diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 6aaeff8554..59d748bc5d 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -13,7 +13,8 @@ namespace osu.Game.Overlays public class DialogOverlay : OsuFocusedOverlayContainer { private readonly Container dialogContainer; - private PopupDialog currentDialog; + + public PopupDialog CurrentDialog { get; private set; } public DialogOverlay() { @@ -31,15 +32,15 @@ namespace osu.Game.Overlays public void Push(PopupDialog dialog) { - if (dialog == currentDialog) return; + if (dialog == CurrentDialog) return; - currentDialog?.Hide(); - currentDialog = dialog; + CurrentDialog?.Hide(); + CurrentDialog = dialog; - dialogContainer.Add(currentDialog); + dialogContainer.Add(CurrentDialog); - currentDialog.Show(); - currentDialog.State.ValueChanged += state => onDialogOnStateChanged(dialog, state.NewValue); + CurrentDialog.Show(); + CurrentDialog.State.ValueChanged += state => onDialogOnStateChanged(dialog, state.NewValue); Show(); } @@ -52,8 +53,11 @@ namespace osu.Game.Overlays //handle the dialog being dismissed. dialog.Delay(PopupDialog.EXIT_DURATION).Expire(); - if (dialog == currentDialog) + if (dialog == CurrentDialog) + { Hide(); + CurrentDialog = null; + } } protected override void PopIn() @@ -66,9 +70,9 @@ namespace osu.Game.Overlays { base.PopOut(); - if (currentDialog?.State.Value == Visibility.Visible) + if (CurrentDialog?.State.Value == Visibility.Visible) { - currentDialog.Hide(); + CurrentDialog.Hide(); return; } @@ -80,7 +84,7 @@ namespace osu.Game.Overlays switch (action) { case GlobalAction.Select: - currentDialog?.Buttons.OfType().FirstOrDefault()?.Click(); + CurrentDialog?.Buttons.OfType().FirstOrDefault()?.Click(); return true; } diff --git a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs index 519834859d..aaa3a77e74 100644 --- a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs +++ b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs @@ -9,6 +9,10 @@ namespace osu.Game.Screens.Menu { public class ExitConfirmOverlay : HoldToConfirmOverlay, IKeyBindingHandler { + protected override bool AllowMultipleFires => true; + + public void Abort() => AbortConfirm(); + public bool OnPressed(GlobalAction action) { if (action == GlobalAction.Back) @@ -24,7 +28,8 @@ namespace osu.Game.Screens.Menu { if (action == GlobalAction.Back) { - AbortConfirm(); + if (!Fired) + AbortConfirm(); return true; } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 2d8c48873a..23fb2d8e37 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -64,11 +64,13 @@ namespace osu.Game.Screens.Menu private Bindable holdDelay; + private ExitConfirmOverlay exitConfirmOverlay; + [BackgroundDependencyLoader(true)] private void load(DirectOverlay direct, SettingsOverlay settings, OsuConfigManager config) { if (host.CanExit) - AddInternal(new ExitConfirmOverlay { Action = this.Exit }); + AddInternal(exitConfirmOverlay = new ExitConfirmOverlay { Action = this.Exit }); holdDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); @@ -237,12 +239,15 @@ namespace osu.Game.Screens.Menu public override bool OnExiting(IScreen next) { - if (holdDelay.Value == 0 && !exitConfirmed && dialogOverlay != null) + if (holdDelay.Value == 0 && !exitConfirmed && dialogOverlay != null && !(dialogOverlay.CurrentDialog is ConfirmExitDialog)) { dialogOverlay.Push(new ConfirmExitDialog(() => { exitConfirmed = true; this.Exit(); + }, () => + { + exitConfirmOverlay.Abort(); })); return true; @@ -253,9 +258,9 @@ namespace osu.Game.Screens.Menu return base.OnExiting(next); } - public class ConfirmExitDialog : PopupDialog + private class ConfirmExitDialog : PopupDialog { - public ConfirmExitDialog(Action confirm) + public ConfirmExitDialog(Action confirm, Action cancel) { HeaderText = "Are you sure you want to exit?"; BodyText = "Last chance to back out."; @@ -271,7 +276,8 @@ namespace osu.Game.Screens.Menu }, new PopupDialogCancelButton { - Text = @"Just a little more" + Text = @"Just a little more", + Action = cancel }, }; } From 94d3bcc612b96c63814e6fb5523f3b794ee12877 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Sep 2019 20:47:33 +0900 Subject: [PATCH 1373/2815] Fix top score not being selectable --- osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 71aa8a8a31..337d46ecdd 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -96,7 +96,10 @@ namespace osu.Game.Screens.Select.Leaderboards UpdateScores(); }; - Content.Add(topScoreContainer = new UserTopScoreContainer()); + Content.Add(topScoreContainer = new UserTopScoreContainer + { + ScoreSelected = s => ScoreSelected?.Invoke(s) + }); } protected override void Reset() From 23c5cb6367e1cf976eaae4cec6f6662b48adc9e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2019 22:35:14 +0900 Subject: [PATCH 1374/2815] Expand tests to cover new behaviour --- .../Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs index d4143f3f8d..feef1dae6b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs @@ -52,11 +52,18 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("start confirming", () => overlay.Begin()); AddStep("abort confirming", () => overlay.Abort()); + AddAssert("ensure not fired internally", () => !overlay.Fired); AddAssert("ensure aborted", () => !fired); AddStep("start confirming", () => overlay.Begin()); AddUntilStep("wait until confirmed", () => fired); + AddAssert("ensure fired internally", () => overlay.Fired); + + AddStep("abort after fire", () => overlay.Abort()); + AddAssert("ensure not fired internally", () => !overlay.Fired); + AddStep("start confirming", () => overlay.Begin()); + AddUntilStep("wait until fired again", () => overlay.Fired); } private class TestHoldToConfirmOverlay : ExitConfirmOverlay From 67796e098258dd194367aa2bf031c66064b205cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2019 22:46:21 +0900 Subject: [PATCH 1375/2815] Apply code styling suggestions --- osu.Game/Screens/Menu/MainMenu.cs | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 23fb2d8e37..0274973161 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -87,11 +87,7 @@ namespace osu.Game.Screens.Menu OnEdit = delegate { this.Push(new Editor()); }, OnSolo = onSolo, OnMulti = delegate { this.Push(new Multiplayer()); }, - OnExit = delegate - { - exitConfirmed = true; - this.Exit(); - }, + OnExit = confirmAndExit, } } }, @@ -120,6 +116,12 @@ namespace osu.Game.Screens.Menu preloadSongSelect(); } + private void confirmAndExit() + { + exitConfirmed = true; + this.Exit(); + } + private void preloadSongSelect() { if (songSelect == null) @@ -241,15 +243,7 @@ namespace osu.Game.Screens.Menu { if (holdDelay.Value == 0 && !exitConfirmed && dialogOverlay != null && !(dialogOverlay.CurrentDialog is ConfirmExitDialog)) { - dialogOverlay.Push(new ConfirmExitDialog(() => - { - exitConfirmed = true; - this.Exit(); - }, () => - { - exitConfirmOverlay.Abort(); - })); - + dialogOverlay.Push(new ConfirmExitDialog(confirmAndExit, () => exitConfirmOverlay.Abort())); return true; } From 0cf4db899ff94292bac6945e8296c93931f64769 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 19 Sep 2019 17:03:52 +0300 Subject: [PATCH 1376/2815] Few cleanups --- .../Visual/Online/TestSceneLeaderboardModSelector.cs | 9 +++++---- .../Overlays/BeatmapSet/LeaderboardModSelector.cs | 11 ++++------- osu.Game/Rulesets/UI/ModIcon.cs | 4 ++-- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs index eb7fe5591d..3a64ac79f6 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs @@ -53,11 +53,12 @@ namespace osu.Game.Tests.Visual.Online }); }); - AddStep("osu mods", () => ruleset.Value = new OsuRuleset().RulesetInfo); - AddStep("mania mods", () => ruleset.Value = new ManiaRuleset().RulesetInfo); - AddStep("taiko mods", () => ruleset.Value = new TaikoRuleset().RulesetInfo); - AddStep("catch mods", () => ruleset.Value = new CatchRuleset().RulesetInfo); + AddStep("osu ruleset", () => ruleset.Value = new OsuRuleset().RulesetInfo); + AddStep("mania ruleset", () => ruleset.Value = new ManiaRuleset().RulesetInfo); + AddStep("taiko ruleset", () => ruleset.Value = new TaikoRuleset().RulesetInfo); + AddStep("catch ruleset", () => ruleset.Value = new CatchRuleset().RulesetInfo); AddStep("Deselect all", () => modSelector.DeselectAll()); + AddStep("null ruleset", () => ruleset.Value = null); } } } diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index 66d78f927a..d09464aba6 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -74,10 +74,10 @@ namespace osu.Game.Overlays.BeatmapSet else mods.Remove(mod); + SelectedMods.Value = mods; + if (!mods.Any() && !IsHovered) highlightAll(); - - SelectedMods.Value = mods; } protected override bool OnHover(HoverEvent e) @@ -116,6 +116,7 @@ namespace osu.Game.Overlays.BeatmapSet : base(mod) { Scale = new Vector2(mod_scale); + Highlighted.Value = true; Add(new HoverClickSounds()); } @@ -123,8 +124,6 @@ namespace osu.Game.Overlays.BeatmapSet { base.LoadComplete(); - Highlighted.Value = true; - Selected.BindValueChanged(selected => { updateState(); @@ -152,10 +151,8 @@ namespace osu.Game.Overlays.BeatmapSet private void updateState() => Highlighted.Value = IsHovered || Selected.Value; - protected override void OnHighlightedChange(ValueChangedEvent highlighted) - { + protected override void OnHighlightedChanged(ValueChangedEvent highlighted) => this.FadeColour(highlighted.NewValue ? Color4.White : Color4.Gray, duration, Easing.OutQuint); - } } private class NoMod : Mod diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index d3607757ab..b54ddf8f12 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -114,10 +114,10 @@ namespace osu.Game.Rulesets.UI protected override void LoadComplete() { base.LoadComplete(); - Highlighted.BindValueChanged(OnHighlightedChange); + Highlighted.BindValueChanged(OnHighlightedChanged, true); } - protected virtual void OnHighlightedChange(ValueChangedEvent highlighted) + protected virtual void OnHighlightedChanged(ValueChangedEvent highlighted) { background.Colour = highlighted.NewValue ? highlightedColour : backgroundColour; } From f10b390ca0ccc1aa3cce2392cfbb58b1428bfc06 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 19 Sep 2019 16:32:39 +0000 Subject: [PATCH 1377/2815] Bump Microsoft.NET.Test.Sdk from 16.2.0 to 16.3.0 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 16.2.0 to 16.3.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v16.2.0...v16.3) Signed-off-by: dependabot-preview[bot] --- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index c527a81f51..36342024b0 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index af10d5e06e..09bf9241f2 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index c331c811d2..791043bcc6 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index d2a0a8fa6f..b0e0efdc68 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 84f67c9319..75e6354612 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -3,7 +3,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index bba3c92245..491cf54686 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -5,7 +5,7 @@ - + From 636582e089c12f3eb9b814e84c7f3b54cd370e0d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Sep 2019 02:22:49 +0900 Subject: [PATCH 1378/2815] Always show exit confirmation when closing via alt-f4 or window control --- osu.Game/Screens/Menu/MainMenu.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 0274973161..dd81569e26 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -69,11 +69,22 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader(true)] private void load(DirectOverlay direct, SettingsOverlay settings, OsuConfigManager config) { - if (host.CanExit) - AddInternal(exitConfirmOverlay = new ExitConfirmOverlay { Action = this.Exit }); - holdDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); + if (host.CanExit) + { + AddInternal(exitConfirmOverlay = new ExitConfirmOverlay + { + Action = () => + { + if (holdDelay.Value > 0) + confirmAndExit(); + else + this.Exit(); + } + }); + } + AddRangeInternal(new Drawable[] { new ParallaxContainer @@ -241,7 +252,7 @@ namespace osu.Game.Screens.Menu public override bool OnExiting(IScreen next) { - if (holdDelay.Value == 0 && !exitConfirmed && dialogOverlay != null && !(dialogOverlay.CurrentDialog is ConfirmExitDialog)) + if (!exitConfirmed && dialogOverlay != null && !(dialogOverlay.CurrentDialog is ConfirmExitDialog)) { dialogOverlay.Push(new ConfirmExitDialog(confirmAndExit, () => exitConfirmOverlay.Abort())); return true; From 573da7b1e78b44a91db02fecfd8830fdd362d5b5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 19 Sep 2019 20:34:37 +0300 Subject: [PATCH 1379/2815] Implement ChangelogEntryType --- .../Online/API/Requests/Responses/APIChangelogEntry.cs | 8 +++++++- osu.Game/Overlays/Changelog/ChangelogBuild.cs | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIChangelogEntry.cs b/osu.Game/Online/API/Requests/Responses/APIChangelogEntry.cs index 140e228acd..f949ab5da5 100644 --- a/osu.Game/Online/API/Requests/Responses/APIChangelogEntry.cs +++ b/osu.Game/Online/API/Requests/Responses/APIChangelogEntry.cs @@ -24,7 +24,7 @@ namespace osu.Game.Online.API.Requests.Responses public string Url { get; set; } [JsonProperty("type")] - public string Type { get; set; } + public ChangelogEntryType Type { get; set; } [JsonProperty("category")] public string Category { get; set; } @@ -44,4 +44,10 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("github_user")] public APIChangelogUser GithubUser { get; set; } } + + public enum ChangelogEntryType + { + Add, + Fix + } } diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs index 3d145af562..6b24f39d98 100644 --- a/osu.Game/Overlays/Changelog/ChangelogBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs @@ -76,7 +76,7 @@ namespace osu.Game.Overlays.Changelog var entryColour = entry.Major ? colours.YellowLight : Color4.White; - title.AddIcon(FontAwesome.Solid.Check, t => + title.AddIcon(entry.Type == ChangelogEntryType.Fix ? FontAwesome.Solid.Check : FontAwesome.Solid.Plus, t => { t.Font = fontSmall; t.Colour = entryColour; From daa64f1be7c9de8643014ca9d1aacf610a7d4ac9 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 19 Sep 2019 20:53:06 +0300 Subject: [PATCH 1380/2815] Adjust icon padding --- osu.Game/Overlays/Changelog/ChangelogBuild.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs index 6b24f39d98..6d7b36b832 100644 --- a/osu.Game/Overlays/Changelog/ChangelogBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs @@ -80,7 +80,7 @@ namespace osu.Game.Overlays.Changelog { t.Font = fontSmall; t.Colour = entryColour; - t.Padding = new MarginPadding { Left = -17, Right = 5 }; + t.Padding = new MarginPadding { Left = -17, Top = 5 }; }); title.AddText(entry.Title, t => From 5663e3e6b3b682c8c78c74fd49ebdf1502c0157d Mon Sep 17 00:00:00 2001 From: Lucas A Date: Thu, 19 Sep 2019 20:08:14 +0200 Subject: [PATCH 1381/2815] Fix escaped html strings not being unescaped in changelog entries. --- osu.Game/Overlays/Changelog/ChangelogBuild.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs index 3d145af562..f44faabe52 100644 --- a/osu.Game/Overlays/Changelog/ChangelogBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs @@ -14,6 +14,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Users; using osuTK.Graphics; using osu.Framework.Allocation; +using System.Net; namespace osu.Game.Overlays.Changelog { @@ -149,7 +150,7 @@ namespace osu.Game.Overlays.Changelog }; // todo: use markdown parsing once API returns markdown - message.AddText(Regex.Replace(entry.MessageHtml, @"<(.|\n)*?>", string.Empty), t => + message.AddText(WebUtility.HtmlDecode(Regex.Replace(entry.MessageHtml, @"<(.|\n)*?>", string.Empty)), t => { t.Font = fontSmall; t.Colour = new Color4(235, 184, 254, 255); From f7f9c0f7e0cb93bbbc423fbb3065713271c7b4a3 Mon Sep 17 00:00:00 2001 From: Revel Date: Thu, 19 Sep 2019 15:47:32 -0400 Subject: [PATCH 1382/2815] Update BeatmapDetailAreaTabControl.cs --- osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs index 7f82d3cc12..6caef8e2aa 100644 --- a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs +++ b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs @@ -49,7 +49,7 @@ namespace osu.Game.Screens.Select { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, - Text = @"Mods", + Text = @"Selected Mods", Alpha = 0, }, }; From 033ed2e1f56c670dc42926c26aa1e9209e8b3c4e Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 20 Sep 2019 00:10:28 +0300 Subject: [PATCH 1383/2815] Add setting to always tint slider ball --- .../Configuration/OsuRulesetConfigManager.cs | 2 ++ osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs index f76635a932..ab6e6bac6c 100644 --- a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs @@ -18,6 +18,7 @@ namespace osu.Game.Rulesets.Osu.Configuration base.InitialiseDefaults(); Set(OsuRulesetSetting.SnakingInSliders, true); Set(OsuRulesetSetting.SnakingOutSliders, true); + Set(OsuRulesetSetting.AlwaysTintSliderBall, true); Set(OsuRulesetSetting.ShowCursorTrail, true); } } @@ -26,6 +27,7 @@ namespace osu.Game.Rulesets.Osu.Configuration { SnakingInSliders, SnakingOutSliders, + AlwaysTintSliderBall, ShowCursorTrail } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs index 88adf72551..be86614b57 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs @@ -35,6 +35,11 @@ namespace osu.Game.Rulesets.Osu.UI Bindable = config.GetBindable(OsuRulesetSetting.SnakingOutSliders) }, new SettingsCheckbox + { + LabelText = "Always tint slider ball with combo colour", + Bindable = config.GetBindable(OsuRulesetSetting.AlwaysTintSliderBall) + }, + new SettingsCheckbox { LabelText = "Cursor trail", Bindable = config.GetBindable(OsuRulesetSetting.ShowCursorTrail) From 8fcfd823169948811ca4000afde188ededb2d043 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 20 Sep 2019 00:10:55 +0300 Subject: [PATCH 1384/2815] Add AllowSliderBallTint to skin configuration --- osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs index e7b686d27d..98219cafe8 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs @@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Osu.Skinning HitCircleOverlap, SliderBorderSize, SliderPathRadius, + AllowSliderBallTint, CursorExpand, } } From f6291170b197bb200b2b3c1cd66b65623263047c Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 20 Sep 2019 00:11:37 +0300 Subject: [PATCH 1385/2815] Implement tinting slider ball with combo colour --- .../Objects/Drawables/DrawableSlider.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 643a0f7336..d894fe2a31 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -34,6 +34,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly IBindable scaleBindable = new Bindable(); private readonly IBindable pathBindable = new Bindable(); + private readonly Bindable alwaysTintSliderBall = new Bindable(true); + private bool allowSliderBallTint; + [Resolved(CanBeNull = true)] private OsuRulesetConfigManager config { get; set; } @@ -106,6 +109,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { config?.BindWith(OsuRulesetSetting.SnakingInSliders, Body.SnakingIn); config?.BindWith(OsuRulesetSetting.SnakingOutSliders, Body.SnakingOut); + config?.BindWith(OsuRulesetSetting.AlwaysTintSliderBall, alwaysTintSliderBall); positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); scaleBindable.BindValueChanged(scale => @@ -120,9 +124,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables pathBindable.BindValueChanged(_ => Body.Refresh()); + alwaysTintSliderBall.BindValueChanged(_ => updateSliderBallTint()); + AccentColour.BindValueChanged(colour => { Body.AccentColour = colour.NewValue; + updateSliderBallTint(); foreach (var drawableHitObject in NestedHitObjects) drawableHitObject.AccentColour.Value = colour.NewValue; @@ -173,10 +180,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Body.AccentColour = skin.GetConfig(OsuSkinColour.SliderTrackOverride)?.Value ?? AccentColour.Value; Body.BorderColour = skin.GetConfig(OsuSkinColour.SliderBorder)?.Value ?? Color4.White; + + allowSliderBallTint = skin.GetConfig(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false; + updateSliderBallTint(); } private void updatePathRadius() => Body.PathRadius = slider.Scale * sliderPathRadius; + private void updateSliderBallTint() => Ball.Colour = (alwaysTintSliderBall.Value | allowSliderBallTint) ? AccentColour.Value : Color4.White; + protected override void CheckForResult(bool userTriggered, double timeOffset) { if (userTriggered || Time.Current < slider.EndTime) From 1b45014ff678ccd256a5a3c57885fbb58357e1f8 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 20 Sep 2019 00:25:16 +0300 Subject: [PATCH 1386/2815] Use logical-OR --- 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 d894fe2a31..36ac0be723 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -187,7 +187,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private void updatePathRadius() => Body.PathRadius = slider.Scale * sliderPathRadius; - private void updateSliderBallTint() => Ball.Colour = (alwaysTintSliderBall.Value | allowSliderBallTint) ? AccentColour.Value : Color4.White; + private void updateSliderBallTint() => Ball.Colour = (alwaysTintSliderBall.Value || allowSliderBallTint) ? AccentColour.Value : Color4.White; protected override void CheckForResult(bool userTriggered, double timeOffset) { From d0a4e1e3c2ff107327091d7ece191742f45e1429 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Sep 2019 15:00:27 +0900 Subject: [PATCH 1387/2815] Force a checksum check before skipping FileStore copy op --- osu.Game/IO/FileStore.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/IO/FileStore.cs b/osu.Game/IO/FileStore.cs index 370d6786f5..bf4e881ed0 100644 --- a/osu.Game/IO/FileStore.cs +++ b/osu.Game/IO/FileStore.cs @@ -50,7 +50,16 @@ namespace osu.Game.IO string path = info.StoragePath; // we may be re-adding a file to fix missing store entries. - if (!Storage.Exists(path)) + bool requiresCopy = !Storage.Exists(path); + + if (!requiresCopy) + { + // even if the file already exists, check the existing checksum for safety. + using (var stream = Storage.GetStream(path)) + requiresCopy |= stream.ComputeSHA2Hash() != hash; + } + + if (requiresCopy) { data.Seek(0, SeekOrigin.Begin); From f306fe27d82d790ec6df2764a04b6e7ab1903593 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Sep 2019 15:05:48 +0900 Subject: [PATCH 1388/2815] Add test to cover corruption case --- .../Beatmaps/IO/ImportBeatmapTest.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 8b39946ab0..385ab4064d 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -90,6 +90,48 @@ namespace osu.Game.Tests.Beatmaps.IO } } + [Test] + public async Task TestImportCorruptThenImport() + { + //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImport")) + { + try + { + var osu = loadOsu(host); + + var imported = await LoadOszIntoOsu(osu); + + var firstFile = imported.Files.First(); + + var files = osu.Dependencies.Get(); + + long originalLength; + using (var stream = files.Storage.GetStream(firstFile.FileInfo.StoragePath)) + originalLength = stream.Length; + + using (var stream = files.Storage.GetStream(firstFile.FileInfo.StoragePath, FileAccess.Write, FileMode.Create)) + stream.WriteByte(0); + + var importedSecondTime = await LoadOszIntoOsu(osu); + + using (var stream = files.Storage.GetStream(firstFile.FileInfo.StoragePath)) + Assert.AreEqual(stream.Length, originalLength, "Corruption was not fixed on second import"); + + // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. + Assert.IsTrue(imported.ID == importedSecondTime.ID); + Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID); + + checkBeatmapSetCount(osu, 1); + checkSingleReferencedFileCount(osu, 18); + } + finally + { + host.Exit(); + } + } + } + [Test] public async Task TestRollbackOnFailure() { From e0a97cfac533487450b8067656674858cef958a0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Sep 2019 18:35:15 +0900 Subject: [PATCH 1389/2815] Add a LabelledComponent base class --- .../TestSceneLabelledComponent.cs | 89 +++++++++++++ .../LabelledComponents/LabelledComponent.cs | 121 ++++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs create mode 100644 osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledComponent.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs new file mode 100644 index 0000000000..73e0191adb --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs @@ -0,0 +1,89 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Sprites; +using osu.Game.Screens.Edit.Setup.Components.LabelledComponents; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneLabelledComponent : OsuTestScene + { + [TestCase(false)] + [TestCase(true)] + public void TestPadded(bool hasDescription) => createPaddedComponent(hasDescription); + + [TestCase(false)] + [TestCase(true)] + public void TestNonPadded(bool hasDescription) => createPaddedComponent(hasDescription, false); + + private void createPaddedComponent(bool hasDescription = false, bool padded = true) + { + AddStep("create component", () => + { + LabelledComponent component; + + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 500, + AutoSizeAxes = Axes.Y, + Child = component = padded ? (LabelledComponent)new PaddedLabelledComponent() : new NonPaddedLabelledComponent(), + }; + + component.Label = "a sample component"; + component.Description = hasDescription ? "this text describes the component" : string.Empty; + }); + } + + private class PaddedLabelledComponent : LabelledComponent + { + public PaddedLabelledComponent() + : base(true) + { + } + + protected override Drawable CreateComponent() => new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = Color4.Red, + Text = @"(( Component ))" + }; + } + + private class NonPaddedLabelledComponent : LabelledComponent + { + public NonPaddedLabelledComponent() + : base(false) + { + } + + protected override Drawable CreateComponent() => new Container + { + RelativeSizeAxes = Axes.X, + Height = 40, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.SlateGray + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = Color4.Red, + Text = @"(( Component ))" + } + } + }; + } + } +} diff --git a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledComponent.cs b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledComponent.cs new file mode 100644 index 0000000000..b4c91d6d57 --- /dev/null +++ b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledComponent.cs @@ -0,0 +1,121 @@ +// 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.Game.Graphics; +using osu.Game.Graphics.Containers; +using osuTK; + +namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents +{ + public abstract class LabelledComponent : CompositeDrawable + { + protected const float CONTENT_PADDING_VERTICAL = 10; + protected const float CONTENT_PADDING_HORIZONTAL = 15; + protected const float CORNER_RADIUS = 15; + + protected readonly Drawable Component; + + private readonly OsuTextFlowContainer labelText; + private readonly OsuTextFlowContainer descriptionText; + + protected LabelledComponent(bool padded) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + CornerRadius = CORNER_RADIUS; + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex("1c2125"), + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = padded + ? new MarginPadding { Horizontal = CONTENT_PADDING_HORIZONTAL, Vertical = CONTENT_PADDING_VERTICAL } + : new MarginPadding { Left = CONTENT_PADDING_HORIZONTAL }, + Spacing = new Vector2(0, 12), + Children = new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] + { + new Drawable[] + { + labelText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(weight: FontWeight.Bold)) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = 20 } + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = Component = CreateComponent().With(d => + { + d.Anchor = Anchor.CentreRight; + d.Origin = Anchor.CentreRight; + }) + } + }, + }, + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, + ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } + }, + descriptionText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold, italics: true)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Bottom = padded ? 0 : CONTENT_PADDING_VERTICAL }, + Alpha = 0, + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour osuColour) + { + descriptionText.Colour = osuColour.Yellow; + } + + public string Label + { + set => labelText.Text = value; + } + + public string Description + { + set + { + descriptionText.Text = value; + + if (!string.IsNullOrEmpty(value)) + descriptionText.Show(); + else + descriptionText.Hide(); + } + } + + protected abstract Drawable CreateComponent(); + } +} From 523272edab145dbd08b40980ca71a9aba16fe9d6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Sep 2019 18:39:04 +0900 Subject: [PATCH 1390/2815] Add xmldocs --- .../LabelledComponents/LabelledComponent.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledComponent.cs b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledComponent.cs index b4c91d6d57..19e9c329d6 100644 --- a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledComponent.cs +++ b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledComponent.cs @@ -17,11 +17,18 @@ namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents protected const float CONTENT_PADDING_HORIZONTAL = 15; protected const float CORNER_RADIUS = 15; + /// + /// The component that is being displayed. + /// protected readonly Drawable Component; private readonly OsuTextFlowContainer labelText; private readonly OsuTextFlowContainer descriptionText; + /// + /// Creates a new . + /// + /// Whether the component should be padded or should be expanded to the bounds of this . protected LabelledComponent(bool padded) { RelativeSizeAxes = Axes.X; @@ -116,6 +123,10 @@ namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents } } + /// + /// Creates the component that should be displayed. + /// + /// The component. protected abstract Drawable CreateComponent(); } } From 2bbf4ca4b59abf2a0e87b7c2481d922bacd773b5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Sep 2019 18:50:50 +0900 Subject: [PATCH 1391/2815] Update LabelledTextBox to use LabelledComponent --- .../UserInterface/TestSceneLabelledTextBox.cs | 32 ++++- .../LabelledComponents/LabelledTextBox.cs | 117 +++--------------- 2 files changed, 51 insertions(+), 98 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs index 395905a30d..f9a5369576 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs @@ -19,6 +19,36 @@ namespace osu.Game.Tests.Visual.UserInterface typeof(LabelledTextBox), }; + [TestCase(false)] + [TestCase(true)] + public void TestTextBox(bool hasDescription) => createTextBox(hasDescription); + + private void createTextBox(bool hasDescription = false) + { + AddStep("create component", () => + { + LabelledComponent component; + + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 500, + AutoSizeAxes = Axes.Y, + Child = component = new LabelledTextBox + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Label = "Testing text", + PlaceholderText = "This is definitely working as intended", + } + }; + + component.Label = "a sample component"; + component.Description = hasDescription ? "this text describes the component" : string.Empty; + }); + } + [BackgroundDependencyLoader] private void load() { @@ -32,7 +62,7 @@ namespace osu.Game.Tests.Visual.UserInterface { Anchor = Anchor.Centre, Origin = Anchor.Centre, - LabelText = "Testing text", + Label = "Testing text", PlaceholderText = "This is definitely working as intended", } }; diff --git a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs index 1c53fc7088..992371fedf 100644 --- a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs +++ b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs @@ -3,127 +3,50 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osuTK.Graphics; namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents { - public class LabelledTextBox : CompositeDrawable + public class LabelledTextBox : LabelledComponent { - private const float label_container_width = 150; - private const float corner_radius = 15; - private const float default_height = 40; - private const float default_label_left_padding = 15; - private const float default_label_top_padding = 12; - private const float default_label_text_size = 16; - public event TextBox.OnCommitHandler OnCommit; + protected new OsuTextBox Component => (OsuTextBox)base.Component; + + public LabelledTextBox() + : base(false) + { + } + public bool ReadOnly { - get => textBox.ReadOnly; - set => textBox.ReadOnly = value; - } - - public string LabelText - { - get => label.Text; - set => label.Text = value; - } - - public float LabelTextSize - { - get => label.Font.Size; - set => label.Font = label.Font.With(size: value); + set => Component.ReadOnly = value; } public string PlaceholderText { - get => textBox.PlaceholderText; - set => textBox.PlaceholderText = value; + set => Component.PlaceholderText = value; } public string Text { - get => textBox.Text; - set => textBox.Text = value; - } - - public Color4 LabelTextColour - { - get => label.Colour; - set => label.Colour = value; - } - - private readonly OsuTextBox textBox; - private readonly OsuSpriteText label; - - public LabelledTextBox() - { - RelativeSizeAxes = Axes.X; - Height = default_height; - CornerRadius = corner_radius; - Masking = true; - - InternalChild = new Container - { - RelativeSizeAxes = Axes.Both, - CornerRadius = corner_radius, - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex("1c2125"), - }, - new GridContainer - { - RelativeSizeAxes = Axes.X, - Height = default_height, - Content = new[] - { - new Drawable[] - { - label = new OsuSpriteText - { - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, - Padding = new MarginPadding { Left = default_label_left_padding, Top = default_label_top_padding }, - Colour = Color4.White, - Font = OsuFont.GetFont(size: default_label_text_size, weight: FontWeight.Bold), - }, - textBox = new OsuTextBox - { - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, - RelativeSizeAxes = Axes.Both, - Height = 1, - CornerRadius = corner_radius, - }, - }, - }, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, label_container_width), - new Dimension() - } - } - } - }; - - textBox.OnCommit += OnCommit; + set => Component.Text = value; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - textBox.BorderColour = colours.Blue; + Component.BorderColour = colours.Blue; } + + protected override Drawable CreateComponent() => new OsuTextBox + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + CornerRadius = CORNER_RADIUS, + }.With(t => t.OnCommit += (sender, newText) => OnCommit?.Invoke(sender, newText)); } } From dfc0928ebe1af4fcb5b1837835d0b7642205526f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Sep 2019 19:39:21 +0900 Subject: [PATCH 1392/2815] Fix scores importing with deleted beatmap sets --- osu.Game/Database/ArchiveModelManager.cs | 2 +- osu.Game/Scoring/Legacy/DatabasedLegacyScoreParser.cs | 2 +- osu.Game/Scoring/ScoreManager.cs | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 6c79b0d472..2e30a7da3e 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -590,7 +590,7 @@ namespace osu.Game.Database /// /// The existing model. /// The newly imported model. - /// Whether the existing model should be restored and used. Returning false will delete the existing a force a re-import. + /// Whether the existing model should be restored and used. Returning false will delete the existing and force a re-import. protected virtual bool CanUndelete(TModel existing, TModel import) => true; private DbSet queryModel() => ContextFactory.Get().Set(); diff --git a/osu.Game/Scoring/Legacy/DatabasedLegacyScoreParser.cs b/osu.Game/Scoring/Legacy/DatabasedLegacyScoreParser.cs index 77edd24612..2115d784a0 100644 --- a/osu.Game/Scoring/Legacy/DatabasedLegacyScoreParser.cs +++ b/osu.Game/Scoring/Legacy/DatabasedLegacyScoreParser.cs @@ -22,6 +22,6 @@ namespace osu.Game.Scoring.Legacy } protected override Ruleset GetRuleset(int rulesetId) => rulesets.GetRuleset(rulesetId).CreateInstance(); - protected override WorkingBeatmap GetBeatmap(string md5Hash) => beatmaps.GetWorkingBeatmap(beatmaps.QueryBeatmap(b => b.MD5Hash == md5Hash)); + protected override WorkingBeatmap GetBeatmap(string md5Hash) => beatmaps.GetWorkingBeatmap(beatmaps.QueryBeatmap(b => !b.BeatmapSet.DeletePending && b.MD5Hash == md5Hash)); } } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 8475158c78..6da195cd7c 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -56,6 +56,8 @@ namespace osu.Game.Scoring } } + protected override bool CanUndelete(ScoreInfo existing, ScoreInfo import) => false; + protected override IEnumerable GetStableImportPaths(Storage stableStorage) => stableStorage.GetFiles(ImportFromStablePath).Where(p => HandledExtensions.Any(ext => Path.GetExtension(p)?.Equals(ext, StringComparison.InvariantCultureIgnoreCase) ?? false)); From ff2f3cde02c6c841ca10ea2367bf75a2b411c43d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Sep 2019 19:47:54 +0900 Subject: [PATCH 1393/2815] Add test --- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 41 +++++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index 4babb07213..ef71a38028 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -117,6 +117,43 @@ namespace osu.Game.Tests.Scores.IO } } + [Test] + public async Task TestImportWithDeletedBeatmapSet() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithDeletedBeatmapSet")) + { + try + { + var osu = await loadOsu(host); + + var toImport = new ScoreInfo + { + Hash = Guid.NewGuid().ToString(), + Statistics = new Dictionary + { + { HitResult.Perfect, 100 }, + { HitResult.Miss, 50 } + } + }; + + var imported = await loadIntoOsu(osu, toImport); + + var beatmapManager = osu.Dependencies.Get(); + var scoreManager = osu.Dependencies.Get(); + + beatmapManager.Delete(imported.Beatmap.BeatmapSet); + Assert.That(scoreManager.Query(s => s.ID == imported.ID).DeletePending, Is.EqualTo(true)); + + await scoreManager.Import(imported); + Assert.That(scoreManager.Query(s => s.ID == imported.ID).DeletePending, Is.EqualTo(true)); + } + finally + { + host.Exit(); + } + } + } + private async Task loadIntoOsu(OsuGameBase osu, ScoreInfo score) { var beatmapManager = osu.Dependencies.Get(); @@ -125,9 +162,7 @@ namespace osu.Game.Tests.Scores.IO score.Ruleset = new OsuRuleset().RulesetInfo; var scoreManager = osu.Dependencies.Get(); - await scoreManager.Import(score); - - return scoreManager.GetAllUsableScores().First(); + return await scoreManager.Import(score); } private async Task loadOsu(GameHost host) From c89c092b989bedb1bc49c65c144874b4cc48f89f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Sep 2019 19:55:59 +0900 Subject: [PATCH 1394/2815] Allow undeleting scores if their beatmap exists --- osu.Game/Scoring/ScoreManager.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 6da195cd7c..8475158c78 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -56,8 +56,6 @@ namespace osu.Game.Scoring } } - protected override bool CanUndelete(ScoreInfo existing, ScoreInfo import) => false; - protected override IEnumerable GetStableImportPaths(Storage stableStorage) => stableStorage.GetFiles(ImportFromStablePath).Where(p => HandledExtensions.Any(ext => Path.GetExtension(p)?.Equals(ext, StringComparison.InvariantCultureIgnoreCase) ?? false)); From 093ed8421e34d76255083fd4bba7890f4818fbfe Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 20 Sep 2019 15:08:00 +0300 Subject: [PATCH 1395/2815] Remove "allow slider ball tinting" ruleset setting --- .../Configuration/OsuRulesetConfigManager.cs | 2 -- .../Objects/Drawables/DrawableSlider.cs | 10 ++-------- osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs | 5 ----- 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs index ab6e6bac6c..f76635a932 100644 --- a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs @@ -18,7 +18,6 @@ namespace osu.Game.Rulesets.Osu.Configuration base.InitialiseDefaults(); Set(OsuRulesetSetting.SnakingInSliders, true); Set(OsuRulesetSetting.SnakingOutSliders, true); - Set(OsuRulesetSetting.AlwaysTintSliderBall, true); Set(OsuRulesetSetting.ShowCursorTrail, true); } } @@ -27,7 +26,6 @@ namespace osu.Game.Rulesets.Osu.Configuration { SnakingInSliders, SnakingOutSliders, - AlwaysTintSliderBall, ShowCursorTrail } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 36ac0be723..a0b60479ac 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -109,7 +109,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { config?.BindWith(OsuRulesetSetting.SnakingInSliders, Body.SnakingIn); config?.BindWith(OsuRulesetSetting.SnakingOutSliders, Body.SnakingOut); - config?.BindWith(OsuRulesetSetting.AlwaysTintSliderBall, alwaysTintSliderBall); positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); scaleBindable.BindValueChanged(scale => @@ -124,12 +123,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables pathBindable.BindValueChanged(_ => Body.Refresh()); - alwaysTintSliderBall.BindValueChanged(_ => updateSliderBallTint()); - AccentColour.BindValueChanged(colour => { Body.AccentColour = colour.NewValue; - updateSliderBallTint(); foreach (var drawableHitObject in NestedHitObjects) drawableHitObject.AccentColour.Value = colour.NewValue; @@ -181,14 +177,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Body.AccentColour = skin.GetConfig(OsuSkinColour.SliderTrackOverride)?.Value ?? AccentColour.Value; Body.BorderColour = skin.GetConfig(OsuSkinColour.SliderBorder)?.Value ?? Color4.White; - allowSliderBallTint = skin.GetConfig(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false; - updateSliderBallTint(); + bool allowBallTint = skin.GetConfig(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false; + Ball.Colour = allowBallTint ? AccentColour.Value : Color4.White; } private void updatePathRadius() => Body.PathRadius = slider.Scale * sliderPathRadius; - private void updateSliderBallTint() => Ball.Colour = (alwaysTintSliderBall.Value || allowSliderBallTint) ? AccentColour.Value : Color4.White; - protected override void CheckForResult(bool userTriggered, double timeOffset) { if (userTriggered || Time.Current < slider.EndTime) diff --git a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs index be86614b57..88adf72551 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs @@ -35,11 +35,6 @@ namespace osu.Game.Rulesets.Osu.UI Bindable = config.GetBindable(OsuRulesetSetting.SnakingOutSliders) }, new SettingsCheckbox - { - LabelText = "Always tint slider ball with combo colour", - Bindable = config.GetBindable(OsuRulesetSetting.AlwaysTintSliderBall) - }, - new SettingsCheckbox { LabelText = "Cursor trail", Bindable = config.GetBindable(OsuRulesetSetting.ShowCursorTrail) From 57310c86c7ab8d57f1edad558f4f9fdc917595fb Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 20 Sep 2019 15:09:51 +0300 Subject: [PATCH 1396/2815] Remove unnecessary fields --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index a0b60479ac..9e8ad9851c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -34,9 +34,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly IBindable scaleBindable = new Bindable(); private readonly IBindable pathBindable = new Bindable(); - private readonly Bindable alwaysTintSliderBall = new Bindable(true); - private bool allowSliderBallTint; - [Resolved(CanBeNull = true)] private OsuRulesetConfigManager config { get; set; } From 8300e86f20815f9add57bdc4132437186af7346d Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 20 Sep 2019 15:46:43 +0300 Subject: [PATCH 1397/2815] Specify model name on import notification messages --- osu.Game/Database/ArchiveModelManager.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 6c79b0d472..969673993b 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Humanizer; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using osu.Framework; @@ -110,7 +111,7 @@ namespace osu.Game.Database protected async Task Import(ProgressNotification notification, params string[] paths) { notification.Progress = 0; - notification.Text = "Import is initialising..."; + notification.Text = $"{HumanisedModelName.Humanize()}s import is initialising..."; int current = 0; @@ -146,7 +147,7 @@ namespace osu.Game.Database if (imported.Count == 0) { - notification.Text = "Import failed!"; + notification.Text = $"{HumanisedModelName.Humanize()}s import failed!"; notification.State = ProgressNotificationState.Cancelled; } else From 3be03a26c93257dcfa0858dfca872b635127194f Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 20 Sep 2019 16:18:55 +0300 Subject: [PATCH 1398/2815] Pluralize instead of adding 's' --- osu.Game/Database/ArchiveModelManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 969673993b..dc17526201 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -111,7 +111,7 @@ namespace osu.Game.Database protected async Task Import(ProgressNotification notification, params string[] paths) { notification.Progress = 0; - notification.Text = $"{HumanisedModelName.Humanize()}s import is initialising..."; + notification.Text = $"{HumanisedModelName.Pluralize(paths.Length == 1)} import is initialising...".Humanize(); int current = 0; @@ -147,7 +147,7 @@ namespace osu.Game.Database if (imported.Count == 0) { - notification.Text = $"{HumanisedModelName.Humanize()}s import failed!"; + notification.Text = $"{HumanisedModelName.Pluralize(paths.Length == 1)} import failed!".Humanize(); notification.State = ProgressNotificationState.Cancelled; } else From 2d99d41a6dc1fb79173f91f5dcbb22514ab96d0b Mon Sep 17 00:00:00 2001 From: Vperus Date: Fri, 20 Sep 2019 18:17:35 +0300 Subject: [PATCH 1399/2815] Remove unused CORNER_RADIUS --- osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 31221c05ee..8f353ae138 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -18,8 +18,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// public class DrawableNote : DrawableManiaHitObject, IKeyBindingHandler { - public const float CORNER_RADIUS = NotePiece.NOTE_HEIGHT / 2; - private readonly NotePiece headPiece; public DrawableNote(Note hitObject) From ac8fe6045f2cba92ec1d3e819b0f6fa0b3c15164 Mon Sep 17 00:00:00 2001 From: Vperus Date: Fri, 20 Sep 2019 19:58:39 +0300 Subject: [PATCH 1400/2815] Fixed typo Changed CreateReourceStore() to CreateResourceStore() --- osu.Game/Rulesets/Ruleset.cs | 2 +- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 197c089f71..dd1b3615c7 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.QuestionCircle }; - public virtual IResourceStore CreateReourceStore() => new NamespacedResourceStore(new DllResourceStore(GetType().Assembly.Location), @"Resources"); + public virtual IResourceStore CreateResourceStore() => new NamespacedResourceStore(new DllResourceStore(GetType().Assembly.Location), @"Resources"); public abstract string Description { get; } diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index a34bb6e8ea..d68b0e94c5 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.UI { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - var resources = Ruleset.CreateReourceStore(); + var resources = Ruleset.CreateResourceStore(); if (resources != null) { From 92f9cf3e06e75eb67d0fa40498e2b436136b9e85 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 21 Sep 2019 02:08:19 +0900 Subject: [PATCH 1401/2815] 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 969eb205e0..c57fc342ba 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -62,6 +62,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index a733a0e7f9..a27a94b8f9 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 4bfa1ebcd0..a6516e6d1b 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,8 +118,8 @@ - - + + From bbf3ac77f840a546fe500de6b2638540b674e3a8 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 20 Sep 2019 21:35:26 +0200 Subject: [PATCH 1402/2815] Add visual test for HTML string unescaping. --- .../Online/TestSceneChangelogOverlay.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs index f555c276f4..658f678b10 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs @@ -68,6 +68,34 @@ namespace osu.Game.Tests.Visual.Online changelog.ShowListing(); changelog.Show(); }); + + AddStep(@"Ensure HTML string unescaping", () => + { + changelog.ShowBuild(new APIChangelogBuild + { + Version = "2019.920.0", + DisplayVersion = "2019.920.0", + UpdateStream = new APIUpdateStream + { + Name = "Test", + DisplayName = "Test" + }, + ChangelogEntries = new List + { + new APIChangelogEntry + { + Category = "Testing HTML strings unescaping", + Title = "Ensuring HTML strings are being unescaped", + MessageHtml = """"This text should appear triple-quoted""" >_<", + GithubUser = new APIChangelogUser + { + DisplayName = "Dummy", + OsuUsername = "Dummy", + } + }, + } + }); + }); } } } From e7118a9272600b7e4ac2ffddef8bbecab336d12d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 20 Sep 2019 23:47:21 +0300 Subject: [PATCH 1403/2815] Use System mod type for NoMod --- osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs | 2 +- osu.Game/Rulesets/Mods/ModType.cs | 3 +-- osu.Game/Rulesets/UI/ModIcon.cs | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index d09464aba6..4b6fd5bfc2 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -165,7 +165,7 @@ namespace osu.Game.Overlays.BeatmapSet public override IconUsage Icon => FontAwesome.Solid.Ban; - public override ModType Type => ModType.Custom; + public override ModType Type => ModType.System; } } } diff --git a/osu.Game/Rulesets/Mods/ModType.cs b/osu.Game/Rulesets/Mods/ModType.cs index 1cdc4415ac..e3c82e42f5 100644 --- a/osu.Game/Rulesets/Mods/ModType.cs +++ b/osu.Game/Rulesets/Mods/ModType.cs @@ -10,7 +10,6 @@ namespace osu.Game.Rulesets.Mods Conversion, Automation, Fun, - System, - Custom + System } } diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index b54ddf8f12..19211e0c80 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.UI highlightedColour = colours.PinkLight; break; - case ModType.Custom: + case ModType.System: backgroundColour = colours.Gray6; highlightedColour = colours.Gray7; modIcon.Colour = colours.Yellow; From befdd140f462c80d6aa2027b22f77310ecd45ed5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 20 Sep 2019 23:50:19 +0300 Subject: [PATCH 1404/2815] Reverse padding changes --- osu.Game/Overlays/Changelog/ChangelogBuild.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs index 6d7b36b832..6b24f39d98 100644 --- a/osu.Game/Overlays/Changelog/ChangelogBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs @@ -80,7 +80,7 @@ namespace osu.Game.Overlays.Changelog { t.Font = fontSmall; t.Colour = entryColour; - t.Padding = new MarginPadding { Left = -17, Top = 5 }; + t.Padding = new MarginPadding { Left = -17, Right = 5 }; }); title.AddText(entry.Title, t => From c99b48f9342189ba0290702893fbdcc1a7b72f2f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 21 Sep 2019 23:30:54 +0900 Subject: [PATCH 1405/2815] Bring up-to-date and use IApplicableFailOverride --- osu.Game/Rulesets/Mods/ModEasy.cs | 40 +++++++++++++++++++------------ 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index 4365ed256f..a55ebc51d6 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -2,16 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Mods { - public abstract class ModEasy : Mod, IApplicableToDifficulty, IApplicableToScoreProcessor + public abstract class ModEasy : Mod, IApplicableToDifficulty, IApplicableFailOverride, IApplicableToScoreProcessor { - private int lives; public override string Name => "Easy"; public override string Acronym => "EZ"; public override IconUsage Icon => OsuIcon.ModEasy; @@ -20,6 +21,10 @@ namespace osu.Game.Rulesets.Mods public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModHardRock) }; + private int retries = 2; + + private BindableNumber health; + public void ApplyToDifficulty(BeatmapDifficulty difficulty) { const float ratio = 0.5f; @@ -29,21 +34,26 @@ namespace osu.Game.Rulesets.Mods difficulty.OverallDifficulty *= ratio; } + public bool AllowFail + { + get + { + if (retries == 0) return true; + + health.Value = health.MaxValue; + retries--; + + return false; + } + } + + public bool RestartOnFail => false; + public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { - //Note : The lives has to be instaciated here in order to prevent the values from different plays to interfear - //with each other / not reseting after a restart , as this method is called once a play starts (to my knowlegde). - //This will be better implemented with a List once I know how to reliably get the game time and update it. - //If you know any information about that, please contact me because I didn't find a sollution to that. - lives = 2; - scoreProcessor.Health.ValueChanged += valueChanged => - { - if (scoreProcessor.Health.Value == scoreProcessor.Health.MinValue && lives > 0) - { - lives--; - scoreProcessor.Health.Value = scoreProcessor.Health.MaxValue; - } - }; + health = scoreProcessor.Health.GetBoundCopy(); } + + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; } } From 24cc8ce0b7c53f5d4a30b3865c363dcc82483816 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sun, 22 Sep 2019 00:59:01 +0900 Subject: [PATCH 1406/2815] Fix deleting null beatmap set --- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index ef71a38028..b784cc4682 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -141,7 +141,7 @@ namespace osu.Game.Tests.Scores.IO var beatmapManager = osu.Dependencies.Get(); var scoreManager = osu.Dependencies.Get(); - beatmapManager.Delete(imported.Beatmap.BeatmapSet); + beatmapManager.Delete(beatmapManager.QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == imported.Beatmap.ID))); Assert.That(scoreManager.Query(s => s.ID == imported.ID).DeletePending, Is.EqualTo(true)); await scoreManager.Import(imported); From 08440ce5fd404f03763de43f95012a8dca64dafa Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sun, 22 Sep 2019 00:59:40 +0900 Subject: [PATCH 1407/2815] Adjust test to assert that the import failed --- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index b784cc4682..89b5db9e1b 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -144,8 +144,8 @@ namespace osu.Game.Tests.Scores.IO beatmapManager.Delete(beatmapManager.QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == imported.Beatmap.ID))); Assert.That(scoreManager.Query(s => s.ID == imported.ID).DeletePending, Is.EqualTo(true)); - await scoreManager.Import(imported); - Assert.That(scoreManager.Query(s => s.ID == imported.ID).DeletePending, Is.EqualTo(true)); + var secondImport = await loadIntoOsu(osu, imported); + Assert.That(secondImport, Is.Null); } finally { @@ -158,11 +158,16 @@ namespace osu.Game.Tests.Scores.IO { var beatmapManager = osu.Dependencies.Get(); - score.Beatmap = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(); - score.Ruleset = new OsuRuleset().RulesetInfo; + if (score.Beatmap == null) + score.Beatmap = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(); + + if (score.Ruleset == null) + score.Ruleset = new OsuRuleset().RulesetInfo; var scoreManager = osu.Dependencies.Get(); - return await scoreManager.Import(score); + await scoreManager.Import(score); + + return scoreManager.GetAllUsableScores().FirstOrDefault(); } private async Task loadOsu(GameHost host) From 6bb0f3eb4149a3649c62543b16d765abcfa4f990 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sat, 21 Sep 2019 20:04:12 +0300 Subject: [PATCH 1408/2815] Move humanizing to the model name instead --- osu.Game/Database/ArchiveModelManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index dc17526201..36084fefeb 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -111,7 +111,7 @@ namespace osu.Game.Database protected async Task Import(ProgressNotification notification, params string[] paths) { notification.Progress = 0; - notification.Text = $"{HumanisedModelName.Pluralize(paths.Length == 1)} import is initialising...".Humanize(); + notification.Text = $"{HumanisedModelName.Humanize().Pluralize(paths.Length == 1)} import is initialising..."; int current = 0; @@ -147,7 +147,7 @@ namespace osu.Game.Database if (imported.Count == 0) { - notification.Text = $"{HumanisedModelName.Pluralize(paths.Length == 1)} import failed!".Humanize(); + notification.Text = $"{HumanisedModelName.Humanize().Pluralize(paths.Length == 1)} import failed!"; notification.State = ProgressNotificationState.Cancelled; } else From 9be8bdef521d1a90f1e8462835bb308865994fab Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sat, 21 Sep 2019 21:00:24 +0300 Subject: [PATCH 1409/2815] Remove pluralize and use title letter casing --- osu.Game/Database/ArchiveModelManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 36084fefeb..703cc53318 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -111,7 +111,7 @@ namespace osu.Game.Database protected async Task Import(ProgressNotification notification, params string[] paths) { notification.Progress = 0; - notification.Text = $"{HumanisedModelName.Humanize().Pluralize(paths.Length == 1)} import is initialising..."; + notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is initialising..."; int current = 0; @@ -147,7 +147,7 @@ namespace osu.Game.Database if (imported.Count == 0) { - notification.Text = $"{HumanisedModelName.Humanize().Pluralize(paths.Length == 1)} import failed!"; + notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import failed!"; notification.State = ProgressNotificationState.Cancelled; } else From 33c51d5178fc5191d143fd77407042e5dc6b6687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 20 Sep 2019 20:55:30 +0200 Subject: [PATCH 1410/2815] Extract parsing filter queries to class For the sake of testability without having to spin up visual tests, extract methods related to parsing filter queries from FilterControl to a static FilterQueryParser class. --- osu.Game/Screens/Select/FilterControl.cs | 131 +----------------- osu.Game/Screens/Select/FilterQueryParser.cs | 138 +++++++++++++++++++ 2 files changed, 139 insertions(+), 130 deletions(-) create mode 100644 osu.Game/Screens/Select/FilterQueryParser.cs diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index e3c23f7e22..91f1ca0307 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -16,8 +16,6 @@ using Container = osu.Framework.Graphics.Containers.Container; using osu.Framework.Graphics.Shapes; using osu.Game.Configuration; using osu.Game.Rulesets; -using System.Text.RegularExpressions; -using osu.Game.Beatmaps; namespace osu.Game.Screens.Select { @@ -47,10 +45,7 @@ namespace osu.Game.Screens.Select Ruleset = ruleset.Value }; - applyQueries(criteria, ref query); - - criteria.SearchText = query; - + FilterQueryParser.ApplyQueries(criteria, query); return criteria; } @@ -181,129 +176,5 @@ namespace osu.Game.Screens.Select } private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria()); - - private static readonly Regex query_syntax_regex = new Regex( - @"\b(?stars|ar|dr|cs|divisor|length|objects|bpm|status)(?[=:><]+)(?\S*)", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - private void applyQueries(FilterCriteria criteria, ref string query) - { - foreach (Match match in query_syntax_regex.Matches(query)) - { - var key = match.Groups["key"].Value.ToLower(); - var op = match.Groups["op"].Value; - var value = match.Groups["value"].Value; - - switch (key) - { - case "stars" when float.TryParse(value, out var stars): - updateCriteriaRange(ref criteria.StarDifficulty, op, stars); - break; - - case "ar" when float.TryParse(value, out var ar): - updateCriteriaRange(ref criteria.ApproachRate, op, ar); - break; - - case "dr" when float.TryParse(value, out var dr): - updateCriteriaRange(ref criteria.DrainRate, op, dr); - break; - - case "cs" when float.TryParse(value, out var cs): - updateCriteriaRange(ref criteria.CircleSize, op, cs); - break; - - case "bpm" when double.TryParse(value, out var bpm): - updateCriteriaRange(ref criteria.BPM, op, bpm); - break; - - case "length" when double.TryParse(value.TrimEnd('m', 's', 'h'), out var length): - var scale = - value.EndsWith("ms") ? 1 : - value.EndsWith("s") ? 1000 : - value.EndsWith("m") ? 60000 : - value.EndsWith("h") ? 3600000 : 1000; - - updateCriteriaRange(ref criteria.Length, op, length * scale, scale / 2.0); - break; - - case "divisor" when int.TryParse(value, out var divisor): - updateCriteriaRange(ref criteria.BeatDivisor, op, divisor); - break; - - case "status" when Enum.TryParse(value, true, out var statusValue): - updateCriteriaRange(ref criteria.OnlineStatus, op, statusValue); - break; - } - - query = query.Replace(match.ToString(), ""); - } - } - - private void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, float value, float tolerance = 0.05f) - { - updateCriteriaRange(ref range, op, value); - - switch (op) - { - case "=": - case ":": - range.Min = value - tolerance; - range.Max = value + tolerance; - break; - } - } - - private void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, double value, double tolerance = 0.05) - { - updateCriteriaRange(ref range, op, value); - - switch (op) - { - case "=": - case ":": - range.Min = value - tolerance; - range.Max = value + tolerance; - break; - } - } - - private void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, T value) - where T : struct, IComparable - { - switch (op) - { - default: - return; - - case "=": - case ":": - range.IsInclusive = true; - range.Min = value; - range.Max = value; - break; - - case ">": - range.IsInclusive = false; - range.Min = value; - break; - - case ">=": - case ">:": - range.IsInclusive = true; - range.Min = value; - break; - - case "<": - range.IsInclusive = false; - range.Max = value; - break; - - case "<=": - case "<:": - range.IsInclusive = true; - range.Max = value; - break; - } - } } } diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs new file mode 100644 index 0000000000..4e2b591fc9 --- /dev/null +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -0,0 +1,138 @@ +// 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.Text.RegularExpressions; +using osu.Game.Beatmaps; + +namespace osu.Game.Screens.Select +{ + internal static class FilterQueryParser + { + private static readonly Regex query_syntax_regex = new Regex( + @"\b(?stars|ar|dr|cs|divisor|length|objects|bpm|status)(?[=:><]+)(?\S*)", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + internal static void ApplyQueries(FilterCriteria criteria, string query) + { + foreach (Match match in query_syntax_regex.Matches(query)) + { + var key = match.Groups["key"].Value.ToLower(); + var op = match.Groups["op"].Value; + var value = match.Groups["value"].Value; + + switch (key) + { + case "stars" when float.TryParse(value, out var stars): + updateCriteriaRange(ref criteria.StarDifficulty, op, stars); + break; + + case "ar" when float.TryParse(value, out var ar): + updateCriteriaRange(ref criteria.ApproachRate, op, ar); + break; + + case "dr" when float.TryParse(value, out var dr): + updateCriteriaRange(ref criteria.DrainRate, op, dr); + break; + + case "cs" when float.TryParse(value, out var cs): + updateCriteriaRange(ref criteria.CircleSize, op, cs); + break; + + case "bpm" when double.TryParse(value, out var bpm): + updateCriteriaRange(ref criteria.BPM, op, bpm); + break; + + case "length" when double.TryParse(value.TrimEnd('m', 's', 'h'), out var length): + var scale = + value.EndsWith("ms") ? 1 : + value.EndsWith("s") ? 1000 : + value.EndsWith("m") ? 60000 : + value.EndsWith("h") ? 3600000 : 1000; + + updateCriteriaRange(ref criteria.Length, op, length * scale, scale / 2.0); + break; + + case "divisor" when int.TryParse(value, out var divisor): + updateCriteriaRange(ref criteria.BeatDivisor, op, divisor); + break; + + case "status" when Enum.TryParse(value, true, out var statusValue): + updateCriteriaRange(ref criteria.OnlineStatus, op, statusValue); + break; + } + + query = query.Replace(match.ToString(), ""); + } + + criteria.SearchText = query; + } + + private static void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, float value, float tolerance = 0.05f) + { + updateCriteriaRange(ref range, op, value); + + switch (op) + { + case "=": + case ":": + range.Min = value - tolerance; + range.Max = value + tolerance; + break; + } + } + + private static void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, double value, double tolerance = 0.05) + { + updateCriteriaRange(ref range, op, value); + + switch (op) + { + case "=": + case ":": + range.Min = value - tolerance; + range.Max = value + tolerance; + break; + } + } + + private static void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, T value) + where T : struct, IComparable + { + switch (op) + { + default: + return; + + case "=": + case ":": + range.IsInclusive = true; + range.Min = value; + range.Max = value; + break; + + case ">": + range.IsInclusive = false; + range.Min = value; + break; + + case ">=": + case ">:": + range.IsInclusive = true; + range.Min = value; + break; + + case "<": + range.IsInclusive = false; + range.Max = value; + break; + + case "<=": + case "<:": + range.IsInclusive = true; + range.Max = value; + break; + } + } + } +} From dddd94684bd233e1377211d086e0f47c736fb934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 20 Sep 2019 21:34:38 +0200 Subject: [PATCH 1411/2815] Split out lower and upper interval inclusivity A single IsInclusive field causes unexpected issues when trying to formulate a half-open interval query. Split out IsInclusive into two fields, Is{Lower,Upper}Inclusive and update usages accordingly. --- osu.Game/Screens/Select/FilterCriteria.cs | 10 ++++++---- osu.Game/Screens/Select/FilterQueryParser.cs | 10 +++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index a3fa1b10ca..97a7f12724 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Select if (comparison < 0) return false; - if (comparison == 0 && !IsInclusive) + if (comparison == 0 && !IsLowerInclusive) return false; } @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Select if (comparison > 0) return false; - if (comparison == 0 && !IsInclusive) + if (comparison == 0 && !IsUpperInclusive) return false; } @@ -73,12 +73,14 @@ namespace osu.Game.Screens.Select public T? Min; public T? Max; - public bool IsInclusive; + public bool IsLowerInclusive; + public bool IsUpperInclusive; public bool Equals(OptionalRange other) => Min.Equals(other.Min) && Max.Equals(other.Max) - && IsInclusive.Equals(other.IsInclusive); + && IsLowerInclusive.Equals(other.IsLowerInclusive) + && IsUpperInclusive.Equals(other.IsUpperInclusive); } } } diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 4e2b591fc9..800f1afd03 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -106,30 +106,30 @@ namespace osu.Game.Screens.Select case "=": case ":": - range.IsInclusive = true; + range.IsLowerInclusive = range.IsUpperInclusive = true; range.Min = value; range.Max = value; break; case ">": - range.IsInclusive = false; + range.IsLowerInclusive = false; range.Min = value; break; case ">=": case ">:": - range.IsInclusive = true; + range.IsLowerInclusive = true; range.Min = value; break; case "<": - range.IsInclusive = false; + range.IsUpperInclusive = false; range.Max = value; break; case "<=": case "<:": - range.IsInclusive = true; + range.IsUpperInclusive = true; range.Max = value; break; } From f5f5094611257f50fc778becf402105868e9757f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 20 Sep 2019 22:10:46 +0200 Subject: [PATCH 1412/2815] Take culture into account when parsing filters Culture was not taken into account when parsing filters, which meant that in cultures that use the comma (,) as a decimal delimiter, it would conflict with the comma used to delimit search criteria. To remove any ambiguity, introduce local helper functions that allow the decimal point to be utilised, using the invariant culture. This also matches stable behaviour. The decision to not reuse osu.Game.Beatmaps.Formats.Parsing was deliberate due to differing semantics (it's not really sane to throw exceptions on receiving user-facing input). --- osu.Game/Screens/Select/FilterQueryParser.cs | 24 ++++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 800f1afd03..d6d19c8650 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Globalization; using System.Text.RegularExpressions; using osu.Game.Beatmaps; @@ -23,27 +24,27 @@ namespace osu.Game.Screens.Select switch (key) { - case "stars" when float.TryParse(value, out var stars): + case "stars" when parseFloatWithPoint(value, out var stars): updateCriteriaRange(ref criteria.StarDifficulty, op, stars); break; - case "ar" when float.TryParse(value, out var ar): + case "ar" when parseFloatWithPoint(value, out var ar): updateCriteriaRange(ref criteria.ApproachRate, op, ar); break; - case "dr" when float.TryParse(value, out var dr): + case "dr" when parseFloatWithPoint(value, out var dr): updateCriteriaRange(ref criteria.DrainRate, op, dr); break; - case "cs" when float.TryParse(value, out var cs): + case "cs" when parseFloatWithPoint(value, out var cs): updateCriteriaRange(ref criteria.CircleSize, op, cs); break; - case "bpm" when double.TryParse(value, out var bpm): + case "bpm" when parseDoubleWithPoint(value, out var bpm): updateCriteriaRange(ref criteria.BPM, op, bpm); break; - case "length" when double.TryParse(value.TrimEnd('m', 's', 'h'), out var length): + case "length" when parseDoubleWithPoint(value.TrimEnd('m', 's', 'h'), out var length): var scale = value.EndsWith("ms") ? 1 : value.EndsWith("s") ? 1000 : @@ -53,7 +54,7 @@ namespace osu.Game.Screens.Select updateCriteriaRange(ref criteria.Length, op, length * scale, scale / 2.0); break; - case "divisor" when int.TryParse(value, out var divisor): + case "divisor" when parseInt(value, out var divisor): updateCriteriaRange(ref criteria.BeatDivisor, op, divisor); break; @@ -68,6 +69,15 @@ namespace osu.Game.Screens.Select criteria.SearchText = query; } + private static bool parseFloatWithPoint(string value, out float result) => + float.TryParse(value, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out result); + + private static bool parseDoubleWithPoint(string value, out double result) => + double.TryParse(value, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out result); + + private static bool parseInt(string value, out int result) => + int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out result); + private static void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, float value, float tolerance = 0.05f) { updateCriteriaRange(ref range, op, value); From d11d932a8747273f56f4128dbcf8b31bd6a329c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 20 Sep 2019 22:19:45 +0200 Subject: [PATCH 1413/2815] Add filter parsing tests Introduce unit tests covering parsing for the originally introduced filtering features. The introduced improvements (lower and upper interval and decimal point support) also tested. --- .../Filtering/FilterQueryParserTest.cs | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs new file mode 100644 index 0000000000..f98ad1fc43 --- /dev/null +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -0,0 +1,129 @@ +// 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 NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Screens.Select; + +namespace osu.Game.Tests.NonVisual.Filtering +{ + [TestFixture] + public class FilterQueryParserTest + { + [Test] + public void TestApplyQueriesBareWords() + { + const string query = "looking for a beatmap"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("looking for a beatmap", filterCriteria.SearchText); + Assert.AreEqual(4, filterCriteria.SearchTerms.Length); + } + + [Test] + public void TestApplyStarQueries() + { + const string query = "stars<4 easy"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("easy", filterCriteria.SearchText.Trim()); + Assert.AreEqual(1, filterCriteria.SearchTerms.Length); + Assert.AreEqual(4.0f, filterCriteria.StarDifficulty.Max); + Assert.IsFalse(filterCriteria.StarDifficulty.IsUpperInclusive); + Assert.IsNull(filterCriteria.StarDifficulty.Min); + } + + [Test] + public void TestApplyApproachRateQueries() + { + const string query = "ar>=9 difficult"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("difficult", filterCriteria.SearchText.Trim()); + Assert.AreEqual(1, filterCriteria.SearchTerms.Length); + Assert.AreEqual(9.0f, filterCriteria.ApproachRate.Min); + Assert.IsTrue(filterCriteria.ApproachRate.IsLowerInclusive); + Assert.IsNull(filterCriteria.ApproachRate.Max); + } + + [Test] + public void TestApplyDrainRateQueries() + { + const string query = "dr>2 quite specific dr<:6"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("quite specific", filterCriteria.SearchText.Trim()); + Assert.AreEqual(2, filterCriteria.SearchTerms.Length); + Assert.AreEqual(2.0f, filterCriteria.DrainRate.Min); + Assert.IsFalse(filterCriteria.DrainRate.IsLowerInclusive); + Assert.AreEqual(6.0f, filterCriteria.DrainRate.Max); + Assert.IsTrue(filterCriteria.DrainRate.IsUpperInclusive); + } + + [Test] + public void TestApplyBPMQueries() + { + const string query = "bpm>:200 gotta go fast"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("gotta go fast", filterCriteria.SearchText.Trim()); + Assert.AreEqual(3, filterCriteria.SearchTerms.Length); + Assert.AreEqual(200d, filterCriteria.BPM.Min); + Assert.IsTrue(filterCriteria.BPM.IsLowerInclusive); + Assert.IsNull(filterCriteria.BPM.Max); + } + + private static object[] lengthQueryExamples = + { + new object[] { "6ms", TimeSpan.FromMilliseconds(6), TimeSpan.FromMilliseconds(1) }, + new object[] { "23s", TimeSpan.FromSeconds(23), TimeSpan.FromSeconds(1) }, + new object[] { "9m", TimeSpan.FromMinutes(9), TimeSpan.FromMinutes(1) }, + new object[] { "0.25h", TimeSpan.FromHours(0.25), TimeSpan.FromHours(1) }, + new object[] { "70", TimeSpan.FromSeconds(70), TimeSpan.FromSeconds(1) }, + }; + + [Test] + [TestCaseSource(nameof(lengthQueryExamples))] + public void TestApplyLengthQueries(string lengthQuery, TimeSpan expectedLength, TimeSpan scale) + { + string query = $"length={lengthQuery} time"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("time", filterCriteria.SearchText.Trim()); + Assert.AreEqual(1, filterCriteria.SearchTerms.Length); + Assert.AreEqual(expectedLength.TotalMilliseconds - scale.TotalMilliseconds / 2.0, filterCriteria.Length.Min); + Assert.IsTrue(filterCriteria.Length.IsLowerInclusive); + Assert.AreEqual(expectedLength.TotalMilliseconds + scale.TotalMilliseconds / 2.0, filterCriteria.Length.Max); + Assert.IsTrue(filterCriteria.Length.IsUpperInclusive); + } + + [Test] + public void TestApplyDivisorQueries() + { + const string query = "that's a time signature alright! divisor:12"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("that's a time signature alright!", filterCriteria.SearchText.Trim()); + Assert.AreEqual(5, filterCriteria.SearchTerms.Length); + Assert.AreEqual(12, filterCriteria.BeatDivisor.Min); + Assert.IsTrue(filterCriteria.BeatDivisor.IsLowerInclusive); + Assert.AreEqual(12, filterCriteria.BeatDivisor.Max); + Assert.IsTrue(filterCriteria.BeatDivisor.IsUpperInclusive); + } + + [Test] + public void TestApplyStatusQueries() + { + const string query = "I want the pp status=ranked"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("I want the pp", filterCriteria.SearchText.Trim()); + Assert.AreEqual(4, filterCriteria.SearchTerms.Length); + Assert.AreEqual(BeatmapSetOnlineStatus.Ranked, filterCriteria.OnlineStatus.Min); + Assert.IsTrue(filterCriteria.OnlineStatus.IsLowerInclusive); + Assert.AreEqual(BeatmapSetOnlineStatus.Ranked, filterCriteria.OnlineStatus.Max); + Assert.IsTrue(filterCriteria.OnlineStatus.IsUpperInclusive); + } + } +} From 41569fd2b63f82aa5a19f4f026120d0d2f2717ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 20 Sep 2019 22:48:30 +0200 Subject: [PATCH 1414/2815] Add filter evaluating unit tests Introduce unit tests covering the actual evaluation of filters for beatmaps. Partially covers most scenarios. --- .../NonVisual/Filtering/FilterMatchingTest.cs | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs new file mode 100644 index 0000000000..24e735310d --- /dev/null +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -0,0 +1,136 @@ +// 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.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Screens.Select; +using osu.Game.Screens.Select.Carousel; + +namespace osu.Game.Tests.NonVisual.Filtering +{ + [TestFixture] + public class FilterMatchingTest + { + private readonly BeatmapInfo exampleBeatmapInfo = new BeatmapInfo + { + Ruleset = new RulesetInfo { ID = 5 }, + StarDifficulty = 4.0d, + BaseDifficulty = new BeatmapDifficulty + { + ApproachRate = 5.0f, + DrainRate = 3.0f, + CircleSize = 2.0f, + }, + Metadata = new BeatmapMetadata + { + Artist = "The Artist", + ArtistUnicode = "The Artist", + Title = "Title goes here", + TitleUnicode = "Title goes here", + AuthorString = "Author", + Source = "unit tests", + Tags = "look for tags too", + }, + Version = "version as well", + Length = 2500, + BPM = 160, + BeatDivisor = 12, + Status = BeatmapSetOnlineStatus.Loved + }; + + [Test] + public void TestCriteriaMatchingNoRuleset() + { + var criteria = new FilterCriteria(); + var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); + carouselItem.Filter(criteria); + Assert.IsFalse(carouselItem.Filtered.Value); + } + + [Test] + public void TestCriteriaMatchingSpecificRuleset() + { + var criteria = new FilterCriteria + { + Ruleset = new RulesetInfo { ID = 6 } + }; + var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); + carouselItem.Filter(criteria); + Assert.IsTrue(carouselItem.Filtered.Value); + } + + [Test] + public void TestCriteriaMatchingConvertedBeatmaps() + { + var criteria = new FilterCriteria + { + Ruleset = new RulesetInfo { ID = 6 }, + AllowConvertedBeatmaps = true + }; + var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); + carouselItem.Filter(criteria); + Assert.IsFalse(carouselItem.Filtered.Value); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void TestCriteriaMatchingRangeMin(bool inclusive) + { + var criteria = new FilterCriteria + { + Ruleset = new RulesetInfo { ID = 6 }, + AllowConvertedBeatmaps = true, + ApproachRate = new FilterCriteria.OptionalRange + { + IsLowerInclusive = inclusive, + Min = 5.0f + } + }; + var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); + carouselItem.Filter(criteria); + Assert.AreEqual(!inclusive, carouselItem.Filtered.Value); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void TestCriteriaMatchingRangeMax(bool inclusive) + { + var criteria = new FilterCriteria + { + Ruleset = new RulesetInfo { ID = 6 }, + AllowConvertedBeatmaps = true, + BPM = new FilterCriteria.OptionalRange + { + IsUpperInclusive = inclusive, + Max = 160d + } + }; + var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); + carouselItem.Filter(criteria); + Assert.AreEqual(!inclusive, carouselItem.Filtered.Value); + } + + [Test] + [TestCase("artist", false)] + [TestCase("artist title author", false)] + [TestCase("an artist", true)] + [TestCase("tags too", false)] + [TestCase("version", false)] + [TestCase("an auteur", true)] + public void TestCriteriaMatchingTerms(string terms, bool filtered) + { + var criteria = new FilterCriteria + { + Ruleset = new RulesetInfo { ID = 6 }, + AllowConvertedBeatmaps = true, + SearchText = terms + }; + var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); + carouselItem.Filter(criteria); + Assert.AreEqual(filtered, carouselItem.Filtered.Value); + } + } +} From 51509f6be03523771e61269311086d6fdb49c7ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 20 Sep 2019 23:06:20 +0200 Subject: [PATCH 1415/2815] Add filter steps to carousel visual test Just a couple of steps for added coverage in visual tests. Very on-the-surface, the unit tests are supposed to cover the gory details. --- .../Visual/SongSelect/TestSceneBeatmapCarousel.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 6669ec7da3..71399106f4 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -242,6 +242,21 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false)); AddAssert("Selection is non-null", () => currentSelection != null); + + setSelected(1, 3); + AddStep("Apply a range filter", () => carousel.Filter(new FilterCriteria + { + SearchText = "#3", + StarDifficulty = new FilterCriteria.OptionalRange + { + Min = 2, + Max = 5.5, + IsLowerInclusive = true + } + }, false)); + checkSelected(3, 2); + + AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false)); } /// From b262ba13cd5d2ea1c9a908bd542f7c28758299fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 21 Sep 2019 23:16:23 +0200 Subject: [PATCH 1416/2815] Add creator= and artist= filters To match stable, add creator= and artist= filters to the beatmap carousel on song select screen. Contrary to stable, this implementation supports phrase queries with spaces within using double quotes. The quote handling is not entirely correct (can't nest), but quotes should rarely happen within names, and it is an edge case of an edge case - leaving best-effort as is. Test coverage also included. --- .../NonVisual/Filtering/FilterMatchingTest.cs | 71 ++++++++++++++++++- .../Filtering/FilterQueryParserTest.cs | 44 ++++++++++++ .../Select/Carousel/CarouselBeatmap.cs | 4 ++ osu.Game/Screens/Select/FilterCriteria.cs | 21 ++++++ osu.Game/Screens/Select/FilterQueryParser.cs | 21 +++++- 5 files changed, 157 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 24e735310d..30686cb947 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tests.NonVisual.Filtering [TestFixture] public class FilterMatchingTest { - private readonly BeatmapInfo exampleBeatmapInfo = new BeatmapInfo + private BeatmapInfo getExampleBeatmap() => new BeatmapInfo { Ruleset = new RulesetInfo { ID = 5 }, StarDifficulty = 4.0d, @@ -25,10 +25,10 @@ namespace osu.Game.Tests.NonVisual.Filtering Metadata = new BeatmapMetadata { Artist = "The Artist", - ArtistUnicode = "The Artist", + ArtistUnicode = "check unicode too", Title = "Title goes here", TitleUnicode = "Title goes here", - AuthorString = "Author", + AuthorString = "The Author", Source = "unit tests", Tags = "look for tags too", }, @@ -42,6 +42,7 @@ namespace osu.Game.Tests.NonVisual.Filtering [Test] public void TestCriteriaMatchingNoRuleset() { + var exampleBeatmapInfo = getExampleBeatmap(); var criteria = new FilterCriteria(); var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); carouselItem.Filter(criteria); @@ -51,6 +52,7 @@ namespace osu.Game.Tests.NonVisual.Filtering [Test] public void TestCriteriaMatchingSpecificRuleset() { + var exampleBeatmapInfo = getExampleBeatmap(); var criteria = new FilterCriteria { Ruleset = new RulesetInfo { ID = 6 } @@ -63,6 +65,7 @@ namespace osu.Game.Tests.NonVisual.Filtering [Test] public void TestCriteriaMatchingConvertedBeatmaps() { + var exampleBeatmapInfo = getExampleBeatmap(); var criteria = new FilterCriteria { Ruleset = new RulesetInfo { ID = 6 }, @@ -78,6 +81,7 @@ namespace osu.Game.Tests.NonVisual.Filtering [TestCase(false)] public void TestCriteriaMatchingRangeMin(bool inclusive) { + var exampleBeatmapInfo = getExampleBeatmap(); var criteria = new FilterCriteria { Ruleset = new RulesetInfo { ID = 6 }, @@ -98,6 +102,7 @@ namespace osu.Game.Tests.NonVisual.Filtering [TestCase(false)] public void TestCriteriaMatchingRangeMax(bool inclusive) { + var exampleBeatmapInfo = getExampleBeatmap(); var criteria = new FilterCriteria { Ruleset = new RulesetInfo { ID = 6 }, @@ -122,6 +127,7 @@ namespace osu.Game.Tests.NonVisual.Filtering [TestCase("an auteur", true)] public void TestCriteriaMatchingTerms(string terms, bool filtered) { + var exampleBeatmapInfo = getExampleBeatmap(); var criteria = new FilterCriteria { Ruleset = new RulesetInfo { ID = 6 }, @@ -132,5 +138,64 @@ namespace osu.Game.Tests.NonVisual.Filtering carouselItem.Filter(criteria); Assert.AreEqual(filtered, carouselItem.Filtered.Value); } + + [Test] + [TestCase("", false)] + [TestCase("The", false)] + [TestCase("THE", false)] + [TestCase("author", false)] + [TestCase("the author", false)] + [TestCase("the author AND then something else", true)] + [TestCase("unknown", true)] + public void TestCriteriaMatchingCreator(string creatorName, bool filtered) + { + var exampleBeatmapInfo = getExampleBeatmap(); + var criteria = new FilterCriteria + { + Creator = new FilterCriteria.OptionalTextFilter { SearchTerm = creatorName } + }; + var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); + carouselItem.Filter(criteria); + Assert.AreEqual(filtered, carouselItem.Filtered.Value); + } + + [Test] + [TestCase("", false)] + [TestCase("The", false)] + [TestCase("THE", false)] + [TestCase("artist", false)] + [TestCase("the artist", false)] + [TestCase("the artist AND then something else", true)] + [TestCase("unicode too", false)] + [TestCase("unknown", true)] + public void TestCriteriaMatchingArtist(string artistName, bool filtered) + { + var exampleBeatmapInfo = getExampleBeatmap(); + var criteria = new FilterCriteria + { + Artist = new FilterCriteria.OptionalTextFilter { SearchTerm = artistName } + }; + var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); + carouselItem.Filter(criteria); + Assert.AreEqual(filtered, carouselItem.Filtered.Value); + } + + [Test] + [TestCase("", false)] + [TestCase("artist", false)] + [TestCase("unknown", true)] + public void TestCriteriaMatchingArtistWithNullUnicodeName(string artistName, bool filtered) + { + var exampleBeatmapInfo = getExampleBeatmap(); + exampleBeatmapInfo.Metadata.ArtistUnicode = null; + + var criteria = new FilterCriteria + { + Artist = new FilterCriteria.OptionalTextFilter { SearchTerm = artistName } + }; + var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); + carouselItem.Filter(criteria); + Assert.AreEqual(filtered, carouselItem.Filtered.Value); + } } } diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index f98ad1fc43..daab690a84 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -125,5 +125,49 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.AreEqual(BeatmapSetOnlineStatus.Ranked, filterCriteria.OnlineStatus.Max); Assert.IsTrue(filterCriteria.OnlineStatus.IsUpperInclusive); } + + [Test] + public void TestApplyCreatorQueries() + { + const string query = "beatmap specifically by creator=my_fav"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("beatmap specifically by", filterCriteria.SearchText.Trim()); + Assert.AreEqual(3, filterCriteria.SearchTerms.Length); + Assert.AreEqual("my_fav", filterCriteria.Creator.SearchTerm); + } + + [Test] + public void TestApplyArtistQueries() + { + const string query = "find me songs by artist=singer please"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("find me songs by please", filterCriteria.SearchText.Trim()); + Assert.AreEqual(5, filterCriteria.SearchTerms.Length); + Assert.AreEqual("singer", filterCriteria.Artist.SearchTerm); + } + + [Test] + public void TestApplyArtistQueriesWithSpaces() + { + const string query = "really like artist=\"name with space\" yes"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("really like yes", filterCriteria.SearchText.Trim()); + Assert.AreEqual(3, filterCriteria.SearchTerms.Length); + Assert.AreEqual("name with space", filterCriteria.Artist.SearchTerm); + } + + [Test] + public void TestApplyArtistQueriesOneDoubleQuote() + { + const string query = "weird artist=double\"quote"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("weird", filterCriteria.SearchText.Trim()); + Assert.AreEqual(1, filterCriteria.SearchTerms.Length); + Assert.AreEqual("double\"quote", filterCriteria.Artist.SearchTerm); + } } } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 9cc84c8bdd..6c3c9d20f3 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -39,6 +39,10 @@ namespace osu.Game.Screens.Select.Carousel match &= criteria.BeatDivisor.IsInRange(Beatmap.BeatDivisor); match &= criteria.OnlineStatus.IsInRange(Beatmap.Status); + match &= criteria.Creator.Matches(Beatmap.Metadata.AuthorString); + match &= criteria.Artist.Matches(Beatmap.Metadata.Artist) || + criteria.Artist.Matches(Beatmap.Metadata.ArtistUnicode); + if (match) foreach (var criteriaTerm in criteria.SearchTerms) match &= diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 97a7f12724..c2cbac905e 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -23,6 +23,8 @@ namespace osu.Game.Screens.Select public OptionalRange BPM; public OptionalRange BeatDivisor; public OptionalRange OnlineStatus; + public OptionalTextFilter Creator; + public OptionalTextFilter Artist; public string[] SearchTerms = Array.Empty(); @@ -82,5 +84,24 @@ namespace osu.Game.Screens.Select && IsLowerInclusive.Equals(other.IsLowerInclusive) && IsUpperInclusive.Equals(other.IsUpperInclusive); } + + public struct OptionalTextFilter : IEquatable + { + public bool Matches(string value) + { + if (string.IsNullOrEmpty(SearchTerm)) + return true; + + // search term is guaranteed to be non-empty, so if the string we're comparing is empty, it's not matching + if (string.IsNullOrEmpty(value)) + return false; + + return value.IndexOf(SearchTerm, StringComparison.InvariantCultureIgnoreCase) >= 0; + } + + public string SearchTerm; + + public bool Equals(OptionalTextFilter other) => SearchTerm?.Equals(other.SearchTerm) ?? true; + } } } diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index d6d19c8650..b9281c5d6f 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -11,7 +11,7 @@ namespace osu.Game.Screens.Select internal static class FilterQueryParser { private static readonly Regex query_syntax_regex = new Regex( - @"\b(?stars|ar|dr|cs|divisor|length|objects|bpm|status)(?[=:><]+)(?\S*)", + @"\b(?stars|ar|dr|cs|divisor|length|objects|bpm|status|creator|artist)(?[=:><]+)(?("".*"")|(\S*))", RegexOptions.Compiled | RegexOptions.IgnoreCase); internal static void ApplyQueries(FilterCriteria criteria, string query) @@ -61,6 +61,14 @@ namespace osu.Game.Screens.Select case "status" when Enum.TryParse(value, true, out var statusValue): updateCriteriaRange(ref criteria.OnlineStatus, op, statusValue); break; + + case "creator": + updateCriteriaText(ref criteria.Creator, op, value); + break; + + case "artist": + updateCriteriaText(ref criteria.Artist, op, value); + break; } query = query.Replace(match.ToString(), ""); @@ -78,6 +86,17 @@ namespace osu.Game.Screens.Select private static bool parseInt(string value, out int result) => int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out result); + private static void updateCriteriaText(ref FilterCriteria.OptionalTextFilter textFilter, string op, string value) + { + switch (op) + { + case "=": + case ":": + textFilter.SearchTerm = value.Trim('"'); + break; + } + } + private static void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, float value, float tolerance = 0.05f) { updateCriteriaRange(ref range, op, value); From 70842f71f4b54484ea1ef22c19379950c2a01a0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 22 Sep 2019 00:11:13 +0200 Subject: [PATCH 1417/2815] Fix floating point handling in filter intervals Due to floating-point rounding and representation errors, filters could wrongly display results incongruous with the wedge display text (ie. a beatmap with the BPM of 139.99999 would be displayed as having 140 BPM and also pass the bpm<140 filter). Apply tolerance when parsing floating-point constraints. The tolerance chosen is half of what the UI displays for the particular values (so for example half of 0.1 for AR/DR/CS, 0.01 for stars, etc.) Tests updated accordingly. --- .../Filtering/FilterQueryParserTest.cs | 35 ++++++++---- osu.Game/Screens/Select/FilterQueryParser.cs | 56 ++++++++++++++++--- 2 files changed, 70 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index daab690a84..9869ddde41 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -21,6 +21,16 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.AreEqual(4, filterCriteria.SearchTerms.Length); } + /* + * The following tests have been written a bit strangely (they don't check exact + * bound equality with what the filter says). + * This is to account for floating-point arithmetic issues. + * For example, specifying a bpm<140 filter would previously match beatmaps with BPM + * of 139.99999, which would be displayed in the UI as 140. + * Due to this the tests check the last tick inside the range and the first tick + * outside of the range. + */ + [Test] public void TestApplyStarQueries() { @@ -29,8 +39,9 @@ namespace osu.Game.Tests.NonVisual.Filtering FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual("easy", filterCriteria.SearchText.Trim()); Assert.AreEqual(1, filterCriteria.SearchTerms.Length); - Assert.AreEqual(4.0f, filterCriteria.StarDifficulty.Max); - Assert.IsFalse(filterCriteria.StarDifficulty.IsUpperInclusive); + Assert.IsNotNull(filterCriteria.StarDifficulty.Max); + Assert.Greater(filterCriteria.StarDifficulty.Max, 3.99d); + Assert.Less(filterCriteria.StarDifficulty.Max, 4.00d); Assert.IsNull(filterCriteria.StarDifficulty.Min); } @@ -42,8 +53,9 @@ namespace osu.Game.Tests.NonVisual.Filtering FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual("difficult", filterCriteria.SearchText.Trim()); Assert.AreEqual(1, filterCriteria.SearchTerms.Length); - Assert.AreEqual(9.0f, filterCriteria.ApproachRate.Min); - Assert.IsTrue(filterCriteria.ApproachRate.IsLowerInclusive); + Assert.IsNotNull(filterCriteria.ApproachRate.Min); + Assert.Greater(filterCriteria.ApproachRate.Min, 8.9f); + Assert.Less(filterCriteria.ApproachRate.Min, 9.0f); Assert.IsNull(filterCriteria.ApproachRate.Max); } @@ -55,10 +67,10 @@ namespace osu.Game.Tests.NonVisual.Filtering FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual("quite specific", filterCriteria.SearchText.Trim()); Assert.AreEqual(2, filterCriteria.SearchTerms.Length); - Assert.AreEqual(2.0f, filterCriteria.DrainRate.Min); - Assert.IsFalse(filterCriteria.DrainRate.IsLowerInclusive); - Assert.AreEqual(6.0f, filterCriteria.DrainRate.Max); - Assert.IsTrue(filterCriteria.DrainRate.IsUpperInclusive); + Assert.Greater(filterCriteria.DrainRate.Min, 2.0f); + Assert.Less(filterCriteria.DrainRate.Min, 2.1f); + Assert.Greater(filterCriteria.DrainRate.Max, 6.0f); + Assert.Less(filterCriteria.DrainRate.Min, 6.1f); } [Test] @@ -69,8 +81,9 @@ namespace osu.Game.Tests.NonVisual.Filtering FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual("gotta go fast", filterCriteria.SearchText.Trim()); Assert.AreEqual(3, filterCriteria.SearchTerms.Length); - Assert.AreEqual(200d, filterCriteria.BPM.Min); - Assert.IsTrue(filterCriteria.BPM.IsLowerInclusive); + Assert.IsNotNull(filterCriteria.BPM.Min); + Assert.Greater(filterCriteria.BPM.Min, 199.99d); + Assert.Less(filterCriteria.BPM.Min, 200.00d); Assert.IsNull(filterCriteria.BPM.Max); } @@ -93,9 +106,7 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.AreEqual("time", filterCriteria.SearchText.Trim()); Assert.AreEqual(1, filterCriteria.SearchTerms.Length); Assert.AreEqual(expectedLength.TotalMilliseconds - scale.TotalMilliseconds / 2.0, filterCriteria.Length.Min); - Assert.IsTrue(filterCriteria.Length.IsLowerInclusive); Assert.AreEqual(expectedLength.TotalMilliseconds + scale.TotalMilliseconds / 2.0, filterCriteria.Length.Max); - Assert.IsTrue(filterCriteria.Length.IsUpperInclusive); } [Test] diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index b9281c5d6f..3ee704201e 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -25,23 +25,23 @@ namespace osu.Game.Screens.Select switch (key) { case "stars" when parseFloatWithPoint(value, out var stars): - updateCriteriaRange(ref criteria.StarDifficulty, op, stars); + updateCriteriaRange(ref criteria.StarDifficulty, op, stars, 0.01f / 2); break; case "ar" when parseFloatWithPoint(value, out var ar): - updateCriteriaRange(ref criteria.ApproachRate, op, ar); + updateCriteriaRange(ref criteria.ApproachRate, op, ar, 0.1f / 2); break; case "dr" when parseFloatWithPoint(value, out var dr): - updateCriteriaRange(ref criteria.DrainRate, op, dr); + updateCriteriaRange(ref criteria.DrainRate, op, dr, 0.1f / 2); break; case "cs" when parseFloatWithPoint(value, out var cs): - updateCriteriaRange(ref criteria.CircleSize, op, cs); + updateCriteriaRange(ref criteria.CircleSize, op, cs, 0.1f / 2); break; case "bpm" when parseDoubleWithPoint(value, out var bpm): - updateCriteriaRange(ref criteria.BPM, op, bpm); + updateCriteriaRange(ref criteria.BPM, op, bpm, 0.01d / 2); break; case "length" when parseDoubleWithPoint(value.TrimEnd('m', 's', 'h'), out var length): @@ -99,29 +99,67 @@ namespace osu.Game.Screens.Select private static void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, float value, float tolerance = 0.05f) { - updateCriteriaRange(ref range, op, value); - switch (op) { + default: + return; + case "=": case ":": range.Min = value - tolerance; range.Max = value + tolerance; break; + + case ">": + range.Min = value + tolerance; + break; + + case ">=": + case ">:": + range.Min = value - tolerance; + break; + + case "<": + range.Max = value - tolerance; + break; + + case "<=": + case "<:": + range.Max = value + tolerance; + break; } } private static void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, double value, double tolerance = 0.05) { - updateCriteriaRange(ref range, op, value); - switch (op) { + default: + return; + case "=": case ":": range.Min = value - tolerance; range.Max = value + tolerance; break; + + case ">": + range.Min = value + tolerance; + break; + + case ">=": + case ">:": + range.Min = value - tolerance; + break; + + case "<": + range.Max = value - tolerance; + break; + + case "<=": + case "<:": + range.Max = value + tolerance; + break; } } From fc1d49631a934437b24ccab3014aa31f2c06f579 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 22 Sep 2019 20:30:58 +0900 Subject: [PATCH 1418/2815] Allow top-level menu key pressed to progress the osu! logo --- osu.Game/Screens/Menu/Button.cs | 7 ++++--- osu.Game/Screens/Menu/ButtonSystem.cs | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs index 1bf25a2504..ffeadb96c7 100644 --- a/osu.Game/Screens/Menu/Button.cs +++ b/osu.Game/Screens/Menu/Button.cs @@ -31,6 +31,8 @@ namespace osu.Game.Screens.Menu { public event Action StateChanged; + public readonly Key TriggerKey; + private readonly Container iconText; private readonly Container box; private readonly Box boxHoverLayer; @@ -43,7 +45,6 @@ namespace osu.Game.Screens.Menu public ButtonSystemState VisibleState = ButtonSystemState.TopLevel; private readonly Action clickAction; - private readonly Key triggerKey; private SampleChannel sampleClick; private SampleChannel sampleHover; @@ -53,7 +54,7 @@ namespace osu.Game.Screens.Menu { this.sampleName = sampleName; this.clickAction = clickAction; - this.triggerKey = triggerKey; + TriggerKey = triggerKey; AutoSizeAxes = Axes.Both; Alpha = 0; @@ -210,7 +211,7 @@ namespace osu.Game.Screens.Menu if (e.Repeat || e.ControlPressed || e.ShiftPressed || e.AltPressed) return false; - if (triggerKey == e.Key && triggerKey != Key.Unknown) + if (TriggerKey == e.Key && TriggerKey != Key.Unknown) { trigger(); return true; diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 1a3e1213b4..0dee478a4c 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Threading; @@ -180,6 +181,20 @@ namespace osu.Game.Screens.Menu State = ButtonSystemState.Initial; } + protected override bool OnKeyDown(KeyDownEvent e) + { + if (State == ButtonSystemState.Initial) + { + if (buttonsTopLevel.Any(b => e.PressedKeys.Contains(b.TriggerKey))) + { + logo.Click(); + return true; + } + } + + return base.OnKeyDown(e); + } + public bool OnPressed(GlobalAction action) { switch (action) From e5b14ce74de34d23ce42913a3c24152d75022866 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 22 Sep 2019 21:42:32 +0900 Subject: [PATCH 1419/2815] Add null check for safety Co-Authored-By: Salman Ahmed --- osu.Game/Screens/Menu/ButtonSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 0dee478a4c..7ac6b5c696 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -187,7 +187,7 @@ namespace osu.Game.Screens.Menu { if (buttonsTopLevel.Any(b => e.PressedKeys.Contains(b.TriggerKey))) { - logo.Click(); + logo?.Click(); return true; } } From 3b52e7c72408555fbf6d42d988033ef30bac7f0e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 22 Sep 2019 02:10:04 +0900 Subject: [PATCH 1420/2815] Add boilerplate logic --- .../Screens/TestSceneSetupScreen.cs | 17 +++++++++++++++++ osu.Game.Tournament/Screens/SetupScreen.cs | 9 +++++++++ osu.Game.Tournament/TournamentSceneManager.cs | 2 ++ 3 files changed, 28 insertions(+) create mode 100644 osu.Game.Tournament.Tests/Screens/TestSceneSetupScreen.cs create mode 100644 osu.Game.Tournament/Screens/SetupScreen.cs diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneSetupScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneSetupScreen.cs new file mode 100644 index 0000000000..650b4c5412 --- /dev/null +++ b/osu.Game.Tournament.Tests/Screens/TestSceneSetupScreen.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.Allocation; +using osu.Game.Tournament.Screens; + +namespace osu.Game.Tournament.Tests.Screens +{ + public class TestSceneSetupScreen : TournamentTestScene + { + [BackgroundDependencyLoader] + private void load() + { + Add(new SetupScreen()); + } + } +} diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs new file mode 100644 index 0000000000..a5e0e5927a --- /dev/null +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -0,0 +1,9 @@ +// 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.Tournament.Screens +{ + public class SetupScreen : TournamentScreen + { + } +} diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index 4c255be463..b1384023d3 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -106,6 +106,8 @@ namespace osu.Game.Tournament Direction = FillDirection.Vertical, Children = new Drawable[] { + new OsuButton { RelativeSizeAxes = Axes.X, Text = "Setup", Action = () => SetScreen(typeof(SetupScreen)) }, + new Container { RelativeSizeAxes = Axes.X, Height = 50 }, new OsuButton { RelativeSizeAxes = Axes.X, Text = "Team Editor", Action = () => SetScreen(typeof(TeamEditorScreen)) }, new OsuButton { RelativeSizeAxes = Axes.X, Text = "Rounds Editor", Action = () => SetScreen(typeof(RoundEditorScreen)) }, new OsuButton { RelativeSizeAxes = Axes.X, Text = "Bracket Editor", Action = () => SetScreen(typeof(LadderEditorScreen)) }, From 47a89231ad3da90ca774fb524a75cd61c68a4bbd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 22 Sep 2019 04:15:02 +0900 Subject: [PATCH 1421/2815] Read from (and allow reloading) IPC source --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 186 +++++++++++---------- osu.Game.Tournament/Screens/SetupScreen.cs | 15 ++ 2 files changed, 117 insertions(+), 84 deletions(-) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 4fd858bd12..e05d96e098 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Platform.Windows; +using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Legacy; using osu.Game.Online.API; @@ -26,103 +27,120 @@ namespace osu.Game.Tournament.IPC [Resolved] protected RulesetStore Rulesets { get; private set; } + [Resolved] + private GameHost host { get; set; } + + [Resolved] + private LadderInfo ladder { get; set; } + private int lastBeatmapId; + private ScheduledDelegate scheduled; + + public Storage Storage { get; private set; } [BackgroundDependencyLoader] - private void load(LadderInfo ladder, GameHost host) + private void load() { - StableStorage stable; + LocateStableStorage(); + } + + public Storage LocateStableStorage() + { + scheduled?.Cancel(); + + Storage = null; try { - stable = new StableStorage(host as DesktopGameHost); + Storage = new StableStorage(host as DesktopGameHost); + + const string file_ipc_filename = "ipc.txt"; + const string file_ipc_state_filename = "ipc-state.txt"; + const string file_ipc_scores_filename = "ipc-scores.txt"; + const string file_ipc_channel_filename = "ipc-channel.txt"; + + if (Storage.Exists(file_ipc_filename)) + scheduled = Scheduler.AddDelayed(delegate + { + try + { + using (var stream = Storage.GetStream(file_ipc_filename)) + using (var sr = new StreamReader(stream)) + { + var beatmapId = int.Parse(sr.ReadLine()); + var mods = int.Parse(sr.ReadLine()); + + if (lastBeatmapId != beatmapId) + { + lastBeatmapId = beatmapId; + + var existing = ladder.CurrentMatch.Value?.Round.Value?.Beatmaps.FirstOrDefault(b => b.ID == beatmapId && b.BeatmapInfo != null); + + if (existing != null) + Beatmap.Value = existing.BeatmapInfo; + else + { + var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = beatmapId }); + req.Success += b => Beatmap.Value = b.ToBeatmap(Rulesets); + API.Queue(req); + } + } + + Mods.Value = (LegacyMods)mods; + } + } + catch + { + // file might be in use. + } + + try + { + using (var stream = Storage.GetStream(file_ipc_channel_filename)) + using (var sr = new StreamReader(stream)) + { + ChatChannel.Value = sr.ReadLine(); + } + } + catch (Exception) + { + // file might be in use. + } + + try + { + using (var stream = Storage.GetStream(file_ipc_state_filename)) + using (var sr = new StreamReader(stream)) + { + State.Value = (TourneyState)Enum.Parse(typeof(TourneyState), sr.ReadLine()); + } + } + catch (Exception) + { + // file might be in use. + } + + try + { + using (var stream = Storage.GetStream(file_ipc_scores_filename)) + using (var sr = new StreamReader(stream)) + { + Score1.Value = int.Parse(sr.ReadLine()); + Score2.Value = int.Parse(sr.ReadLine()); + } + } + catch (Exception) + { + // file might be in use. + } + }, 250, true); } catch (Exception e) { Logger.Error(e, "Stable installation could not be found; disabling file based IPC"); - return; } - const string file_ipc_filename = "ipc.txt"; - const string file_ipc_state_filename = "ipc-state.txt"; - const string file_ipc_scores_filename = "ipc-scores.txt"; - const string file_ipc_channel_filename = "ipc-channel.txt"; - - if (stable.Exists(file_ipc_filename)) - Scheduler.AddDelayed(delegate - { - try - { - using (var stream = stable.GetStream(file_ipc_filename)) - using (var sr = new StreamReader(stream)) - { - var beatmapId = int.Parse(sr.ReadLine()); - var mods = int.Parse(sr.ReadLine()); - - if (lastBeatmapId != beatmapId) - { - lastBeatmapId = beatmapId; - - var existing = ladder.CurrentMatch.Value?.Round.Value?.Beatmaps.FirstOrDefault(b => b.ID == beatmapId && b.BeatmapInfo != null); - - if (existing != null) - Beatmap.Value = existing.BeatmapInfo; - else - { - var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = beatmapId }); - req.Success += b => Beatmap.Value = b.ToBeatmap(Rulesets); - API.Queue(req); - } - } - - Mods.Value = (LegacyMods)mods; - } - } - catch - { - // file might be in use. - } - - try - { - using (var stream = stable.GetStream(file_ipc_channel_filename)) - using (var sr = new StreamReader(stream)) - { - ChatChannel.Value = sr.ReadLine(); - } - } - catch (Exception) - { - // file might be in use. - } - - try - { - using (var stream = stable.GetStream(file_ipc_state_filename)) - using (var sr = new StreamReader(stream)) - { - State.Value = (TourneyState)Enum.Parse(typeof(TourneyState), sr.ReadLine()); - } - } - catch (Exception) - { - // file might be in use. - } - - try - { - using (var stream = stable.GetStream(file_ipc_scores_filename)) - using (var sr = new StreamReader(stream)) - { - Score1.Value = int.Parse(sr.ReadLine()); - Score2.Value = int.Parse(sr.ReadLine()); - } - } - catch (Exception) - { - // file might be in use. - } - }, 250, true); + return Storage; } /// diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index a5e0e5927a..b75b0056ce 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -1,9 +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.Framework.Allocation; +using osu.Framework.Graphics.Sprites; +using osu.Game.Tournament.IPC; + namespace osu.Game.Tournament.Screens { public class SetupScreen : TournamentScreen { + [Resolved] + private MatchIPCInfo ipc { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + AddInternal(new SpriteText + { + Text = (ipc as FileBasedIPC)?.Storage.GetFullPath(string.Empty) + }); + } } } From 96c0c80dc58622af64cba3b020b2434b60132bb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 22 Sep 2019 21:20:50 +0200 Subject: [PATCH 1422/2815] Factor out methods in FilterQueryParser Factor FilterQueryParser.ApplyQueries into shorter methods to reduce method complexity. --- osu.Game/Screens/Select/FilterQueryParser.cs | 102 ++++++++++--------- 1 file changed, 54 insertions(+), 48 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 3ee704201e..ffe1258168 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -22,54 +22,7 @@ namespace osu.Game.Screens.Select var op = match.Groups["op"].Value; var value = match.Groups["value"].Value; - switch (key) - { - case "stars" when parseFloatWithPoint(value, out var stars): - updateCriteriaRange(ref criteria.StarDifficulty, op, stars, 0.01f / 2); - break; - - case "ar" when parseFloatWithPoint(value, out var ar): - updateCriteriaRange(ref criteria.ApproachRate, op, ar, 0.1f / 2); - break; - - case "dr" when parseFloatWithPoint(value, out var dr): - updateCriteriaRange(ref criteria.DrainRate, op, dr, 0.1f / 2); - break; - - case "cs" when parseFloatWithPoint(value, out var cs): - updateCriteriaRange(ref criteria.CircleSize, op, cs, 0.1f / 2); - break; - - case "bpm" when parseDoubleWithPoint(value, out var bpm): - updateCriteriaRange(ref criteria.BPM, op, bpm, 0.01d / 2); - break; - - case "length" when parseDoubleWithPoint(value.TrimEnd('m', 's', 'h'), out var length): - var scale = - value.EndsWith("ms") ? 1 : - value.EndsWith("s") ? 1000 : - value.EndsWith("m") ? 60000 : - value.EndsWith("h") ? 3600000 : 1000; - - updateCriteriaRange(ref criteria.Length, op, length * scale, scale / 2.0); - break; - - case "divisor" when parseInt(value, out var divisor): - updateCriteriaRange(ref criteria.BeatDivisor, op, divisor); - break; - - case "status" when Enum.TryParse(value, true, out var statusValue): - updateCriteriaRange(ref criteria.OnlineStatus, op, statusValue); - break; - - case "creator": - updateCriteriaText(ref criteria.Creator, op, value); - break; - - case "artist": - updateCriteriaText(ref criteria.Artist, op, value); - break; - } + parseKeywordCriteria(criteria, key, value, op); query = query.Replace(match.ToString(), ""); } @@ -77,6 +30,59 @@ namespace osu.Game.Screens.Select criteria.SearchText = query; } + private static void parseKeywordCriteria(FilterCriteria criteria, string key, string value, string op) + { + switch (key) + { + case "stars" when parseFloatWithPoint(value, out var stars): + updateCriteriaRange(ref criteria.StarDifficulty, op, stars, 0.01f / 2); + break; + + case "ar" when parseFloatWithPoint(value, out var ar): + updateCriteriaRange(ref criteria.ApproachRate, op, ar, 0.1f / 2); + break; + + case "dr" when parseFloatWithPoint(value, out var dr): + updateCriteriaRange(ref criteria.DrainRate, op, dr, 0.1f / 2); + break; + + case "cs" when parseFloatWithPoint(value, out var cs): + updateCriteriaRange(ref criteria.CircleSize, op, cs, 0.1f / 2); + break; + + case "bpm" when parseDoubleWithPoint(value, out var bpm): + updateCriteriaRange(ref criteria.BPM, op, bpm, 0.01d / 2); + break; + + case "length" when parseDoubleWithPoint(value.TrimEnd('m', 's', 'h'), out var length): + var scale = getLengthScale(value); + updateCriteriaRange(ref criteria.Length, op, length * scale, scale / 2.0); + break; + + case "divisor" when parseInt(value, out var divisor): + updateCriteriaRange(ref criteria.BeatDivisor, op, divisor); + break; + + case "status" when Enum.TryParse(value, true, out var statusValue): + updateCriteriaRange(ref criteria.OnlineStatus, op, statusValue); + break; + + case "creator": + updateCriteriaText(ref criteria.Creator, op, value); + break; + + case "artist": + updateCriteriaText(ref criteria.Artist, op, value); + break; + } + } + + private static int getLengthScale(string value) => + value.EndsWith("ms") ? 1 : + value.EndsWith("s") ? 1000 : + value.EndsWith("m") ? 60000 : + value.EndsWith("h") ? 3600000 : 1000; + private static bool parseFloatWithPoint(string value, out float result) => float.TryParse(value, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out result); From e07aa94fc8701f0a2807ef04cbca20d26f75566c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Sep 2019 04:22:50 +0900 Subject: [PATCH 1423/2815] Allow reloading ipc source --- osu.Game.Tournament/Screens/SetupScreen.cs | 84 ++++++++++++++++++++-- 1 file changed, 80 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index b75b0056ce..992762431e 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -1,9 +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 osu.Framework.Allocation; -using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Edit.Setup.Components.LabelledComponents; using osu.Game.Tournament.IPC; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Tournament.Screens { @@ -15,10 +22,79 @@ namespace osu.Game.Tournament.Screens [BackgroundDependencyLoader] private void load() { - AddInternal(new SpriteText + reload(); + } + + private void reload() + { + var fileBasedIpc = ipc as FileBasedIPC; + + InternalChildren = new Drawable[] { - Text = (ipc as FileBasedIPC)?.Storage.GetFullPath(string.Empty) - }); + new ActionableInfo + { + Label = "Current IPC source", + ButtonText = "Refresh", + Action = () => + { + fileBasedIpc?.LocateStableStorage(); + reload(); + }, + Value = fileBasedIpc?.Storage?.GetFullPath(string.Empty) ?? "Not found", + Failing = fileBasedIpc?.Storage == null, + Description = "The osu!stable installation which is currently being used as a data source. If a source is not found, make sure you have created an empty ipc.txt in your stable cutting-edge installation, and that it is registered as the default osu! install." + } + }; + } + + private class ActionableInfo : LabelledComponent + { + private OsuButton button; + + public ActionableInfo() + : base(true) + { + } + + public string ButtonText + { + set => button.Text = value; + } + + public string Value + { + set => valueText.Text = value; + } + + public bool Failing + { + set => valueText.Colour = value ? Color4.Red : Color4.White; + } + + public Action Action; + + private OsuSpriteText valueText; + + protected override Drawable CreateComponent() => new Container + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Children = new Drawable[] + { + valueText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + button = new TriangleButton + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Size = new Vector2(100, 30), + Action = () => Action?.Invoke() + }, + } + }; } } } From b41ac543c5a4a4d31e783a4c802f80d25871a427 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Sep 2019 04:45:23 +0900 Subject: [PATCH 1424/2815] Allow changing logged in user --- osu.Game.Tournament/Screens/SetupScreen.cs | 44 ++++++++++++++++++- osu.Game.Tournament/TournamentSceneManager.cs | 1 + .../Sections/General/LoginSettings.cs | 4 +- 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 992762431e..8ccb469b13 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -7,6 +7,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Overlays; using osu.Game.Screens.Edit.Setup.Components.LabelledComponents; using osu.Game.Tournament.IPC; using osuTK; @@ -16,12 +18,29 @@ namespace osu.Game.Tournament.Screens { public class SetupScreen : TournamentScreen { + private FillFlowContainer fillFlow; + + private LoginOverlay loginOverlay; + [Resolved] private MatchIPCInfo ipc { get; set; } + [Resolved] + private IAPIProvider api { get; set; } + [BackgroundDependencyLoader] private void load() { + InternalChild = fillFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = new MarginPadding(10), + Spacing = new Vector2(10), + }; + + api.LocalUser.BindValueChanged(_ => Schedule(reload)); reload(); } @@ -29,7 +48,7 @@ namespace osu.Game.Tournament.Screens { var fileBasedIpc = ipc as FileBasedIPC; - InternalChildren = new Drawable[] + fillFlow.Children = new Drawable[] { new ActionableInfo { @@ -43,6 +62,29 @@ namespace osu.Game.Tournament.Screens Value = fileBasedIpc?.Storage?.GetFullPath(string.Empty) ?? "Not found", Failing = fileBasedIpc?.Storage == null, Description = "The osu!stable installation which is currently being used as a data source. If a source is not found, make sure you have created an empty ipc.txt in your stable cutting-edge installation, and that it is registered as the default osu! install." + }, + new ActionableInfo + { + Label = "Current User", + ButtonText = "Change Login", + Action = () => + { + api.Logout(); + + if (loginOverlay == null) + { + AddInternal(loginOverlay = new LoginOverlay() + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }); + } + + loginOverlay.State.Value = Visibility.Visible; + }, + Value = api?.LocalUser.Value.Username, + Failing = api?.IsLoggedIn != true, + Description = "In order to access the API and display metadata, a login is required." } }; } diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index b1384023d3..58c6e3fee6 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -69,6 +69,7 @@ namespace osu.Game.Tournament RelativeSizeAxes = Axes.Both, Children = new Drawable[] { + new SetupScreen(), new ScheduleScreen(), new LadderScreen(), new LadderEditorScreen(), diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index 66fec1ecf9..b02b1a5489 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -67,7 +67,7 @@ namespace osu.Game.Overlays.Settings.Sections.General api?.Register(this); } - public void APIStateChanged(IAPIProvider api, APIState state) + public void APIStateChanged(IAPIProvider api, APIState state) => Schedule(() => { form = null; @@ -184,7 +184,7 @@ namespace osu.Game.Overlays.Settings.Sections.General } if (form != null) GetContainingInputManager()?.ChangeFocus(form); - } + }); public override bool AcceptsFocus => true; From 4b7a42119119bbfdb03f7a458572a938714fdd30 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Sep 2019 04:47:51 +0900 Subject: [PATCH 1425/2815] Set setup screen as default when opening --- osu.Game.Tournament/TournamentSceneManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index 58c6e3fee6..02ee1c8603 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -130,7 +130,7 @@ namespace osu.Game.Tournament }, }; - SetScreen(typeof(ScheduleScreen)); + SetScreen(typeof(SetupScreen)); } public void SetScreen(Type screenType) From bafb429e9b6ae8ddaf120089f0bf6e845d7c3fc2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Sep 2019 04:49:21 +0900 Subject: [PATCH 1426/2815] Don't show video background --- osu.Game.Tournament/Screens/SetupScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 8ccb469b13..1cb4917790 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -16,7 +16,7 @@ using osuTK.Graphics; namespace osu.Game.Tournament.Screens { - public class SetupScreen : TournamentScreen + public class SetupScreen : TournamentScreen, IProvideVideo { private FillFlowContainer fillFlow; @@ -73,7 +73,7 @@ namespace osu.Game.Tournament.Screens if (loginOverlay == null) { - AddInternal(loginOverlay = new LoginOverlay() + AddInternal(loginOverlay = new LoginOverlay { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, From c92545e294d61ad151aac2197d1bb0882bf8d73d Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 23 Sep 2019 01:41:59 +0200 Subject: [PATCH 1427/2815] Update osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs Co-Authored-By: Salman Ahmed --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index dc099b14b9..36c8ecf020 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Gameplay /// Sets the input manager child to a new test player loader container instance. /// /// If the test player should behave like the production one. - public void ResetPlayer(bool interactive) + private void resetPlayer(bool interactive) { player = new TestPlayer(interactive, interactive); loader = new TestPlayerLoader(() => player); From e3e245ab20456afd2cf5a4b700a103c73ff9b79f Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 23 Sep 2019 08:15:27 +0300 Subject: [PATCH 1428/2815] Introduce SessionStatics --- osu.Game/Configuration/SessionStatics.cs | 21 +++++++++++++++++++++ osu.Game/OsuGameBase.cs | 1 + 2 files changed, 22 insertions(+) create mode 100644 osu.Game/Configuration/SessionStatics.cs diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs new file mode 100644 index 0000000000..9afb1bda36 --- /dev/null +++ b/osu.Game/Configuration/SessionStatics.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.Framework.Configuration; + +namespace osu.Game.Configuration +{ + public class SessionStatics : ConfigManager + { + // This is an in-memory store. + protected override void PerformLoad() + { + } + + protected override bool PerformSave() => true; + } + + public enum Statics + { + } +} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index d6b8ad3e67..b79de0aa94 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -189,6 +189,7 @@ namespace osu.Game dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore)); + dependencies.Cache(new SessionStatics()); dependencies.Cache(new OsuColour()); fileImporters.Add(BeatmapManager); From 5024770544999feda8849f7463102b1db5fe2283 Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Mon, 23 Sep 2019 20:52:44 +0800 Subject: [PATCH 1429/2815] move common logic to IntroScreen --- osu.Game/Screens/Menu/IntroCircles.cs | 74 ++++++----------------- osu.Game/Screens/Menu/IntroScreen.cs | 80 +++++++++++++++++++++++-- osu.Game/Screens/Menu/IntroTriangles.cs | 65 ++++---------------- 3 files changed, 104 insertions(+), 115 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroCircles.cs b/osu.Game/Screens/Menu/IntroCircles.cs index c069f82134..d5d7f5cb7a 100644 --- a/osu.Game/Screens/Menu/IntroCircles.cs +++ b/osu.Game/Screens/Menu/IntroCircles.cs @@ -2,86 +2,46 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Audio.Sample; -using osu.Framework.Audio.Track; -using osu.Framework.Bindables; +using osu.Framework.Audio; using osu.Framework.Screens; using osu.Framework.Graphics; -using osu.Framework.MathUtils; -using osu.Game.Beatmaps; -using osu.Game.Configuration; -using osu.Game.IO.Archives; namespace osu.Game.Screens.Menu { public class IntroCircles : IntroScreen { - private const string menu_music_beatmap_hash = "3c8b1fcc9434dbb29e2fb613d3b9eada9d7bb6c125ceb32396c3b53437280c83"; + protected override string BeatmapHash => "3c8b1fcc9434dbb29e2fb613d3b9eada9d7bb6c125ceb32396c3b53437280c83"; - private SampleChannel welcome; - - private Bindable menuMusic; - - private Track track; - - private WorkingBeatmap introBeatmap; - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game, ISampleStore samples) - { - menuMusic = config.GetBindable(OsuSetting.MenuMusic); - - BeatmapSetInfo setInfo = null; - - if (!menuMusic.Value) - { - var sets = beatmaps.GetAllUsableBeatmapSets(); - if (sets.Count > 0) - setInfo = beatmaps.QueryBeatmapSet(s => s.ID == sets[RNG.Next(0, sets.Count - 1)].ID); - } - - if (setInfo == null) - { - setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == menu_music_beatmap_hash); - - if (setInfo == null) - { - // we need to import the default menu background beatmap - setInfo = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream(@"Tracks/circles.osz"), "circles.osz")).Result; - - setInfo.Protected = true; - beatmaps.Update(setInfo); - } - } - - introBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); - track = introBeatmap.Track; - - if (config.Get(OsuSetting.MenuVoice)) - welcome = samples.Get(@"welcome"); - } + protected override string BeatmapFile => "circles.osz"; private const double delay_step_one = 2300; private const double delay_step_two = 600; + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + if (MenuVoice.Value) + SetWelcome(); + } + protected override void LogoArriving(OsuLogo logo, bool resuming) { base.LogoArriving(logo, resuming); if (!resuming) { - Beatmap.Value = introBeatmap; - introBeatmap = null; + Beatmap.Value = IntroBeatmap; + IntroBeatmap = null; - welcome?.Play(); + Welcome?.Play(); Scheduler.AddDelayed(delegate { // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu. - if (menuMusic.Value) + if (MenuMusic.Value) { - track.Restart(); - track = null; + Track.Restart(); + Track = null; } PrepareMenuLoad(); @@ -97,7 +57,7 @@ namespace osu.Game.Screens.Menu public override void OnSuspending(IScreen next) { - track = null; + Track = null; this.FadeOut(300); base.OnSuspending(next); diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 4d0f7ff87a..651fa7583d 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -4,12 +4,19 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.MathUtils; using osu.Framework.Screens; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.IO.Archives; using osu.Game.Screens.Backgrounds; +using osu.Game.Skinning; +using osu.Game.Online.API; +using osu.Game.Users; using osuTK; using osuTK.Graphics; @@ -17,6 +24,10 @@ namespace osu.Game.Screens.Menu { public abstract class IntroScreen : StartupScreen { + protected abstract string BeatmapHash { get; } + + protected abstract string BeatmapFile { get; } + private readonly BindableDouble exitingVolumeFade = new BindableDouble(1); public const int EXIT_DELAY = 3000; @@ -24,24 +35,83 @@ namespace osu.Game.Screens.Menu [Resolved] private AudioManager audio { get; set; } + protected SampleChannel Welcome; + private SampleChannel seeya; - private Bindable menuVoice; + protected Bindable MenuVoice; + + protected Bindable MenuMusic; + + protected Track Track; + + protected WorkingBeatmap IntroBeatmap; private LeasedBindable beatmap; public new Bindable Beatmap => beatmap; + protected Bindable User; + + protected Bindable Skin; + protected override BackgroundScreen CreateBackground() => new BackgroundScreenBlack(); [BackgroundDependencyLoader] - private void load(OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game) + private void load(OsuConfigManager config, IAPIProvider api, SkinManager skinManager, BeatmapManager beatmaps, Framework.Game game) { // prevent user from changing beatmap while the intro is still runnning. beatmap = base.Beatmap.BeginLease(false); - menuVoice = config.GetBindable(OsuSetting.MenuVoice); - seeya = audio.Samples.Get(@"seeya"); + MenuVoice = config.GetBindable(OsuSetting.MenuVoice); + MenuMusic = config.GetBindable(OsuSetting.MenuMusic); + + User = api.LocalUser.GetBoundCopy(); + Skin = skinManager.CurrentSkin.GetBoundCopy(); + + Skin.BindValueChanged(_ => updateSeeya(), true); + + BeatmapSetInfo setInfo = null; + + if (!MenuMusic.Value) + { + var sets = beatmaps.GetAllUsableBeatmapSets(); + if (sets.Count > 0) + setInfo = beatmaps.QueryBeatmapSet(s => s.ID == sets[RNG.Next(0, sets.Count - 1)].ID); + } + + if (setInfo == null) + { + setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == BeatmapHash); + + if (setInfo == null) + { + // we need to import the default menu background beatmap + setInfo = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).Result; + + setInfo.Protected = true; + beatmaps.Update(setInfo); + } + } + + IntroBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); + Track = IntroBeatmap.Track; + } + + private void updateSeeya() + { + if (User.Value?.IsSupporter ?? false) + seeya = Skin.Value.GetSample(new SampleInfo("seeya")) ?? audio.Samples.Get(@"seeya"); + else + seeya = audio.Samples.Get(@"seeya"); + } + + protected void SetWelcome() + { + if (User.Value?.IsSupporter ?? false) + Welcome = Skin.Value.GetSample(new SampleInfo("welcome")) ?? audio.Samples.Get(@"welcome"); + else + Welcome = audio.Samples.Get(@"welcome"); } /// @@ -61,7 +131,7 @@ namespace osu.Game.Screens.Menu double fadeOutTime = EXIT_DELAY; //we also handle the exit transition. - if (menuVoice.Value) + if (MenuVoice.Value) seeya.Play(); else fadeOutTime = 500; diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index db970dd76e..77700900a8 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -6,9 +6,6 @@ using System.Collections.Generic; using System.IO; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Audio.Sample; -using osu.Framework.Audio.Track; -using osu.Framework.Bindables; using osu.Framework.Screens; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -17,12 +14,9 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Video; using osu.Framework.MathUtils; using osu.Framework.Timing; -using osu.Game.Beatmaps; -using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.IO.Archives; using osu.Game.Rulesets; using osu.Game.Screens.Backgrounds; using osuTK; @@ -32,9 +26,9 @@ namespace osu.Game.Screens.Menu { public class IntroTriangles : IntroScreen { - private const string menu_music_beatmap_hash = "a1556d0801b3a6b175dda32ef546f0ec812b400499f575c44fccbe9c67f9b1e5"; + protected override string BeatmapHash => "a1556d0801b3a6b175dda32ef546f0ec812b400499f575c44fccbe9c67f9b1e5"; - private SampleChannel welcome; + protected override string BeatmapFile => "triangles.osz"; protected override BackgroundScreen CreateBackground() => background = new BackgroundScreenDefault(false) { @@ -44,48 +38,13 @@ namespace osu.Game.Screens.Menu [Resolved] private AudioManager audio { get; set; } - private Bindable menuMusic; - private Track track; - private WorkingBeatmap introBeatmap; - private BackgroundScreenDefault background; [BackgroundDependencyLoader] - private void load(OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game) + private void load(AudioManager audio) { - menuMusic = config.GetBindable(OsuSetting.MenuMusic); - - BeatmapSetInfo setInfo = null; - - if (!menuMusic.Value) - { - var sets = beatmaps.GetAllUsableBeatmapSets(); - if (sets.Count > 0) - setInfo = beatmaps.QueryBeatmapSet(s => s.ID == sets[RNG.Next(0, sets.Count - 1)].ID); - } - - if (setInfo == null) - { - setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == menu_music_beatmap_hash); - - if (setInfo == null) - { - // we need to import the default menu background beatmap - setInfo = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream(@"Tracks/triangles.osz"), "triangles.osz")).Result; - - setInfo.Protected = true; - beatmaps.Update(setInfo); - } - } - - introBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); - - track = introBeatmap.Track; - track.Reset(); - - if (config.Get(OsuSetting.MenuVoice) && !menuMusic.Value) - // triangles has welcome sound included in the track. only play this if the user doesn't want menu music. - welcome = audio.Samples.Get(@"welcome"); + if (MenuVoice.Value && !MenuMusic.Value) + SetWelcome(); } protected override void LogoArriving(OsuLogo logo, bool resuming) @@ -96,24 +55,24 @@ namespace osu.Game.Screens.Menu if (!resuming) { - Beatmap.Value = introBeatmap; - introBeatmap = null; + Beatmap.Value = IntroBeatmap; + IntroBeatmap = null; PrepareMenuLoad(); LoadComponentAsync(new TrianglesIntroSequence(logo, background) { RelativeSizeAxes = Axes.Both, - Clock = new FramedClock(menuMusic.Value ? track : null), + Clock = new FramedClock(MenuMusic.Value ? Track : null), LoadMenu = LoadMenu }, t => { AddInternal(t); - welcome?.Play(); + Welcome?.Play(); // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu. - if (menuMusic.Value) - track.Start(); + if (MenuMusic.Value) + Track.Start(); }); } } @@ -126,7 +85,7 @@ namespace osu.Game.Screens.Menu public override void OnSuspending(IScreen next) { - track = null; + Track = null; base.OnSuspending(next); } From ffbab2535856f37dacdb6f49f494aa1ed20f3ccf Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 23 Sep 2019 16:12:43 +0300 Subject: [PATCH 1430/2815] Fix incorrect icon margin in ChangelogOverlay --- osu.Game/Overlays/Changelog/ChangelogBuild.cs | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs index 11dc2049fd..05bf56bc33 100644 --- a/osu.Game/Overlays/Changelog/ChangelogBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs @@ -15,6 +15,7 @@ using osu.Game.Users; using osuTK.Graphics; using osu.Framework.Allocation; using System.Net; +using osuTK; namespace osu.Game.Overlays.Changelog { @@ -67,21 +68,31 @@ namespace osu.Game.Overlays.Changelog foreach (APIChangelogEntry entry in categoryEntries) { - LinkFlowContainer title = new LinkFlowContainer + LinkFlowContainer title; + + Container titleContainer = new Container { - Direction = FillDirection.Full, - RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, Margin = new MarginPadding { Vertical = 5 }, + Child = title = new LinkFlowContainer + { + Direction = FillDirection.Full, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } }; var entryColour = entry.Major ? colours.YellowLight : Color4.White; - title.AddIcon(entry.Type == ChangelogEntryType.Fix ? FontAwesome.Solid.Check : FontAwesome.Solid.Plus, t => + titleContainer.Add(new SpriteIcon { - t.Font = fontSmall; - t.Colour = entryColour; - t.Padding = new MarginPadding { Left = -17, Right = 5 }; + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreRight, + Size = new Vector2(fontSmall.Size), + Icon = entry.Type == ChangelogEntryType.Fix ? FontAwesome.Solid.Check : FontAwesome.Solid.Plus, + Colour = entryColour, + Margin = new MarginPadding { Right = 5 }, }); title.AddText(entry.Title, t => @@ -139,7 +150,7 @@ namespace osu.Game.Overlays.Changelog t.Colour = entryColour; }); - ChangelogEntries.Add(title); + ChangelogEntries.Add(titleContainer); if (!string.IsNullOrEmpty(entry.MessageHtml)) { From 5c4dfe0809c6865adf8f8daec0756935200f1ee0 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 23 Sep 2019 17:05:19 +0300 Subject: [PATCH 1431/2815] Apply suggested change --- osu.Game/Overlays/Changelog/ChangelogBuild.cs | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs index 05bf56bc33..bce1be5941 100644 --- a/osu.Game/Overlays/Changelog/ChangelogBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs @@ -68,6 +68,8 @@ namespace osu.Game.Overlays.Changelog foreach (APIChangelogEntry entry in categoryEntries) { + var entryColour = entry.Major ? colours.YellowLight : Color4.White; + LinkFlowContainer title; Container titleContainer = new Container @@ -75,26 +77,26 @@ namespace osu.Game.Overlays.Changelog AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Margin = new MarginPadding { Vertical = 5 }, - Child = title = new LinkFlowContainer + Children = new Drawable[] { - Direction = FillDirection.Full, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreRight, + Size = new Vector2(fontSmall.Size), + Icon = entry.Type == ChangelogEntryType.Fix ? FontAwesome.Solid.Check : FontAwesome.Solid.Plus, + Colour = entryColour, + Margin = new MarginPadding { Right = 5 }, + }, + title = new LinkFlowContainer + { + Direction = FillDirection.Full, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } } }; - var entryColour = entry.Major ? colours.YellowLight : Color4.White; - - titleContainer.Add(new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreRight, - Size = new Vector2(fontSmall.Size), - Icon = entry.Type == ChangelogEntryType.Fix ? FontAwesome.Solid.Check : FontAwesome.Solid.Plus, - Colour = entryColour, - Margin = new MarginPadding { Right = 5 }, - }); - title.AddText(entry.Title, t => { t.Font = fontLarge; From 8df77ffe924a36c750cc1a3d30216e038b57d06c Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 23 Sep 2019 16:48:30 +0200 Subject: [PATCH 1432/2815] Revert test scene changes --- .../Visual/Gameplay/TestScenePlayerLoader.cs | 90 ++----------------- 1 file changed, 7 insertions(+), 83 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index dc099b14b9..ab519360ac 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -3,20 +3,14 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.MathUtils; using osu.Framework.Screens; -using osu.Game.Graphics.Containers; -using osu.Game.Overlays; -using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; @@ -30,35 +24,19 @@ namespace osu.Game.Tests.Visual.Gameplay public class TestScenePlayerLoader : ManualInputManagerTestScene { private TestPlayerLoader loader; - private TestPlayerLoaderContainer container; - private TestPlayer player; - - [Resolved] - private AudioManager audioManager { get; set; } + private OsuScreenStack stack; [SetUp] public void Setup() => Schedule(() => { - ResetPlayer(false); + InputManager.Child = stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }; Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); }); - /// - /// Sets the input manager child to a new test player loader container instance. - /// - /// If the test player should behave like the production one. - public void ResetPlayer(bool interactive) - { - player = new TestPlayer(interactive, interactive); - loader = new TestPlayerLoader(() => player); - container = new TestPlayerLoaderContainer(loader); - InputManager.Child = container; - } - [Test] public void TestBlockLoadViaMouseMovement() { - AddStep("load dummy beatmap", () => ResetPlayer(false)); + AddStep("load dummy beatmap", () => stack.Push(loader = new TestPlayerLoader(() => new TestPlayer(false, false)))); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); AddRepeatStep("move mouse", () => InputManager.MoveMouseTo(loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft + (loader.VisualSettings.ScreenSpaceDrawQuad.BottomRight - loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft) * RNG.NextSingle()), 20); AddAssert("loader still active", () => loader.IsCurrentScreen()); @@ -71,13 +49,13 @@ namespace osu.Game.Tests.Visual.Gameplay Player player = null; SlowLoadPlayer slowPlayer = null; - AddStep("load dummy beatmap", () => ResetPlayer(false)); + AddStep("load dummy beatmap", () => stack.Push(loader = new TestPlayerLoader(() => player = new TestPlayer(false, false)))); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); AddUntilStep("wait for player to be current", () => player.IsCurrentScreen()); AddStep("load slow dummy beatmap", () => { - InputManager.Children = container = new TestPlayerLoaderContainer(loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false))); + stack.Push(loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false))); Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000); }); @@ -87,6 +65,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestModReinstantiation() { + TestPlayer player = null; TestMod gameMod = null; TestMod playerMod1 = null; TestMod playerMod2 = null; @@ -94,7 +73,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("load player", () => { Mods.Value = new[] { gameMod = new TestMod() }; - ResetPlayer(true); + stack.Push(loader = new TestPlayerLoader(() => player = new TestPlayer())); }); AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen()); @@ -118,61 +97,6 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("player mods applied", () => playerMod2.Applied); } - [Test] - public void TestMutedNotification() - { - AddStep("set master volume to 0%", () => audioManager.Volume.Value = 0); - AddStep("reset notification lock", () => PlayerLoader.ResetNotificationLock()); - //AddStep("reset notification overlay", () => notificationOverlay); - AddStep("load player", () => ResetPlayer(false)); - AddUntilStep("wait for player", () => player.IsLoaded); - - AddAssert("check for notification", () => container.NotificationOverlay.UnreadCount.Value == 1); - AddAssert("click notification", () => - { - var scrollContainer = container.NotificationOverlay.Children.Last() as OsuScrollContainer; - var flowContainer = scrollContainer.Children.First() as FillFlowContainer; - return flowContainer.Children.First().First().Click(); - }); - AddAssert("check master volume", () => audioManager.Volume.IsDefault); - - AddStep("restart player", () => - { - var lastPlayer = player; - player = null; - lastPlayer.Restart(); - }); - } - - private class TestPlayerLoaderContainer : Container - { - private TestPlayerLoader loader; - - [Cached] - public NotificationOverlay NotificationOverlay { get; } = new NotificationOverlay - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - }; - - [Cached] - public VolumeOverlay VolumeOverlay { get; } = new VolumeOverlay - { - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, - }; - - public TestPlayerLoaderContainer(TestPlayerLoader testPlayerLoader) - { - Children = new Drawable[] - { - loader = testPlayerLoader, - NotificationOverlay, - VolumeOverlay - }; - } - } - private class TestPlayerLoader : PlayerLoader { public new VisualSettings VisualSettings => base.VisualSettings; From 53603b559176e4ddbc62e5e3e9d54f67785b2d62 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2019 20:15:51 +0000 Subject: [PATCH 1433/2815] Bump System.ComponentModel.Annotations from 4.5.0 to 4.6.0 Bumps [System.ComponentModel.Annotations](https://github.com/dotnet/corefx) from 4.5.0 to 4.6.0. - [Release notes](https://github.com/dotnet/corefx/releases) - [Commits](https://github.com/dotnet/corefx/commits) 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 a27a94b8f9..a699217503 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -30,6 +30,6 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index a6516e6d1b..7803ea1e49 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -123,7 +123,7 @@ - + From 78ce62b187798d8e194c0c89ea6ee3a29154bc3b Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2019 20:29:36 +0000 Subject: [PATCH 1434/2815] Bump System.IO.Packaging from 4.5.0 to 4.6.0 Bumps [System.IO.Packaging](https://github.com/dotnet/corefx) from 4.5.0 to 4.6.0. - [Release notes](https://github.com/dotnet/corefx/releases) - [Commits](https://github.com/dotnet/corefx/commits) Signed-off-by: dependabot-preview[bot] --- osu.Desktop/osu.Desktop.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 538aaf2d7a..03b002add7 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -26,7 +26,7 @@ - + From 50dcb70342c22f97398e9c45b2f7c8576f52807d Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2019 21:04:20 +0000 Subject: [PATCH 1435/2815] Bump Microsoft.Win32.Registry from 4.5.0 to 4.6.0 Bumps [Microsoft.Win32.Registry](https://github.com/dotnet/corefx) from 4.5.0 to 4.6.0. - [Release notes](https://github.com/dotnet/corefx/releases) - [Commits](https://github.com/dotnet/corefx/commits) Signed-off-by: dependabot-preview[bot] --- osu.Desktop/osu.Desktop.csproj | 2 +- osu.Game.Tournament/osu.Game.Tournament.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 538aaf2d7a..9e8bb431c0 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.Game.Tournament/osu.Game.Tournament.csproj b/osu.Game.Tournament/osu.Game.Tournament.csproj index 4790fcbcde..bddaff0a80 100644 --- a/osu.Game.Tournament/osu.Game.Tournament.csproj +++ b/osu.Game.Tournament/osu.Game.Tournament.csproj @@ -11,6 +11,6 @@ - + \ No newline at end of file From ec78889e94178357e1d18303ca0f7164f5e31955 Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Tue, 24 Sep 2019 08:14:20 +0800 Subject: [PATCH 1436/2815] remove unused dependencies --- osu.Game/Screens/Menu/IntroCircles.cs | 3 +-- osu.Game/Screens/Menu/IntroTriangles.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroCircles.cs b/osu.Game/Screens/Menu/IntroCircles.cs index d5d7f5cb7a..a861d54663 100644 --- a/osu.Game/Screens/Menu/IntroCircles.cs +++ b/osu.Game/Screens/Menu/IntroCircles.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Audio; using osu.Framework.Screens; using osu.Framework.Graphics; @@ -18,7 +17,7 @@ namespace osu.Game.Screens.Menu private const double delay_step_two = 600; [BackgroundDependencyLoader] - private void load(AudioManager audio) + private void load() { if (MenuVoice.Value) SetWelcome(); diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 77700900a8..5b49a81a5a 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Menu private BackgroundScreenDefault background; [BackgroundDependencyLoader] - private void load(AudioManager audio) + private void load() { if (MenuVoice.Value && !MenuMusic.Value) SetWelcome(); From 143d7ab640b588569e5dc31bd0d87f79b8a78401 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 24 Sep 2019 04:53:26 +0300 Subject: [PATCH 1437/2815] Add test scene for spinner rotation --- .../TestSceneSpinnerRotation.cs | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs new file mode 100644 index 0000000000..e9fa8e46ed --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -0,0 +1,104 @@ +// 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.Audio; +using osu.Framework.Logging; +using osu.Framework.MathUtils; +using osu.Framework.Testing; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; +using osuTK; +using System.Collections.Generic; +using System.Linq; +using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneSpinnerRotation : TestSceneOsuPlayer + { + [Resolved] + private AudioManager audioManager { get; set; } + + private TrackVirtualManual track; + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) + { + var working = new ClockBackedTestWorkingBeatmap(beatmap, new FramedClock(new ManualClock { Rate = 1 }), audioManager); + track = (TrackVirtualManual)working.Track; + return working; + } + + private DrawableSpinner drawableSpinner; + + [SetUpSteps] + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("retrieve spinner", () => drawableSpinner = (DrawableSpinner)((TestPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.First()); + + // wait for frame stable clock time to hit 0 (for some reason, executing a seek while current time is below 0 doesn't seek successfully) + addSeekStep(0); + } + + [Test] + public void TestSpinnerRewindingRotation() + { + addSeekStep(5000); + AddAssert("is rotation absolute not almost 0", () => !Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, 0, 100)); + + addSeekStep(0); + AddAssert("is rotation absolute almost 0", () => Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, 0, 100)); + } + + [Test] + public void TestSpinnerMiddleRewindingRotation() + { + double estimatedRotation = 0; + + addSeekStep(5000); + AddStep("retrieve rotation", () => estimatedRotation = drawableSpinner.Disc.RotationAbsolute); + + addSeekStep(2500); + addSeekStep(5000); + AddAssert("is rotation absolute almost same", () => Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, estimatedRotation, 100)); + } + + private void addSeekStep(double time) + { + AddStep($"seek to {time}", () => track.Seek(time)); + + AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, ((TestPlayer)Player).DrawableRuleset.FrameStableClock.CurrentTime, 100)); + } + + protected override Player CreatePlayer(Ruleset ruleset) + { + Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); + return new TestPlayer(); + } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap + { + HitObjects = new List + { + new Spinner + { + Position = new Vector2(256, 192), + EndTime = 5000, + }, + // placeholder object to avoid hitting the results screen + new HitObject + { + StartTime = 99999, + } + } + }; + } +} From ba679684be0aab707d545b111973253d5926f647 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 24 Sep 2019 04:59:57 +0300 Subject: [PATCH 1438/2815] Trim whitespaces --- osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index e9fa8e46ed..4b98e84866 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Tests Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); return new TestPlayer(); } - + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap { HitObjects = new List From 18fd7aa8055a406ceecd242a359a2ec6072dee10 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 24 Sep 2019 05:10:06 +0300 Subject: [PATCH 1439/2815] Remove redundant using directive --- osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index 4b98e84866..7e792f0b40 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -4,7 +4,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Logging; using osu.Framework.MathUtils; using osu.Framework.Testing; using osu.Framework.Timing; From 267e12ce3c0d88cab712d562a1c3f27dd6321fe3 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 24 Sep 2019 05:45:47 +0300 Subject: [PATCH 1440/2815] Add sample usage to the session statics --- osu.Game/Configuration/SessionStatics.cs | 1 + osu.Game/Screens/Menu/MainMenu.cs | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index 9afb1bda36..0c1ea1e568 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.cs @@ -17,5 +17,6 @@ namespace osu.Game.Configuration public enum Statics { + LoginOverlayDisplayed, } } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index dd81569e26..1834221d35 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -52,6 +52,9 @@ namespace osu.Game.Screens.Menu [Resolved(canBeNull: true)] private LoginOverlay login { get; set; } + [Resolved] + private SessionStatics statics { get; set; } + [Resolved] private IAPIProvider api { get; set; } @@ -170,7 +173,6 @@ namespace osu.Game.Screens.Menu Beatmap.ValueChanged += beatmap_ValueChanged; } - private bool loginDisplayed; private bool exitConfirmed; protected override void LogoArriving(OsuLogo logo, bool resuming) @@ -198,10 +200,12 @@ namespace osu.Game.Screens.Menu bool displayLogin() { - if (!loginDisplayed) + var loginDisplayed = statics.GetBindable(Statics.LoginOverlayDisplayed); + + if (!loginDisplayed.Value) { Scheduler.AddDelayed(() => login?.Show(), 500); - loginDisplayed = true; + loginDisplayed.Value = true; } return true; From 4555ecc5e05d4c053e8a7822ca736c4328481e75 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 24 Sep 2019 15:09:08 +0900 Subject: [PATCH 1441/2815] Check for exact key --- osu.Game/Screens/Menu/ButtonSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 7ac6b5c696..ed8e4c70f9 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -185,7 +185,7 @@ namespace osu.Game.Screens.Menu { if (State == ButtonSystemState.Initial) { - if (buttonsTopLevel.Any(b => e.PressedKeys.Contains(b.TriggerKey))) + if (buttonsTopLevel.Any(b => e.Key == b.TriggerKey)) { logo?.Click(); return true; From 6e619fbd71ad8b5654e60ed7850b05ee0865f1a1 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 24 Sep 2019 06:35:04 +0000 Subject: [PATCH 1442/2815] Bump ppy.osu.Framework from 2019.921.0 to 2019.924.0 Bumps [ppy.osu.Framework](https://github.com/ppy/osu-framework) from 2019.921.0 to 2019.924.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.921.0...2019.924.0) 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 a699217503..83632f3d41 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 7803ea1e49..29a12f3707 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,7 +118,7 @@ - + From 50efb4414f87a78b3fda6595056b6851389740f7 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 24 Sep 2019 06:35:31 +0000 Subject: [PATCH 1443/2815] Bump ppy.osu.Framework.Android from 2019.921.0 to 2019.924.0 Bumps [ppy.osu.Framework.Android](https://github.com/ppy/osu-framework) from 2019.921.0 to 2019.924.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.921.0...2019.924.0) Signed-off-by: dependabot-preview[bot] --- osu.Android.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Android.props b/osu.Android.props index c57fc342ba..46fd5424df 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -62,6 +62,6 @@ - + From 60c9519095313948a05c27ca04aece93a9f2d7fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Sep 2019 16:21:37 +0900 Subject: [PATCH 1444/2815] Add proper signing and github release support --- fastlane/Fastfile | 48 +++++++++++++++++++++++++++++++++++++++++----- fastlane/README.md | 5 +++++ 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 906d284bc9..7adf42a1eb 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -4,14 +4,12 @@ platform :android do desc 'Deploy to play store' lane :beta do |options| - # update csproj version update_version( - solution_path: 'osu.Android.sln', version: options[:version], build: options[:build], ) - build() + build(options) supply( apk: './osu.Android/bin/Release/sh.ppy.osulazer-Signed.apk', @@ -21,8 +19,35 @@ desc 'Deploy to play store' ) end + desc 'Deploy to github release' + lane :build_github do |options| + + update_version( + version: options[:version], + build: options[:build], + ) + + build(options) + + client = HTTPClient.new + changelog = client.get_content 'https://gist.githubusercontent.com/peppy/aaa2ec1a323554b619671cac6dbbb776/raw' + changelog.gsub!('$BUILD_ID', options[:build]) + + set_github_release( + repository_name: "ppy/osu", + api_token: ENV["GITHUB_TOKEN"], + name: options[:build], + tag_name: options[:build], + is_draft: true, + description: changelog, + commitish: "master", + upload_assets: ["osu.Android/bin/Release/sh.ppy.osulazer.apk"] + ) + + end + desc 'Compile the project' - lane :build do + lane :build do |options| nuget_restore( project_path: 'osu.Android.sln' ) @@ -31,11 +56,24 @@ desc 'Deploy to play store' build_configuration: 'Release', solution_path: 'osu.Android.sln', platform: "android", + output_path: "osu.Android/bin/Release/", + keystore_path: options[:keystore_path], + keystore_alias: options[:keystore_alias], + keystore_password: ENV["KEYSTORE_PASSWORD"] ) end lane :update_version do |options| - app_version(options) + + split = options[:build].split('.') + split[1] = split[1].to_s.rjust(4, '0') + android_build = split.join('') + + app_version( + solution_path: 'osu.Android.sln', + version: options[:version], + build: android_build, + ) end end diff --git a/fastlane/README.md b/fastlane/README.md index 6145620870..a400ed9516 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -21,6 +21,11 @@ or alternatively using `brew cask install fastlane` fastlane android beta ``` Deploy to play store +### android build_github +``` +fastlane android build_github +``` +Deploy to github release ### android build ``` fastlane android build From 1860c2f9cea07e13cbf8c81c4d23ba45f2732266 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Sep 2019 16:22:30 +0900 Subject: [PATCH 1445/2815] 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 c57fc342ba..46fd5424df 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -62,6 +62,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index a699217503..83632f3d41 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 7803ea1e49..30f1da362d 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,8 +118,8 @@ - - + + From d08fc563707f1bfe010bc889cbb51ae824583da0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Sep 2019 16:33:48 +0900 Subject: [PATCH 1446/2815] Add apk download link in README --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 56491a4be4..aefeb2e96e 100644 --- a/README.md +++ b/README.md @@ -31,12 +31,10 @@ If you are not interested in developing the game, you can still consume our [bin **Latest build:** -| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | -| ------------- | ------------- | +| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [iOS(iOS 10+)](https://testflight.apple.com/join/2tLcjWlF) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) +| ------------- | ------------- | ------------- | ------------- | - **Linux** users are recommended to self-compile until we have official deployment in place. -- **iOS** users can join the [TestFlight beta program](https://testflight.apple.com/join/2tLcjWlF) (note that due to high demand this is regularly full). -- **Android** users can self-compile, and expect a public beta soon. If your platform is not listed above, there is still a chance you can manually build it by following the instructions below. From 315dcc81583ad8da74fb0ded9e8d64b7b5f22b4a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Sep 2019 16:42:05 +0900 Subject: [PATCH 1447/2815] Update fastlane --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index ac46fddb41..7df9c46482 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,7 +5,7 @@ GEM addressable (2.7.0) public_suffix (>= 2.0.2, < 5.0) atomos (0.1.3) - babosa (1.0.2) + babosa (1.0.3) claide (1.0.3) colored (1.2) colored2 (3.1.2) From 75cceb9e303568ab94b2ce416ac4791044010ac4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 24 Sep 2019 16:47:34 +0900 Subject: [PATCH 1448/2815] Fix LifetimeChanged being invoked before lifetime is set --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index b94de0df89..8d8f8a419f 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -316,8 +316,8 @@ namespace osu.Game.Rulesets.Objects.Drawables get => lifetimeStart ?? (HitObject.StartTime - InitialLifetimeOffset); set { - base.LifetimeStart = value; lifetimeStart = value; + base.LifetimeStart = value; } } From af0c15a93c171c3559b6fafb344ac3376b7eb986 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 24 Sep 2019 16:48:39 +0900 Subject: [PATCH 1449/2815] Fix initial hitobject states not being recomputed correctly --- osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index bd1f496dfa..e00597dd56 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -77,6 +77,9 @@ namespace osu.Game.Rulesets.UI.Scrolling if (!initialStateCache.IsValid) { + foreach (var cached in hitObjectInitialStateCache.Values) + cached.Invalidate(); + switch (direction.Value) { case ScrollingDirection.Up: From 4abe0473b9093c8d6dd4f9d6e28364c7e34e3f89 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 24 Sep 2019 16:49:42 +0900 Subject: [PATCH 1450/2815] Fix relative beat length not considering slider multiplier --- .../TestSceneDrawableScrollingRuleset.cs | 16 ++++++++++++++++ .../UI/Scrolling/DrawableScrollingRuleset.cs | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index 60ace8ea69..86f7896457 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -115,6 +116,19 @@ namespace osu.Game.Tests.Visual.Gameplay assertPosition(4, 1f); } + [Test] + public void TestSliderMultiplierDoesnotAffectRelativeBeatLength() + { + var beatmap = createBeatmap(new TimingControlPoint { BeatLength = time_range }); + beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2; + + createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true); + AddStep("adjust time range", () => drawableRuleset.TimeRange.Value = 5000); + + for (int i = 0; i < 5; i++) + assertPosition(i, i / 5f); + } + private void assertPosition(int index, float relativeY) => AddAssert($"hitobject {index} at {relativeY}", () => Precision.AlmostEquals(drawableRuleset.Playfield.AllHitObjects.ElementAt(index).DrawPosition.Y, drawableRuleset.Playfield.HitObjectContainer.DrawHeight * relativeY)); @@ -193,6 +207,8 @@ namespace osu.Game.Tests.Visual.Gameplay protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping; + public new Bindable TimeRange => base.TimeRange; + public TestDrawableScrollingRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 64e491858b..3d56543bab 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.UI.Scrolling if (duration > maxDuration) { maxDuration = duration; - baseBeatLength = timingPoints[i].BeatLength; + baseBeatLength = timingPoints[i].BeatLength / Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier; } } } From c6fe8587e3c6c023db6fe81f77a75ee4de33a612 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Sep 2019 17:45:45 +0900 Subject: [PATCH 1451/2815] Read build from VersionCode --- osu.Android/OsuGameAndroid.cs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index d9bdd9c0c2..aca36b59aa 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -9,6 +9,23 @@ namespace osu.Android { public class OsuGameAndroid : OsuGame { - public override Version AssemblyVersion => new Version(Application.Context.ApplicationContext.PackageManager.GetPackageInfo(Application.Context.ApplicationContext.PackageName, 0).VersionName); + public override Version AssemblyVersion + { + get + { + string versionName = Application.Context.ApplicationContext.PackageManager.GetPackageInfo(Application.Context.ApplicationContext.PackageName, 0).VersionCode.ToString(); + + try + { + // undo play store version garbling + return new Version(int.Parse(versionName.Substring(0, 4)), int.Parse(versionName.Substring(4, 4)), int.Parse(versionName.Substring(8, 1))); + } + catch + { + } + + return new Version(versionName); + } + } } -} +} \ No newline at end of file From 1c474de0ed8cd3a9cf778340bba142efcf4780e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Sep 2019 18:03:01 +0900 Subject: [PATCH 1452/2815] Move post-update notification logic to UpdateManager base class --- osu.Desktop/OsuGameDesktop.cs | 1 + osu.Desktop/Overlays/VersionManager.cs | 55 +-------------- osu.Desktop/Updater/SquirrelUpdateManager.cs | 2 +- .../Updater/SimpleUpdateManager.cs | 18 +++-- osu.Game/Updater/UpdateManager.cs | 67 +++++++++++++++++++ 5 files changed, 78 insertions(+), 65 deletions(-) rename {osu.Desktop => osu.Game}/Updater/SimpleUpdateManager.cs (87%) create mode 100644 osu.Game/Updater/UpdateManager.cs diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 761f52f961..7725ee6451 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -17,6 +17,7 @@ using osu.Framework.Logging; using osu.Framework.Platform.Windows; using osu.Framework.Screens; using osu.Game.Screens.Menu; +using osu.Game.Updater; namespace osu.Desktop { diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs index 51e801c185..6eed46867a 100644 --- a/osu.Desktop/Overlays/VersionManager.cs +++ b/osu.Desktop/Overlays/VersionManager.cs @@ -8,11 +8,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game; -using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Overlays; -using osu.Game.Overlays.Notifications; using osuTK; using osuTK.Graphics; @@ -20,17 +17,9 @@ namespace osu.Desktop.Overlays { public class VersionManager : OverlayContainer { - private OsuConfigManager config; - private OsuGameBase game; - private NotificationOverlay notificationOverlay; - [BackgroundDependencyLoader] - private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game, OsuConfigManager config) + private void load(OsuColour colours, TextureStore textures, OsuGameBase game) { - notificationOverlay = notification; - this.config = config; - this.game = game; - AutoSizeAxes = Axes.Both; Anchor = Anchor.BottomCentre; Origin = Anchor.BottomCentre; @@ -85,48 +74,6 @@ namespace osu.Desktop.Overlays }; } - protected override void LoadComplete() - { - base.LoadComplete(); - - var version = game.Version; - var lastVersion = config.Get(OsuSetting.Version); - - if (game.IsDeployedBuild && version != lastVersion) - { - config.Set(OsuSetting.Version, version); - - // only show a notification if we've previously saved a version to the config file (ie. not the first run). - if (!string.IsNullOrEmpty(lastVersion)) - notificationOverlay.Post(new UpdateCompleteNotification(version)); - } - } - - private class UpdateCompleteNotification : SimpleNotification - { - private readonly string version; - - public UpdateCompleteNotification(string version) - { - this.version = version; - Text = $"You are now running osu!lazer {version}.\nClick to see what's new!"; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours, ChangelogOverlay changelog, NotificationOverlay notificationOverlay) - { - Icon = FontAwesome.Solid.CheckSquare; - IconBackgound.Colour = colours.BlueDark; - - Activated = delegate - { - notificationOverlay.Hide(); - changelog.ShowBuild(OsuGameBase.CLIENT_STREAM_NAME, version); - return true; - }; - } - } - protected override void PopIn() { this.FadeIn(1400, Easing.OutQuint); diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index fa41c061b5..60b47a8b3a 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -20,7 +20,7 @@ using LogLevel = Splat.LogLevel; namespace osu.Desktop.Updater { - public class SquirrelUpdateManager : Component + public class SquirrelUpdateManager : osu.Game.Updater.UpdateManager { private UpdateManager updateManager; private NotificationOverlay notificationOverlay; diff --git a/osu.Desktop/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs similarity index 87% rename from osu.Desktop/Updater/SimpleUpdateManager.cs rename to osu.Game/Updater/SimpleUpdateManager.cs index 5184791de1..eec27d3325 100644 --- a/osu.Desktop/Updater/SimpleUpdateManager.cs +++ b/osu.Game/Updater/SimpleUpdateManager.cs @@ -6,31 +6,25 @@ using System.Threading.Tasks; using Newtonsoft.Json; using osu.Framework; using osu.Framework.Allocation; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.IO.Network; using osu.Framework.Platform; -using osu.Game; -using osu.Game.Overlays; using osu.Game.Overlays.Notifications; -namespace osu.Desktop.Updater +namespace osu.Game.Updater { /// /// An update manager that shows notifications if a newer release is detected. /// Installation is left up to the user. /// - internal class SimpleUpdateManager : CompositeDrawable + public class SimpleUpdateManager : UpdateManager { - private NotificationOverlay notificationOverlay; private string version; private GameHost host; [BackgroundDependencyLoader] - private void load(NotificationOverlay notification, OsuGameBase game, GameHost host) + private void load(OsuGameBase game, GameHost host) { - notificationOverlay = notification; - this.host = host; version = game.Version; @@ -50,7 +44,7 @@ namespace osu.Desktop.Updater if (latest.TagName != version) { - notificationOverlay.Post(new SimpleNotification + Notifications.Post(new SimpleNotification { Text = $"A newer release of osu! has been found ({version} → {latest.TagName}).\n\n" + "Click here to download the new version, which can be installed over the top of your existing installation", @@ -82,6 +76,10 @@ namespace osu.Desktop.Updater case RuntimeInfo.Platform.MacOsx: bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".app.zip")); break; + + case RuntimeInfo.Platform.Android: + bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".apk")); + break; } return bestAsset?.BrowserDownloadUrl ?? release.HtmlUrl; diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs new file mode 100644 index 0000000000..cfca7dc858 --- /dev/null +++ b/osu.Game/Updater/UpdateManager.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.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; + +namespace osu.Game.Updater +{ + public abstract class UpdateManager : CompositeDrawable + { + [Resolved] + private OsuConfigManager config { get; set; } + + [Resolved] + private OsuGameBase game { get; set; } + + [Resolved] + protected NotificationOverlay Notifications { get; set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + var version = game.Version; + var lastVersion = config.Get(OsuSetting.Version); + + if (game.IsDeployedBuild && version != lastVersion) + { + config.Set(OsuSetting.Version, version); + + // only show a notification if we've previously saved a version to the config file (ie. not the first run). + if (!string.IsNullOrEmpty(lastVersion)) + Notifications.Post(new UpdateCompleteNotification(version)); + } + } + + private class UpdateCompleteNotification : SimpleNotification + { + private readonly string version; + + public UpdateCompleteNotification(string version) + { + this.version = version; + Text = $"You are now running osu!lazer {version}.\nClick to see what's new!"; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, ChangelogOverlay changelog, NotificationOverlay notificationOverlay) + { + Icon = FontAwesome.Solid.CheckSquare; + IconBackgound.Colour = colours.BlueDark; + + Activated = delegate + { + notificationOverlay.Hide(); + changelog.ShowBuild(OsuGameBase.CLIENT_STREAM_NAME, version); + return true; + }; + } + } + } +} From 07ec163daa856235a5046d2428b2a924c41478bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Sep 2019 18:12:04 +0900 Subject: [PATCH 1453/2815] Fix version fallback logic --- osu.Android/OsuGameAndroid.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index aca36b59aa..a91c010809 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -4,6 +4,7 @@ using System; using Android.App; using osu.Game; +using osu.Game.Updater; namespace osu.Android { @@ -13,10 +14,11 @@ namespace osu.Android { get { - string versionName = Application.Context.ApplicationContext.PackageManager.GetPackageInfo(Application.Context.ApplicationContext.PackageName, 0).VersionCode.ToString(); + var packageInfo = Application.Context.ApplicationContext.PackageManager.GetPackageInfo(Application.Context.ApplicationContext.PackageName, 0); try { + string versionName = packageInfo.VersionCode.ToString(); // undo play store version garbling return new Version(int.Parse(versionName.Substring(0, 4)), int.Parse(versionName.Substring(4, 4)), int.Parse(versionName.Substring(8, 1))); } @@ -24,8 +26,15 @@ namespace osu.Android { } - return new Version(versionName); + return new Version(packageInfo.VersionName); } } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Add(new SimpleUpdateManager()); + } } } \ No newline at end of file From 84d9f98ff1cfde97c4d51b264f11d7d226f243ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Sep 2019 18:15:57 +0900 Subject: [PATCH 1454/2815] fixup! Move post-update notification logic to UpdateManager base class --- osu.Game/Updater/UpdateManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index cfca7dc858..e256cdbe45 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -20,7 +20,7 @@ namespace osu.Game.Updater private OsuGameBase game { get; set; } [Resolved] - protected NotificationOverlay Notifications { get; set; } + protected NotificationOverlay Notifications { get; private set; } protected override void LoadComplete() { From df692b091c415ee0c9090d00698130368023626b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 24 Sep 2019 18:22:02 +0900 Subject: [PATCH 1455/2815] Make LabelledComponent generic --- .../UserInterface/TestSceneLabelledComponent.cs | 8 ++++---- osu.Game.Tournament/Screens/SetupScreen.cs | 2 +- .../LabelledComponents/LabelledComponent.cs | 11 ++++++----- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs index 73e0191adb..a762d561c2 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("create component", () => { - LabelledComponent component; + LabelledComponent component; Child = new Container { @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.UserInterface Origin = Anchor.Centre, Width = 500, AutoSizeAxes = Axes.Y, - Child = component = padded ? (LabelledComponent)new PaddedLabelledComponent() : new NonPaddedLabelledComponent(), + Child = component = padded ? (LabelledComponent)new PaddedLabelledComponent() : new NonPaddedLabelledComponent(), }; component.Label = "a sample component"; @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.UserInterface }); } - private class PaddedLabelledComponent : LabelledComponent + private class PaddedLabelledComponent : LabelledComponent { public PaddedLabelledComponent() : base(true) @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.UserInterface }; } - private class NonPaddedLabelledComponent : LabelledComponent + private class NonPaddedLabelledComponent : LabelledComponent { public NonPaddedLabelledComponent() : base(false) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 1cb4917790..7a44e7a0e1 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -89,7 +89,7 @@ namespace osu.Game.Tournament.Screens }; } - private class ActionableInfo : LabelledComponent + private class ActionableInfo : LabelledComponent { private OsuButton button; diff --git a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledComponent.cs b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledComponent.cs index 19e9c329d6..770065cb0e 100644 --- a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledComponent.cs +++ b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledComponent.cs @@ -11,7 +11,8 @@ using osuTK; namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents { - public abstract class LabelledComponent : CompositeDrawable + public abstract class LabelledComponent : CompositeDrawable + where T : Drawable { protected const float CONTENT_PADDING_VERTICAL = 10; protected const float CONTENT_PADDING_HORIZONTAL = 15; @@ -20,15 +21,15 @@ namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents /// /// The component that is being displayed. /// - protected readonly Drawable Component; + protected readonly T Component; private readonly OsuTextFlowContainer labelText; private readonly OsuTextFlowContainer descriptionText; /// - /// Creates a new . + /// Creates a new . /// - /// Whether the component should be padded or should be expanded to the bounds of this . + /// Whether the component should be padded or should be expanded to the bounds of this . protected LabelledComponent(bool padded) { RelativeSizeAxes = Axes.X; @@ -127,6 +128,6 @@ namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents /// Creates the component that should be displayed. /// /// The component. - protected abstract Drawable CreateComponent(); + protected abstract T CreateComponent(); } } From 6b702eb6de7820dcb4245db7f0fa737731bdf3f9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 24 Sep 2019 18:25:17 +0900 Subject: [PATCH 1456/2815] Update with generic labelledcomponent --- .../Visual/UserInterface/TestSceneLabelledTextBox.cs | 3 ++- .../Setup/Components/LabelledComponents/LabelledTextBox.cs | 6 ++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs index f9a5369576..5c5b1262a6 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Edit.Setup.Components.LabelledComponents; namespace osu.Game.Tests.Visual.UserInterface @@ -27,7 +28,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("create component", () => { - LabelledComponent component; + LabelledComponent component; Child = new Container { diff --git a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs index 992371fedf..67bacea9f8 100644 --- a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs +++ b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs @@ -9,12 +9,10 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents { - public class LabelledTextBox : LabelledComponent + public class LabelledTextBox : LabelledComponent { public event TextBox.OnCommitHandler OnCommit; - protected new OsuTextBox Component => (OsuTextBox)base.Component; - public LabelledTextBox() : base(false) { @@ -41,7 +39,7 @@ namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents Component.BorderColour = colours.Blue; } - protected override Drawable CreateComponent() => new OsuTextBox + protected override OsuTextBox CreateComponent() => new OsuTextBox { Anchor = Anchor.Centre, Origin = Anchor.Centre, From c226d52b53acbdb84003a736f81488b688b2f01a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Sep 2019 18:34:54 +0900 Subject: [PATCH 1457/2815] Don't automatic download for now --- osu.Game/Updater/SimpleUpdateManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs index eec27d3325..4789ac94d2 100644 --- a/osu.Game/Updater/SimpleUpdateManager.cs +++ b/osu.Game/Updater/SimpleUpdateManager.cs @@ -78,7 +78,8 @@ namespace osu.Game.Updater break; case RuntimeInfo.Platform.Android: - bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".apk")); + // on our testing device this causes the download to magically disappear. + //bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".apk")); break; } From 42b60417639455df5bb6d53000f011ce86e38ee3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Sep 2019 18:03:01 +0900 Subject: [PATCH 1458/2815] Move post-update notification logic to UpdateManager base class --- osu.Desktop/OsuGameDesktop.cs | 1 + osu.Desktop/Overlays/VersionManager.cs | 55 +-------------- osu.Desktop/Updater/SquirrelUpdateManager.cs | 2 +- .../Updater/SimpleUpdateManager.cs | 18 +++-- osu.Game/Updater/UpdateManager.cs | 67 +++++++++++++++++++ 5 files changed, 78 insertions(+), 65 deletions(-) rename {osu.Desktop => osu.Game}/Updater/SimpleUpdateManager.cs (87%) create mode 100644 osu.Game/Updater/UpdateManager.cs diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 761f52f961..7725ee6451 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -17,6 +17,7 @@ using osu.Framework.Logging; using osu.Framework.Platform.Windows; using osu.Framework.Screens; using osu.Game.Screens.Menu; +using osu.Game.Updater; namespace osu.Desktop { diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs index 51e801c185..6eed46867a 100644 --- a/osu.Desktop/Overlays/VersionManager.cs +++ b/osu.Desktop/Overlays/VersionManager.cs @@ -8,11 +8,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game; -using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Overlays; -using osu.Game.Overlays.Notifications; using osuTK; using osuTK.Graphics; @@ -20,17 +17,9 @@ namespace osu.Desktop.Overlays { public class VersionManager : OverlayContainer { - private OsuConfigManager config; - private OsuGameBase game; - private NotificationOverlay notificationOverlay; - [BackgroundDependencyLoader] - private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game, OsuConfigManager config) + private void load(OsuColour colours, TextureStore textures, OsuGameBase game) { - notificationOverlay = notification; - this.config = config; - this.game = game; - AutoSizeAxes = Axes.Both; Anchor = Anchor.BottomCentre; Origin = Anchor.BottomCentre; @@ -85,48 +74,6 @@ namespace osu.Desktop.Overlays }; } - protected override void LoadComplete() - { - base.LoadComplete(); - - var version = game.Version; - var lastVersion = config.Get(OsuSetting.Version); - - if (game.IsDeployedBuild && version != lastVersion) - { - config.Set(OsuSetting.Version, version); - - // only show a notification if we've previously saved a version to the config file (ie. not the first run). - if (!string.IsNullOrEmpty(lastVersion)) - notificationOverlay.Post(new UpdateCompleteNotification(version)); - } - } - - private class UpdateCompleteNotification : SimpleNotification - { - private readonly string version; - - public UpdateCompleteNotification(string version) - { - this.version = version; - Text = $"You are now running osu!lazer {version}.\nClick to see what's new!"; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours, ChangelogOverlay changelog, NotificationOverlay notificationOverlay) - { - Icon = FontAwesome.Solid.CheckSquare; - IconBackgound.Colour = colours.BlueDark; - - Activated = delegate - { - notificationOverlay.Hide(); - changelog.ShowBuild(OsuGameBase.CLIENT_STREAM_NAME, version); - return true; - }; - } - } - protected override void PopIn() { this.FadeIn(1400, Easing.OutQuint); diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index fa41c061b5..60b47a8b3a 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -20,7 +20,7 @@ using LogLevel = Splat.LogLevel; namespace osu.Desktop.Updater { - public class SquirrelUpdateManager : Component + public class SquirrelUpdateManager : osu.Game.Updater.UpdateManager { private UpdateManager updateManager; private NotificationOverlay notificationOverlay; diff --git a/osu.Desktop/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs similarity index 87% rename from osu.Desktop/Updater/SimpleUpdateManager.cs rename to osu.Game/Updater/SimpleUpdateManager.cs index 5184791de1..eec27d3325 100644 --- a/osu.Desktop/Updater/SimpleUpdateManager.cs +++ b/osu.Game/Updater/SimpleUpdateManager.cs @@ -6,31 +6,25 @@ using System.Threading.Tasks; using Newtonsoft.Json; using osu.Framework; using osu.Framework.Allocation; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.IO.Network; using osu.Framework.Platform; -using osu.Game; -using osu.Game.Overlays; using osu.Game.Overlays.Notifications; -namespace osu.Desktop.Updater +namespace osu.Game.Updater { /// /// An update manager that shows notifications if a newer release is detected. /// Installation is left up to the user. /// - internal class SimpleUpdateManager : CompositeDrawable + public class SimpleUpdateManager : UpdateManager { - private NotificationOverlay notificationOverlay; private string version; private GameHost host; [BackgroundDependencyLoader] - private void load(NotificationOverlay notification, OsuGameBase game, GameHost host) + private void load(OsuGameBase game, GameHost host) { - notificationOverlay = notification; - this.host = host; version = game.Version; @@ -50,7 +44,7 @@ namespace osu.Desktop.Updater if (latest.TagName != version) { - notificationOverlay.Post(new SimpleNotification + Notifications.Post(new SimpleNotification { Text = $"A newer release of osu! has been found ({version} → {latest.TagName}).\n\n" + "Click here to download the new version, which can be installed over the top of your existing installation", @@ -82,6 +76,10 @@ namespace osu.Desktop.Updater case RuntimeInfo.Platform.MacOsx: bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".app.zip")); break; + + case RuntimeInfo.Platform.Android: + bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".apk")); + break; } return bestAsset?.BrowserDownloadUrl ?? release.HtmlUrl; diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs new file mode 100644 index 0000000000..e256cdbe45 --- /dev/null +++ b/osu.Game/Updater/UpdateManager.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.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; + +namespace osu.Game.Updater +{ + public abstract class UpdateManager : CompositeDrawable + { + [Resolved] + private OsuConfigManager config { get; set; } + + [Resolved] + private OsuGameBase game { get; set; } + + [Resolved] + protected NotificationOverlay Notifications { get; private set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + var version = game.Version; + var lastVersion = config.Get(OsuSetting.Version); + + if (game.IsDeployedBuild && version != lastVersion) + { + config.Set(OsuSetting.Version, version); + + // only show a notification if we've previously saved a version to the config file (ie. not the first run). + if (!string.IsNullOrEmpty(lastVersion)) + Notifications.Post(new UpdateCompleteNotification(version)); + } + } + + private class UpdateCompleteNotification : SimpleNotification + { + private readonly string version; + + public UpdateCompleteNotification(string version) + { + this.version = version; + Text = $"You are now running osu!lazer {version}.\nClick to see what's new!"; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, ChangelogOverlay changelog, NotificationOverlay notificationOverlay) + { + Icon = FontAwesome.Solid.CheckSquare; + IconBackgound.Colour = colours.BlueDark; + + Activated = delegate + { + notificationOverlay.Hide(); + changelog.ShowBuild(OsuGameBase.CLIENT_STREAM_NAME, version); + return true; + }; + } + } + } +} From 028c958431728deb42811a92a3993f2382ee49a6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Sep 2019 19:19:43 +0900 Subject: [PATCH 1459/2815] Initial implementation of a switch button --- .../UserInterface/TestSceneSwitchButton.cs | 20 +++ .../Graphics/UserInterface/SwitchButton.cs | 117 ++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneSwitchButton.cs create mode 100644 osu.Game/Graphics/UserInterface/SwitchButton.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSwitchButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSwitchButton.cs new file mode 100644 index 0000000000..8fe381f141 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSwitchButton.cs @@ -0,0 +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 osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneSwitchButton : OsuTestScene + { + public TestSceneSwitchButton() + { + Child = new SwitchButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + } + } +} diff --git a/osu.Game/Graphics/UserInterface/SwitchButton.cs b/osu.Game/Graphics/UserInterface/SwitchButton.cs new file mode 100644 index 0000000000..21712051ef --- /dev/null +++ b/osu.Game/Graphics/UserInterface/SwitchButton.cs @@ -0,0 +1,117 @@ +// 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.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Graphics.UserInterface +{ + public class SwitchButton : Checkbox + { + private const float border_thickness = 4.5f; + private const float padding = 1.25f; + + private readonly Box fill; + private readonly Container switchContainer; + private readonly Drawable switchCircle; + private readonly CircularBorderContainer circularContainer; + + private Color4 enabledColour; + private Color4 disabledColour; + + public SwitchButton() + { + Size = new Vector2(45, 20); + + InternalChild = circularContainer = new CircularBorderContainer + { + RelativeSizeAxes = Axes.Both, + BorderColour = Color4.White, + BorderThickness = border_thickness, + Masking = true, + Children = new Drawable[] + { + fill = new Box + { + RelativeSizeAxes = Axes.Both, + AlwaysPresent = true, + Alpha = 0 + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(border_thickness + padding), + Child = switchContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Child = switchCircle = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Masking = true, + Child = new Box { RelativeSizeAxes = Axes.Both } + } + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + enabledColour = colours.BlueDark; + disabledColour = colours.Gray3; + + switchContainer.Colour = enabledColour; + fill.Colour = disabledColour; + + updateBorder(); + } + + protected override void OnUserChange(bool value) + { + base.OnUserChange(value); + + if (value) + switchCircle.MoveToX(switchContainer.DrawWidth - switchCircle.DrawWidth, 200, Easing.OutQuint); + else + switchCircle.MoveToX(0, 200, Easing.OutQuint); + + fill.FadeTo(value ? 1 : 0, 250, Easing.OutQuint); + + updateBorder(); + } + + protected override bool OnHover(HoverEvent e) + { + updateBorder(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateBorder(); + base.OnHoverLost(e); + } + + private void updateBorder() + { + circularContainer.TransformBorderTo((Current.Value ? enabledColour : disabledColour).Lighten(IsHovered ? 0.3f : 0)); + } + + private class CircularBorderContainer : CircularContainer + { + public void TransformBorderTo(SRGBColour colour) + => this.TransformTo(nameof(BorderColour), colour, 250, Easing.OutQuint); + } + } +} From b8d147a3b41586c2e10556145a04aea69193e57a Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Tue, 24 Sep 2019 17:42:06 +0800 Subject: [PATCH 1460/2815] introduce main menu background modes --- osu.Game/Configuration/BackgroundMode.cs | 12 +++++++ osu.Game/Configuration/OsuConfigManager.cs | 5 ++- .../Graphics/Backgrounds/BeatmapBackground.cs | 28 ++++++++++++++++ .../Sections/Audio/MainMenuSettings.cs | 6 ++++ .../Backgrounds/BackgroundScreenBeatmap.cs | 17 ---------- .../Backgrounds/BackgroundScreenDefault.cs | 33 +++++++++++++++++-- 6 files changed, 80 insertions(+), 21 deletions(-) create mode 100644 osu.Game/Configuration/BackgroundMode.cs create mode 100644 osu.Game/Graphics/Backgrounds/BeatmapBackground.cs diff --git a/osu.Game/Configuration/BackgroundMode.cs b/osu.Game/Configuration/BackgroundMode.cs new file mode 100644 index 0000000000..21554891ca --- /dev/null +++ b/osu.Game/Configuration/BackgroundMode.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.Configuration +{ + public enum BackgroundMode + { + Default, + Seasonal, + Beatmap + } +} diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 64b1f2d7bc..9b964e8893 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -117,6 +117,8 @@ namespace osu.Game.Configuration Set(OsuSetting.UIHoldActivationDelay, 200, 0, 500); Set(OsuSetting.IntroSequence, IntroSequence.Triangles); + + Set(OsuSetting.BackgroundMode, BackgroundMode.Default); } public OsuConfigManager(Storage storage) @@ -186,6 +188,7 @@ namespace osu.Game.Configuration UIScale, IntroSequence, UIHoldActivationDelay, - HitLighting + HitLighting, + BackgroundMode } } diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs new file mode 100644 index 0000000000..40c6dae43c --- /dev/null +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs @@ -0,0 +1,28 @@ +// 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.Textures; +using osu.Game.Beatmaps; + +namespace osu.Game.Graphics.Backgrounds +{ + public class BeatmapBackground : Background + { + public readonly WorkingBeatmap Beatmap; + + private readonly string fallbackTextureName; + + public BeatmapBackground(WorkingBeatmap beatmap, string fallbackTextureName = @"Backgrounds/bg1") + { + Beatmap = beatmap; + this.fallbackTextureName = fallbackTextureName; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + Sprite.Texture = Beatmap?.Background ?? textures.Get(fallbackTextureName); + } + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs index 5ccdc952ba..76a6aafe45 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs @@ -34,6 +34,12 @@ namespace osu.Game.Overlays.Settings.Sections.Audio Bindable = config.GetBindable(OsuSetting.IntroSequence), Items = Enum.GetValues(typeof(IntroSequence)).Cast() }, + new SettingsDropdown + { + LabelText = "Background", + Bindable = config.GetBindable(OsuSetting.BackgroundMode), + Items = Enum.GetValues(typeof(BackgroundMode)).Cast() + } }; } } diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 2730b0b90d..3de0ab191c 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -6,7 +6,6 @@ using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.Backgrounds; @@ -107,22 +106,6 @@ namespace osu.Game.Screens.Backgrounds return base.Equals(other) && beatmap == otherBeatmapBackground.Beatmap; } - protected class BeatmapBackground : Background - { - public readonly WorkingBeatmap Beatmap; - - public BeatmapBackground(WorkingBeatmap beatmap) - { - Beatmap = beatmap; - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - Sprite.Texture = Beatmap?.Background ?? textures.Get(@"Backgrounds/bg1"); - } - } - public class DimmableBackground : UserDimContainer { /// diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 2d7fe6a6a3..a8ba17ef49 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -6,6 +6,8 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.MathUtils; using osu.Framework.Threading; +using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Graphics.Backgrounds; using osu.Game.Online.API; using osu.Game.Skinning; @@ -24,6 +26,10 @@ namespace osu.Game.Screens.Backgrounds private Bindable user; private Bindable skin; + private Bindable mode; + + [Resolved] + private IBindable beatmap { get; set; } public BackgroundScreenDefault(bool animateOnEnter = true) : base(animateOnEnter) @@ -31,13 +37,15 @@ namespace osu.Game.Screens.Backgrounds } [BackgroundDependencyLoader] - private void load(IAPIProvider api, SkinManager skinManager) + private void load(IAPIProvider api, SkinManager skinManager, OsuConfigManager config) { user = api.LocalUser.GetBoundCopy(); skin = skinManager.CurrentSkin.GetBoundCopy(); + mode = config.GetBindable(OsuSetting.BackgroundMode); user.ValueChanged += _ => Next(); skin.ValueChanged += _ => Next(); + mode.ValueChanged += _ => Next(); currentDisplay = RNG.Next(0, background_count); @@ -66,9 +74,28 @@ namespace osu.Game.Screens.Backgrounds Background newBackground; if (user.Value?.IsSupporter ?? false) - newBackground = new SkinnedBackground(skin.Value, backgroundName); + { + switch (mode.Value) + { + case BackgroundMode.Beatmap: + newBackground = new BeatmapBackground(beatmap.Value, backgroundName); + break; + + default: + newBackground = new SkinnedBackground(skin.Value, backgroundName); + break; + } + } else - newBackground = new Background(backgroundName); + { + switch (mode.Value) + { + case BackgroundMode.Seasonal: + default: + newBackground = new Background(backgroundName); + break; + } + } newBackground.Depth = currentDisplay; From f493f1c71d2bee7242b6a4d552bc1ce0f3f05df7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Sep 2019 19:00:26 +0900 Subject: [PATCH 1461/2815] Move new components to v2 namespace --- .../Visual/UserInterface/TestSceneLabelledComponent.cs | 2 +- .../Visual/UserInterface/TestSceneLabelledTextBox.cs | 2 +- osu.Game.Tournament/Screens/SetupScreen.cs | 2 +- .../UserInterfaceV2}/LabelledComponent.cs | 3 +-- .../UserInterfaceV2}/LabelledTextBox.cs | 3 +-- 5 files changed, 5 insertions(+), 7 deletions(-) rename osu.Game/{Screens/Edit/Setup/Components/LabelledComponents => Graphics/UserInterfaceV2}/LabelledComponent.cs (98%) rename osu.Game/{Screens/Edit/Setup/Components/LabelledComponents => Graphics/UserInterfaceV2}/LabelledTextBox.cs (93%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs index a762d561c2..700adad9cb 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Sprites; -using osu.Game.Screens.Edit.Setup.Components.LabelledComponents; +using osu.Game.Graphics.UserInterfaceV2; using osuTK.Graphics; namespace osu.Game.Tests.Visual.UserInterface diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs index 5c5b1262a6..53a2bfabbc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs @@ -8,7 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Edit.Setup.Components.LabelledComponents; +using osu.Game.Graphics.UserInterfaceV2; namespace osu.Game.Tests.Visual.UserInterface { diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 7a44e7a0e1..091a837745 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -7,9 +7,9 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Online.API; using osu.Game.Overlays; -using osu.Game.Screens.Edit.Setup.Components.LabelledComponents; using osu.Game.Tournament.IPC; using osuTK; using osuTK.Graphics; diff --git a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledComponent.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs similarity index 98% rename from osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledComponent.cs rename to osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs index 770065cb0e..2e659825b7 100644 --- a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledComponent.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs @@ -5,11 +5,10 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; -namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents +namespace osu.Game.Graphics.UserInterfaceV2 { public abstract class LabelledComponent : CompositeDrawable where T : Drawable diff --git a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs similarity index 93% rename from osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs rename to osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs index 67bacea9f8..50d2a14482 100644 --- a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs @@ -4,10 +4,9 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; -namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents +namespace osu.Game.Graphics.UserInterfaceV2 { public class LabelledTextBox : LabelledComponent { From afa043aa7de522e6436276d2c000f9820800b970 Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Tue, 24 Sep 2019 18:17:27 +0800 Subject: [PATCH 1462/2815] always use default samples --- osu.Game/Screens/Menu/IntroCircles.cs | 5 +++-- osu.Game/Screens/Menu/IntroScreen.cs | 30 ++----------------------- osu.Game/Screens/Menu/IntroTriangles.cs | 4 ++-- 3 files changed, 7 insertions(+), 32 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroCircles.cs b/osu.Game/Screens/Menu/IntroCircles.cs index a861d54663..6c643860a0 100644 --- a/osu.Game/Screens/Menu/IntroCircles.cs +++ b/osu.Game/Screens/Menu/IntroCircles.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Screens; using osu.Framework.Graphics; @@ -17,10 +18,10 @@ namespace osu.Game.Screens.Menu private const double delay_step_two = 600; [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { if (MenuVoice.Value) - SetWelcome(); + Welcome = audio.Samples.Get(@"welcome"); } protected override void LogoArriving(OsuLogo logo, bool resuming) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 651fa7583d..c81fef6436 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -9,14 +9,11 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.MathUtils; using osu.Framework.Screens; -using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.IO.Archives; using osu.Game.Screens.Backgrounds; using osu.Game.Skinning; -using osu.Game.Online.API; -using osu.Game.Users; using osuTK; using osuTK.Graphics; @@ -51,14 +48,10 @@ namespace osu.Game.Screens.Menu public new Bindable Beatmap => beatmap; - protected Bindable User; - - protected Bindable Skin; - protected override BackgroundScreen CreateBackground() => new BackgroundScreenBlack(); [BackgroundDependencyLoader] - private void load(OsuConfigManager config, IAPIProvider api, SkinManager skinManager, BeatmapManager beatmaps, Framework.Game game) + private void load(OsuConfigManager config, SkinManager skinManager, BeatmapManager beatmaps, Framework.Game game) { // prevent user from changing beatmap while the intro is still runnning. beatmap = base.Beatmap.BeginLease(false); @@ -66,10 +59,7 @@ namespace osu.Game.Screens.Menu MenuVoice = config.GetBindable(OsuSetting.MenuVoice); MenuMusic = config.GetBindable(OsuSetting.MenuMusic); - User = api.LocalUser.GetBoundCopy(); - Skin = skinManager.CurrentSkin.GetBoundCopy(); - - Skin.BindValueChanged(_ => updateSeeya(), true); + seeya = audio.Samples.Get(@"seeya"); BeatmapSetInfo setInfo = null; @@ -98,22 +88,6 @@ namespace osu.Game.Screens.Menu Track = IntroBeatmap.Track; } - private void updateSeeya() - { - if (User.Value?.IsSupporter ?? false) - seeya = Skin.Value.GetSample(new SampleInfo("seeya")) ?? audio.Samples.Get(@"seeya"); - else - seeya = audio.Samples.Get(@"seeya"); - } - - protected void SetWelcome() - { - if (User.Value?.IsSupporter ?? false) - Welcome = Skin.Value.GetSample(new SampleInfo("welcome")) ?? audio.Samples.Get(@"welcome"); - else - Welcome = audio.Samples.Get(@"welcome"); - } - /// /// Whether we have loaded the menu previously. /// diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 5b49a81a5a..590069ab43 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -41,10 +41,10 @@ namespace osu.Game.Screens.Menu private BackgroundScreenDefault background; [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { if (MenuVoice.Value && !MenuMusic.Value) - SetWelcome(); + Welcome = audio.Samples.Get(@"welcome"); } protected override void LogoArriving(OsuLogo logo, bool resuming) From e78f134b90792a65e0b8e7d59236c32ea415f156 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Sep 2019 19:35:42 +0900 Subject: [PATCH 1463/2815] Mark configuration lookup test headless --- osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs index bbcc4140a9..578030748b 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; +using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.Skinning; using osu.Game.Tests.Visual; @@ -17,6 +18,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Skins { [TestFixture] + [HeadlessTest] public class TestSceneSkinConfigurationLookup : OsuTestScene { private LegacySkin source1; From 03947e5b8532b21f52dcc73f755b2d31c64c000b Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Tue, 24 Sep 2019 19:49:46 +0800 Subject: [PATCH 1464/2815] change background for ScalingContainer --- osu.Game/Graphics/Containers/ScalingContainer.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index 8f07c3a656..84053ee5d9 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; +using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Screens; using osu.Game.Screens.Backgrounds; @@ -154,6 +155,15 @@ namespace osu.Game.Graphics.Containers private class ScalingBackgroundScreen : BackgroundScreenDefault { + private Bindable beatmap; + + [BackgroundDependencyLoader] + private void load(IBindable beatmap) + { + this.beatmap = (Bindable)beatmap; + this.beatmap.ValueChanged += _ => Next(); + } + public override void OnEntering(IScreen last) { this.FadeInFromZero(4000, Easing.OutQuint); From 0bc59e17dc30cfd240407cc3d9fc4928c55b6daa Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Tue, 24 Sep 2019 21:56:32 +0800 Subject: [PATCH 1465/2815] remove Seasonal and apply suggestions --- osu.Game/Configuration/BackgroundMode.cs | 1 - osu.Game/Graphics/Containers/ScalingContainer.cs | 4 +--- .../Screens/Backgrounds/BackgroundScreenDefault.cs | 10 +--------- 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/osu.Game/Configuration/BackgroundMode.cs b/osu.Game/Configuration/BackgroundMode.cs index 21554891ca..50d54f1eb2 100644 --- a/osu.Game/Configuration/BackgroundMode.cs +++ b/osu.Game/Configuration/BackgroundMode.cs @@ -6,7 +6,6 @@ namespace osu.Game.Configuration public enum BackgroundMode { Default, - Seasonal, Beatmap } } diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index 84053ee5d9..1701223f44 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -155,13 +155,11 @@ namespace osu.Game.Graphics.Containers private class ScalingBackgroundScreen : BackgroundScreenDefault { - private Bindable beatmap; [BackgroundDependencyLoader] private void load(IBindable beatmap) { - this.beatmap = (Bindable)beatmap; - this.beatmap.ValueChanged += _ => Next(); + beatmap.ValueChanged += _ => Next(); } public override void OnEntering(IScreen last) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index a8ba17ef49..93590b0543 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -87,15 +87,7 @@ namespace osu.Game.Screens.Backgrounds } } else - { - switch (mode.Value) - { - case BackgroundMode.Seasonal: - default: - newBackground = new Background(backgroundName); - break; - } - } + newBackground = new Background(backgroundName); newBackground.Depth = currentDisplay; From 851e42a444d642ed08e3505f5314398db8401a67 Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Tue, 24 Sep 2019 22:57:29 +0800 Subject: [PATCH 1466/2815] avoid memory leak --- osu.Game/Graphics/Containers/ScalingContainer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index 1701223f44..023d295b08 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -155,11 +155,13 @@ namespace osu.Game.Graphics.Containers private class ScalingBackgroundScreen : BackgroundScreenDefault { + private IBindable beatmap; [BackgroundDependencyLoader] private void load(IBindable beatmap) { - beatmap.ValueChanged += _ => Next(); + this.beatmap = beatmap.GetBoundCopy(); + this.beatmap.ValueChanged += _ => Next(); } public override void OnEntering(IScreen last) From 4a59e3351edb6f5b9eb12c398e43bbe1c43be3a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Sep 2019 02:42:12 +0900 Subject: [PATCH 1467/2815] Update beatmap carousel tests code style Also fixes one issue I spotted in BeatmapCarousel related to incorrectly holding a selection after new sets are loaded. --- .../SongSelect/TestSceneBeatmapCarousel.cs | 98 +++++++++++-------- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 + 2 files changed, 62 insertions(+), 39 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 71399106f4..16e873a84c 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.SongSelect private readonly Stack selectedSets = new Stack(); private readonly HashSet eagerSelectedIDs = new HashSet(); - private BeatmapInfo currentSelection; + private BeatmapInfo currentSelection => carousel.SelectedBeatmap; private const int set_count = 5; @@ -56,37 +56,26 @@ namespace osu.Game.Tests.Visual.SongSelect { RelativeSizeAxes = Axes.Both, }); - - List beatmapSets = new List(); - - for (int i = 1; i <= set_count; i++) - beatmapSets.Add(createTestBeatmapSet(i)); - - carousel.SelectionChanged = s => currentSelection = s; - - loadBeatmaps(beatmapSets); - - testTraversal(); - testFiltering(); - testRandom(); - testAddRemove(); - testSorting(); - - testRemoveAll(); - testEmptyTraversal(); - testHiding(); - testSelectingFilteredRuleset(); - testCarouselRootIsRandom(); } - private void loadBeatmaps(List beatmapSets) + private void loadBeatmaps(List beatmapSets = null) { + if (beatmapSets == null) + { + beatmapSets = new List(); + + for (int i = 1; i <= set_count; i++) + beatmapSets.Add(createTestBeatmapSet(i)); + } + bool changed = false; AddStep($"Load {beatmapSets.Count} Beatmaps", () => { + carousel.Filter(new FilterCriteria()); carousel.BeatmapSetsChanged = () => changed = true; carousel.BeatmapSets = beatmapSets; }); + AddUntilStep("Wait for load", () => changed); } @@ -173,8 +162,11 @@ namespace osu.Game.Tests.Visual.SongSelect /// /// Test keyboard traversal /// - private void testTraversal() + [Test] + public void TestTraversal() { + loadBeatmaps(); + advanceSelection(direction: 1, diff: false); checkSelected(1, 1); @@ -199,8 +191,11 @@ namespace osu.Game.Tests.Visual.SongSelect /// /// Test filtering /// - private void testFiltering() + [Test] + public void TestFiltering() { + loadBeatmaps(); + // basic filtering setSelected(1, 1); @@ -262,8 +257,11 @@ namespace osu.Game.Tests.Visual.SongSelect /// /// Test random non-repeating algorithm /// - private void testRandom() + [Test] + public void TestRandom() { + loadBeatmaps(); + setSelected(1, 1); nextRandom(); @@ -299,8 +297,11 @@ namespace osu.Game.Tests.Visual.SongSelect /// /// Test adding and removing beatmap sets /// - private void testAddRemove() + [Test] + public void TestAddRemove() { + loadBeatmaps(); + AddStep("Add new set", () => carousel.UpdateBeatmapSet(createTestBeatmapSet(set_count + 1))); AddStep("Add new set", () => carousel.UpdateBeatmapSet(createTestBeatmapSet(set_count + 2))); @@ -322,16 +323,22 @@ namespace osu.Game.Tests.Visual.SongSelect /// /// Test sorting /// - private void testSorting() + [Test] + public void TestSorting() { + loadBeatmaps(); + AddStep("Sort by author", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Author }, false)); AddAssert("Check zzzzz is at bottom", () => carousel.BeatmapSets.Last().Metadata.AuthorString == "zzzzz"); AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false)); AddAssert($"Check #{set_count} is at bottom", () => carousel.BeatmapSets.Last().Metadata.Title.EndsWith($"#{set_count}!")); } - private void testRemoveAll() + [Test] + public void TestRemoveAll() { + loadBeatmaps(); + setSelected(2, 1); AddAssert("Selection is non-null", () => currentSelection != null); @@ -353,8 +360,11 @@ namespace osu.Game.Tests.Visual.SongSelect checkNoSelection(); } - private void testEmptyTraversal() + [Test] + public void TestEmptyTraversal() { + loadBeatmaps(new List()); + advanceSelection(direction: 1, diff: false); checkNoSelection(); @@ -368,11 +378,18 @@ namespace osu.Game.Tests.Visual.SongSelect checkNoSelection(); } - private void testHiding() + [Test] + public void TestHiding() { - var hidingSet = createTestBeatmapSet(1); - hidingSet.Beatmaps[1].Hidden = true; - AddStep("Add set with diff 2 hidden", () => carousel.UpdateBeatmapSet(hidingSet)); + BeatmapSetInfo hidingSet = null; + + AddStep("Add set with diff 2 hidden", () => + { + hidingSet = createTestBeatmapSet(1); + hidingSet.Beatmaps[1].Hidden = true; + carousel.UpdateBeatmapSet(hidingSet); + }); + setSelected(1, 1); checkVisibleItemCount(true, 2); @@ -402,7 +419,8 @@ namespace osu.Game.Tests.Visual.SongSelect } } - private void testSelectingFilteredRuleset() + [Test] + public void TestSelectingFilteredRuleset() { var testMixed = createTestBeatmapSet(set_count + 1); AddStep("add mixed ruleset beatmapset", () => @@ -437,14 +455,16 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("remove single ruleset set", () => carousel.RemoveBeatmapSet(testSingle)); } - private void testCarouselRootIsRandom() + [Test] + public void TestCarouselRootIsRandom() { - List beatmapSets = new List(); + List manySets = new List(); for (int i = 1; i <= 50; i++) - beatmapSets.Add(createTestBeatmapSet(i)); + manySets.Add(createTestBeatmapSet(i)); + + loadBeatmaps(manySets); - loadBeatmaps(beatmapSets); advanceSelection(direction: 1, diff: false); checkNonmatchingFilter(); checkNonmatchingFilter(); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 23c581c6f9..c3436ffd45 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -82,6 +82,9 @@ namespace osu.Game.Screens.Select var _ = newRoot.Drawables; root = newRoot; + if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet)) + selectedBeatmapSet = null; + scrollableContent.Clear(false); itemsCache.Invalidate(); scrollPositionCache.Invalidate(); From 56b460365b7cd5b96ba03ba7ca118fb8db6bb9dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 24 Sep 2019 20:34:09 +0200 Subject: [PATCH 1468/2815] Add bar line anchoring checks in mania test stage Add steps checking bar line anchoring in the mania Stage visual test to reproduce the regression in #6215 and prevent it from happening in the future. --- osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs index e7fd601abe..56401dd082 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs @@ -67,6 +67,8 @@ namespace osu.Game.Rulesets.Mania.Tests AddAssert("check note anchors", () => notesInStageAreAnchored(stages[0], Anchor.TopCentre)); AddAssert("check note anchors", () => notesInStageAreAnchored(stages[1], Anchor.BottomCentre)); + AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[0], Anchor.TopCentre)); + AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[1], Anchor.BottomCentre)); AddStep("flip direction", () => { @@ -76,10 +78,14 @@ namespace osu.Game.Rulesets.Mania.Tests AddAssert("check note anchors", () => notesInStageAreAnchored(stages[0], Anchor.BottomCentre)); AddAssert("check note anchors", () => notesInStageAreAnchored(stages[1], Anchor.TopCentre)); + AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[0], Anchor.BottomCentre)); + 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 barsInStageAreAnchored(ManiaStage stage, Anchor anchor) => stage.AllHitObjects.Where(obj => obj is DrawableBarLine).All(o => o.Anchor == anchor); + private void createNote() { foreach (var stage in stages) From 09864d7f0edb3a6164d2d384acc977c2e8795ad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 24 Sep 2019 21:42:40 +0200 Subject: [PATCH 1469/2815] Add bar line visual check in taiko playfield test Add a step checking alignment of a centre and a bar line in taiko playfield. Purely visual test without asserts. --- osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs index 3aa461e779..cbbf5b0c09 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs @@ -53,6 +53,11 @@ namespace osu.Game.Rulesets.Taiko.Tests AddStep("Strong Rim", () => addRimHit(true)); AddStep("Add bar line", () => addBarLine(false)); AddStep("Add major bar line", () => addBarLine(true)); + AddStep("Add centre w/ bar line", () => + { + addCentreHit(false); + addBarLine(true); + }); AddStep("Height test 1", () => changePlayfieldSize(1)); AddStep("Height test 2", () => changePlayfieldSize(2)); AddStep("Height test 3", () => changePlayfieldSize(3)); From d013b73d33082c3f1471bc922c4d77147122c81d Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 25 Sep 2019 01:25:05 +0300 Subject: [PATCH 1470/2815] Move in-memory logic to a base class --- .../Configuration/InMemoryConfigManager.cs | 22 +++++++++++++++++++ osu.Game/Configuration/SessionStatics.cs | 10 +++------ 2 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 osu.Game/Configuration/InMemoryConfigManager.cs diff --git a/osu.Game/Configuration/InMemoryConfigManager.cs b/osu.Game/Configuration/InMemoryConfigManager.cs new file mode 100644 index 0000000000..b0dc6b0e9c --- /dev/null +++ b/osu.Game/Configuration/InMemoryConfigManager.cs @@ -0,0 +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.Configuration; + +namespace osu.Game.Configuration +{ + public class InMemoryConfigManager : ConfigManager + where T : struct + { + public InMemoryConfigManager() + { + InitialiseDefaults(); + } + + protected override void PerformLoad() + { + } + + protected override bool PerformSave() => true; + } +} diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index 0c1ea1e568..b4b5e914bb 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.cs @@ -1,18 +1,14 @@ // 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.Configuration; - namespace osu.Game.Configuration { - public class SessionStatics : ConfigManager + public class SessionStatics : InMemoryConfigManager { - // This is an in-memory store. - protected override void PerformLoad() + protected override void InitialiseDefaults() { + Set(Statics.LoginOverlayDisplayed, false); } - - protected override bool PerformSave() => true; } public enum Statics From cde7f49db1b37d3325b64f0ceddd312476e3f2c8 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 25 Sep 2019 01:26:02 +0300 Subject: [PATCH 1471/2815] Use direct get and set instead --- osu.Game/Screens/Menu/MainMenu.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 1834221d35..98ceb315a2 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -200,12 +200,10 @@ namespace osu.Game.Screens.Menu bool displayLogin() { - var loginDisplayed = statics.GetBindable(Statics.LoginOverlayDisplayed); - - if (!loginDisplayed.Value) + if (!statics.Get(Statics.LoginOverlayDisplayed)) { Scheduler.AddDelayed(() => login?.Show(), 500); - loginDisplayed.Value = true; + statics.Set(Statics.LoginOverlayDisplayed, true); } return true; From 9323df26a1cafa6fa90e316bfa659fcb5584fa13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 24 Sep 2019 23:03:55 +0200 Subject: [PATCH 1472/2815] Decouple bar line hitobjects from generator Introduce an IBarLine interface, which together with generic constraints helps decouple BarLineGenerator from the actual hitobject types it creates. Thanks to this, all rulesets that want bar lines can provide an implementation of IBarLine that also derives from the base hitobject class. This allows DrawableBarLines in taiko and mania to be migrated back to DrawableTaikoHitObject and DrawableManiaHitObject base classes respectively. This in turn resolves #6215 without code duplication, since the missing anchoring application is now done in mania's DrawableBarLine through deriving from DrawableManiaHitObject. --- osu.Game.Rulesets.Mania/Objects/BarLine.cs | 12 ++++++++++ .../Objects/Drawables/DrawableBarLine.cs | 3 +-- .../UI/DrawableManiaRuleset.cs | 2 +- osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 1 - osu.Game.Rulesets.Taiko/Objects/BarLine.cs | 12 ++++++++++ .../UI/DrawableTaikoRuleset.cs | 2 +- osu.Game/Rulesets/Objects/BarLine.cs | 16 -------------- osu.Game/Rulesets/Objects/BarLineGenerator.cs | 7 +++--- osu.Game/Rulesets/Objects/IBarLine.cs | 22 +++++++++++++++++++ 9 files changed, 53 insertions(+), 24 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/Objects/BarLine.cs create mode 100644 osu.Game.Rulesets.Taiko/Objects/BarLine.cs delete mode 100644 osu.Game/Rulesets/Objects/BarLine.cs create mode 100644 osu.Game/Rulesets/Objects/IBarLine.cs diff --git a/osu.Game.Rulesets.Mania/Objects/BarLine.cs b/osu.Game.Rulesets.Mania/Objects/BarLine.cs new file mode 100644 index 0000000000..0981b028b2 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Objects/BarLine.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. + +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Mania.Objects +{ + public class BarLine : ManiaHitObject, IBarLine + { + public bool Major { get; set; } + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs index be21610525..56bc797c7f 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs @@ -4,7 +4,6 @@ using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osuTK.Graphics; @@ -14,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// Visualises a . Although this derives DrawableManiaHitObject, /// this does not handle input/sound like a normal hit object. /// - public class DrawableBarLine : DrawableHitObject + public class DrawableBarLine : DrawableManiaHitObject { /// /// Height of major bar line triangles. diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 29863fba2e..d371c1f7a8 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.UI public DrawableManiaRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { - BarLines = new BarLineGenerator(Beatmap).BarLines; + BarLines = new BarLineGenerator(Beatmap).BarLines; } [BackgroundDependencyLoader] diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 12faa499ad..5ab07416a6 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -8,7 +8,6 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osuTK; diff --git a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs b/osu.Game.Rulesets.Taiko/Objects/BarLine.cs new file mode 100644 index 0000000000..2afbbc737c --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Objects/BarLine.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. + +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Taiko.Objects +{ + public class BarLine : TaikoHitObject, IBarLine + { + public bool Major { get; set; } + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 5caa9e4626..fc109bf6a6 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load() { - new BarLineGenerator(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar.Major ? new DrawableBarLineMajor(bar) : new DrawableBarLine(bar))); + new BarLineGenerator(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar.Major ? new DrawableBarLineMajor(bar) : new DrawableBarLine(bar))); } public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this); diff --git a/osu.Game/Rulesets/Objects/BarLine.cs b/osu.Game/Rulesets/Objects/BarLine.cs deleted file mode 100644 index a5c716e127..0000000000 --- a/osu.Game/Rulesets/Objects/BarLine.cs +++ /dev/null @@ -1,16 +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.Objects -{ - /// - /// A hit object representing the end of a bar. - /// - public class BarLine : HitObject - { - /// - /// Whether this barline is a prominent beat (based on time signature of beatmap). - /// - public bool Major; - } -} diff --git a/osu.Game/Rulesets/Objects/BarLineGenerator.cs b/osu.Game/Rulesets/Objects/BarLineGenerator.cs index ce571d7b17..4f9395435e 100644 --- a/osu.Game/Rulesets/Objects/BarLineGenerator.cs +++ b/osu.Game/Rulesets/Objects/BarLineGenerator.cs @@ -10,12 +10,13 @@ using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Objects { - public class BarLineGenerator + public class BarLineGenerator + where TBarLine : class, IBarLine, new() { /// /// The generated bar lines. /// - public readonly List BarLines = new List(); + public readonly List BarLines = new List(); /// /// Constructs and generates bar lines for provided beatmap. @@ -46,7 +47,7 @@ namespace osu.Game.Rulesets.Objects for (double t = currentTimingPoint.Time; Precision.DefinitelyBigger(endTime, t); t += barLength, currentBeat++) { - BarLines.Add(new BarLine + BarLines.Add(new TBarLine { StartTime = t, Major = currentBeat % (int)currentTimingPoint.TimeSignature == 0 diff --git a/osu.Game/Rulesets/Objects/IBarLine.cs b/osu.Game/Rulesets/Objects/IBarLine.cs new file mode 100644 index 0000000000..14df80e3b9 --- /dev/null +++ b/osu.Game/Rulesets/Objects/IBarLine.cs @@ -0,0 +1,22 @@ +// 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.Objects +{ + /// + /// Interface for bar line hitobjects. + /// Used to decouple bar line generation from ruleset-specific rendering/drawing hierarchies. + /// + public interface IBarLine + { + /// + /// The time position of the bar. + /// + double StartTime { set; } + + /// + /// Whether this bar line is a prominent beat (based on time signature of beatmap). + /// + bool Major { set; } + } +} From 7fab1a4337c4db0094e4179fb34037984f1d322b Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 24 Sep 2019 16:06:33 -0700 Subject: [PATCH 1473/2815] Truncate long metadata on beatmap info wedge --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 22 +++++++++++++++------ osu.Game/Screens/Select/SongSelect.cs | 2 +- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 65ecd7b812..6c3eed8610 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -31,7 +31,9 @@ namespace osu.Game.Screens.Select { public class BeatmapInfoWedge : OverlayContainer { - private static readonly Vector2 wedged_container_shear = new Vector2(0.15f, 0); + private const float shear_width = 36.75f; + + private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / SongSelect.wedged_container_size.Y, 0); private readonly IBindable ruleset = new Bindable(); @@ -200,14 +202,17 @@ namespace osu.Game.Screens.Select Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, Direction = FillDirection.Vertical, - Margin = new MarginPadding { Top = 10, Left = 25, Right = 10, Bottom = 20 }, - AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 10, Left = 25, Right = shear_width * 2.5f, Bottom = 20 }, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, Children = new Drawable[] { VersionLabel = new OsuSpriteText { Text = beatmapInfo.Version, Font = OsuFont.GetFont(size: 24, italics: true), + RelativeSizeAxes = Axes.X, + Truncate = true, }, } }, @@ -217,7 +222,7 @@ namespace osu.Game.Screens.Select Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Direction = FillDirection.Vertical, - Margin = new MarginPadding { Top = 14, Left = 10, Right = 18, Bottom = 20 }, + Padding = new MarginPadding { Top = 14, Left = 10, Right = shear_width / 2, Bottom = 20 }, AutoSizeAxes = Axes.Both, Children = new Drawable[] { @@ -236,17 +241,22 @@ namespace osu.Game.Screens.Select Origin = Anchor.TopLeft, Y = -22, Direction = FillDirection.Vertical, - Margin = new MarginPadding { Top = 15, Left = 25, Right = 10, Bottom = 20 }, - AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 15, Left = 25, Right = shear_width, Bottom = 20 }, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, Children = new Drawable[] { TitleLabel = new OsuSpriteText { Font = OsuFont.GetFont(size: 28, italics: true), + RelativeSizeAxes = Axes.X, + Truncate = true, }, ArtistLabel = new OsuSpriteText { Font = OsuFont.GetFont(size: 17, italics: true), + RelativeSizeAxes = Axes.X, + Truncate = true, }, MapperContainer = new FillFlowContainer { diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index fca801ce78..6c0d8a0f36 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Select { public abstract class SongSelect : OsuScreen, IKeyBindingHandler { - private static readonly Vector2 wedged_container_size = new Vector2(0.5f, 245); + public static readonly Vector2 wedged_container_size = new Vector2(0.5f, 245); protected const float BACKGROUND_BLUR = 20; private const float left_area_padding = 20; From 8efab559c8d65138c2a38097970797957f396233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 25 Sep 2019 01:13:42 +0200 Subject: [PATCH 1474/2815] Remove unused using directives --- osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs | 1 - osu.Game.Rulesets.Mania/UI/ManiaStage.cs | 1 - .../Objects/Drawables/DrawableBarLineMajor.cs | 1 - 3 files changed, 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs index 56401dd082..d5fd2808b8 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs @@ -15,7 +15,6 @@ using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; using osuTK; diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs index 98a4b7d0b6..a28de7ea58 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs @@ -12,7 +12,6 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs index f5b75a781b..4d3a1a3f8a 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs @@ -5,7 +5,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osuTK; using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { From 8efba255c383acd4114b1625939eaf745bb59cbd Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 24 Sep 2019 16:21:08 -0700 Subject: [PATCH 1475/2815] Add truncation test --- .../SongSelect/TestSceneBeatmapInfoWedge.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index 932e114580..cda6bedf5d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -127,6 +127,12 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("check no info labels", () => !infoWedge.Info.InfoLabelContainer.Children.Any()); } + [Test] + public void testTruncation() + { + selectBeatmap(createLongMetadata()); + } + private void selectBeatmap([CanBeNull] IBeatmap b) { BeatmapInfoWedge.BufferedWedgeInfo infoBefore = null; @@ -166,6 +172,25 @@ namespace osu.Game.Tests.Visual.SongSelect }; } + private IBeatmap createLongMetadata() + { + return new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + AuthorString = $"WWWWWWWWWWWWWWW", + Artist = $"Verrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrry long Artist", + Source = $"Verrrrry long Source", + Title = $"Verrrrry long Title" + }, + Version = $"Verrrrrrrrrrrrrrrrrrrrrrrrrrrrry long Version", + Status = BeatmapSetOnlineStatus.Graveyard, + }, + }; + } + private class TestBeatmapInfoWedge : BeatmapInfoWedge { public new BufferedWedgeInfo Info => base.Info; From cc6030ca14d3f6377c657afeb44faa96bca3bbd2 Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 24 Sep 2019 16:23:36 -0700 Subject: [PATCH 1476/2815] Update beatmap info wedge tests --- .../Visual/SongSelect/TestSceneBeatmapInfoWedge.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index cda6bedf5d..017e9c527b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -75,7 +75,6 @@ namespace osu.Game.Tests.Visual.SongSelect testBeatmapLabels(instance); - // TODO: adjust cases once more info is shown for other gamemodes switch (instance) { case OsuRuleset _: @@ -99,8 +98,6 @@ namespace osu.Game.Tests.Visual.SongSelect break; } } - - testNullBeatmap(); } private void testBeatmapLabels(Ruleset ruleset) @@ -117,7 +114,8 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("check info labels count", () => infoWedge.Info.InfoLabelContainer.Children.Count == expectedCount); } - private void testNullBeatmap() + [Test] + public void testNullBeatmap() { selectBeatmap(null); AddAssert("check empty version", () => string.IsNullOrEmpty(infoWedge.Info.VersionLabel.Text)); From 9861b214407a5e4861722b2cfb876bd524db76de Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 24 Sep 2019 16:28:40 -0700 Subject: [PATCH 1477/2815] Remove unnecessary padding/margin --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 6c3eed8610..33060e0735 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -202,7 +202,7 @@ namespace osu.Game.Screens.Select Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, Direction = FillDirection.Vertical, - Padding = new MarginPadding { Top = 10, Left = 25, Right = shear_width * 2.5f, Bottom = 20 }, + Padding = new MarginPadding { Top = 10, Left = 25, Right = shear_width * 2.5f }, AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Children = new Drawable[] @@ -222,7 +222,7 @@ namespace osu.Game.Screens.Select Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Direction = FillDirection.Vertical, - Padding = new MarginPadding { Top = 14, Left = 10, Right = shear_width / 2, Bottom = 20 }, + Padding = new MarginPadding { Top = 14, Right = shear_width / 2 }, AutoSizeAxes = Axes.Both, Children = new Drawable[] { @@ -239,9 +239,9 @@ namespace osu.Game.Screens.Select Name = "Centre-aligned metadata", Anchor = Anchor.CentreLeft, Origin = Anchor.TopLeft, - Y = -22, + Y = -7, Direction = FillDirection.Vertical, - Padding = new MarginPadding { Top = 15, Left = 25, Right = shear_width, Bottom = 20 }, + Padding = new MarginPadding { Left = 25, Right = shear_width }, AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Children = new Drawable[] From 102dbd85bdd4fd4f4589314e3164512502860b51 Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 24 Sep 2019 16:48:22 -0700 Subject: [PATCH 1478/2815] Fix CI errors --- .../Visual/SongSelect/TestSceneBeatmapInfoWedge.cs | 14 +++++++------- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 12 ++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index 017e9c527b..f49d7a14a6 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -115,7 +115,7 @@ namespace osu.Game.Tests.Visual.SongSelect } [Test] - public void testNullBeatmap() + public void TestNullBeatmap() { selectBeatmap(null); AddAssert("check empty version", () => string.IsNullOrEmpty(infoWedge.Info.VersionLabel.Text)); @@ -126,7 +126,7 @@ namespace osu.Game.Tests.Visual.SongSelect } [Test] - public void testTruncation() + public void TestTruncation() { selectBeatmap(createLongMetadata()); } @@ -178,12 +178,12 @@ namespace osu.Game.Tests.Visual.SongSelect { Metadata = new BeatmapMetadata { - AuthorString = $"WWWWWWWWWWWWWWW", - Artist = $"Verrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrry long Artist", - Source = $"Verrrrry long Source", - Title = $"Verrrrry long Title" + AuthorString = "WWWWWWWWWWWWWWW", + Artist = "Verrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrry long Artist", + Source = "Verrrrry long Source", + Title = "Verrrrry long Title" }, - Version = $"Verrrrrrrrrrrrrrrrrrrrrrrrrrrrry long Version", + Version = "Verrrrrrrrrrrrrrrrrrrrrrrrrrrrry long Version", Status = BeatmapSetOnlineStatus.Graveyard, }, }; diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 33060e0735..8b360d4a86 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Select { private const float shear_width = 36.75f; - private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / SongSelect.wedged_container_size.Y, 0); + private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / SongSelect.WEDGED_CONTAINER_SIZE.Y, 0); private readonly IBindable ruleset = new Bindable(); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 6c0d8a0f36..653f62dd15 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Select { public abstract class SongSelect : OsuScreen, IKeyBindingHandler { - public static readonly Vector2 wedged_container_size = new Vector2(0.5f, 245); + public static readonly Vector2 WEDGED_CONTAINER_SIZE = new Vector2(0.5f, 245); protected const float BACKGROUND_BLUR = 20; private const float left_area_padding = 20; @@ -109,7 +109,7 @@ namespace osu.Game.Screens.Select { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Right = -150 }, - Size = new Vector2(wedged_container_size.X, 1), + Size = new Vector2(WEDGED_CONTAINER_SIZE.X, 1), } } }, @@ -118,11 +118,11 @@ namespace osu.Game.Screens.Select Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, RelativeSizeAxes = Axes.Both, - Size = new Vector2(wedged_container_size.X, 1), + Size = new Vector2(WEDGED_CONTAINER_SIZE.X, 1), Padding = new MarginPadding { Bottom = Footer.HEIGHT, - Top = wedged_container_size.Y + left_area_padding, + Top = WEDGED_CONTAINER_SIZE.Y + left_area_padding, Left = left_area_padding, Right = left_area_padding * 2, }, @@ -158,7 +158,7 @@ namespace osu.Game.Screens.Select Child = Carousel = new BeatmapCarousel { RelativeSizeAxes = Axes.Both, - Size = new Vector2(1 - wedged_container_size.X, 1), + Size = new Vector2(1 - WEDGED_CONTAINER_SIZE.X, 1), Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, SelectionChanged = updateSelectedBeatmap, @@ -182,7 +182,7 @@ namespace osu.Game.Screens.Select }, beatmapInfoWedge = new BeatmapInfoWedge { - Size = wedged_container_size, + Size = WEDGED_CONTAINER_SIZE, RelativeSizeAxes = Axes.X, Margin = new MarginPadding { From 2089f6fc42ed65d088712553def79a8078716b8b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Sep 2019 14:28:35 +0900 Subject: [PATCH 1479/2815] Fix potential test fail case --- .../Visual/SongSelect/TestSceneBeatmapCarousel.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 16e873a84c..f12a613bf1 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -381,14 +381,10 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestHiding() { - BeatmapSetInfo hidingSet = null; + BeatmapSetInfo hidingSet = createTestBeatmapSet(1); + hidingSet.Beatmaps[1].Hidden = true; - AddStep("Add set with diff 2 hidden", () => - { - hidingSet = createTestBeatmapSet(1); - hidingSet.Beatmaps[1].Hidden = true; - carousel.UpdateBeatmapSet(hidingSet); - }); + loadBeatmaps(new List { hidingSet }); setSelected(1, 1); From c83db94eb7ccbe458da15d6f1d7563e14af0cfba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Sep 2019 15:00:08 +0900 Subject: [PATCH 1480/2815] Use isolated storage/api --- .../Visual/Menus/TestSceneScreenNavigation.cs | 25 +++++++--------- osu.Game/Online/API/APIRequest.cs | 5 +++- osu.Game/OsuGameBase.cs | 29 ++++++++++++------- 3 files changed, 33 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs index 515f4cdce6..ee160e6a15 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual.Menus { private const float click_padding = 25; - private GameHost gameHost; + private GameHost host; private TestOsuGame osuGame; private Vector2 backButtonPosition => osuGame.ToScreenSpace(new Vector2(click_padding, osuGame.LayoutRectangle.Bottom - click_padding)); @@ -36,9 +36,9 @@ namespace osu.Game.Tests.Visual.Menus private Vector2 optionsButtonPosition => osuGame.ToScreenSpace(new Vector2(click_padding, click_padding)); [BackgroundDependencyLoader] - private void load(GameHost gameHost) + private void load(GameHost host) { - this.gameHost = gameHost; + this.host = host; Child = new Box { @@ -58,8 +58,8 @@ namespace osu.Game.Tests.Visual.Menus osuGame.Dispose(); } - osuGame = new TestOsuGame(); - osuGame.SetHost(gameHost); + osuGame = new TestOsuGame(LocalStorage, API); + osuGame.SetHost(host); Add(osuGame); }); @@ -163,19 +163,16 @@ namespace osu.Game.Tests.Visual.Menus protected override Loader CreateLoader() => new TestLoader(); - private DependencyContainer dependencies; - - private DummyAPIAccess dummyAPI; - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => - dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + public TestOsuGame(Storage storage, IAPIProvider api) + { + Storage = storage; + API = api; + } protected override void LoadComplete() { base.LoadComplete(); - dependencies.CacheAs(dummyAPI = new DummyAPIAccess()); - - dummyAPI.Login("Rhythm Champion", "osu!"); + API.Login("Rhythm Champion", "osu!"); } } diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index e8eff5a3a9..4f613d5c3c 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -64,7 +64,10 @@ namespace osu.Game.Online.API public void Perform(IAPIProvider api) { if (!(api is APIAccess apiAccess)) - throw new NotSupportedException($"A {nameof(APIAccess)} is required to perform requests."); + { + Fail(new NotSupportedException($"A {nameof(APIAccess)} is required to perform requests.")); + return; + } API = apiAccess; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index d6b8ad3e67..9d1eff4819 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -65,7 +65,7 @@ namespace osu.Game protected RulesetConfigCache RulesetConfigCache; - protected APIAccess API; + protected IAPIProvider API; protected MenuCursorContainer MenuCursorContainer; @@ -73,6 +73,8 @@ namespace osu.Game protected override Container Content => content; + protected Storage Storage { get; set; } + private Bindable beatmap; // cached via load() method [Cached] @@ -123,7 +125,7 @@ namespace osu.Game { Resources.AddStore(new DllResourceStore(@"osu.Game.Resources.dll")); - dependencies.Cache(contextFactory = new DatabaseContextFactory(Host.Storage)); + dependencies.Cache(contextFactory = new DatabaseContextFactory(Storage)); var largeStore = new LargeTextureStore(Host.CreateTextureLoaderStore(new NamespacedResourceStore(Resources, @"Textures"))); largeStore.AddStore(Host.CreateTextureLoaderStore(new OnlineStore())); @@ -158,21 +160,21 @@ namespace osu.Game runMigrations(); - dependencies.Cache(SkinManager = new SkinManager(Host.Storage, contextFactory, Host, Audio, new NamespacedResourceStore(Resources, "Skins/Legacy"))); + dependencies.Cache(SkinManager = new SkinManager(Storage, contextFactory, Host, Audio, new NamespacedResourceStore(Resources, "Skins/Legacy"))); dependencies.CacheAs(SkinManager); - API = new APIAccess(LocalConfig); + if (API == null) API = new APIAccess(LocalConfig); - dependencies.CacheAs(API); + dependencies.CacheAs(API); var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures); dependencies.Cache(RulesetStore = new RulesetStore(contextFactory)); - dependencies.Cache(FileStore = new FileStore(contextFactory, Host.Storage)); + dependencies.Cache(FileStore = new FileStore(contextFactory, Storage)); // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() - dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Host.Storage, API, contextFactory, Host)); - dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, API, Audio, Host, defaultBeatmap)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host)); + dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Host, defaultBeatmap)); // this should likely be moved to ArchiveModelManager when another case appers where it is necessary // to have inter-dependent model managers. this could be obtained with an IHasForeign interface to @@ -206,7 +208,8 @@ namespace osu.Game FileStore.Cleanup(); - AddInternal(API); + if (API is APIAccess apiAcces) + AddInternal(apiAcces); AddInternal(RulesetConfigCache); GlobalActionContainer globalBinding; @@ -266,9 +269,13 @@ namespace osu.Game public override void SetHost(GameHost host) { - if (LocalConfig == null) - LocalConfig = new OsuConfigManager(host.Storage); base.SetHost(host); + + if (Storage == null) + Storage = host.Storage; + + if (LocalConfig == null) + LocalConfig = new OsuConfigManager(Storage); } private readonly List fileImporters = new List(); From 8fd1a45a4278389c8b3fdc8e76209218cb4804d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Sep 2019 15:29:02 +0900 Subject: [PATCH 1481/2815] Change intro displayed for tests As the triangles intro relies on the audio track's clock advancing, we can't use it just yet (CI server has no audio device). This is a temporary workaround for that shortcoming. --- osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs index ee160e6a15..17535cae98 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Overlays; @@ -21,6 +22,7 @@ using osu.Game.Screens.Select; using osuTK; using osuTK.Graphics; using osuTK.Input; +using IntroSequence = osu.Game.Configuration.IntroSequence; namespace osu.Game.Tests.Visual.Menus { @@ -61,6 +63,10 @@ namespace osu.Game.Tests.Visual.Menus osuGame = new TestOsuGame(LocalStorage, API); osuGame.SetHost(host); + // todo: this can be removed once we can run audio trakcs without a device present + // see https://github.com/ppy/osu/issues/1302 + osuGame.LocalConfig.Set(OsuSetting.IntroSequence, IntroSequence.Circles); + Add(osuGame); }); AddUntilStep("Wait for load", () => osuGame.IsLoaded); @@ -161,6 +167,8 @@ namespace osu.Game.Tests.Visual.Menus public new SettingsPanel Settings => base.Settings; + public new OsuConfigManager LocalConfig => base.LocalConfig; + protected override Loader CreateLoader() => new TestLoader(); public TestOsuGame(Storage storage, IAPIProvider api) From 74b2e99247ab38d210d76aac8050aac8533057f6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 25 Sep 2019 16:25:15 +0900 Subject: [PATCH 1482/2815] Fix invalid cursor trail parts being drawn --- .../UI/Cursor/CursorTrail.cs | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index b32dfd483f..80291c002e 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -40,9 +40,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor for (int i = 0; i < max_sprites; i++) { - // InvalidationID 1 forces an update of each part of the cursor trail the first time ApplyState is run on the draw node - // This is to prevent garbage data from being sent to the vertex shader, resulting in visual issues on some platforms - parts[i].InvalidationID = 1; + // -1 signals that the part is unusable, and should not be drawn + parts[i].InvalidationID = -1; } } @@ -112,7 +111,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor for (int i = 0; i < parts.Length; ++i) { parts[i].Time -= time; - ++parts[i].InvalidationID; + + if (parts[i].InvalidationID != -1) + ++parts[i].InvalidationID; } time = 0; @@ -205,8 +206,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor public TrailDrawNode(CursorTrail source) : base(source) { - for (int i = 0; i < max_sprites; i++) - parts[i].InvalidationID = 0; } public override void ApplyState() @@ -218,11 +217,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor size = Source.partSize; time = Source.time; - for (int i = 0; i < Source.parts.Length; ++i) - { - if (Source.parts[i].InvalidationID > parts[i].InvalidationID) - parts[i] = Source.parts[i]; - } + Source.parts.CopyTo(parts, 0); } public override void Draw(Action vertexAction) @@ -234,6 +229,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor for (int i = 0; i < parts.Length; ++i) { + if (parts[i].InvalidationID == -1) + continue; + vertexBatch.DrawTime = parts[i].Time; Vector2 pos = parts[i].Position; From 261ba5c80ac4b82d5d73ce02c9609fe8bc829783 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 25 Sep 2019 17:42:27 +0900 Subject: [PATCH 1483/2815] Fix button not transforming correctly in some cases --- .../UserInterface/TestSceneSwitchButton.cs | 30 +++++++++++++++++-- .../Graphics/UserInterface/SwitchButton.cs | 19 ++++++------ 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSwitchButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSwitchButton.cs index 8fe381f141..bf9071b812 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneSwitchButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSwitchButton.cs @@ -1,20 +1,44 @@ // 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.Bindables; using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; +using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface { - public class TestSceneSwitchButton : OsuTestScene + public class TestSceneSwitchButton : ManualInputManagerTestScene { - public TestSceneSwitchButton() + private SwitchButton switchButton; + + [SetUp] + public void Setup() => Schedule(() => { - Child = new SwitchButton + Child = switchButton = new SwitchButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, }; + }); + + [Test] + public void TestChangeThroughInput() + { + AddStep("move to switch button", () => InputManager.MoveMouseTo(switchButton)); + AddStep("click on", () => InputManager.Click(MouseButton.Left)); + AddStep("click off", () => InputManager.Click(MouseButton.Left)); + } + + [Test] + public void TestChangeThroughBindable() + { + BindableBool bindable = null; + + AddStep("bind bindable", () => switchButton.Current.BindTo(bindable = new BindableBool())); + AddStep("toggle bindable", () => bindable.Toggle()); + AddStep("toggle bindable", () => bindable.Toggle()); } } } diff --git a/osu.Game/Graphics/UserInterface/SwitchButton.cs b/osu.Game/Graphics/UserInterface/SwitchButton.cs index 21712051ef..9964af91d5 100644 --- a/osu.Game/Graphics/UserInterface/SwitchButton.cs +++ b/osu.Game/Graphics/UserInterface/SwitchButton.cs @@ -2,6 +2,7 @@ // 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; @@ -73,20 +74,20 @@ namespace osu.Game.Graphics.UserInterface switchContainer.Colour = enabledColour; fill.Colour = disabledColour; - - updateBorder(); } - protected override void OnUserChange(bool value) + protected override void LoadComplete() { - base.OnUserChange(value); + base.LoadComplete(); - if (value) - switchCircle.MoveToX(switchContainer.DrawWidth - switchCircle.DrawWidth, 200, Easing.OutQuint); - else - switchCircle.MoveToX(0, 200, Easing.OutQuint); + Current.BindValueChanged(updateState, true); + FinishTransforms(true); + } - fill.FadeTo(value ? 1 : 0, 250, Easing.OutQuint); + private void updateState(ValueChangedEvent state) + { + switchCircle.MoveToX(state.NewValue ? switchContainer.DrawWidth - switchCircle.DrawWidth : 0, 200, Easing.OutQuint); + fill.FadeTo(state.NewValue ? 1 : 0, 250, Easing.OutQuint); updateBorder(); } From c9e39c124e5817bce9909acc8e057606701ea0ce Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 25 Sep 2019 17:42:35 +0900 Subject: [PATCH 1484/2815] Add a labelled switch button --- .../TestSceneLabelledSwitchButton.cs | 50 +++++++++++++++++++ .../LabelledSwitchButton.cs | 17 +++++++ 2 files changed, 67 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSwitchButton.cs create mode 100644 osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledSwitchButton.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSwitchButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSwitchButton.cs new file mode 100644 index 0000000000..dbce08c898 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSwitchButton.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; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Edit.Setup.Components.LabelledComponents; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneLabelledSwitchButton : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(LabelledSwitchButton), + typeof(SwitchButton) + }; + + [TestCase(false)] + [TestCase(true)] + public void TestSwitchButton(bool hasDescription) => createSwitchButton(hasDescription); + + private void createSwitchButton(bool hasDescription = false) + { + AddStep("create component", () => + { + LabelledSwitchButton component; + + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 500, + AutoSizeAxes = Axes.Y, + Child = component = new LabelledSwitchButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }; + + component.Label = "a sample component"; + component.Description = hasDescription ? "this text describes the component" : string.Empty; + }); + } + } +} diff --git a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledSwitchButton.cs b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledSwitchButton.cs new file mode 100644 index 0000000000..54f6184578 --- /dev/null +++ b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledSwitchButton.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.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents +{ + public class LabelledSwitchButton : LabelledComponent + { + public LabelledSwitchButton() + : base(true) + { + } + + protected override SwitchButton CreateComponent() => new SwitchButton(); + } +} From 9f77a1ef35c081db6812bbd40e38185101ae4fcc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 25 Sep 2019 17:53:08 +0900 Subject: [PATCH 1485/2815] Adjust namespaces --- .../Visual/UserInterface/TestSceneLabelledSwitchButton.cs | 3 +-- osu.Game.Tests/Visual/UserInterface/TestSceneSwitchButton.cs | 2 +- osu.Game/Graphics/UserInterfaceV2/LabelledSwitchButton.cs | 4 +--- .../{UserInterface => UserInterfaceV2}/SwitchButton.cs | 2 +- 4 files changed, 4 insertions(+), 7 deletions(-) rename osu.Game/Graphics/{UserInterface => UserInterfaceV2}/SwitchButton.cs (98%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSwitchButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSwitchButton.cs index dbce08c898..6ca4d9fa4c 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSwitchButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSwitchButton.cs @@ -6,8 +6,7 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Edit.Setup.Components.LabelledComponents; +using osu.Game.Graphics.UserInterfaceV2; namespace osu.Game.Tests.Visual.UserInterface { diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSwitchButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSwitchButton.cs index bf9071b812..4a104b4a41 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneSwitchButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSwitchButton.cs @@ -4,7 +4,7 @@ using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledSwitchButton.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledSwitchButton.cs index 54f6184578..c973f1d13e 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledSwitchButton.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledSwitchButton.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.Game.Graphics.UserInterface; - -namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents +namespace osu.Game.Graphics.UserInterfaceV2 { public class LabelledSwitchButton : LabelledComponent { diff --git a/osu.Game/Graphics/UserInterface/SwitchButton.cs b/osu.Game/Graphics/UserInterfaceV2/SwitchButton.cs similarity index 98% rename from osu.Game/Graphics/UserInterface/SwitchButton.cs rename to osu.Game/Graphics/UserInterfaceV2/SwitchButton.cs index 9964af91d5..a7fd25b554 100644 --- a/osu.Game/Graphics/UserInterface/SwitchButton.cs +++ b/osu.Game/Graphics/UserInterfaceV2/SwitchButton.cs @@ -13,7 +13,7 @@ using osu.Framework.Input.Events; using osuTK; using osuTK.Graphics; -namespace osu.Game.Graphics.UserInterface +namespace osu.Game.Graphics.UserInterfaceV2 { public class SwitchButton : Checkbox { From f11156c2dc644e34f68c883b2b7d9bdf3a63c380 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Sep 2019 19:24:05 +0900 Subject: [PATCH 1486/2815] Fix tests not working correctly --- .../Visual/Gameplay/TestScenePlayerLoader.cs | 107 +++++++++++++++--- osu.Game/Screens/Play/PlayerLoader.cs | 2 +- 2 files changed, 93 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index ab519360ac..eb54eca0cb 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -7,10 +7,15 @@ using System.Linq; using System.Threading; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.MathUtils; using osu.Framework.Screens; +using osu.Game.Graphics.Containers; +using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; @@ -18,25 +23,41 @@ using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Play; using osu.Game.Screens.Play.PlayerSettings; +using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { public class TestScenePlayerLoader : ManualInputManagerTestScene { private TestPlayerLoader loader; - private OsuScreenStack stack; + private TestPlayerLoaderContainer container; + private TestPlayer player; - [SetUp] - public void Setup() => Schedule(() => + [Resolved] + private AudioManager audioManager { get; set; } + + /// + /// Sets the input manager child to a new test player loader container instance. + /// + /// If the test player should behave like the production one. + /// An action to run before player load but after bindable leases are returned. + public void ResetPlayer(bool interactive, Action beforeLoadAction = null) { - InputManager.Child = stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }; + audioManager.Volume.SetDefault(); + + InputManager.Clear(); + + beforeLoadAction?.Invoke(); Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); - }); + + InputManager.Child = container = new TestPlayerLoaderContainer( + loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive))); + } [Test] public void TestBlockLoadViaMouseMovement() { - AddStep("load dummy beatmap", () => stack.Push(loader = new TestPlayerLoader(() => new TestPlayer(false, false)))); + AddStep("load dummy beatmap", () => ResetPlayer(false)); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); AddRepeatStep("move mouse", () => InputManager.MoveMouseTo(loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft + (loader.VisualSettings.ScreenSpaceDrawQuad.BottomRight - loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft) * RNG.NextSingle()), 20); AddAssert("loader still active", () => loader.IsCurrentScreen()); @@ -46,16 +67,17 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestLoadContinuation() { - Player player = null; SlowLoadPlayer slowPlayer = null; - AddStep("load dummy beatmap", () => stack.Push(loader = new TestPlayerLoader(() => player = new TestPlayer(false, false)))); + AddStep("load dummy beatmap", () => ResetPlayer(false)); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); AddUntilStep("wait for player to be current", () => player.IsCurrentScreen()); AddStep("load slow dummy beatmap", () => { - stack.Push(loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false))); + InputManager.Child = container = new TestPlayerLoaderContainer( + loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false))); + Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000); }); @@ -65,16 +87,11 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestModReinstantiation() { - TestPlayer player = null; TestMod gameMod = null; TestMod playerMod1 = null; TestMod playerMod2 = null; - AddStep("load player", () => - { - Mods.Value = new[] { gameMod = new TestMod() }; - stack.Push(loader = new TestPlayerLoader(() => player = new TestPlayer())); - }); + AddStep("load player", () => { ResetPlayer(true, () => Mods.Value = new[] { gameMod = new TestMod() }); }); AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen()); AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); @@ -97,6 +114,66 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("player mods applied", () => playerMod2.Applied); } + [Test] + public void TestMutedNotification() + { + AddStep("reset notification", PlayerLoader.ResetNotificationLock); + + AddStep("load player", () => ResetPlayer(false, () => audioManager.Volume.Value = 0)); + AddUntilStep("wait for player", () => player.IsLoaded); + + AddAssert("check for notification", () => container.NotificationOverlay.UnreadCount.Value == 1); + AddStep("click notification", () => + { + var scrollContainer = (OsuScrollContainer)container.NotificationOverlay.Children.Last(); + var flowContainer = scrollContainer.Children.OfType>().First(); + var notification = flowContainer.First(); + + InputManager.MoveMouseTo(notification); + InputManager.Click(MouseButton.Left); + }); + AddAssert("check master volume", () => audioManager.Volume.IsDefault); + + AddStep("restart player", () => + { + var lastPlayer = player; + player = null; + lastPlayer.Restart(); + }); + } + + private class TestPlayerLoaderContainer : Container + { + [Cached] + public readonly NotificationOverlay NotificationOverlay; + + [Cached] + public readonly VolumeOverlay VolumeOverlay; + + public TestPlayerLoaderContainer(IScreen screen) + { + RelativeSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new OsuScreenStack(screen) + { + RelativeSizeAxes = Axes.Both, + }, + NotificationOverlay = new NotificationOverlay + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, + VolumeOverlay = new VolumeOverlay + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + } + }; + } + } + private class TestPlayerLoader : PlayerLoader { public new VisualSettings VisualSettings => base.VisualSettings; diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 8f2435d2f7..053e11104f 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -530,7 +530,7 @@ namespace osu.Game.Screens.Play } /// - /// Sets to , reserved for testing. + /// Sets to false, reserved for testing. /// public static void ResetNotificationLock() { From ccb56234877e204009e97e58de868b6a8710b155 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 25 Sep 2019 20:03:03 +0900 Subject: [PATCH 1487/2815] Fix test name --- .../Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index 86f7896457..c3b61fa420 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -117,7 +117,7 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - public void TestSliderMultiplierDoesnotAffectRelativeBeatLength() + public void TestSliderMultiplierDoesNotAffectRelativeBeatLength() { var beatmap = createBeatmap(new TimingControlPoint { BeatLength = time_range }); beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2; From 244627ff10cb1b517634a0278fa26eb27a000d3c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 25 Sep 2019 20:12:01 +0900 Subject: [PATCH 1488/2815] Add comment + test for slider multiplier --- .../Gameplay/TestSceneDrawableScrollingRuleset.cs | 13 +++++++++++++ .../UI/Scrolling/DrawableScrollingRuleset.cs | 2 ++ 2 files changed, 15 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index c3b61fa420..dcab964d6d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -129,6 +129,19 @@ namespace osu.Game.Tests.Visual.Gameplay assertPosition(i, i / 5f); } + [Test] + public void TestSliderMultiplierAffectsNonRelativeBeatLength() + { + var beatmap = createBeatmap(new TimingControlPoint { BeatLength = time_range }); + beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2; + + createTest(beatmap); + AddStep("adjust time range", () => drawableRuleset.TimeRange.Value = 2000); + + assertPosition(0, 0); + assertPosition(1, 1); + } + private void assertPosition(int index, float relativeY) => AddAssert($"hitobject {index} at {relativeY}", () => Precision.AlmostEquals(drawableRuleset.Playfield.AllHitObjects.ElementAt(index).DrawPosition.Y, drawableRuleset.Playfield.HitObjectContainer.DrawHeight * relativeY)); diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 3d56543bab..f178c01fd6 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -131,6 +131,8 @@ namespace osu.Game.Rulesets.UI.Scrolling if (duration > maxDuration) { maxDuration = duration; + // The slider multiplier is post-multiplied to determine the final velocity, but for relative scale beat lengths + // the multiplier should not affect the effective timing point (the longest in the beatmap), so it is factored out here baseBeatLength = timingPoints[i].BeatLength / Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier; } } From 8844d567cb1220dfe1b261ff30108dea5ae1c527 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 25 Sep 2019 15:56:47 +0300 Subject: [PATCH 1489/2815] Use bindable setting instead --- osu.Game/Screens/Menu/MainMenu.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 98ceb315a2..004bba20d4 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -52,9 +52,6 @@ namespace osu.Game.Screens.Menu [Resolved(canBeNull: true)] private LoginOverlay login { get; set; } - [Resolved] - private SessionStatics statics { get; set; } - [Resolved] private IAPIProvider api { get; set; } @@ -66,13 +63,15 @@ namespace osu.Game.Screens.Menu protected override BackgroundScreen CreateBackground() => background; private Bindable holdDelay; + private Bindable loginDisplayed; private ExitConfirmOverlay exitConfirmOverlay; [BackgroundDependencyLoader(true)] - private void load(DirectOverlay direct, SettingsOverlay settings, OsuConfigManager config) + private void load(DirectOverlay direct, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics) { holdDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); + loginDisplayed = statics.GetBindable(Statics.LoginOverlayDisplayed); if (host.CanExit) { @@ -200,10 +199,10 @@ namespace osu.Game.Screens.Menu bool displayLogin() { - if (!statics.Get(Statics.LoginOverlayDisplayed)) + if (!loginDisplayed.Value) { Scheduler.AddDelayed(() => login?.Show(), 500); - statics.Set(Statics.LoginOverlayDisplayed, true); + loginDisplayed.Value = true; } return true; From 42fd323020f3df9f16be166a91935e6ffb046302 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Sep 2019 22:13:49 +0900 Subject: [PATCH 1490/2815] Move protected method --- osu.Game/OsuGame.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index aaaa320093..3a7e53905c 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -106,8 +106,6 @@ namespace osu.Game private readonly List visibleBlockingOverlays = new List(); - protected virtual Loader CreateLoader() => new Loader(); - public OsuGame(string[] args = null) { this.args = args; @@ -322,6 +320,8 @@ namespace osu.Game }, $"watch {databasedScoreInfo}", bypassScreenAllowChecks: true); } + protected virtual Loader CreateLoader() => new Loader(); + #region Beatmap progression private void beatmapChanged(ValueChangedEvent beatmap) From 45f833ceea17628cc54856bb9aa58916e0dc6589 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Sep 2019 22:14:42 +0900 Subject: [PATCH 1491/2815] Add invocation null checks for safety --- osu.Game/Graphics/UserInterface/BackButton.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/BackButton.cs b/osu.Game/Graphics/UserInterface/BackButton.cs index 5fa634425b..62c33b9a39 100644 --- a/osu.Game/Graphics/UserInterface/BackButton.cs +++ b/osu.Game/Graphics/UserInterface/BackButton.cs @@ -18,7 +18,7 @@ namespace osu.Game.Graphics.UserInterface public BackButton(Receptor receptor) { - receptor.OnBackPressed = () => Action.Invoke(); + receptor.OnBackPressed = () => Action?.Invoke(); Size = TwoLayerButton.SIZE_EXTENDED; @@ -60,7 +60,7 @@ namespace osu.Game.Graphics.UserInterface switch (action) { case GlobalAction.Back: - OnBackPressed.Invoke(); + OnBackPressed?.Invoke(); return true; } From 911094e79049a244a68912c01e717015689c8ff0 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 26 Sep 2019 01:42:56 +0300 Subject: [PATCH 1492/2815] Replace menu button text with "press for menu" on 0ms activation delay --- osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 91c14591b1..6b2bbc13b7 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.MathUtils; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -45,7 +46,6 @@ namespace osu.Game.Screens.Play.HUD { text = new OsuSpriteText { - Text = "hold for menu", Font = OsuFont.GetFont(weight: FontWeight.Bold), Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft @@ -60,6 +60,14 @@ namespace osu.Game.Screens.Play.HUD AutoSizeAxes = Axes.Both; } + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + text.Text = config.Get(OsuSetting.UIHoldActivationDelay) > 0 + ? "hold for menu" + : "press for menu"; + } + protected override void LoadComplete() { text.FadeInFromZero(500, Easing.OutQuint).Delay(1500).FadeOut(500, Easing.OutQuint); From 186ea9821708da3260601fc9f6c9ba08ad4e49be Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 26 Sep 2019 02:23:18 +0300 Subject: [PATCH 1493/2815] Wait for track to start running instead --- osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index 7e792f0b40..cd46bc63a9 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -41,10 +41,8 @@ namespace osu.Game.Rulesets.Osu.Tests { base.SetUpSteps(); + AddUntilStep("wait for track to start running", () => track.IsRunning); AddStep("retrieve spinner", () => drawableSpinner = (DrawableSpinner)((TestPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.First()); - - // wait for frame stable clock time to hit 0 (for some reason, executing a seek while current time is below 0 doesn't seek successfully) - addSeekStep(0); } [Test] From d773f0cce18b7bad01bebd4f593504ae670658cb Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 26 Sep 2019 04:38:20 +0300 Subject: [PATCH 1494/2815] Override autoplay bool instead of adding it --- osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index cd46bc63a9..4eb4c21c90 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -27,6 +27,8 @@ namespace osu.Game.Rulesets.Osu.Tests private TrackVirtualManual track; + protected override bool Autoplay => true; + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) { var working = new ClockBackedTestWorkingBeatmap(beatmap, new FramedClock(new ManualClock { Rate = 1 }), audioManager); @@ -75,12 +77,6 @@ namespace osu.Game.Rulesets.Osu.Tests AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, ((TestPlayer)Player).DrawableRuleset.FrameStableClock.CurrentTime, 100)); } - protected override Player CreatePlayer(Ruleset ruleset) - { - Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); - return new TestPlayer(); - } - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap { HitObjects = new List From c57868795e08063241810b497ccc072d78972513 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 26 Sep 2019 04:38:57 +0300 Subject: [PATCH 1495/2815] Remove redundant using directive --- osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index 4eb4c21c90..cded7f0e95 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -11,7 +11,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Screens.Play; using osu.Game.Tests.Visual; using osuTK; using System.Collections.Generic; From bbf0544a8d0f476eb969aec5852042b286730e94 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 26 Sep 2019 16:55:08 +0900 Subject: [PATCH 1496/2815] Add bindables for IHasComboInformation properties --- .../Objects/CatchHitObject.cs | 25 +++++++++++++-- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 32 ++++++++++++++++--- .../Objects/Types/IHasComboInformation.cs | 8 +++++ 3 files changed, 58 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index a25d9cb67e..77d7de989a 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.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.Bindables; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Catch.Beatmaps; @@ -37,9 +38,21 @@ namespace osu.Game.Rulesets.Catch.Objects public int ComboOffset { get; set; } - public int IndexInCurrentCombo { get; set; } + public Bindable IndexInCurrentComboBindable { get; } = new Bindable(); - public int ComboIndex { get; set; } + public int IndexInCurrentCombo + { + get => IndexInCurrentComboBindable.Value; + set => IndexInCurrentComboBindable.Value = value; + } + + public Bindable ComboIndexBindable { get; } = new Bindable(); + + public int ComboIndex + { + get => ComboIndexBindable.Value; + set => ComboIndexBindable.Value = value; + } /// /// Difference between the distance to the next object @@ -48,10 +61,16 @@ namespace osu.Game.Rulesets.Catch.Objects /// public float DistanceToHyperDash { get; set; } + public Bindable LastInComboBindable { get; } = new Bindable(); + /// /// The next fruit starts a new combo. Used for explodey. /// - public virtual bool LastInCombo { get; set; } + public virtual bool LastInCombo + { + get => LastInComboBindable.Value; + set => LastInComboBindable.Value = value; + } public float Scale { get; set; } = 1; diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 2cf877b000..80e013fe68 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -58,13 +58,37 @@ namespace osu.Game.Rulesets.Osu.Objects public virtual bool NewCombo { get; set; } - public int ComboOffset { get; set; } + public readonly Bindable ComboOffsetBindable = new Bindable(); - public virtual int IndexInCurrentCombo { get; set; } + public int ComboOffset + { + get => ComboOffsetBindable.Value; + set => ComboOffsetBindable.Value = value; + } - public virtual int ComboIndex { get; set; } + public Bindable IndexInCurrentComboBindable { get; } = new Bindable(); - public bool LastInCombo { get; set; } + public virtual int IndexInCurrentCombo + { + get => IndexInCurrentComboBindable.Value; + set => IndexInCurrentComboBindable.Value = value; + } + + public Bindable ComboIndexBindable { get; } = new Bindable(); + + public virtual int ComboIndex + { + get => ComboIndexBindable.Value; + set => ComboIndexBindable.Value = value; + } + + public Bindable LastInComboBindable { get; } = new Bindable(); + + public bool LastInCombo + { + get => LastInComboBindable.Value; + set => LastInComboBindable.Value = value; + } protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { diff --git a/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs b/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs index e07da93a3a..4e3de04278 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs @@ -1,6 +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 osu.Framework.Bindables; + namespace osu.Game.Rulesets.Objects.Types { /// @@ -8,16 +10,22 @@ namespace osu.Game.Rulesets.Objects.Types /// public interface IHasComboInformation : IHasCombo { + Bindable IndexInCurrentComboBindable { get; } + /// /// The offset of this hitobject in the current combo. /// int IndexInCurrentCombo { get; set; } + Bindable ComboIndexBindable { get; } + /// /// The offset of this combo in relation to the beatmap. /// int ComboIndex { get; set; } + Bindable LastInComboBindable { get; } + /// /// Whether this is the last object in the current combo. /// From 3155a9050119ecb1edf3aad8fa6518fe2cd104fa Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 26 Sep 2019 16:57:58 +0900 Subject: [PATCH 1497/2815] Use bindables for displayed circle piece numbers --- .../Objects/Drawables/DrawableHitCircle.cs | 2 +- .../Objects/Drawables/Pieces/MainCirclePiece.cs | 16 +++++++++------- .../Skinning/LegacyMainCirclePiece.cs | 16 +++++++++------- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index c90f230f93..bb227d76df 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return true; }, }, - CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.HitCircle), _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)), + CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.HitCircle), _ => new MainCirclePiece()), ApproachCircle = new ApproachCircle { Alpha = 0, diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs index 944c93bb6d..e364c96426 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private readonly NumberPiece number; private readonly GlowPiece glow; - public MainCirclePiece(int index) + public MainCirclePiece() { Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); @@ -31,10 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { glow = new GlowPiece(), circle = new CirclePiece(), - number = new NumberPiece - { - Text = (index + 1).ToString(), - }, + number = new NumberPiece(), ring = new RingPiece(), flash = new FlashPiece(), explode = new ExplodePiece(), @@ -42,12 +39,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } private readonly IBindable state = new Bindable(); - - private readonly Bindable accentColour = new Bindable(); + private readonly IBindable accentColour = new Bindable(); + private readonly IBindable indexInCurrentCombo = new Bindable(); [BackgroundDependencyLoader] private void load(DrawableHitObject drawableObject) { + OsuHitObject osuObject = (OsuHitObject)drawableObject.HitObject; + state.BindTo(drawableObject.State); state.BindValueChanged(updateState, true); @@ -58,6 +57,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces glow.Colour = colour.NewValue; circle.Colour = colour.NewValue; }, true); + + indexInCurrentCombo.BindTo(osuObject.IndexInCurrentComboBindable); + indexInCurrentCombo.BindValueChanged(index => number.Text = (index.NewValue + 1).ToString(), true); } private void updateState(ValueChangedEvent state) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index 83d507f64b..93ae0371df 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Skinning; using osuTK; @@ -25,13 +24,16 @@ namespace osu.Game.Rulesets.Osu.Skinning } private readonly IBindable state = new Bindable(); - private readonly Bindable accentColour = new Bindable(); + private readonly IBindable indexInCurrentCombo = new Bindable(); [BackgroundDependencyLoader] private void load(DrawableHitObject drawableObject, ISkinSource skin) { + OsuHitObject osuObject = (OsuHitObject)drawableObject.HitObject; + Sprite hitCircleSprite; + SkinnableSpriteText hitCircleText; InternalChildren = new Drawable[] { @@ -42,14 +44,11 @@ namespace osu.Game.Rulesets.Osu.Skinning Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText + hitCircleText = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText { Font = OsuFont.Numeric.With(size: 40), UseFullGlyphHeight = false, - }, confineMode: ConfineMode.NoScaling) - { - Text = (((IHasComboInformation)drawableObject.HitObject).IndexInCurrentCombo + 1).ToString() - }, + }, confineMode: ConfineMode.NoScaling), new Sprite { Texture = skin.GetTexture("hitcircleoverlay"), @@ -63,6 +62,9 @@ namespace osu.Game.Rulesets.Osu.Skinning accentColour.BindTo(drawableObject.AccentColour); accentColour.BindValueChanged(colour => hitCircleSprite.Colour = colour.NewValue, true); + + indexInCurrentCombo.BindTo(osuObject.IndexInCurrentComboBindable); + indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true); } private void updateState(ValueChangedEvent state) From 706e884cc05cdd04cb4e56cb3e1f0c9f583e1d0e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 26 Sep 2019 17:04:38 +0900 Subject: [PATCH 1498/2815] Update accent colour on combo index change --- .../Objects/Drawables/DrawableHitObject.cs | 25 ++++++++++++++----- osu.Game/Skinning/SkinReloadableDrawable.cs | 20 +++++++++------ 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 8d8f8a419f..f8bc74b2a6 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -76,6 +76,8 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public JudgementResult Result { get; private set; } + private Bindable comboIndexBindable; + public override bool RemoveWhenNotAlive => false; public override bool RemoveCompletedTransforms => false; protected override bool RequiresChildrenUpdate => true; @@ -122,6 +124,13 @@ namespace osu.Game.Rulesets.Objects.Drawables protected override void LoadComplete() { base.LoadComplete(); + + if (HitObject is IHasComboInformation combo) + { + comboIndexBindable = combo.ComboIndexBindable.GetBoundCopy(); + comboIndexBindable.BindValueChanged(_ => updateAccentColour()); + } + updateState(ArmedState.Idle, true); } @@ -244,12 +253,7 @@ namespace osu.Game.Rulesets.Objects.Drawables { base.SkinChanged(skin, allowFallback); - if (HitObject is IHasComboInformation combo) - { - var comboColours = skin.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value; - - AccentColour.Value = comboColours?.Count > 0 ? comboColours[combo.ComboIndex % comboColours.Count] : Color4.White; - } + updateAccentColour(); ApplySkin(skin, allowFallback); @@ -257,6 +261,15 @@ namespace osu.Game.Rulesets.Objects.Drawables updateState(State.Value, true); } + private void updateAccentColour() + { + if (HitObject is IHasComboInformation combo) + { + var comboColours = CurrentSkin.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value; + AccentColour.Value = comboColours?.Count > 0 ? comboColours[combo.ComboIndex % comboColours.Count] : Color4.White; + } + } + /// /// Called when a change is made to the skin. /// diff --git a/osu.Game/Skinning/SkinReloadableDrawable.cs b/osu.Game/Skinning/SkinReloadableDrawable.cs index 4bbdeafba5..6d0b22dd51 100644 --- a/osu.Game/Skinning/SkinReloadableDrawable.cs +++ b/osu.Game/Skinning/SkinReloadableDrawable.cs @@ -12,13 +12,17 @@ namespace osu.Game.Skinning /// public abstract class SkinReloadableDrawable : CompositeDrawable { + /// + /// The current skin source. + /// + protected ISkinSource CurrentSkin { get; private set; } + private readonly Func allowFallback; - private ISkinSource skin; /// /// Whether fallback to default skin should be allowed if the custom skin is missing this resource. /// - private bool allowDefaultFallback => allowFallback == null || allowFallback.Invoke(skin); + private bool allowDefaultFallback => allowFallback == null || allowFallback.Invoke(CurrentSkin); /// /// Create a new @@ -32,19 +36,19 @@ namespace osu.Game.Skinning [BackgroundDependencyLoader] private void load(ISkinSource source) { - skin = source; - skin.SourceChanged += onChange; + CurrentSkin = source; + CurrentSkin.SourceChanged += onChange; } private void onChange() => // schedule required to avoid calls after disposed. // note that this has the side-effect of components only performing a skin change when they are alive. - Scheduler.AddOnce(() => SkinChanged(skin, allowDefaultFallback)); + Scheduler.AddOnce(() => SkinChanged(CurrentSkin, allowDefaultFallback)); protected override void LoadAsyncComplete() { base.LoadAsyncComplete(); - SkinChanged(skin, allowDefaultFallback); + SkinChanged(CurrentSkin, allowDefaultFallback); } /// @@ -60,8 +64,8 @@ namespace osu.Game.Skinning { base.Dispose(isDisposing); - if (skin != null) - skin.SourceChanged -= onChange; + if (CurrentSkin != null) + CurrentSkin.SourceChanged -= onChange; } } } From ea76dd6a9e1adde6941005ffb6114bcceced2a52 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 26 Sep 2019 17:18:16 +0900 Subject: [PATCH 1499/2815] Add test scene for hitcircles and combo changes --- .../TestSceneHitCircleComboChange.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleComboChange.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleComboChange.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleComboChange.cs new file mode 100644 index 0000000000..5695462859 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleComboChange.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneHitCircleComboChange : TestSceneHitCircle + { + private readonly Bindable comboIndex = new Bindable(); + + protected override void LoadComplete() + { + base.LoadComplete(); + Scheduler.AddDelayed(() => comboIndex.Value++, 250, true); + } + + protected override TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto) + { + circle.ComboIndexBindable.BindTo(comboIndex); + circle.IndexInCurrentComboBindable.BindTo(comboIndex); + return base.CreateDrawableHitCircle(circle, auto); + } + } +} From 45f2bcc440c4aea51f0905b4da7f1128dcc415f3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 26 Sep 2019 17:39:19 +0900 Subject: [PATCH 1500/2815] Fix combo bindings not being bound to nested hitobjects --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 26 ------------------------- osu.Game/Beatmaps/BeatmapProcessor.cs | 20 ------------------- osu.Game/Rulesets/Objects/HitObject.cs | 10 ++++++++++ 3 files changed, 10 insertions(+), 46 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 2805494021..d8514092bc 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -33,28 +33,6 @@ namespace osu.Game.Rulesets.Osu.Objects public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t); - public override int ComboIndex - { - get => base.ComboIndex; - set - { - base.ComboIndex = value; - foreach (var n in NestedHitObjects.OfType()) - n.ComboIndex = value; - } - } - - public override int IndexInCurrentCombo - { - get => base.IndexInCurrentCombo; - set - { - base.IndexInCurrentCombo = value; - foreach (var n in NestedHitObjects.OfType()) - n.IndexInCurrentCombo = value; - } - } - public readonly Bindable PathBindable = new Bindable(); public SliderPath Path @@ -192,8 +170,6 @@ namespace osu.Game.Rulesets.Osu.Objects Position = Position, Samples = getNodeSamples(0), SampleControlPoint = SampleControlPoint, - IndexInCurrentCombo = IndexInCurrentCombo, - ComboIndex = ComboIndex, }); break; @@ -205,8 +181,6 @@ namespace osu.Game.Rulesets.Osu.Objects { StartTime = e.Time, Position = EndPosition, - IndexInCurrentCombo = IndexInCurrentCombo, - ComboIndex = ComboIndex, }); break; diff --git a/osu.Game/Beatmaps/BeatmapProcessor.cs b/osu.Game/Beatmaps/BeatmapProcessor.cs index 7a612893c9..250cc49ad4 100644 --- a/osu.Game/Beatmaps/BeatmapProcessor.cs +++ b/osu.Game/Beatmaps/BeatmapProcessor.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Beatmaps @@ -45,25 +44,6 @@ namespace osu.Game.Beatmaps public virtual void PostProcess() { - void updateNestedCombo(HitObject obj, int comboIndex, int indexInCurrentCombo) - { - if (obj is IHasComboInformation objectComboInfo) - { - objectComboInfo.ComboIndex = comboIndex; - objectComboInfo.IndexInCurrentCombo = indexInCurrentCombo; - foreach (var nestedObject in obj.NestedHitObjects) - updateNestedCombo(nestedObject, comboIndex, indexInCurrentCombo); - } - } - - foreach (var hitObject in Beatmap.HitObjects) - { - if (hitObject is IHasComboInformation objectComboInfo) - { - foreach (var nested in hitObject.NestedHitObjects) - updateNestedCombo(nested, objectComboInfo.ComboIndex, objectComboInfo.IndexInCurrentCombo); - } - } } } } diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 96297ab44f..6c5627c5d2 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.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 JetBrains.Annotations; using Newtonsoft.Json; using osu.Game.Audio; @@ -82,6 +83,15 @@ namespace osu.Game.Rulesets.Objects CreateNestedHitObjects(); + if (this is IHasComboInformation hasCombo) + { + foreach (var n in NestedHitObjects.OfType()) + { + n.ComboIndexBindable.BindTo(hasCombo.ComboIndexBindable); + n.IndexInCurrentComboBindable.BindTo(hasCombo.IndexInCurrentComboBindable); + } + } + nestedHitObjects.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime)); foreach (var h in nestedHitObjects) From e4e66344322dc9733b61e464712541c4ac1b51fe Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 26 Sep 2019 17:39:26 +0900 Subject: [PATCH 1501/2815] Add slider combo change test --- .../TestSceneSlider.cs | 12 ++++---- .../TestSceneSliderComboChange.cs | 28 +++++++++++++++++++ 2 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneSliderComboChange.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index 29c71a8903..6a4201f84d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -297,11 +297,7 @@ namespace osu.Game.Rulesets.Osu.Tests slider.ApplyDefaults(cpi, new BeatmapDifficulty { CircleSize = circleSize, SliderTickRate = 3 }); - var drawable = new DrawableSlider(slider) - { - Anchor = Anchor.Centre, - Depth = depthIndex++ - }; + var drawable = CreateDrawableSlider(slider); foreach (var mod in Mods.Value.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); @@ -311,6 +307,12 @@ namespace osu.Game.Rulesets.Osu.Tests return drawable; } + protected virtual DrawableSlider CreateDrawableSlider(Slider slider) => new DrawableSlider(slider) + { + Anchor = Anchor.Centre, + Depth = depthIndex++ + }; + private float judgementOffsetDirection = 1; private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderComboChange.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderComboChange.cs new file mode 100644 index 0000000000..13ced3019e --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderComboChange.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneSliderComboChange : TestSceneSlider + { + private readonly Bindable comboIndex = new Bindable(); + + protected override void LoadComplete() + { + base.LoadComplete(); + Scheduler.AddDelayed(() => comboIndex.Value++, 250, true); + } + + protected override DrawableSlider CreateDrawableSlider(Slider slider) + { + slider.ComboIndexBindable.BindTo(comboIndex); + slider.IndexInCurrentComboBindable.BindTo(comboIndex); + + return base.CreateDrawableSlider(slider); + } + } +} From 9a31ccd2e34bc56960e714db87bda72c5fb6282a Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Thu, 26 Sep 2019 14:05:43 +0200 Subject: [PATCH 1502/2815] Add missing test cases for master, track and mute button This also modifies the reset player method to make it possible to set something before the player is loaded but after the container has loaded. --- .../Visual/Gameplay/TestScenePlayerLoader.cs | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index eb54eca0cb..e1a2cdcca3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -41,7 +41,8 @@ namespace osu.Game.Tests.Visual.Gameplay /// /// If the test player should behave like the production one. /// An action to run before player load but after bindable leases are returned. - public void ResetPlayer(bool interactive, Action beforeLoadAction = null) + /// An action to run after container load. + public void ResetPlayer(bool interactive, Action beforeLoadAction = null, Action afterLoadAction = null) { audioManager.Volume.SetDefault(); @@ -51,7 +52,11 @@ namespace osu.Game.Tests.Visual.Gameplay Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); InputManager.Child = container = new TestPlayerLoaderContainer( - loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive))); + loader = new TestPlayerLoader(() => + { + afterLoadAction?.Invoke(); + return player = new TestPlayer(interactive, interactive); + })); } [Test] @@ -115,11 +120,26 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - public void TestMutedNotification() - { - AddStep("reset notification", PlayerLoader.ResetNotificationLock); + public void TestMutedNotificationMasterVolume() => addVolumeSteps("master volume", () => audioManager.Volume.Value = 0, null, () => audioManager.Volume.IsDefault); - AddStep("load player", () => ResetPlayer(false, () => audioManager.Volume.Value = 0)); + [Test] + public void TestMutedNotificationTrackVolume() => addVolumeSteps("music volume", () => audioManager.Volume.Value = 0, null, () => audioManager.Volume.IsDefault); + + [Test] + public void TestMutedNotificationMuteButton() => addVolumeSteps("mute button", null, () => container.VolumeOverlay.IsMuted.Value = true, () => !container.VolumeOverlay.IsMuted.Value); + + /// + /// Created for avoiding copy pasting code for the same steps. + /// + /// What part of the volume system is checked + /// The action to be invoked to set the volume before loading + /// The action to be invoked to set the volume after loading + /// The function to be invoked and checked + private void addVolumeSteps(string volumeName, Action beforeLoad, Action afterLoad, Func assert) + { + AddStep("reset notification lock", PlayerLoader.ResetNotificationLock); + + AddStep("load player", () => ResetPlayer(false, beforeLoad, afterLoad)); AddUntilStep("wait for player", () => player.IsLoaded); AddAssert("check for notification", () => container.NotificationOverlay.UnreadCount.Value == 1); @@ -132,14 +152,8 @@ namespace osu.Game.Tests.Visual.Gameplay InputManager.MoveMouseTo(notification); InputManager.Click(MouseButton.Left); }); - AddAssert("check master volume", () => audioManager.Volume.IsDefault); - AddStep("restart player", () => - { - var lastPlayer = player; - player = null; - lastPlayer.Restart(); - }); + AddAssert("check " + volumeName, assert); } private class TestPlayerLoaderContainer : Container From 7904f77cd5cd716c6547b3ee8a7dea2bd79ce5df Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 27 Sep 2019 02:59:42 +0300 Subject: [PATCH 1503/2815] Bind event to activation delay change --- osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 6b2bbc13b7..9c1435ef3d 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -63,9 +63,13 @@ namespace osu.Game.Screens.Play.HUD [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - text.Text = config.Get(OsuSetting.UIHoldActivationDelay) > 0 - ? "hold for menu" - : "press for menu"; + var activationDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay).GetBoundCopy(); + activationDelay.BindValueChanged(v => + { + text.Text = v.NewValue > 0 + ? "hold for menu" + : "press for menu"; + }, true); } protected override void LoadComplete() From 2670a23e6f313875aeb0301d7cd6fc67b7c253ea Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 27 Sep 2019 08:15:24 +0300 Subject: [PATCH 1504/2815] Assign to field and move to load complete --- osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 9c1435ef3d..2dc50326a8 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -60,21 +60,23 @@ namespace osu.Game.Screens.Play.HUD AutoSizeAxes = Axes.Both; } - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + [Resolved] + private OsuConfigManager config { get; set; } + + private Bindable activationDelay; + + protected override void LoadComplete() { - var activationDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay).GetBoundCopy(); + activationDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); activationDelay.BindValueChanged(v => { text.Text = v.NewValue > 0 ? "hold for menu" : "press for menu"; }, true); - } - protected override void LoadComplete() - { text.FadeInFromZero(500, Easing.OutQuint).Delay(1500).FadeOut(500, Easing.OutQuint); + base.LoadComplete(); } From b50ef8ffa4855cecb9ae195accd455669b279feb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Sep 2019 13:15:33 +0800 Subject: [PATCH 1505/2815] Allow null NotificationManager --- osu.Game/Screens/Play/PlayerLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 053e11104f..6cde7522d4 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -59,7 +59,7 @@ namespace osu.Game.Screens.Play private IdleTracker idleTracker; - [Resolved] + [Resolved(CanBeNull = true)] private NotificationOverlay notificationOverlay { get; set; } [Resolved] @@ -164,7 +164,7 @@ namespace osu.Game.Screens.Play //Checks if the notification has not been shown yet and also if master volume is muted, track/music volume is muted or if the whole game is muted. if (!muteWarningShownOnce && (volumeOverlay.IsMuted.Value || audio.Volume.Value <= audio.Volume.MinValue || audio.VolumeTrack.Value <= audio.VolumeTrack.MinValue)) { - notificationOverlay.Post(new MutedNotification()); + notificationOverlay?.Post(new MutedNotification()); muteWarningShownOnce = true; } } From f4f5a7e9c8be2b2d0f48a9b5be9a8761d4b856b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Sep 2019 13:20:17 +0800 Subject: [PATCH 1506/2815] Fix test regressions --- osu.Game/Screens/Play/PlayerLoader.cs | 32 +++++++++++++++------------ 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 6cde7522d4..157ff8fcd4 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -59,17 +59,6 @@ namespace osu.Game.Screens.Play private IdleTracker idleTracker; - [Resolved(CanBeNull = true)] - private NotificationOverlay notificationOverlay { get; set; } - - [Resolved] - private VolumeOverlay volumeOverlay { get; set; } - - [Resolved] - private AudioManager audioManager { get; set; } - - private static bool muteWarningShownOnce; - public PlayerLoader(Func createPlayer) { this.createPlayer = createPlayer; @@ -159,10 +148,24 @@ namespace osu.Game.Screens.Play content.FadeOut(250); } - private void checkVolume(AudioManager audio) + [Resolved(CanBeNull = true)] + private NotificationOverlay notificationOverlay { get; set; } + + [Resolved(CanBeNull = true)] + private VolumeOverlay volumeOverlay { get; set; } + + [Resolved] + private AudioManager audioManager { get; set; } + + private static bool muteWarningShownOnce; + + private void checkVolume() { + if (muteWarningShownOnce) + return; + //Checks if the notification has not been shown yet and also if master volume is muted, track/music volume is muted or if the whole game is muted. - if (!muteWarningShownOnce && (volumeOverlay.IsMuted.Value || audio.Volume.Value <= audio.Volume.MinValue || audio.VolumeTrack.Value <= audio.VolumeTrack.MinValue)) + if (volumeOverlay?.IsMuted.Value == true || audioManager.Volume.Value <= audioManager.Volume.MinValue || audioManager.VolumeTrack.Value <= audioManager.VolumeTrack.MinValue) { notificationOverlay?.Post(new MutedNotification()); muteWarningShownOnce = true; @@ -213,7 +216,8 @@ namespace osu.Game.Screens.Play { inputManager = GetContainingInputManager(); base.LoadComplete(); - checkVolume(audioManager); + + checkVolume(); } private ScheduledDelegate pushDebounce; From 67bed57cbdf3f0921a6a721c74cc28317afc74c7 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 27 Sep 2019 08:46:49 +0300 Subject: [PATCH 1507/2815] Bind value changed event of cursor trail appearence outside BDL https://github.com/ppy/osu/pull/6270#discussion_r328899728 --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index a944ff88c6..6dbdf0114d 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -40,6 +40,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private void load(OsuRulesetConfigManager config) { config?.BindWith(OsuRulesetSetting.ShowCursorTrail, showTrail); + } + + protected override void LoadComplete() + { + base.LoadComplete(); showTrail.BindValueChanged(v => cursorTrail.FadeTo(v.NewValue ? 1 : 0, 200), true); } From 94eacbca5dde273bec3a0f2073af9e0ce5cb3097 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 27 Sep 2019 09:22:25 +0300 Subject: [PATCH 1508/2815] Fix Bot users have all the profile sections in ProfileOverlay --- osu.Game/Overlays/UserProfileOverlay.cs | 5 ++++- osu.Game/Users/User.cs | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index b924b3302f..bc16711ea8 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -44,7 +44,7 @@ namespace osu.Game.Overlays Clear(); lastSection = null; - sections = new ProfileSection[] + sections = !user.IsBot ? new ProfileSection[] { //new AboutSection(), new RecentSection(), @@ -53,6 +53,9 @@ namespace osu.Game.Overlays new HistoricalSection(), new BeatmapsSection(), new KudosuSection() + } : new ProfileSection[] + { + //new AboutSection(), }; tabs = new ProfileTabControl diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 9986f70557..1cb395fd75 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -78,6 +78,9 @@ namespace osu.Game.Users [JsonProperty(@"is_bng")] public bool IsBNG; + [JsonProperty(@"is_bot")] + public bool IsBot; + [JsonProperty(@"is_active")] public bool Active; From 475455d7cd5f02275ceeed31e34baf63a9bc465a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 27 Sep 2019 09:32:46 +0300 Subject: [PATCH 1509/2815] Add missing line breaks --- osu.Game/Overlays/UserProfileOverlay.cs | 28 +++++++++++++------------ 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index bc16711ea8..57b9b6c965 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -44,19 +44,21 @@ namespace osu.Game.Overlays Clear(); lastSection = null; - sections = !user.IsBot ? new ProfileSection[] - { - //new AboutSection(), - new RecentSection(), - new RanksSection(), - //new MedalsSection(), - new HistoricalSection(), - new BeatmapsSection(), - new KudosuSection() - } : new ProfileSection[] - { - //new AboutSection(), - }; + sections = !user.IsBot ? + new ProfileSection[] + { + //new AboutSection(), + new RecentSection(), + new RanksSection(), + //new MedalsSection(), + new HistoricalSection(), + new BeatmapsSection(), + new KudosuSection() + } : + new ProfileSection[] + { + //new AboutSection(), + }; tabs = new ProfileTabControl { From 4908cb826b78d964025708865cc695adb7202a4e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 27 Sep 2019 09:46:11 +0300 Subject: [PATCH 1510/2815] Fix line breaks --- osu.Game/Overlays/UserProfileOverlay.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 57b9b6c965..468eb22b01 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -44,8 +44,8 @@ namespace osu.Game.Overlays Clear(); lastSection = null; - sections = !user.IsBot ? - new ProfileSection[] + sections = !user.IsBot + ? new ProfileSection[] { //new AboutSection(), new RecentSection(), @@ -54,8 +54,8 @@ namespace osu.Game.Overlays new HistoricalSection(), new BeatmapsSection(), new KudosuSection() - } : - new ProfileSection[] + } + : new ProfileSection[] { //new AboutSection(), }; From 06c32d52dca8e465d34546af08505f234f7183a5 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Fri, 27 Sep 2019 09:19:39 +0200 Subject: [PATCH 1511/2815] Change wrong volume bindable used in test --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index e1a2cdcca3..6866fd4c62 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -123,7 +123,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestMutedNotificationMasterVolume() => addVolumeSteps("master volume", () => audioManager.Volume.Value = 0, null, () => audioManager.Volume.IsDefault); [Test] - public void TestMutedNotificationTrackVolume() => addVolumeSteps("music volume", () => audioManager.Volume.Value = 0, null, () => audioManager.Volume.IsDefault); + public void TestMutedNotificationTrackVolume() => addVolumeSteps("music volume", () => audioManager.VolumeTrack.Value = 0, null, () => audioManager.VolumeTrack.IsDefault); [Test] public void TestMutedNotificationMuteButton() => addVolumeSteps("mute button", null, () => container.VolumeOverlay.IsMuted.Value = true, () => !container.VolumeOverlay.IsMuted.Value); From f64fe22f3669cd2117a295d490ecd6a9f14b8f0e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 27 Sep 2019 18:00:24 +0900 Subject: [PATCH 1512/2815] Remove bindables from osu! selection blueprints --- .../HitCircles/Components/HitCirclePiece.cs | 10 +++++++--- .../Edit/Blueprints/HitObjectPiece.cs | 19 ++----------------- .../Edit/Blueprints/SliderPiece.cs | 17 +++-------------- .../Components/PathControlPointVisualiser.cs | 9 ++------- .../Sliders/Components/SliderBodyPiece.cs | 6 +++--- .../Sliders/Components/SliderCirclePiece.cs | 12 ------------ .../Spinners/Components/SpinnerPiece.cs | 12 +++++++----- 7 files changed, 24 insertions(+), 61 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs index fe11ead94d..99928cdad9 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs @@ -31,10 +31,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components private void load(OsuColour colours) { Colour = colours.Yellow; + } - PositionBindable.BindValueChanged(_ => UpdatePosition(), true); - StackHeightBindable.BindValueChanged(_ => UpdatePosition()); - ScaleBindable.BindValueChanged(scale => Scale = new Vector2(scale.NewValue), true); + protected override void Update() + { + base.Update(); + + UpdatePosition(); + Scale = new Vector2(hitCircle.Scale); } protected virtual void UpdatePosition() => Position = hitCircle.StackedPosition; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitObjectPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitObjectPiece.cs index 315a5a2b9d..3d7d609c6b 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitObjectPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitObjectPiece.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 osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu.Objects; -using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Blueprints { @@ -14,23 +11,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints /// public abstract class HitObjectPiece : CompositeDrawable { - protected readonly IBindable PositionBindable = new Bindable(); - protected readonly IBindable StackHeightBindable = new Bindable(); - protected readonly IBindable ScaleBindable = new Bindable(); - - private readonly OsuHitObject hitObject; + protected readonly OsuHitObject HitObject; protected HitObjectPiece(OsuHitObject hitObject) { - this.hitObject = hitObject; - } - - [BackgroundDependencyLoader] - private void load() - { - PositionBindable.BindTo(hitObject.PositionBindable); - StackHeightBindable.BindTo(hitObject.StackHeightBindable); - ScaleBindable.BindTo(hitObject.ScaleBindable); + HitObject = hitObject; } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/SliderPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/SliderPiece.cs index 8fd1d6d6f9..e0fcf8a000 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/SliderPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/SliderPiece.cs @@ -1,32 +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.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Edit.Blueprints { /// - /// A piece of a blueprint which responds to changes in the state of a . + /// A piece of a blueprint which responds to changes in the state of a . /// public abstract class SliderPiece : HitObjectPiece { - protected readonly IBindable PathBindable = new Bindable(); - - private readonly Slider slider; + protected readonly Slider Slider; protected SliderPiece(Slider slider) : base(slider) { - this.slider = slider; - } - - [BackgroundDependencyLoader] - private void load() - { - PathBindable.BindTo(slider.PathBindable); + Slider = slider; } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index df846b5d5b..3d8e014551 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.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 osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu.Objects; @@ -22,14 +21,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components InternalChild = pieces = new Container { RelativeSizeAxes = Axes.Both }; } - [BackgroundDependencyLoader] - private void load() + protected override void Update() { - PathBindable.BindValueChanged(_ => updatePathControlPoints(), true); - } + base.Update(); - private void updatePathControlPoints() - { while (slider.Path.ControlPoints.Length > pieces.Count) pieces.Add(new PathControlPointPiece(slider, pieces.Count)); while (slider.Path.ControlPoints.Length < pieces.Count) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs index f1f55731b6..aea17a4b8f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs @@ -31,9 +31,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private void load(OsuColour colours) { body.BorderColour = colours.Yellow; - - PositionBindable.BindValueChanged(_ => updatePosition(), true); - ScaleBindable.BindValueChanged(scale => body.PathRadius = scale.NewValue * OsuHitObject.OBJECT_RADIUS, true); } private void updatePosition() => Position = slider.StackedPosition; @@ -42,6 +39,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { base.Update(); + Position = slider.StackedPosition; + body.PathRadius = HitObject.Scale * OsuHitObject.OBJECT_RADIUS; + var vertices = new List(); slider.Path.GetPathToProgress(vertices, 0, 1); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs index 2ecfea2e3e..ec3a1d0034 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs @@ -1,9 +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.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Objects; @@ -11,8 +8,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { public class SliderCirclePiece : HitCirclePiece { - private readonly IBindable pathBindable = new Bindable(); - private readonly Slider slider; private readonly SliderPosition position; @@ -23,13 +18,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components this.position = position; } - [BackgroundDependencyLoader] - private void load() - { - pathBindable.BindTo(slider.PathBindable); - pathBindable.BindValueChanged(_ => UpdatePosition(), true); - } - protected override void UpdatePosition() { switch (position) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs index ae94848c81..e2084bbb7c 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs @@ -52,13 +52,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components private void load(OsuColour colours) { Colour = colours.Yellow; - - PositionBindable.BindValueChanged(_ => updatePosition(), true); - StackHeightBindable.BindValueChanged(_ => updatePosition()); - ScaleBindable.BindValueChanged(scale => ring.Scale = new Vector2(scale.NewValue), true); } - private void updatePosition() => Position = spinner.Position; + protected override void Update() + { + base.Update(); + + Position = spinner.Position; + ring.Scale = new Vector2(spinner.Scale); + } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => circle.ReceivePositionalInputAt(screenSpacePos); } From 4fc37d11376980b2fc34c616b247e8fbaca8b640 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 27 Sep 2019 18:01:55 +0900 Subject: [PATCH 1513/2815] Remove SliderPiece + HitObjectPiece --- .../HitCircles/Components/HitCirclePiece.cs | 4 ++-- .../Edit/Blueprints/HitObjectPiece.cs | 21 ------------------- .../Edit/Blueprints/SliderPiece.cs | 21 ------------------- .../Components/PathControlPointVisualiser.cs | 3 +-- .../Sliders/Components/SliderBodyPiece.cs | 6 +++--- .../Spinners/Components/SpinnerPiece.cs | 3 +-- 6 files changed, 7 insertions(+), 51 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Edit/Blueprints/HitObjectPiece.cs delete mode 100644 osu.Game.Rulesets.Osu/Edit/Blueprints/SliderPiece.cs diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs index 99928cdad9..5e46b3ace4 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; @@ -10,12 +11,11 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components { - public class HitCirclePiece : HitObjectPiece + public class HitCirclePiece : CompositeDrawable { private readonly HitCircle hitCircle; public HitCirclePiece(HitCircle hitCircle) - : base(hitCircle) { this.hitCircle = hitCircle; Origin = Anchor.Centre; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitObjectPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitObjectPiece.cs deleted file mode 100644 index 3d7d609c6b..0000000000 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitObjectPiece.cs +++ /dev/null @@ -1,21 +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.Graphics.Containers; -using osu.Game.Rulesets.Osu.Objects; - -namespace osu.Game.Rulesets.Osu.Edit.Blueprints -{ - /// - /// A piece of a blueprint which responds to changes in the state of a . - /// - public abstract class HitObjectPiece : CompositeDrawable - { - protected readonly OsuHitObject HitObject; - - protected HitObjectPiece(OsuHitObject hitObject) - { - HitObject = hitObject; - } - } -} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/SliderPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/SliderPiece.cs deleted file mode 100644 index e0fcf8a000..0000000000 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/SliderPiece.cs +++ /dev/null @@ -1,21 +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.Game.Rulesets.Osu.Objects; - -namespace osu.Game.Rulesets.Osu.Edit.Blueprints -{ - /// - /// A piece of a blueprint which responds to changes in the state of a . - /// - public abstract class SliderPiece : HitObjectPiece - { - protected readonly Slider Slider; - - protected SliderPiece(Slider slider) - : base(slider) - { - Slider = slider; - } - } -} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 3d8e014551..24fcc460d1 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -7,14 +7,13 @@ using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { - public class PathControlPointVisualiser : SliderPiece + public class PathControlPointVisualiser : CompositeDrawable { private readonly Slider slider; private readonly Container pieces; public PathControlPointVisualiser(Slider slider) - : base(slider) { this.slider = slider; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs index aea17a4b8f..239feee431 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; @@ -11,13 +12,12 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { - public class SliderBodyPiece : SliderPiece + public class SliderBodyPiece : CompositeDrawable { private readonly Slider slider; private readonly ManualSliderBody body; public SliderBodyPiece(Slider slider) - : base(slider) { this.slider = slider; @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components base.Update(); Position = slider.StackedPosition; - body.PathRadius = HitObject.Scale * OsuHitObject.OBJECT_RADIUS; + body.PathRadius = slider.Scale * OsuHitObject.OBJECT_RADIUS; var vertices = new List(); slider.Path.GetPathToProgress(vertices, 0, 1); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs index e2084bbb7c..5dab501a24 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs @@ -12,14 +12,13 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components { - public class SpinnerPiece : HitObjectPiece + public class SpinnerPiece : CompositeDrawable { private readonly Spinner spinner; private readonly CircularContainer circle; private readonly RingPiece ring; public SpinnerPiece(Spinner spinner) - : base(spinner) { this.spinner = spinner; From bddaead72e5b650b4e2e0205572b3ac232c865bf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 27 Sep 2019 18:45:22 +0900 Subject: [PATCH 1514/2815] Make hitobject pieces able to update dynamically --- .../Edit/Blueprints/BlueprintPiece.cs | 25 +++++++++++++ .../HitCircles/Components/HitCirclePiece.cs | 18 +++------- .../HitCircles/HitCirclePlacementBlueprint.cs | 11 +++++- .../HitCircles/HitCircleSelectionBlueprint.cs | 13 +++++-- .../Edit/Blueprints/OsuSelectionBlueprint.cs | 7 ++-- .../Components/PathControlPointPiece.cs | 2 +- .../Sliders/Components/SliderBodyPiece.cs | 21 ++++------- .../Sliders/Components/SliderCirclePiece.cs | 35 ------------------- .../Sliders/SliderCircleSelectionBlueprint.cs | 21 ++++++++--- .../Sliders/SliderPlacementBlueprint.cs | 15 ++++++-- .../Sliders/SliderSelectionBlueprint.cs | 16 ++++++--- .../Spinners/Components/SpinnerPiece.cs | 16 +++------ .../Spinners/SpinnerPlacementBlueprint.cs | 9 ++++- .../Spinners/SpinnerSelectionBlueprint.cs | 11 ++++-- 14 files changed, 125 insertions(+), 95 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Edit/Blueprints/BlueprintPiece.cs delete mode 100644 osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/BlueprintPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/BlueprintPiece.cs new file mode 100644 index 0000000000..95e926fdfa --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/BlueprintPiece.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.Containers; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints +{ + /// + /// A piece of a selection or placement blueprint which visualises an . + /// + /// The type of which this visualises. + public abstract class BlueprintPiece : CompositeDrawable + where T : OsuHitObject + { + /// + /// Updates this using the properties of a . + /// + /// The to reference properties from. + public virtual void UpdateFrom(T hitObject) + { + Position = hitObject.Position; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs index 5e46b3ace4..2b6b93a590 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs @@ -3,7 +3,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; @@ -11,17 +10,13 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components { - public class HitCirclePiece : CompositeDrawable + public class HitCirclePiece : BlueprintPiece { - private readonly HitCircle hitCircle; - - public HitCirclePiece(HitCircle hitCircle) + public HitCirclePiece() { - this.hitCircle = hitCircle; Origin = Anchor.Centre; Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); - Scale = new Vector2(hitCircle.Scale); CornerRadius = Size.X / 2; InternalChild = new RingPiece(); @@ -33,14 +28,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components Colour = colours.Yellow; } - protected override void Update() + public override void UpdateFrom(HitCircle hitObject) { - base.Update(); + base.UpdateFrom(hitObject); - UpdatePosition(); - Scale = new Vector2(hitCircle.Scale); + Scale = new Vector2(hitObject.Scale); } - - protected virtual void UpdatePosition() => Position = hitCircle.StackedPosition; } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs index a4050f0c31..cccef52737 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs @@ -13,10 +13,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles { public new HitCircle HitObject => (HitCircle)base.HitObject; + private readonly HitCirclePiece circlePiece; + public HitCirclePlacementBlueprint() : base(new HitCircle()) { - InternalChild = new HitCirclePiece(HitObject); + InternalChild = circlePiece = new HitCirclePiece(); } protected override void LoadComplete() @@ -27,6 +29,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles HitObject.Position = Parent?.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position) ?? Vector2.Zero; } + protected override void Update() + { + base.Update(); + + circlePiece.UpdateFrom(HitObject); + } + protected override bool OnClick(ClickEvent e) { HitObject.StartTime = EditorClock.CurrentTime; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs index 83787e2219..430d4a0222 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs @@ -7,12 +7,21 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles { - public class HitCircleSelectionBlueprint : OsuSelectionBlueprint + public class HitCircleSelectionBlueprint : OsuSelectionBlueprint { + private readonly HitCirclePiece circlePiece; + public HitCircleSelectionBlueprint(DrawableHitCircle hitCircle) : base(hitCircle) { - InternalChild = new HitCirclePiece((HitCircle)hitCircle.HitObject); + InternalChild = circlePiece = new HitCirclePiece(); + } + + protected override void Update() + { + base.Update(); + + circlePiece.UpdateFrom(HitObject); } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs index dd524252f3..2e4b990db8 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs @@ -7,11 +7,12 @@ using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Edit.Blueprints { - public class OsuSelectionBlueprint : SelectionBlueprint + public abstract class OsuSelectionBlueprint : SelectionBlueprint + where T : OsuHitObject { - protected OsuHitObject OsuObject => (OsuHitObject)HitObject.HitObject; + protected new T HitObject => (T)base.HitObject.HitObject; - public OsuSelectionBlueprint(DrawableHitObject hitObject) + protected OsuSelectionBlueprint(DrawableHitObject hitObject) : base(hitObject) { } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index e257369ad9..3aec7c2872 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -14,7 +14,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { - public class PathControlPointPiece : CompositeDrawable + public class PathControlPointPiece : BlueprintPiece { private readonly Slider slider; private readonly int index; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs index 239feee431..d28cf7b492 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using osu.Framework.Allocation; -using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; @@ -12,18 +11,15 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { - public class SliderBodyPiece : CompositeDrawable + public class SliderBodyPiece : BlueprintPiece { - private readonly Slider slider; private readonly ManualSliderBody body; - public SliderBodyPiece(Slider slider) + public SliderBodyPiece() { - this.slider = slider; - InternalChild = body = new ManualSliderBody { - AccentColour = Color4.Transparent, + AccentColour = Color4.Transparent }; } @@ -33,17 +29,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components body.BorderColour = colours.Yellow; } - private void updatePosition() => Position = slider.StackedPosition; - - protected override void Update() + public override void UpdateFrom(Slider hitObject) { - base.Update(); + base.UpdateFrom(hitObject); - Position = slider.StackedPosition; - body.PathRadius = slider.Scale * OsuHitObject.OBJECT_RADIUS; + body.PathRadius = hitObject.Scale * OsuHitObject.OBJECT_RADIUS; var vertices = new List(); - slider.Path.GetPathToProgress(vertices, 0, 1); + hitObject.Path.GetPathToProgress(vertices, 0, 1); body.SetVertices(vertices); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs deleted file mode 100644 index ec3a1d0034..0000000000 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs +++ /dev/null @@ -1,35 +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.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; -using osu.Game.Rulesets.Osu.Objects; - -namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components -{ - public class SliderCirclePiece : HitCirclePiece - { - private readonly Slider slider; - private readonly SliderPosition position; - - public SliderCirclePiece(Slider slider, SliderPosition position) - : base(slider.HeadCircle) - { - this.slider = slider; - this.position = position; - } - - protected override void UpdatePosition() - { - switch (position) - { - case SliderPosition.Start: - Position = slider.StackedPosition + slider.Path.PositionAt(0); - break; - - case SliderPosition.End: - Position = slider.StackedPosition + slider.Path.PositionAt(1); - break; - } - } - } -} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs index c9f005495c..8f9a9c3a64 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs @@ -1,22 +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.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; +using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { - public class SliderCircleSelectionBlueprint : OsuSelectionBlueprint + public class SliderCircleSelectionBlueprint : OsuSelectionBlueprint { - public SliderCircleSelectionBlueprint(DrawableOsuHitObject hitObject, Slider slider, SliderPosition position) - : base(hitObject) + private readonly SliderPosition position; + private readonly HitCirclePiece circlePiece; + + public SliderCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) + : base(slider) { - InternalChild = new SliderCirclePiece(slider, position); + this.position = position; + InternalChild = circlePiece = new HitCirclePiece(); Select(); } + protected override void Update() + { + base.Update(); + + circlePiece.UpdateFrom(position == SliderPosition.Start ? HitObject.HeadCircle : HitObject.TailCircle); + } + // Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input. public override bool HandlePositionalInput => false; } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 55de626d7d..4c281a0e7d 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -11,6 +11,7 @@ using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osuTK; using osuTK.Input; @@ -21,6 +22,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { public new Objects.Slider HitObject => (Objects.Slider)base.HitObject; + private SliderBodyPiece bodyPiece; + private HitCirclePiece headCirclePiece; + private HitCirclePiece tailCirclePiece; + private readonly List segments = new List(); private Vector2 cursor; @@ -38,9 +43,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { InternalChildren = new Drawable[] { - new SliderBodyPiece(HitObject), - new SliderCirclePiece(HitObject, SliderPosition.Start), - new SliderCirclePiece(HitObject, SliderPosition.End), + bodyPiece = new SliderBodyPiece(), + headCirclePiece = new HitCirclePiece(), + tailCirclePiece = new HitCirclePiece(), new PathControlPointVisualiser(HitObject), }; @@ -130,6 +135,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { var newControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray(); HitObject.Path = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints); + + bodyPiece.UpdateFrom(HitObject); + headCirclePiece.UpdateFrom(HitObject.HeadCircle); + tailCirclePiece.UpdateFrom(HitObject.TailCircle); } private void setState(PlacementState newState) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index fb8c081ff7..bc760c9456 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -9,8 +9,9 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { - public class SliderSelectionBlueprint : OsuSelectionBlueprint + public class SliderSelectionBlueprint : OsuSelectionBlueprint { + private readonly SliderBodyPiece bodyPiece; private readonly SliderCircleSelectionBlueprint headBlueprint; public SliderSelectionBlueprint(DrawableSlider slider) @@ -20,13 +21,20 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders InternalChildren = new Drawable[] { - new SliderBodyPiece(sliderObject), - headBlueprint = new SliderCircleSelectionBlueprint(slider.HeadCircle, sliderObject, SliderPosition.Start), - new SliderCircleSelectionBlueprint(slider.TailCircle, sliderObject, SliderPosition.End), + bodyPiece = new SliderBodyPiece(), + headBlueprint = new SliderCircleSelectionBlueprint(slider, SliderPosition.Start), + new SliderCircleSelectionBlueprint(slider, SliderPosition.End), new PathControlPointVisualiser(sliderObject), }; } + protected override void Update() + { + base.Update(); + + bodyPiece.UpdateFrom(HitObject); + } + public override Vector2 SelectionPoint => headBlueprint.SelectionPoint; } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs index 5dab501a24..65c8720031 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs @@ -12,16 +12,13 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components { - public class SpinnerPiece : CompositeDrawable + public class SpinnerPiece : BlueprintPiece { - private readonly Spinner spinner; private readonly CircularContainer circle; private readonly RingPiece ring; - public SpinnerPiece(Spinner spinner) + public SpinnerPiece() { - this.spinner = spinner; - Origin = Anchor.Centre; RelativeSizeAxes = Axes.Both; @@ -43,8 +40,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components Origin = Anchor.Centre } }; - - ring.Scale = new Vector2(spinner.Scale); } [BackgroundDependencyLoader] @@ -53,12 +48,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components Colour = colours.Yellow; } - protected override void Update() + public override void UpdateFrom(Spinner hitObject) { - base.Update(); + base.UpdateFrom(hitObject); - Position = spinner.Position; - ring.Scale = new Vector2(spinner.Scale); + ring.Scale = new Vector2(hitObject.Scale); } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => circle.ReceivePositionalInputAt(screenSpacePos); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs index 03d761c67f..8d9dea736b 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs @@ -21,7 +21,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners public SpinnerPlacementBlueprint() : base(new Spinner { Position = OsuPlayfield.BASE_SIZE / 2 }) { - InternalChild = piece = new SpinnerPiece(HitObject) { Alpha = 0.5f }; + InternalChild = piece = new SpinnerPiece { Alpha = 0.5f }; + } + + protected override void Update() + { + base.Update(); + + piece.UpdateFrom(HitObject); } protected override bool OnClick(ClickEvent e) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs index 25cef3b251..f05d4f8435 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs @@ -8,14 +8,21 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners { - public class SpinnerSelectionBlueprint : OsuSelectionBlueprint + public class SpinnerSelectionBlueprint : OsuSelectionBlueprint { private readonly SpinnerPiece piece; public SpinnerSelectionBlueprint(DrawableSpinner spinner) : base(spinner) { - InternalChild = piece = new SpinnerPiece((Spinner)spinner.HitObject); + InternalChild = piece = new SpinnerPiece(); + } + + protected override void Update() + { + base.Update(); + + piece.UpdateFrom(HitObject); } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => piece.ReceivePositionalInputAt(screenSpacePos); From 2a395956aa4278f7eca532049c88947599f8171e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 27 Sep 2019 19:00:17 +0300 Subject: [PATCH 1515/2815] Merge dependencies --- .../UserInterface/TestSceneMetricNumbers.cs | 58 +++++++++++++++++++ .../API/Requests/GetCountryRankingsRequest.cs | 18 ++++++ .../Online/API/Requests/GetRankingsRequest.cs | 34 +++++++++++ .../API/Requests/GetUserRankingsRequest.cs | 40 +++++++++++++ .../Requests/Responses/APICountryRankings.cs | 14 +++++ .../API/Requests/Responses/APIUserRankings.cs | 14 +++++ osu.Game/Users/CountryStatistics.cs | 25 ++++++++ osu.Game/Utils/HumanizerUtils.cs | 27 +++++++++ 8 files changed, 230 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneMetricNumbers.cs create mode 100644 osu.Game/Online/API/Requests/GetCountryRankingsRequest.cs create mode 100644 osu.Game/Online/API/Requests/GetRankingsRequest.cs create mode 100644 osu.Game/Online/API/Requests/GetUserRankingsRequest.cs create mode 100644 osu.Game/Online/API/Requests/Responses/APICountryRankings.cs create mode 100644 osu.Game/Online/API/Requests/Responses/APIUserRankings.cs create mode 100644 osu.Game/Users/CountryStatistics.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMetricNumbers.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneMetricNumbers.cs new file mode 100644 index 0000000000..74470f22fc --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneMetricNumbers.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 osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; +using osu.Game.Utils; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneMetricNumbers : OsuTestScene + { + public TestSceneMetricNumbers() + { + Child = new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new DrawableNumber(0), + new DrawableNumber(1001), + new DrawableNumber(999_999), + new DrawableNumber(1_000_000), + new DrawableNumber(845_006_456), + new DrawableNumber(999_999_999), + new DrawableNumber(1_000_000_000), + new DrawableNumber(7_875_454_545), + new DrawableNumber(999_999_999_999), + new DrawableNumber(1_000_000_000_000), + new DrawableNumber(687_545_454_554_545), + new DrawableNumber(999_999_999_999_999), + new DrawableNumber(1_000_000_000_000_000), + new DrawableNumber(587_545_454_554_545_455), + new DrawableNumber(999_999_999_999_999_999), + new DrawableNumber(1_000_000_000_000_000_000), + new DrawableNumber(long.MaxValue), + } + }; + } + + private class DrawableNumber : SpriteText, IHasTooltip + { + public string TooltipText => value.ToString("F0"); + + private readonly long value; + + public DrawableNumber(long value) + { + this.value = value; + Text = HumanizerUtils.ToMetric(value); + } + } + } +} diff --git a/osu.Game/Online/API/Requests/GetCountryRankingsRequest.cs b/osu.Game/Online/API/Requests/GetCountryRankingsRequest.cs new file mode 100644 index 0000000000..3bd772732f --- /dev/null +++ b/osu.Game/Online/API/Requests/GetCountryRankingsRequest.cs @@ -0,0 +1,18 @@ +// 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.Online.API.Requests.Responses; +using osu.Game.Rulesets; + +namespace osu.Game.Online.API.Requests +{ + public class GetCountryRankingsRequest : GetRankingsRequest + { + public GetCountryRankingsRequest(RulesetInfo ruleset, int page = 1) + : base(ruleset, page) + { + } + + protected override string TargetPostfix() => "country"; + } +} diff --git a/osu.Game/Online/API/Requests/GetRankingsRequest.cs b/osu.Game/Online/API/Requests/GetRankingsRequest.cs new file mode 100644 index 0000000000..efaaa2cb40 --- /dev/null +++ b/osu.Game/Online/API/Requests/GetRankingsRequest.cs @@ -0,0 +1,34 @@ +// 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.IO.Network; +using osu.Game.Rulesets; +using System.Collections.Generic; + +namespace osu.Game.Online.API.Requests +{ + public abstract class GetRankingsRequest : APIRequest> + { + private readonly RulesetInfo ruleset; + private readonly int page; + + protected GetRankingsRequest(RulesetInfo ruleset, int page = 1) + { + this.ruleset = ruleset; + this.page = page; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + + req.AddParameter("page", page.ToString()); + + return req; + } + + protected override string Target => $"rankings/{ruleset.ShortName}/{TargetPostfix()}"; + + protected abstract string TargetPostfix(); + } +} diff --git a/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs b/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs new file mode 100644 index 0000000000..bbba6a210d --- /dev/null +++ b/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs @@ -0,0 +1,40 @@ +// 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.IO.Network; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Rulesets; + +namespace osu.Game.Online.API.Requests +{ + public class GetUserRankingsRequest : GetRankingsRequest + { + private readonly string country; + private readonly UserRankingsType type; + + public GetUserRankingsRequest(RulesetInfo ruleset, UserRankingsType type = UserRankingsType.Performance, int page = 1, string country = null) + : base(ruleset, page) + { + this.type = type; + this.country = country; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + + if (country != null) + req.AddParameter("country", country); + + return req; + } + + protected override string TargetPostfix() => type.ToString().ToLowerInvariant(); + } + + public enum UserRankingsType + { + Performance, + Score + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APICountryRankings.cs b/osu.Game/Online/API/Requests/Responses/APICountryRankings.cs new file mode 100644 index 0000000000..91086876a9 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APICountryRankings.cs @@ -0,0 +1,14 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; +using osu.Game.Users; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APICountryRankings : CountryStatistics + { + [JsonProperty] + public Country Country; + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIUserRankings.cs b/osu.Game/Online/API/Requests/Responses/APIUserRankings.cs new file mode 100644 index 0000000000..1cdb6ecb8c --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIUserRankings.cs @@ -0,0 +1,14 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; +using osu.Game.Users; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIUserRankings : UserStatistics + { + [JsonProperty] + public User User; + } +} diff --git a/osu.Game/Users/CountryStatistics.cs b/osu.Game/Users/CountryStatistics.cs new file mode 100644 index 0000000000..53fa70f0d4 --- /dev/null +++ b/osu.Game/Users/CountryStatistics.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 Newtonsoft.Json; + +namespace osu.Game.Users +{ + public class CountryStatistics + { + [JsonProperty(@"code")] + public string FlagName; + + [JsonProperty(@"active_users")] + public long ActiveUsers; + + [JsonProperty(@"play_count")] + public long PlayCount; + + [JsonProperty(@"ranked_score")] + public long RankedScore; + + [JsonProperty(@"performance")] + public long Performance; + } +} diff --git a/osu.Game/Utils/HumanizerUtils.cs b/osu.Game/Utils/HumanizerUtils.cs index 5b7c3630d9..ee6f6f95a3 100644 --- a/osu.Game/Utils/HumanizerUtils.cs +++ b/osu.Game/Utils/HumanizerUtils.cs @@ -26,5 +26,32 @@ namespace osu.Game.Utils return input.Humanize(culture: new CultureInfo("en-US")); } } + + /// + /// Turns the current or provided big number into a readable string. + /// + /// The number to be humanized. + /// Simplified number with a suffix. + public static string ToMetric(long input) + { + const int k = 1000; + + if (input < k) + return input.ToString(); + + int i = (int)Math.Floor(Math.Round(Math.Log(input, k))); + return $"{input / Math.Pow(k, i):F} {suffixes[i]}"; + } + + private static readonly string[] suffixes = new[] + { + "", + "k", + "million", + "billion", + "trillion", + "quadrillion", + "quintillion", + }; } } From bbaf21a69d0d025019e8cd9035485e473e0006d4 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 27 Sep 2019 19:33:52 +0300 Subject: [PATCH 1516/2815] Tables implementation --- .../Visual/Online/TestSceneRankingsTables.cs | 148 ++++++++++++++++++ .../Rankings/Tables/CountriesTable.cs | 88 +++++++++++ .../Rankings/Tables/PerformanceTable.cs | 102 ++++++++++++ .../Overlays/Rankings/Tables/RankingsTable.cs | 128 +++++++++++++++ .../Overlays/Rankings/Tables/ScoresTable.cs | 108 +++++++++++++ .../Rankings/Tables/TableRowBackground.cs | 56 +++++++ 6 files changed, 630 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs create mode 100644 osu.Game/Overlays/Rankings/Tables/CountriesTable.cs create mode 100644 osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs create mode 100644 osu.Game/Overlays/Rankings/Tables/RankingsTable.cs create mode 100644 osu.Game/Overlays/Rankings/Tables/ScoresTable.cs create mode 100644 osu.Game/Overlays/Rankings/Tables/TableRowBackground.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs new file mode 100644 index 0000000000..08a6da734f --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs @@ -0,0 +1,148 @@ +// 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.Containers; +using osu.Game.Overlays.Rankings.Tables; +using osu.Framework.Graphics; +using osu.Game.Online.API.Requests; +using osu.Game.Rulesets; +using osu.Game.Graphics.UserInterface; +using System.Threading; +using osu.Game.Online.API; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Taiko; +using osu.Game.Rulesets.Catch; +using osu.Framework.Allocation; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneRankingsTables : OsuTestScene + { + protected override bool UseOnlineAPI => true; + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(PerformanceTable), + typeof(ScoresTable), + typeof(CountriesTable), + typeof(TableRowBackground), + }; + + [Resolved] + private IAPIProvider api { get; set; } + + private readonly BasicScrollContainer scrollFlow; + private readonly DimmedLoadingLayer loading; + private CancellationTokenSource cancellationToken; + private APIRequest request; + + public TestSceneRankingsTables() + { + Children = new Drawable[] + { + scrollFlow = new BasicScrollContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Width = 0.8f, + }, + loading = new DimmedLoadingLayer(), + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + AddStep("Osu performance", () => createPerformanceTable(new OsuRuleset().RulesetInfo, null)); + AddStep("Mania scores", () => createScoreTable(new ManiaRuleset().RulesetInfo)); + AddStep("Taiko country scores", () => createCountryTable(new TaikoRuleset().RulesetInfo)); + AddStep("Catch US performance page 10", () => createPerformanceTable(new CatchRuleset().RulesetInfo, "US", 10)); + } + + private void createCountryTable(RulesetInfo ruleset, int page = 1) + { + loading.Show(); + + request?.Cancel(); + cancellationToken?.Cancel(); + cancellationToken = new CancellationTokenSource(); + + request = new GetCountryRankingsRequest(ruleset, page); + ((GetCountryRankingsRequest)request).Success += rankings => Schedule(() => + { + var table = new CountriesTable(page) + { + Rankings = rankings, + }; + + LoadComponentAsync(table, t => + { + scrollFlow.Clear(); + scrollFlow.Add(t); + loading.Hide(); + }, cancellationToken.Token); + }); + + api.Queue(request); + } + + private void createPerformanceTable(RulesetInfo ruleset, string country, int page = 1) + { + loading.Show(); + + request?.Cancel(); + cancellationToken?.Cancel(); + cancellationToken = new CancellationTokenSource(); + + request = new GetUserRankingsRequest(ruleset, country: country, page: page); + ((GetUserRankingsRequest)request).Success += rankings => Schedule(() => + { + var table = new PerformanceTable(page) + { + Rankings = rankings, + }; + + LoadComponentAsync(table, t => + { + scrollFlow.Clear(); + scrollFlow.Add(t); + loading.Hide(); + }, cancellationToken.Token); + }); + + api.Queue(request); + } + + private void createScoreTable(RulesetInfo ruleset, int page = 1) + { + loading.Show(); + + request?.Cancel(); + cancellationToken?.Cancel(); + cancellationToken = new CancellationTokenSource(); + + request = new GetUserRankingsRequest(ruleset, UserRankingsType.Score, page); + ((GetUserRankingsRequest)request).Success += rankings => Schedule(() => + { + var table = new ScoresTable(page) + { + Rankings = rankings, + }; + + LoadComponentAsync(table, t => + { + scrollFlow.Clear(); + scrollFlow.Add(t); + loading.Hide(); + }, cancellationToken.Token); + }); + + api.Queue(request); + } + } +} diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs new file mode 100644 index 0000000000..cfd404ff07 --- /dev/null +++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs @@ -0,0 +1,88 @@ +// 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.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Users.Drawables; +using osuTK; +using osu.Game.Online.API.Requests.Responses; +using System; + +namespace osu.Game.Overlays.Rankings.Tables +{ + public class CountriesTable : RankingsTable + { + public CountriesTable(int page = 1) + : base(page) + { + } + + protected override TableColumn[] CreateHeaders() => new[] + { + new TableColumn("", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 50)), // place + new TableColumn("", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed)), // flag and country name + new TableColumn("Active Users", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 100)), + new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 100)), + new TableColumn("Ranked Score", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 100)), + new TableColumn("Avg. Score", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 100)), + new TableColumn("Performance", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 80)), + new TableColumn("Avg. Perf.", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 50)), + }; + + protected override Drawable[] CreateContent(int index, APICountryRankings item) => new Drawable[] + { + new OsuSpriteText + { + Text = $"#{index + 1}", + Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold) + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(7, 0), + Children = new Drawable[] + { + new UpdateableFlag(item.Country) + { + Size = new Vector2(20, 13), + ShowPlaceholderOnNull = false, + }, + new OsuSpriteText + { + Text = $@"{item.Country.FullName}", + Font = OsuFont.GetFont(size: TEXT_SIZE), + } + } + }, + new ColoredText + { + Text = $@"{item.ActiveUsers:N0}", + Font = OsuFont.GetFont(size: TEXT_SIZE), + }, + new ColoredMetricNumber(item.PlayCount) + { + Font = OsuFont.GetFont(size: TEXT_SIZE), + }, + new ColoredMetricNumber(item.RankedScore) + { + Font = OsuFont.GetFont(size: TEXT_SIZE), + }, + new ColoredMetricNumber(item.RankedScore / Math.Max(item.ActiveUsers, 1)) + { + Font = OsuFont.GetFont(size: TEXT_SIZE), + }, + new MetricNumber(item.Performance) + { + Font = OsuFont.GetFont(size: TEXT_SIZE), + }, + new ColoredText + { + Text = $@"{item.Performance / Math.Max(item.ActiveUsers, 1):N0}", + Font = OsuFont.GetFont(size: TEXT_SIZE), + } + }; + } +} diff --git a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs new file mode 100644 index 0000000000..eab3e67f39 --- /dev/null +++ b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs @@ -0,0 +1,102 @@ +// 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.Containers; +using System.Collections.Generic; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Users.Drawables; +using osuTK; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Overlays.Rankings.Tables +{ + public class PerformanceTable : RankingsTable + { + public PerformanceTable(int page = 1) + : base(page) + { + } + + protected override TableColumn[] CreateHeaders() => new[] + { + new TableColumn("", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 50)), // place + new TableColumn("", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed)), // flag and username + new TableColumn("Accuracy", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 80)), + new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 80)), + new TableColumn("Performance", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 80)), + new TableColumn("SS", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 50)), + new TableColumn("S", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 50)), + new TableColumn("A", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 50)), + }; + + protected override Drawable[] CreateContent(int index, APIUserRankings item) + { + var content = new List + { + new OsuSpriteText + { + Text = $"#{index + 1}", + Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold) + }, + }; + + var username = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: TEXT_SIZE)) { AutoSizeAxes = Axes.Both }; + username.AddUserLink(item.User); + + content.Add(new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(7, 0), + Children = new Drawable[] + { + new UpdateableFlag(item.User.Country) + { + Size = new Vector2(20, 13), + ShowPlaceholderOnNull = false, + }, + username + } + }); + + content.AddRange(new Drawable[] + { + new ColoredText + { + Text = $@"{item.Accuracy:F2}%", + Font = OsuFont.GetFont(size: TEXT_SIZE), + }, + new ColoredText + { + Text = $@"{item.PlayCount:N0}", + Font = OsuFont.GetFont(size: TEXT_SIZE), + }, + new OsuSpriteText + { + Text = $@"{item.PP:N0}", + Font = OsuFont.GetFont(size: TEXT_SIZE), + }, + new ColoredText + { + Text = $@"{item.GradesCount.SS + item.GradesCount.SSPlus:N0}", + Font = OsuFont.GetFont(size: TEXT_SIZE), + }, + new ColoredText + { + Text = $@"{item.GradesCount.S + item.GradesCount.SPlus:N0}", + Font = OsuFont.GetFont(size: TEXT_SIZE), + }, + new ColoredText + { + Text = $@"{item.GradesCount.A:N0}", + Font = OsuFont.GetFont(size: TEXT_SIZE), + }, + }); + + return content.ToArray(); + } + } +} diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs new file mode 100644 index 0000000000..0734004913 --- /dev/null +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -0,0 +1,128 @@ +// 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.Containers; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics.Cursor; +using osu.Game.Utils; + +namespace osu.Game.Overlays.Rankings.Tables +{ + public abstract class RankingsTable : TableContainer + { + protected const int TEXT_SIZE = 14; + private const float horizontal_inset = 20; + private const float row_height = 25; + private const int items_per_page = 50; + + private readonly int page; + private readonly FillFlowContainer backgroundFlow; + + public IReadOnlyList Rankings + { + set + { + Content = null; + backgroundFlow.Clear(); + + if (value?.Any() != true) + return; + + value.ForEach(_ => backgroundFlow.Add(new TableRowBackground())); + + Columns = CreateHeaders(); + Content = value.Select((s, i) => CreateContent(page * items_per_page - (items_per_page - i), s)).ToArray().ToRectangular(); + } + } + + protected RankingsTable(int page) + { + this.page = page; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Padding = new MarginPadding { Horizontal = horizontal_inset }; + RowSize = new Dimension(GridSizeMode.Absolute, row_height); + + AddInternal(backgroundFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Depth = 1f, + Margin = new MarginPadding { Top = row_height } + }); + } + + protected abstract TableColumn[] CreateHeaders(); + + protected abstract Drawable[] CreateContent(int index, TModel item); + + protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty, HighlightedColumn()); + + protected virtual string HighlightedColumn() => @"Performance"; + + private class HeaderText : OsuSpriteText + { + private readonly string highlighted; + + public HeaderText(string text, string highlighted) + { + this.highlighted = highlighted; + + Text = text; + Font = OsuFont.GetFont(size: 12); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + if (Text != highlighted) + Colour = colours.GreySeafoamLighter; + } + } + + protected class MetricNumber : OsuSpriteText, IHasTooltip + { + public string TooltipText => $"{value:N0}"; + + private readonly long value; + + public MetricNumber(long value) + { + this.value = value; + + Text = HumanizerUtils.ToMetric(value); + } + } + + protected class ColoredMetricNumber : MetricNumber + { + public ColoredMetricNumber(long value) + : base(value) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.GreySeafoamLighter; + } + } + + protected class ColoredText : OsuSpriteText + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.GreySeafoamLighter; + } + } + } +} diff --git a/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs b/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs new file mode 100644 index 0000000000..561b08472b --- /dev/null +++ b/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs @@ -0,0 +1,108 @@ +// 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.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Users.Drawables; +using osuTK; +using osu.Game.Online.API.Requests.Responses; +using System.Collections.Generic; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Overlays.Rankings.Tables +{ + public class ScoresTable : RankingsTable + { + public ScoresTable(int page = 1) + : base(page) + { + } + + protected override TableColumn[] CreateHeaders() => new[] + { + new TableColumn("", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 50)), // place + new TableColumn("", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed)), // flag and username + new TableColumn("Accuracy", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 80)), + new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 80)), + new TableColumn("Total Score", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 100)), + new TableColumn("Ranked Score", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 100)), + new TableColumn("SS", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 70)), + new TableColumn("S", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 70)), + new TableColumn("A", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 70)), + }; + + protected override Drawable[] CreateContent(int index, APIUserRankings item) + { + var content = new List + { + new OsuSpriteText + { + Text = $"#{index + 1}", + Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold) + }, + }; + + var username = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: TEXT_SIZE)) { AutoSizeAxes = Axes.Both }; + username.AddUserLink(item.User); + + content.Add(new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(7, 0), + Children = new Drawable[] + { + new UpdateableFlag(item.User.Country) + { + Size = new Vector2(20, 13), + ShowPlaceholderOnNull = false, + }, + username + } + }); + + content.AddRange(new Drawable[] + { + new ColoredText + { + Text = $@"{item.Accuracy:F2}%", + Font = OsuFont.GetFont(size: TEXT_SIZE), + }, + new ColoredText + { + Text = $@"{item.PlayCount:N0}", + Font = OsuFont.GetFont(size: TEXT_SIZE), + }, + new ColoredMetricNumber(item.TotalScore) + { + Font = OsuFont.GetFont(size: TEXT_SIZE), + }, + new MetricNumber(item.RankedScore) + { + Font = OsuFont.GetFont(size: TEXT_SIZE), + }, + new ColoredText + { + Text = $@"{item.GradesCount.SS + item.GradesCount.SSPlus:N0}", + Font = OsuFont.GetFont(size: TEXT_SIZE), + }, + new ColoredText + { + Text = $@"{item.GradesCount.S + item.GradesCount.SPlus:N0}", + Font = OsuFont.GetFont(size: TEXT_SIZE), + }, + new ColoredText + { + Text = $@"{item.GradesCount.A:N0}", + Font = OsuFont.GetFont(size: TEXT_SIZE), + }, + }); + + return content.ToArray(); + } + + protected override string HighlightedColumn() => @"Ranked Score"; + } +} diff --git a/osu.Game/Overlays/Rankings/Tables/TableRowBackground.cs b/osu.Game/Overlays/Rankings/Tables/TableRowBackground.cs new file mode 100644 index 0000000000..04e1c22dae --- /dev/null +++ b/osu.Game/Overlays/Rankings/Tables/TableRowBackground.cs @@ -0,0 +1,56 @@ +// 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.Input.Events; +using osu.Game.Graphics; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Rankings.Tables +{ + public class TableRowBackground : CompositeDrawable + { + private const int fade_duration = 100; + + private readonly Box background; + + private Color4 idleColour; + private Color4 hoverColour; + + public TableRowBackground() + { + RelativeSizeAxes = Axes.X; + Height = 25; + + CornerRadius = 3; + Masking = true; + + InternalChild = background = new Box + { + RelativeSizeAxes = Axes.Both, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = idleColour = colours.GreySeafoam; + hoverColour = colours.GreySeafoamLight; + } + + protected override bool OnHover(HoverEvent e) + { + background.FadeColour(hoverColour, fade_duration, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + background.FadeColour(idleColour, fade_duration, Easing.OutQuint); + base.OnHoverLost(e); + } + } +} From 3af7c910fb311ac4319e9b2eb218926e6b52c04a Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 27 Sep 2019 19:09:07 +0000 Subject: [PATCH 1517/2815] Bump Humanizer from 2.7.2 to 2.7.9 Bumps [Humanizer](https://github.com/Humanizr/Humanizer) from 2.7.2 to 2.7.9. - [Release notes](https://github.com/Humanizr/Humanizer/releases) - [Changelog](https://github.com/Humanizr/Humanizer/blob/master/release_notes.md) - [Commits](https://github.com/Humanizr/Humanizer/compare/v2.7.2...v2.7.9) 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 83632f3d41..8fd5f1c3cd 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -21,7 +21,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 30f1da362d..521552bd4b 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -113,7 +113,7 @@ - + From fb9f21237ebb66b2e0631975d12d65b4f877c8e7 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sat, 28 Sep 2019 04:18:16 +0300 Subject: [PATCH 1518/2815] Reset track adjustments on resuming from another screen --- osu.Game/Overlays/MusicController.cs | 6 +++--- osu.Game/Screens/Select/SongSelect.cs | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index db94b0278f..172ae4e5cb 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -57,7 +57,7 @@ namespace osu.Game.Overlays protected override void LoadComplete() { beatmap.BindValueChanged(beatmapChanged, true); - mods.BindValueChanged(_ => updateAudioAdjustments(), true); + mods.BindValueChanged(_ => ResetTrackAdjustments(), true); base.LoadComplete(); } @@ -213,12 +213,12 @@ namespace osu.Game.Overlays current = beatmap.NewValue; TrackChanged?.Invoke(current, direction); - updateAudioAdjustments(); + ResetTrackAdjustments(); queuedDirection = null; } - private void updateAudioAdjustments() + public void ResetTrackAdjustments() { var track = current?.Track; if (track == null) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index fca801ce78..d40dd9414a 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -490,6 +490,7 @@ namespace osu.Game.Screens.Select BeatmapDetails.Leaderboard.RefreshScores(); Beatmap.Value.Track.Looping = true; + music?.ResetTrackAdjustments(); if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending) { From 2487d4f0f208524b433dff5dd6395b61268498a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 28 Sep 2019 11:58:27 +0200 Subject: [PATCH 1519/2815] Migrate beatmap carousel test to AddUntilStep Due to non-deterministic test failures in TestSceneBeatmapCarousel, migrate the checkSelected helper step from AddAssert to AddUntilStep. This adds more leniency for performance-related issues while still checking the desired behaviour. --- 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 f12a613bf1..51dc11ebf8 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("ensure prev random fetch worked", () => selectedSets.Peek() == carousel.SelectedBeatmapSet); private void checkSelected(int set, int? diff = null) => - AddAssert($"selected is set{set}{(diff.HasValue ? $" diff{diff.Value}" : "")}", () => + AddUntilStep($"selected is set{set}{(diff.HasValue ? $" diff{diff.Value}" : "")}", () => { if (diff != null) return carousel.SelectedBeatmap == carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff.Value - 1).First(); From a45f8c968b4669ca345089670455ce740afeb7a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 28 Sep 2019 20:21:51 +0800 Subject: [PATCH 1520/2815] Rename and add simple xmldoc --- osu.Game/Configuration/SessionStatics.cs | 9 ++++++--- osu.Game/Screens/Menu/MainMenu.cs | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index b4b5e914bb..818a95c0be 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.cs @@ -3,15 +3,18 @@ namespace osu.Game.Configuration { - public class SessionStatics : InMemoryConfigManager + /// + /// Stores global per-session statics. These will not be stored after exiting the game. + /// + public class SessionStatics : InMemoryConfigManager { protected override void InitialiseDefaults() { - Set(Statics.LoginOverlayDisplayed, false); + Set(Static.LoginOverlayDisplayed, false); } } - public enum Statics + public enum Static { LoginOverlayDisplayed, } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 004bba20d4..16e9d67cc3 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -71,7 +71,7 @@ namespace osu.Game.Screens.Menu private void load(DirectOverlay direct, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics) { holdDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); - loginDisplayed = statics.GetBindable(Statics.LoginOverlayDisplayed); + loginDisplayed = statics.GetBindable(Static.LoginOverlayDisplayed); if (host.CanExit) { From 52b044b7f60b055df4baccedbc902eb7142788eb Mon Sep 17 00:00:00 2001 From: V1ntagezTV Date: Sun, 29 Sep 2019 00:10:17 +0500 Subject: [PATCH 1521/2815] Add random intro! --- osu.Game/Configuration/IntroSequence.cs | 3 ++- osu.Game/Screens/Loader.cs | 11 ++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/IntroSequence.cs b/osu.Game/Configuration/IntroSequence.cs index 1eb953be36..1ee7da8bac 100644 --- a/osu.Game/Configuration/IntroSequence.cs +++ b/osu.Game/Configuration/IntroSequence.cs @@ -6,6 +6,7 @@ namespace osu.Game.Configuration public enum IntroSequence { Circles, - Triangles + Triangles, + Random } } diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index 850349272e..7d2ad0e0ad 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.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 osu.Framework.Allocation; @@ -12,6 +13,7 @@ using osu.Framework.Screens; using osu.Game.Configuration; using IntroSequence = osu.Game.Configuration.IntroSequence; + namespace osu.Game.Screens { public class Loader : StartupScreen @@ -58,9 +60,16 @@ namespace osu.Game.Screens } private IntroScreen getIntroSequence() - { + {//вот именно что не показывает ни всплывающих подсказок нихера + Random random = new Random(); switch (introSequence) { + case IntroSequence.Random: + if (random.Next(2) == 0) + return new IntroCircles(); + else + return new IntroTriangles(); + case IntroSequence.Circles: return new IntroCircles(); From 1babd139bc066c1d49e48fa4cf3575784d010eaf Mon Sep 17 00:00:00 2001 From: V1ntagezTV Date: Sun, 29 Sep 2019 00:22:23 +0500 Subject: [PATCH 1522/2815] remove needless blank --- osu.Game/Screens/Loader.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index 7d2ad0e0ad..c17463388a 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -13,7 +13,6 @@ using osu.Framework.Screens; using osu.Game.Configuration; using IntroSequence = osu.Game.Configuration.IntroSequence; - namespace osu.Game.Screens { public class Loader : StartupScreen From 2681e2064ae40da81da7ac1b2962481556025d10 Mon Sep 17 00:00:00 2001 From: V1ntagezTV Date: Sun, 29 Sep 2019 00:34:09 +0500 Subject: [PATCH 1523/2815] remove comment --- osu.Game/Screens/Loader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index c17463388a..75ed74978f 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -59,7 +59,7 @@ namespace osu.Game.Screens } private IntroScreen getIntroSequence() - {//вот именно что не показывает ни всплывающих подсказок нихера + { Random random = new Random(); switch (introSequence) { From 9f1c3787333dbaa7957992e0248e317f46c2797c Mon Sep 17 00:00:00 2001 From: V1ntagezTV Date: Sun, 29 Sep 2019 00:35:47 +0500 Subject: [PATCH 1524/2815] moved into the switch case --- osu.Game/Screens/Loader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index 75ed74978f..fb49c8a574 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -60,10 +60,10 @@ namespace osu.Game.Screens private IntroScreen getIntroSequence() { - Random random = new Random(); switch (introSequence) { case IntroSequence.Random: + var random = new Random(); if (random.Next(2) == 0) return new IntroCircles(); else From 740efa57477f75da8078fb0d86e73bc603e7bb64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 29 Sep 2019 12:13:41 +0800 Subject: [PATCH 1525/2815] Handle potential null case --- osu.Game/Overlays/MusicController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 172ae4e5cb..49d16a4f3e 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -75,7 +75,7 @@ namespace osu.Game.Overlays /// /// Returns whether the current beatmap track is playing. /// - public bool IsPlaying => beatmap.Value.Track.IsRunning; + public bool IsPlaying => beatmap.Value?.Track.IsRunning ?? false; private void handleBeatmapAdded(BeatmapSetInfo set) => Schedule(() => beatmapSets.Add(set)); From 539f3329cef87bf1024b569a6ae77e033316e15e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 29 Sep 2019 12:23:18 +0800 Subject: [PATCH 1526/2815] Rename method to match new behaviour --- .../SongSelect/TestSceneBeatmapCarousel.cs | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 51dc11ebf8..90c6c9065c 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.SongSelect private void ensureRandomFetchSuccess() => AddAssert("ensure prev random fetch worked", () => selectedSets.Peek() == carousel.SelectedBeatmapSet); - private void checkSelected(int set, int? diff = null) => + private void waitForSelection(int set, int? diff = null) => AddUntilStep($"selected is set{set}{(diff.HasValue ? $" diff{diff.Value}" : "")}", () => { if (diff != null) @@ -168,24 +168,24 @@ namespace osu.Game.Tests.Visual.SongSelect loadBeatmaps(); advanceSelection(direction: 1, diff: false); - checkSelected(1, 1); + waitForSelection(1, 1); advanceSelection(direction: 1, diff: true); - checkSelected(1, 2); + waitForSelection(1, 2); advanceSelection(direction: -1, diff: false); - checkSelected(set_count, 1); + waitForSelection(set_count, 1); advanceSelection(direction: -1, diff: true); - checkSelected(set_count - 1, 3); + waitForSelection(set_count - 1, 3); advanceSelection(diff: false); advanceSelection(diff: false); - checkSelected(1, 2); + waitForSelection(1, 2); advanceSelection(direction: -1, diff: true); advanceSelection(direction: -1, diff: true); - checkSelected(set_count, 3); + waitForSelection(set_count, 3); } /// @@ -203,10 +203,10 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("Filter", () => carousel.Filter(new FilterCriteria { SearchText = "set #3!" }, false)); checkVisibleItemCount(diff: false, count: 1); checkVisibleItemCount(diff: true, count: 3); - checkSelected(3, 1); + waitForSelection(3, 1); advanceSelection(diff: true, count: 4); - checkSelected(3, 2); + waitForSelection(3, 2); AddStep("Un-filter (debounce)", () => carousel.Filter(new FilterCriteria())); AddUntilStep("Wait for debounce", () => !carousel.PendingFilterTask); @@ -217,10 +217,10 @@ namespace osu.Game.Tests.Visual.SongSelect setSelected(1, 2); AddStep("Filter some difficulties", () => carousel.Filter(new FilterCriteria { SearchText = "Normal" }, false)); - checkSelected(1, 1); + waitForSelection(1, 1); AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false)); - checkSelected(1, 1); + waitForSelection(1, 1); AddStep("Filter all", () => carousel.Filter(new FilterCriteria { SearchText = "Dingo" }, false)); @@ -249,7 +249,7 @@ namespace osu.Game.Tests.Visual.SongSelect IsLowerInclusive = true } }, false)); - checkSelected(3, 2); + waitForSelection(3, 2); AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false)); } @@ -317,7 +317,7 @@ namespace osu.Game.Tests.Visual.SongSelect checkVisibleItemCount(false, set_count); - checkSelected(set_count); + waitForSelection(set_count); } /// @@ -343,11 +343,11 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("Selection is non-null", () => currentSelection != null); AddStep("Remove selected", () => carousel.RemoveBeatmapSet(carousel.SelectedBeatmapSet)); - checkSelected(2); + waitForSelection(2); AddStep("Remove first", () => carousel.RemoveBeatmapSet(carousel.BeatmapSets.First())); AddStep("Remove first", () => carousel.RemoveBeatmapSet(carousel.BeatmapSets.First())); - checkSelected(1); + waitForSelection(1); AddUntilStep("Remove all", () => { @@ -390,17 +390,17 @@ namespace osu.Game.Tests.Visual.SongSelect checkVisibleItemCount(true, 2); advanceSelection(true); - checkSelected(1, 3); + waitForSelection(1, 3); setHidden(3); - checkSelected(1, 1); + waitForSelection(1, 1); setHidden(2, false); advanceSelection(true); - checkSelected(1, 2); + waitForSelection(1, 2); setHidden(1); - checkSelected(1, 2); + waitForSelection(1, 2); setHidden(2); checkNoSelection(); From ce62f3c75b8e2e896053bbf61291bb31404c2775 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 29 Sep 2019 14:35:35 +0800 Subject: [PATCH 1527/2815] Simplify and future-proof random retrieval method Will support future added intros without further code changes. Also uses RNG instead of `new Random`. --- osu.Game/Screens/Loader.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index fb49c8a574..41ee01be20 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -1,12 +1,12 @@ // 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.Shaders; +using osu.Framework.MathUtils; using osu.Game.Screens.Menu; using osuTK; using osu.Framework.Screens; @@ -60,15 +60,11 @@ namespace osu.Game.Screens private IntroScreen getIntroSequence() { + if (introSequence == IntroSequence.Random) + introSequence = (IntroSequence)RNG.Next(0, (int)IntroSequence.Random); + switch (introSequence) { - case IntroSequence.Random: - var random = new Random(); - if (random.Next(2) == 0) - return new IntroCircles(); - else - return new IntroTriangles(); - case IntroSequence.Circles: return new IntroCircles(); From 97a0e0097f6e5f4fe7de2b5d4000e5c48df9cd5a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 29 Sep 2019 14:56:33 +0300 Subject: [PATCH 1528/2815] Add testing --- .../Visual/Online/TestSceneUserProfileOverlay.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index 93e6607ac5..c0457ba0ff 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -106,6 +106,14 @@ namespace osu.Game.Tests.Visual.Online Country = new Country { FullName = @"Japan", FlagName = @"JP" }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" }, api.IsLoggedIn)); + AddStep("Show bancho", () => profile.ShowUser(new User + { + Username = @"BanchoBot", + Id = 3, + IsBot = true, + Country = new Country { FullName = @"Saint Helena", FlagName = @"SH" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c4.jpg" + }, api.IsLoggedIn)); AddStep("Hide", profile.Hide); AddStep("Show without reload", profile.Show); From 883ee9851af74ae0695e9731df269685f9a7ad70 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 29 Sep 2019 15:02:33 +0300 Subject: [PATCH 1529/2815] Update dependencies --- .../Visual/UserInterface/TestSceneMetricNumbers.cs | 2 +- osu.Game/Overlays/Rankings/Tables/RankingsTable.cs | 2 +- osu.Game/Utils/HumanizerUtils.cs | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMetricNumbers.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneMetricNumbers.cs index 74470f22fc..8e3924e1fb 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneMetricNumbers.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneMetricNumbers.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.UserInterface public DrawableNumber(long value) { this.value = value; - Text = HumanizerUtils.ToMetric(value); + Text = HumanizerUtils.ToReadableString(value); } } } diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index 0734004913..d05d642051 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -98,7 +98,7 @@ namespace osu.Game.Overlays.Rankings.Tables { this.value = value; - Text = HumanizerUtils.ToMetric(value); + Text = HumanizerUtils.ToReadableString(value); } } diff --git a/osu.Game/Utils/HumanizerUtils.cs b/osu.Game/Utils/HumanizerUtils.cs index ee6f6f95a3..bc710a799d 100644 --- a/osu.Game/Utils/HumanizerUtils.cs +++ b/osu.Game/Utils/HumanizerUtils.cs @@ -32,7 +32,7 @@ namespace osu.Game.Utils /// /// The number to be humanized. /// Simplified number with a suffix. - public static string ToMetric(long input) + public static string ToReadableString(long input) { const int k = 1000; @@ -42,8 +42,7 @@ namespace osu.Game.Utils int i = (int)Math.Floor(Math.Round(Math.Log(input, k))); return $"{input / Math.Pow(k, i):F} {suffixes[i]}"; } - - private static readonly string[] suffixes = new[] + private static readonly string[] suffixes = { "", "k", From 138e65d7a49c0d9df0c5648148bc8e490b40542f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 29 Sep 2019 15:15:41 +0300 Subject: [PATCH 1530/2815] Add missing blank line --- osu.Game/Utils/HumanizerUtils.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Utils/HumanizerUtils.cs b/osu.Game/Utils/HumanizerUtils.cs index bc710a799d..9920f7d127 100644 --- a/osu.Game/Utils/HumanizerUtils.cs +++ b/osu.Game/Utils/HumanizerUtils.cs @@ -42,6 +42,7 @@ namespace osu.Game.Utils int i = (int)Math.Floor(Math.Round(Math.Log(input, k))); return $"{input / Math.Pow(k, i):F} {suffixes[i]}"; } + private static readonly string[] suffixes = { "", From f24ac04bebbcbbc0ca5d0679172d3fbe6efd6854 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 29 Sep 2019 15:18:29 +0300 Subject: [PATCH 1531/2815] Add suggested blank line for consistency --- osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index c0457ba0ff..98da63508b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -106,6 +106,7 @@ namespace osu.Game.Tests.Visual.Online Country = new Country { FullName = @"Japan", FlagName = @"JP" }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" }, api.IsLoggedIn)); + AddStep("Show bancho", () => profile.ShowUser(new User { Username = @"BanchoBot", From febe0175cd6f6c3b594e2cf2b8dd2e1b1c59f0e0 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 29 Sep 2019 15:27:29 +0300 Subject: [PATCH 1532/2815] Small cleanups --- .../Rankings/Tables/CountriesTable.cs | 22 ++------- .../Rankings/Tables/PerformanceTable.cs | 36 ++++++--------- .../Overlays/Rankings/Tables/RankingsTable.cs | 6 +++ .../Overlays/Rankings/Tables/ScoresTable.cs | 46 +++++++------------ 4 files changed, 42 insertions(+), 68 deletions(-) diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs index cfd404ff07..96ff506814 100644 --- a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs @@ -60,28 +60,14 @@ namespace osu.Game.Overlays.Rankings.Tables new ColoredText { Text = $@"{item.ActiveUsers:N0}", - Font = OsuFont.GetFont(size: TEXT_SIZE), - }, - new ColoredMetricNumber(item.PlayCount) - { - Font = OsuFont.GetFont(size: TEXT_SIZE), - }, - new ColoredMetricNumber(item.RankedScore) - { - Font = OsuFont.GetFont(size: TEXT_SIZE), - }, - new ColoredMetricNumber(item.RankedScore / Math.Max(item.ActiveUsers, 1)) - { - Font = OsuFont.GetFont(size: TEXT_SIZE), - }, - new MetricNumber(item.Performance) - { - Font = OsuFont.GetFont(size: TEXT_SIZE), }, + new ColoredMetricNumber(item.PlayCount), + new ColoredMetricNumber(item.RankedScore), + new ColoredMetricNumber(item.RankedScore / Math.Max(item.ActiveUsers, 1)), + new MetricNumber(item.Performance), new ColoredText { Text = $@"{item.Performance / Math.Max(item.ActiveUsers, 1):N0}", - Font = OsuFont.GetFont(size: TEXT_SIZE), } }; } diff --git a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs index eab3e67f39..c79553a3be 100644 --- a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs @@ -46,33 +46,30 @@ namespace osu.Game.Overlays.Rankings.Tables var username = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: TEXT_SIZE)) { AutoSizeAxes = Axes.Both }; username.AddUserLink(item.User); - content.Add(new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(7, 0), - Children = new Drawable[] - { - new UpdateableFlag(item.User.Country) - { - Size = new Vector2(20, 13), - ShowPlaceholderOnNull = false, - }, - username - } - }); - content.AddRange(new Drawable[] { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(7, 0), + Children = new Drawable[] + { + new UpdateableFlag(item.User.Country) + { + Size = new Vector2(20, 13), + ShowPlaceholderOnNull = false, + }, + username + } + }, new ColoredText { Text = $@"{item.Accuracy:F2}%", - Font = OsuFont.GetFont(size: TEXT_SIZE), }, new ColoredText { Text = $@"{item.PlayCount:N0}", - Font = OsuFont.GetFont(size: TEXT_SIZE), }, new OsuSpriteText { @@ -82,17 +79,14 @@ namespace osu.Game.Overlays.Rankings.Tables new ColoredText { Text = $@"{item.GradesCount.SS + item.GradesCount.SSPlus:N0}", - Font = OsuFont.GetFont(size: TEXT_SIZE), }, new ColoredText { Text = $@"{item.GradesCount.S + item.GradesCount.SPlus:N0}", - Font = OsuFont.GetFont(size: TEXT_SIZE), }, new ColoredText { Text = $@"{item.GradesCount.A:N0}", - Font = OsuFont.GetFont(size: TEXT_SIZE), }, }); diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index d05d642051..6c53a9fa74 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -99,6 +99,7 @@ namespace osu.Game.Overlays.Rankings.Tables this.value = value; Text = HumanizerUtils.ToReadableString(value); + Font = OsuFont.GetFont(size: TEXT_SIZE); } } @@ -118,6 +119,11 @@ namespace osu.Game.Overlays.Rankings.Tables protected class ColoredText : OsuSpriteText { + public ColoredText() + { + Font = OsuFont.GetFont(size: TEXT_SIZE); + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { diff --git a/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs b/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs index 561b08472b..1c8b474689 100644 --- a/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs @@ -47,56 +47,44 @@ namespace osu.Game.Overlays.Rankings.Tables var username = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: TEXT_SIZE)) { AutoSizeAxes = Axes.Both }; username.AddUserLink(item.User); - content.Add(new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(7, 0), - Children = new Drawable[] - { - new UpdateableFlag(item.User.Country) - { - Size = new Vector2(20, 13), - ShowPlaceholderOnNull = false, - }, - username - } - }); - content.AddRange(new Drawable[] { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(7, 0), + Children = new Drawable[] + { + new UpdateableFlag(item.User.Country) + { + Size = new Vector2(20, 13), + ShowPlaceholderOnNull = false, + }, + username + } + }, new ColoredText { Text = $@"{item.Accuracy:F2}%", - Font = OsuFont.GetFont(size: TEXT_SIZE), }, new ColoredText { Text = $@"{item.PlayCount:N0}", - Font = OsuFont.GetFont(size: TEXT_SIZE), - }, - new ColoredMetricNumber(item.TotalScore) - { - Font = OsuFont.GetFont(size: TEXT_SIZE), - }, - new MetricNumber(item.RankedScore) - { - Font = OsuFont.GetFont(size: TEXT_SIZE), }, + new ColoredMetricNumber(item.TotalScore), + new MetricNumber(item.RankedScore), new ColoredText { Text = $@"{item.GradesCount.SS + item.GradesCount.SSPlus:N0}", - Font = OsuFont.GetFont(size: TEXT_SIZE), }, new ColoredText { Text = $@"{item.GradesCount.S + item.GradesCount.SPlus:N0}", - Font = OsuFont.GetFont(size: TEXT_SIZE), }, new ColoredText { Text = $@"{item.GradesCount.A:N0}", - Font = OsuFont.GetFont(size: TEXT_SIZE), }, }); From 9f498d29908a94f2a70a7e3d390a4202d9a5bdec Mon Sep 17 00:00:00 2001 From: miterosan Date: Sun, 29 Sep 2019 19:25:44 +0200 Subject: [PATCH 1533/2815] Log the exception that caused to loading of rulesets to fail. --- osu.Game/Rulesets/RulesetStore.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 2d8c9f5b49..a94b0e0c06 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -135,9 +135,9 @@ namespace osu.Game.Rulesets foreach (string file in files.Where(f => !Path.GetFileName(f).Contains("Tests"))) loadRulesetFromFile(file); } - catch + catch(Exception e) { - Logger.Log($"Could not load rulesets from directory {Environment.CurrentDirectory}"); + Logger.Error(e, $"Could not load rulesets from directory {Environment.CurrentDirectory}"); } } From 42d1379848fa03611cda80eb2336838ddf3165d3 Mon Sep 17 00:00:00 2001 From: miterosan Date: Sun, 29 Sep 2019 20:40:10 +0200 Subject: [PATCH 1534/2815] Load the rulesets lasily --- osu.Game/Rulesets/RulesetStore.cs | 95 ++++++++++++++++--------------- 1 file changed, 49 insertions(+), 46 deletions(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 2d8c9f5b49..6392f982fb 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -16,17 +16,11 @@ namespace osu.Game.Rulesets /// public class RulesetStore : DatabaseBackedStore { - private static readonly Dictionary loaded_assemblies = new Dictionary(); + private static readonly Lazy> loaded_assemblies = new Lazy>(() => loadRulesets()); static RulesetStore() { AppDomain.CurrentDomain.AssemblyResolve += currentDomain_AssemblyResolve; - - // On android in release configuration assemblies are loaded from the apk directly into memory. - // We cannot read assemblies from cwd, so should check loaded assemblies instead. - loadFromAppDomain(); - - loadFromDisk(); } public RulesetStore(IDatabaseContextFactory factory) @@ -54,7 +48,7 @@ namespace osu.Game.Rulesets /// public IEnumerable AvailableRulesets { get; private set; } - private static Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args) => loaded_assemblies.Keys.FirstOrDefault(a => a.FullName == args.Name); + private static Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args) => loaded_assemblies.Value.Keys.FirstOrDefault(a => a.FullName == args.Name); private const string ruleset_library_prefix = "osu.Game.Rulesets"; @@ -64,7 +58,7 @@ namespace osu.Game.Rulesets { var context = usage.Context; - var instances = loaded_assemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r, (RulesetInfo)null)).ToList(); + var instances = loaded_assemblies.Value.Values.Select(r => (Ruleset)Activator.CreateInstance(r, (RulesetInfo)null)).ToList(); //add all legacy modes in correct order foreach (var r in instances.Where(r => r.LegacyID != null).OrderBy(r => r.LegacyID)) @@ -113,8 +107,39 @@ namespace osu.Game.Rulesets } } - private static void loadFromAppDomain() + /// + /// Loads the rulesets that are in the current appdomain an in the current directory. + /// + /// The rulesets that were loaded. + private static Dictionary loadRulesets() { + var rulesets = new Dictionary(); + + foreach (var rulesetAssembly in getRulesetAssemblies()) + { + try + { + rulesets[rulesetAssembly] = rulesetAssembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset))); + } + catch (Exception e) + { + Logger.Error(e, $"Failed to add ruleset {rulesetAssembly}"); + } + } + + return rulesets; + } + + /// + /// Scans the current appdomain and current directory for ruleset assemblies. + /// Rulesets that were found in the current directory are automaticly loaded. + /// + /// The ruleset assemblies that were found in the current appdomain or in the current directory. + private static IEnumerable getRulesetAssemblies() + { + var rulesetAssemblies = new HashSet(); + + // load from appdomain foreach (var ruleset in AppDomain.CurrentDomain.GetAssemblies()) { string rulesetName = ruleset.GetName().Name; @@ -122,55 +147,33 @@ namespace osu.Game.Rulesets if (!rulesetName.StartsWith(ruleset_library_prefix, StringComparison.InvariantCultureIgnoreCase) || ruleset.GetName().Name.Contains("Tests")) continue; - addRuleset(ruleset); + rulesetAssemblies.Add(ruleset); } - } - private static void loadFromDisk() - { + // load from current directory try { string[] files = Directory.GetFiles(Environment.CurrentDirectory, $"{ruleset_library_prefix}.*.dll"); foreach (string file in files.Where(f => !Path.GetFileName(f).Contains("Tests"))) - loadRulesetFromFile(file); + { + try + { + rulesetAssemblies.Add(Assembly.LoadFrom(file)); + } + catch (Exception e) + { + Logger.Error(e, $"Failed to load ruleset assembly {Path.GetFileNameWithoutExtension(file)}"); + return null; + } + } } catch { Logger.Log($"Could not load rulesets from directory {Environment.CurrentDirectory}"); } - } - private static void loadRulesetFromFile(string file) - { - var filename = Path.GetFileNameWithoutExtension(file); - - if (loaded_assemblies.Values.Any(t => t.Namespace == filename)) - return; - - try - { - addRuleset(Assembly.LoadFrom(file)); - } - catch (Exception e) - { - Logger.Error(e, $"Failed to load ruleset {filename}"); - } - } - - private static void addRuleset(Assembly assembly) - { - if (loaded_assemblies.ContainsKey(assembly)) - return; - - try - { - loaded_assemblies[assembly] = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset))); - } - catch (Exception e) - { - Logger.Error(e, $"Failed to add ruleset {assembly}"); - } + return rulesetAssemblies; } } } From 351e89bf182faaaa342c2b0c78f63a10500af18d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Sep 2019 22:03:56 +0900 Subject: [PATCH 1535/2815] 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 46fd5424df..51245351b6 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -62,6 +62,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8fd5f1c3cd..8cbc8b0af3 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 521552bd4b..a15cae55c4 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,8 +118,8 @@ - - + + From 04ac414249a30de3ea34a734a2c0e08183a27af7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 1 Oct 2019 13:48:56 +0900 Subject: [PATCH 1536/2815] Fix memory leaks due to audio track recycle order --- osu.Game/Beatmaps/BindableBeatmap.cs | 23 ----------------------- osu.Game/OsuGameBase.cs | 6 ++++++ osu.Game/Tests/Visual/OsuTestScene.cs | 9 ++++++--- 3 files changed, 12 insertions(+), 26 deletions(-) diff --git a/osu.Game/Beatmaps/BindableBeatmap.cs b/osu.Game/Beatmaps/BindableBeatmap.cs index af627cc6a9..39c633e282 100644 --- a/osu.Game/Beatmaps/BindableBeatmap.cs +++ b/osu.Game/Beatmaps/BindableBeatmap.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.Diagnostics; using osu.Framework.Bindables; namespace osu.Game.Beatmaps @@ -12,31 +11,9 @@ namespace osu.Game.Beatmaps /// public abstract class BindableBeatmap : NonNullableBindable { - private WorkingBeatmap lastBeatmap; - protected BindableBeatmap(WorkingBeatmap defaultValue) : base(defaultValue) { - BindValueChanged(b => updateAudioTrack(b.NewValue), true); - } - - private void updateAudioTrack(WorkingBeatmap beatmap) - { - var trackLoaded = lastBeatmap?.TrackLoaded ?? false; - - // compare to last beatmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo) - if (!trackLoaded || lastBeatmap?.Track != beatmap.Track) - { - if (trackLoaded) - { - Debug.Assert(lastBeatmap != null); - Debug.Assert(lastBeatmap.Track != null); - - lastBeatmap.RecycleTrack(); - } - } - - lastBeatmap = beatmap; } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 59a5e38b2c..a9a9693c76 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -203,6 +203,12 @@ namespace osu.Game Audio.Tracks.AddAdjustment(AdjustableProperty.Volume, new BindableDouble(0.8)); beatmap = new OsuBindableBeatmap(defaultBeatmap); + beatmap.BindValueChanged(b => ScheduleAfterChildren(() => + { + // compare to last beatmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo) + if (b.OldValue?.TrackLoaded == true && b.OldValue?.Track != b.NewValue?.Track) + b.OldValue.RecycleTrack(); + })); dependencies.CacheAs>(beatmap); dependencies.CacheAs(beatmap); diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 8e98d51962..cf128e058f 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -73,10 +73,13 @@ namespace osu.Game.Tests.Visual // This is the earliest we can get OsuGameBase, which is used by the dummy working beatmap to find textures var working = new DummyWorkingBeatmap(parent.Get(), parent.Get()); - beatmap = new OsuTestBeatmap(working) + beatmap = new OsuTestBeatmap(working) { Default = working }; + beatmap.BindValueChanged(b => ScheduleAfterChildren(() => { - Default = working - }; + // compare to last beatmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo) + if (b.OldValue?.TrackLoaded == true && b.OldValue?.Track != b.NewValue?.Track) + b.OldValue.RecycleTrack(); + })); Dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); From ce609302edd25e0a59a36d64d81b2bfc36c2b344 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 1 Oct 2019 15:41:01 +0900 Subject: [PATCH 1537/2815] Fix CI error --- osu.Game/Rulesets/RulesetStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index a94b0e0c06..47aad43966 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -135,7 +135,7 @@ namespace osu.Game.Rulesets foreach (string file in files.Where(f => !Path.GetFileName(f).Contains("Tests"))) loadRulesetFromFile(file); } - catch(Exception e) + catch (Exception e) { Logger.Error(e, $"Could not load rulesets from directory {Environment.CurrentDirectory}"); } From cc533e8fe471c69bb0db52915a803ee1f7ad2a2f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 1 Oct 2019 17:24:47 +0900 Subject: [PATCH 1538/2815] Remove BindableBeatmap --- osu.Game/Beatmaps/BindableBeatmap.cs | 19 ------------------- osu.Game/OsuGameBase.cs | 10 +--------- osu.Game/Tests/Visual/OsuTestScene.cs | 14 +++----------- 3 files changed, 4 insertions(+), 39 deletions(-) delete mode 100644 osu.Game/Beatmaps/BindableBeatmap.cs diff --git a/osu.Game/Beatmaps/BindableBeatmap.cs b/osu.Game/Beatmaps/BindableBeatmap.cs deleted file mode 100644 index 39c633e282..0000000000 --- a/osu.Game/Beatmaps/BindableBeatmap.cs +++ /dev/null @@ -1,19 +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.Bindables; - -namespace osu.Game.Beatmaps -{ - /// - /// A for the beatmap. - /// This should be used sparingly in-favour of . - /// - public abstract class BindableBeatmap : NonNullableBindable - { - protected BindableBeatmap(WorkingBeatmap defaultValue) - : base(defaultValue) - { - } - } -} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index a9a9693c76..8578517a17 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -202,7 +202,7 @@ namespace osu.Game // this adds a global reduction of track volume for the time being. Audio.Tracks.AddAdjustment(AdjustableProperty.Volume, new BindableDouble(0.8)); - beatmap = new OsuBindableBeatmap(defaultBeatmap); + beatmap = new NonNullableBindable(defaultBeatmap); beatmap.BindValueChanged(b => ScheduleAfterChildren(() => { // compare to last beatmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo) @@ -298,14 +298,6 @@ namespace osu.Game public string[] HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions).ToArray(); - private class OsuBindableBeatmap : BindableBeatmap - { - public OsuBindableBeatmap(WorkingBeatmap defaultValue) - : base(defaultValue) - { - } - } - private class OsuUserInputManager : UserInputManager { protected override MouseButtonEventManager CreateButtonManagerFor(MouseButton button) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index cf128e058f..96b39b303e 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -28,9 +28,9 @@ namespace osu.Game.Tests.Visual { [Cached(typeof(Bindable))] [Cached(typeof(IBindable))] - private OsuTestBeatmap beatmap; + private NonNullableBindable beatmap; - protected BindableBeatmap Beatmap => beatmap; + protected Bindable Beatmap => beatmap; [Cached] [Cached(typeof(IBindable))] @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual // This is the earliest we can get OsuGameBase, which is used by the dummy working beatmap to find textures var working = new DummyWorkingBeatmap(parent.Get(), parent.Get()); - beatmap = new OsuTestBeatmap(working) { Default = working }; + beatmap = new NonNullableBindable(working) { Default = working }; beatmap.BindValueChanged(b => ScheduleAfterChildren(() => { // compare to last beatmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo) @@ -320,13 +320,5 @@ namespace osu.Game.Tests.Visual public void RunTestBlocking(TestScene test) => runner.RunTestBlocking(test); } - - private class OsuTestBeatmap : BindableBeatmap - { - public OsuTestBeatmap(WorkingBeatmap defaultValue) - : base(defaultValue) - { - } - } } } From a310c4b65f220afef633a4dc6ff9d8c58c89f0d2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 1 Oct 2019 19:32:47 +0900 Subject: [PATCH 1539/2815] Make selection blueprints a bit more testable --- .../TestSceneHoldNoteSelectionBlueprint.cs | 4 ++- .../TestSceneNoteSelectionBlueprint.cs | 8 +++--- .../TestSceneSpinnerSelectionBlueprint.cs | 9 ++++--- .../Visual/SelectionBlueprintTestScene.cs | 26 +++++-------------- 4 files changed, 20 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs index 622d840a0c..5507ca2ba0 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs @@ -39,6 +39,8 @@ namespace osu.Game.Rulesets.Mania.Tests AccentColour = { Value = OsuColour.Gray(0.3f) } } }; + + AddBlueprint(new HoldNoteSelectionBlueprint(drawableObject)); } protected override void Update() @@ -52,6 +54,6 @@ namespace osu.Game.Rulesets.Mania.Tests } } - protected override SelectionBlueprint CreateBlueprint() => new HoldNoteSelectionBlueprint(drawableObject); + protected override SelectionBlueprint CreateBlueprint() => new HoldNoteSelectionBlueprint(null); } } diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNoteSelectionBlueprint.cs index 6bb344f977..c0482e2150 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNoteSelectionBlueprint.cs @@ -17,8 +17,6 @@ namespace osu.Game.Rulesets.Mania.Tests { public class TestSceneNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene { - private readonly DrawableNote drawableObject; - protected override Container Content => content ?? base.Content; private readonly Container content; @@ -27,6 +25,8 @@ namespace osu.Game.Rulesets.Mania.Tests var note = new Note { Column = 0 }; note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + DrawableNote drawableObject; + base.Content.Child = content = new ScrollingTestContainer(ScrollingDirection.Down) { Anchor = Anchor.Centre, @@ -34,8 +34,10 @@ namespace osu.Game.Rulesets.Mania.Tests Size = new Vector2(50, 20), Child = drawableObject = new DrawableNote(note) }; + + AddBlueprint(new NoteSelectionBlueprint(drawableObject)); } - protected override SelectionBlueprint CreateBlueprint() => new NoteSelectionBlueprint(drawableObject); + protected override SelectionBlueprint CreateBlueprint() => new NoteSelectionBlueprint(null); } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSelectionBlueprint.cs index c5cea76b14..1c195311a4 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSelectionBlueprint.cs @@ -25,8 +25,6 @@ namespace osu.Game.Rulesets.Osu.Tests typeof(SpinnerPiece) }; - private readonly DrawableSpinner drawableSpinner; - public TestSceneSpinnerSelectionBlueprint() { var spinner = new Spinner @@ -35,16 +33,21 @@ namespace osu.Game.Rulesets.Osu.Tests StartTime = -1000, EndTime = 2000 }; + spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); + DrawableSpinner drawableSpinner; + Add(new Container { RelativeSizeAxes = Axes.Both, Size = new Vector2(0.5f), Child = drawableSpinner = new DrawableSpinner(spinner) }); + + AddBlueprint(new SpinnerSelectionBlueprint(drawableSpinner) { Size = new Vector2(0.5f) }); } - protected override SelectionBlueprint CreateBlueprint() => new SpinnerSelectionBlueprint(drawableSpinner) { Size = new Vector2(0.5f) }; + protected override SelectionBlueprint CreateBlueprint() => new SpinnerSelectionBlueprint(null) { Size = new Vector2(0.5f) }; } } diff --git a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs index df3af2cc43..55dda03b16 100644 --- a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.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 osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Rulesets.Edit; @@ -12,8 +10,6 @@ namespace osu.Game.Tests.Visual { public abstract class SelectionBlueprintTestScene : OsuTestScene { - private SelectionBlueprint blueprint; - protected override Container Content => content ?? base.Content; private readonly Container content; @@ -26,23 +22,13 @@ namespace osu.Game.Tests.Visual }); } - [BackgroundDependencyLoader] - private void load() + protected void AddBlueprint(SelectionBlueprint blueprint) { - blueprint = CreateBlueprint(); - blueprint.Depth = float.MinValue; - blueprint.SelectionRequested += (_, __) => blueprint.Select(); - - Add(blueprint); - - AddStep("Select", () => blueprint.Select()); - AddStep("Deselect", () => blueprint.Deselect()); - } - - protected override bool OnClick(ClickEvent e) - { - blueprint.Deselect(); - return true; + Add(blueprint.With(d => + { + d.Depth = float.MinValue; + d.Select(); + })); } protected abstract SelectionBlueprint CreateBlueprint(); From ba5c9547e14a74af6cee3cbfd61a9066bb944734 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 1 Oct 2019 19:33:08 +0900 Subject: [PATCH 1540/2815] Add more tests for hitcircle selection blueprint --- .../TestSceneHitCircleSelectionBlueprint.cs | 45 +++++++++++++++++-- .../HitCircles/HitCircleSelectionBlueprint.cs | 6 +-- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs index 32043bf5d7..7278d923c1 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs @@ -1,10 +1,12 @@ // 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.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; +using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Tests.Visual; @@ -14,16 +16,53 @@ namespace osu.Game.Rulesets.Osu.Tests { public class TestSceneHitCircleSelectionBlueprint : SelectionBlueprintTestScene { - private readonly DrawableHitCircle drawableObject; + private HitCircle hitCircle; + private DrawableHitCircle drawableObject; + private TestBlueprint blueprint; - public TestSceneHitCircleSelectionBlueprint() + [SetUp] + public void Setup() => Schedule(() => { - var hitCircle = new HitCircle { Position = new Vector2(256, 192) }; + Clear(); + + hitCircle = new HitCircle { Position = new Vector2(256, 192) }; hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); Add(drawableObject = new DrawableHitCircle(hitCircle)); + AddBlueprint(blueprint = new TestBlueprint(drawableObject)); + }); + + [Test] + public void TestInitialState() + { + AddAssert("blueprint positioned over hitobject", () => blueprint.CirclePiece.Position == hitCircle.Position); + } + + [Test] + public void TestMoveHitObject() + { + AddStep("move hitobject", () => hitCircle.Position = new Vector2(300, 225)); + AddAssert("blueprint positioned over hitobject", () => blueprint.CirclePiece.Position == hitCircle.Position); + } + + [Test] + public void TestMoveAfterApplyingDefaults() + { + AddStep("apply defaults", () => hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 })); + AddStep("move hitobject", () => hitCircle.Position = new Vector2(300, 225)); + AddAssert("blueprint positioned over hitobject", () => blueprint.CirclePiece.Position == hitCircle.Position); } protected override SelectionBlueprint CreateBlueprint() => new HitCircleSelectionBlueprint(drawableObject); + + private class TestBlueprint : HitCircleSelectionBlueprint + { + public new HitCirclePiece CirclePiece => base.CirclePiece; + + public TestBlueprint(DrawableHitCircle hitCircle) + : base(hitCircle) + { + } + } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs index 430d4a0222..a191dba8ff 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs @@ -9,19 +9,19 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles { public class HitCircleSelectionBlueprint : OsuSelectionBlueprint { - private readonly HitCirclePiece circlePiece; + protected readonly HitCirclePiece CirclePiece; public HitCircleSelectionBlueprint(DrawableHitCircle hitCircle) : base(hitCircle) { - InternalChild = circlePiece = new HitCirclePiece(); + InternalChild = CirclePiece = new HitCirclePiece(); } protected override void Update() { base.Update(); - circlePiece.UpdateFrom(HitObject); + CirclePiece.UpdateFrom(HitObject); } } } From 90ad1c5166fdc4dd116ff89554a585c28853774b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 1 Oct 2019 19:33:24 +0900 Subject: [PATCH 1541/2815] Add more tests for slider selection blueprint --- .../TestSceneSliderSelectionBlueprint.cs | 79 ++++++++++++++++++- .../Sliders/SliderCircleSelectionBlueprint.cs | 7 +- .../Sliders/SliderSelectionBlueprint.cs | 17 ++-- 3 files changed, 90 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs index 8cf5a2f33e..61c8316ed9 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs @@ -3,11 +3,14 @@ using System; using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Objects; @@ -29,11 +32,16 @@ namespace osu.Game.Rulesets.Osu.Tests typeof(PathControlPointPiece) }; - private readonly DrawableSlider drawableObject; + private Slider slider; + private DrawableSlider drawableObject; + private TestSliderBlueprint blueprint; - public TestSceneSliderSelectionBlueprint() + [SetUp] + public void Setup() => Schedule(() => { - var slider = new Slider + Clear(); + + slider = new Slider { Position = new Vector2(256, 192), Path = new SliderPath(PathType.Bezier, new[] @@ -47,8 +55,73 @@ namespace osu.Game.Rulesets.Osu.Tests slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); Add(drawableObject = new DrawableSlider(slider)); + AddBlueprint(blueprint = new TestSliderBlueprint(drawableObject)); + }); + + [Test] + public void TestInitialState() + { + checkPositions(); + } + + [Test] + public void TestMoveHitObject() + { + moveHitObject(); + checkPositions(); + } + + [Test] + public void TestMoveAfterApplyingDefaults() + { + AddStep("apply defaults", () => slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 })); + moveHitObject(); + checkPositions(); + } + + private void moveHitObject() + { + AddStep("move hitobject", () => + { + slider.Position = new Vector2(300, 225); + }); + } + + private void checkPositions() + { + AddAssert("body positioned correctly", () => blueprint.BodyPiece.Position == slider.Position); + + AddAssert("head positioned correctly", + () => Precision.AlmostEquals(blueprint.HeadBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.HeadCircle.ScreenSpaceDrawQuad.Centre)); + + AddAssert("tail positioned correctly", + () => Precision.AlmostEquals(blueprint.TailBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre)); } protected override SelectionBlueprint CreateBlueprint() => new SliderSelectionBlueprint(drawableObject); + + private class TestSliderBlueprint : SliderSelectionBlueprint + { + public new SliderBodyPiece BodyPiece => base.BodyPiece; + public new TestSliderCircleBlueprint HeadBlueprint => (TestSliderCircleBlueprint)base.HeadBlueprint; + public new TestSliderCircleBlueprint TailBlueprint => (TestSliderCircleBlueprint)base.TailBlueprint; + + public TestSliderBlueprint(DrawableSlider slider) + : base(slider) + { + } + + protected override SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new TestSliderCircleBlueprint(slider, position); + } + + private class TestSliderCircleBlueprint : SliderCircleSelectionBlueprint + { + public new HitCirclePiece CirclePiece => base.CirclePiece; + + public TestSliderCircleBlueprint(DrawableSlider slider, SliderPosition position) + : base(slider, position) + { + } + } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs index 8f9a9c3a64..f09279ed73 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs @@ -9,14 +9,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { public class SliderCircleSelectionBlueprint : OsuSelectionBlueprint { + protected readonly HitCirclePiece CirclePiece; + private readonly SliderPosition position; - private readonly HitCirclePiece circlePiece; public SliderCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) : base(slider) { this.position = position; - InternalChild = circlePiece = new HitCirclePiece(); + InternalChild = CirclePiece = new HitCirclePiece(); Select(); } @@ -25,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { base.Update(); - circlePiece.UpdateFrom(position == SliderPosition.Start ? HitObject.HeadCircle : HitObject.TailCircle); + CirclePiece.UpdateFrom(position == SliderPosition.Start ? HitObject.HeadCircle : HitObject.TailCircle); } // Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input. diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index bc760c9456..fdeffc6f8a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -11,8 +11,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { public class SliderSelectionBlueprint : OsuSelectionBlueprint { - private readonly SliderBodyPiece bodyPiece; - private readonly SliderCircleSelectionBlueprint headBlueprint; + protected readonly SliderBodyPiece BodyPiece; + protected readonly SliderCircleSelectionBlueprint HeadBlueprint; + protected readonly SliderCircleSelectionBlueprint TailBlueprint; public SliderSelectionBlueprint(DrawableSlider slider) : base(slider) @@ -21,9 +22,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders InternalChildren = new Drawable[] { - bodyPiece = new SliderBodyPiece(), - headBlueprint = new SliderCircleSelectionBlueprint(slider, SliderPosition.Start), - new SliderCircleSelectionBlueprint(slider, SliderPosition.End), + BodyPiece = new SliderBodyPiece(), + HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start), + TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End), new PathControlPointVisualiser(sliderObject), }; } @@ -32,9 +33,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { base.Update(); - bodyPiece.UpdateFrom(HitObject); + BodyPiece.UpdateFrom(HitObject); } - public override Vector2 SelectionPoint => headBlueprint.SelectionPoint; + public override Vector2 SelectionPoint => HeadBlueprint.SelectionPoint; + + protected virtual SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new SliderCircleSelectionBlueprint(slider, position); } } From c5540048ab1608873b6377f2fa15bbd4b5b7ab01 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 1 Oct 2019 19:39:06 +0900 Subject: [PATCH 1542/2815] Fix tail circle not moving with slider position changes --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index d8514092bc..3ed1f2cdde 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -54,13 +54,13 @@ namespace osu.Game.Rulesets.Osu.Objects { base.Position = value; + endPositionCache.Invalidate(); + if (HeadCircle != null) HeadCircle.Position = value; if (TailCircle != null) TailCircle.Position = EndPosition; - - endPositionCache.Invalidate(); } } From ff477cd56c36eb99a8e238dd0de607fc5f3a6222 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 1 Oct 2019 14:12:03 +0300 Subject: [PATCH 1543/2815] Remove humanized number dependency --- .../UserInterface/TestSceneMetricNumbers.cs | 58 ------------------- .../Rankings/Tables/CountriesTable.cs | 27 ++++++--- .../Rankings/Tables/PerformanceTable.cs | 13 ++--- .../Overlays/Rankings/Tables/RankingsTable.cs | 34 +---------- .../Overlays/Rankings/Tables/ScoresTable.cs | 20 ++++--- osu.Game/Utils/HumanizerUtils.cs | 27 --------- 6 files changed, 41 insertions(+), 138 deletions(-) delete mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneMetricNumbers.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMetricNumbers.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneMetricNumbers.cs deleted file mode 100644 index 8e3924e1fb..0000000000 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneMetricNumbers.cs +++ /dev/null @@ -1,58 +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.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Sprites; -using osu.Game.Utils; - -namespace osu.Game.Tests.Visual.UserInterface -{ - public class TestSceneMetricNumbers : OsuTestScene - { - public TestSceneMetricNumbers() - { - Child = new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new DrawableNumber(0), - new DrawableNumber(1001), - new DrawableNumber(999_999), - new DrawableNumber(1_000_000), - new DrawableNumber(845_006_456), - new DrawableNumber(999_999_999), - new DrawableNumber(1_000_000_000), - new DrawableNumber(7_875_454_545), - new DrawableNumber(999_999_999_999), - new DrawableNumber(1_000_000_000_000), - new DrawableNumber(687_545_454_554_545), - new DrawableNumber(999_999_999_999_999), - new DrawableNumber(1_000_000_000_000_000), - new DrawableNumber(587_545_454_554_545_455), - new DrawableNumber(999_999_999_999_999_999), - new DrawableNumber(1_000_000_000_000_000_000), - new DrawableNumber(long.MaxValue), - } - }; - } - - private class DrawableNumber : SpriteText, IHasTooltip - { - public string TooltipText => value.ToString("F0"); - - private readonly long value; - - public DrawableNumber(long value) - { - this.value = value; - Text = HumanizerUtils.ToReadableString(value); - } - } - } -} diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs index 96ff506814..8e0e55595d 100644 --- a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs @@ -50,22 +50,33 @@ namespace osu.Game.Overlays.Rankings.Tables Size = new Vector2(20, 13), ShowPlaceholderOnNull = false, }, - new OsuSpriteText + new RowText { Text = $@"{item.Country.FullName}", - Font = OsuFont.GetFont(size: TEXT_SIZE), } } }, - new ColoredText + new ColoredRowText { Text = $@"{item.ActiveUsers:N0}", }, - new ColoredMetricNumber(item.PlayCount), - new ColoredMetricNumber(item.RankedScore), - new ColoredMetricNumber(item.RankedScore / Math.Max(item.ActiveUsers, 1)), - new MetricNumber(item.Performance), - new ColoredText + new ColoredRowText + { + Text = $@"{item.PlayCount:N0}", + }, + new ColoredRowText + { + Text = $@"{item.RankedScore:N0}", + }, + new ColoredRowText + { + Text = $@"{item.RankedScore / Math.Max(item.ActiveUsers, 1):N0}", + }, + new RowText + { + Text = $@"{item.Performance:N0}", + }, + new ColoredRowText { Text = $@"{item.Performance / Math.Max(item.ActiveUsers, 1):N0}", } diff --git a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs index c79553a3be..2d582f4321 100644 --- a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs @@ -63,28 +63,27 @@ namespace osu.Game.Overlays.Rankings.Tables username } }, - new ColoredText + new ColoredRowText { Text = $@"{item.Accuracy:F2}%", }, - new ColoredText + new ColoredRowText { Text = $@"{item.PlayCount:N0}", }, - new OsuSpriteText + new RowText { Text = $@"{item.PP:N0}", - Font = OsuFont.GetFont(size: TEXT_SIZE), }, - new ColoredText + new ColoredRowText { Text = $@"{item.GradesCount.SS + item.GradesCount.SSPlus:N0}", }, - new ColoredText + new ColoredRowText { Text = $@"{item.GradesCount.S + item.GradesCount.SPlus:N0}", }, - new ColoredText + new ColoredRowText { Text = $@"{item.GradesCount.A:N0}", }, diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index 6c53a9fa74..a04e8be9d0 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -10,8 +10,6 @@ using osu.Framework.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics.Cursor; -using osu.Game.Utils; namespace osu.Game.Overlays.Rankings.Tables { @@ -88,42 +86,16 @@ namespace osu.Game.Overlays.Rankings.Tables } } - protected class MetricNumber : OsuSpriteText, IHasTooltip + protected class RowText : OsuSpriteText { - public string TooltipText => $"{value:N0}"; - - private readonly long value; - - public MetricNumber(long value) + public RowText() { - this.value = value; - - Text = HumanizerUtils.ToReadableString(value); Font = OsuFont.GetFont(size: TEXT_SIZE); } } - protected class ColoredMetricNumber : MetricNumber + protected class ColoredRowText : RowText { - public ColoredMetricNumber(long value) - : base(value) - { - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Colour = colours.GreySeafoamLighter; - } - } - - protected class ColoredText : OsuSpriteText - { - public ColoredText() - { - Font = OsuFont.GetFont(size: TEXT_SIZE); - } - [BackgroundDependencyLoader] private void load(OsuColour colours) { diff --git a/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs b/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs index 1c8b474689..5d572a6af0 100644 --- a/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs @@ -64,25 +64,31 @@ namespace osu.Game.Overlays.Rankings.Tables username } }, - new ColoredText + new ColoredRowText { Text = $@"{item.Accuracy:F2}%", }, - new ColoredText + new ColoredRowText { Text = $@"{item.PlayCount:N0}", }, - new ColoredMetricNumber(item.TotalScore), - new MetricNumber(item.RankedScore), - new ColoredText + new ColoredRowText + { + Text = $@"{item.TotalScore:N0}", + }, + new RowText + { + Text = $@"{item.RankedScore:N0}", + }, + new ColoredRowText { Text = $@"{item.GradesCount.SS + item.GradesCount.SSPlus:N0}", }, - new ColoredText + new ColoredRowText { Text = $@"{item.GradesCount.S + item.GradesCount.SPlus:N0}", }, - new ColoredText + new ColoredRowText { Text = $@"{item.GradesCount.A:N0}", }, diff --git a/osu.Game/Utils/HumanizerUtils.cs b/osu.Game/Utils/HumanizerUtils.cs index 9920f7d127..5b7c3630d9 100644 --- a/osu.Game/Utils/HumanizerUtils.cs +++ b/osu.Game/Utils/HumanizerUtils.cs @@ -26,32 +26,5 @@ namespace osu.Game.Utils return input.Humanize(culture: new CultureInfo("en-US")); } } - - /// - /// Turns the current or provided big number into a readable string. - /// - /// The number to be humanized. - /// Simplified number with a suffix. - public static string ToReadableString(long input) - { - const int k = 1000; - - if (input < k) - return input.ToString(); - - int i = (int)Math.Floor(Math.Round(Math.Log(input, k))); - return $"{input / Math.Pow(k, i):F} {suffixes[i]}"; - } - - private static readonly string[] suffixes = - { - "", - "k", - "million", - "billion", - "trillion", - "quadrillion", - "quintillion", - }; } } From 208b9a4eba5c5df080683456afae698feafe99c9 Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Tue, 1 Oct 2019 20:47:53 +0700 Subject: [PATCH 1544/2815] Add new virtual float for username to timestamp padding --- .../Visual/Online/TestSceneStandAloneChatDisplay.cs | 12 ++++++++++++ osu.Game/Online/Chat/StandAloneChatDisplay.cs | 1 + osu.Game/Overlays/Chat/ChatLine.cs | 8 +++++--- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 91006bc0d9..3c5641fcd6 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -32,6 +32,12 @@ namespace osu.Game.Tests.Visual.Online Id = 4, }; + private readonly User longUsernameUser = new User + { + Username = "Very Long Long Username", + Id = 5, + }; + [Cached] private ChannelManager channelManager = new ChannelManager(); @@ -99,6 +105,12 @@ namespace osu.Game.Tests.Visual.Online Sender = admin, Content = "Okay okay, calm down guys. Let's do this!" })); + + AddStep("message from long username", () => testChannel.AddNewMessages(new Message(sequence++) + { + Sender = longUsernameUser, + Content = "Hi guys, my new username is lit!" + })); } } } diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index 9dab2f2aba..00defd5f34 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -146,6 +146,7 @@ namespace osu.Game.Online.Chat protected override float HorizontalPadding => 10; protected override float MessagePadding => 120; + protected override float TimestampPadding => 130; public StandAloneMessage(Message message) : base(message) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 7596231a3d..1f5f5d3ff6 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -31,7 +31,9 @@ namespace osu.Game.Overlays.Chat protected virtual float MessagePadding => default_message_padding; - private const float timestamp_padding = 65; + private const float default_timestamp_padding = 65; + + protected virtual float TimestampPadding => default_timestamp_padding; private const float default_horizontal_padding = 15; @@ -94,7 +96,7 @@ namespace osu.Game.Overlays.Chat Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Bold, italics: true), Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - MaxWidth = default_message_padding - timestamp_padding + MaxWidth = default_message_padding - TimestampPadding }; if (hasBackground) @@ -149,7 +151,7 @@ namespace osu.Game.Overlays.Chat new MessageSender(message.Sender) { AutoSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = timestamp_padding }, + Padding = new MarginPadding { Left = TimestampPadding }, Origin = Anchor.TopRight, Anchor = Anchor.TopRight, Child = effectedUsername, From 5f700f2ae9e57139cfa4aa0cb7e249363cc6d7e6 Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 1 Oct 2019 08:26:34 -0700 Subject: [PATCH 1545/2815] Simplify exit logic of screens with textboxes using back button receptor --- .../Graphics/UserInterface/FocusedTextBox.cs | 16 +++++----------- osu.Game/Graphics/UserInterface/OsuTextBox.cs | 17 +---------------- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 4 ---- .../Chat/Selection/ChannelSelectionOverlay.cs | 1 - osu.Game/Overlays/ChatOverlay.cs | 1 - osu.Game/Overlays/Music/FilterControl.cs | 3 --- osu.Game/Overlays/Music/PlaylistOverlay.cs | 1 - .../SearchableList/SearchableListOverlay.cs | 2 -- osu.Game/Overlays/SettingsPanel.cs | 1 - .../Screens/Multi/Lounge/LoungeSubScreen.cs | 2 -- osu.Game/Screens/Multi/Match/MatchSubScreen.cs | 9 +-------- osu.Game/Screens/Select/FilterControl.cs | 8 +------- osu.Game/Screens/Select/SongSelect.cs | 5 ----- 13 files changed, 8 insertions(+), 62 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs index f873db0dcb..62fbb3592c 100644 --- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs +++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs @@ -2,22 +2,20 @@ // See the LICENCE file in the repository root for full licence text. using osuTK.Graphics; -using System; using osu.Framework.Allocation; using osu.Framework.Input.Events; using osu.Framework.Platform; using osu.Game.Input.Bindings; using osuTK.Input; +using osu.Framework.Input.Bindings; namespace osu.Game.Graphics.UserInterface { /// /// A textbox which holds focus eagerly. /// - public class FocusedTextBox : OsuTextBox + public class FocusedTextBox : OsuTextBox, IKeyBindingHandler { - public Action Exit; - private bool focus; private bool allowImmediateFocus => host?.OnScreenKeyboardOverlapsGameWindow != true; @@ -68,7 +66,7 @@ namespace osu.Game.Graphics.UserInterface return base.OnKeyDown(e); } - public override bool OnPressed(GlobalAction action) + public bool OnPressed(GlobalAction action) { if (action == GlobalAction.Back) { @@ -79,14 +77,10 @@ namespace osu.Game.Graphics.UserInterface } } - return base.OnPressed(action); + return false; } - protected override void KillFocus() - { - base.KillFocus(); - Exit?.Invoke(); - } + public bool OnReleased(GlobalAction action) => false; public override bool RequestsFocus => HoldFocus; } diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 89de91bc9b..1cac4d76ab 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -8,13 +8,11 @@ using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.Sprites; using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Game.Input.Bindings; namespace osu.Game.Graphics.UserInterface { - public class OsuTextBox : TextBox, IKeyBindingHandler + public class OsuTextBox : TextBox { protected override float LeftRightPadding => 10; @@ -57,18 +55,5 @@ namespace osu.Game.Graphics.UserInterface } protected override Drawable GetDrawableCharacter(char c) => new OsuSpriteText { Text = c.ToString(), Font = OsuFont.GetFont(size: CalculatedTextSize) }; - - public virtual bool OnPressed(GlobalAction action) - { - if (action == GlobalAction.Back) - { - KillFocus(); - return true; - } - - return false; - } - - public bool OnReleased(GlobalAction action) => false; } } diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index 9dab2f2aba..b9edc36dfb 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -21,8 +21,6 @@ namespace osu.Game.Online.Chat { public readonly Bindable Channel = new Bindable(); - public Action Exit; - private readonly FocusedTextBox textbox; protected ChannelManager ChannelManager; @@ -66,8 +64,6 @@ namespace osu.Game.Online.Chat Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, }); - - textbox.Exit += () => Exit?.Invoke(); } Channel.BindValueChanged(channelChanged); diff --git a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs index e0ded11ec9..621728830a 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs @@ -119,7 +119,6 @@ namespace osu.Game.Overlays.Chat.Selection { RelativeSizeAxes = Axes.X, PlaceholderText = @"Search", - Exit = Hide, }, }, }, diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 6f848c7627..0cadbdfd31 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -138,7 +138,6 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both, Height = 1, PlaceholderText = "type your message", - Exit = Hide, OnCommit = postMessage, ReleaseFocusOnCommit = false, HoldFocus = true, diff --git a/osu.Game/Overlays/Music/FilterControl.cs b/osu.Game/Overlays/Music/FilterControl.cs index 99017579a2..278bb55170 100644 --- a/osu.Game/Overlays/Music/FilterControl.cs +++ b/osu.Game/Overlays/Music/FilterControl.cs @@ -31,7 +31,6 @@ namespace osu.Game.Overlays.Music { RelativeSizeAxes = Axes.X, Height = 40, - Exit = () => ExitRequested?.Invoke(), }, new CollectionsDropdown { @@ -47,8 +46,6 @@ namespace osu.Game.Overlays.Music private void current_ValueChanged(ValueChangedEvent e) => FilterChanged?.Invoke(e.NewValue); - public Action ExitRequested; - public Action FilterChanged; public class FilterTextBox : SearchTextBox diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index ae81a6c117..bb88960280 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -63,7 +63,6 @@ namespace osu.Game.Overlays.Music { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - ExitRequested = Hide, FilterChanged = search => list.Filter(search), Padding = new MarginPadding(10), }, diff --git a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs index 293ee4bcda..177f731f12 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs @@ -88,8 +88,6 @@ namespace osu.Game.Overlays.SearchableList }, }, }; - - Filter.Search.Exit = Hide; } protected override void Update() diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index 9dd0def453..37e7b62483 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -91,7 +91,6 @@ namespace osu.Game.Overlays Top = 20, Bottom = 20 }, - Exit = Hide, }, Footer = CreateFooter() }, diff --git a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs index 7f8e690516..0a48f761cf 100644 --- a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs @@ -69,8 +69,6 @@ namespace osu.Game.Screens.Multi.Lounge }, }, }; - - Filter.Search.Exit += this.Exit; } protected override void UpdateAfterChildren() diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index f3e10db444..c2bb7da6b5 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -62,7 +62,6 @@ namespace osu.Game.Screens.Multi.Match [BackgroundDependencyLoader] private void load() { - MatchChatDisplay chat; Components.Header header; Info info; GridContainer bottomRow; @@ -122,7 +121,7 @@ namespace osu.Game.Screens.Multi.Match Vertical = 10, }, RelativeSizeAxes = Axes.Both, - Child = chat = new MatchChatDisplay + Child = new MatchChatDisplay { RelativeSizeAxes = Axes.Both } @@ -159,12 +158,6 @@ namespace osu.Game.Screens.Multi.Match bottomRow.FadeTo(settingsDisplayed ? 0 : 1, fade_duration, Easing.OutQuint); }, true); - chat.Exit += () => - { - if (this.IsCurrentScreen()) - this.Exit(); - }; - beatmapManager.ItemAdded += beatmapAdded; } diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 91f1ca0307..8755c3fda6 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -49,8 +49,6 @@ namespace osu.Game.Screens.Select return criteria; } - public Action Exit; - private readonly SearchTextBox searchTextBox; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => @@ -75,11 +73,7 @@ namespace osu.Game.Screens.Select Origin = Anchor.TopRight, Children = new Drawable[] { - searchTextBox = new SearchTextBox - { - RelativeSizeAxes = Axes.X, - Exit = () => Exit?.Invoke(), - }, + searchTextBox = new SearchTextBox { RelativeSizeAxes = Axes.X }, new Box { RelativeSizeAxes = Axes.X, diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index d40dd9414a..5ab49fa2b9 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -171,11 +171,6 @@ namespace osu.Game.Screens.Select Height = FilterControl.HEIGHT, FilterChanged = c => Carousel.Filter(c), Background = { Width = 2 }, - Exit = () => - { - if (this.IsCurrentScreen()) - this.Exit(); - }, }, } }, From ff6367fa4b113052fd835a05ff776049e3ca818f Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 1 Oct 2019 08:26:45 -0700 Subject: [PATCH 1546/2815] Make back button glow when pressing escape --- osu.Game/Graphics/UserInterface/BackButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/BackButton.cs b/osu.Game/Graphics/UserInterface/BackButton.cs index 62c33b9a39..23565e8742 100644 --- a/osu.Game/Graphics/UserInterface/BackButton.cs +++ b/osu.Game/Graphics/UserInterface/BackButton.cs @@ -18,7 +18,7 @@ namespace osu.Game.Graphics.UserInterface public BackButton(Receptor receptor) { - receptor.OnBackPressed = () => Action?.Invoke(); + receptor.OnBackPressed = () => button.Click(); Size = TwoLayerButton.SIZE_EXTENDED; From e3502f52008e01e6de850ab7820332a9e6f2a694 Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 1 Oct 2019 08:37:08 -0700 Subject: [PATCH 1547/2815] Fix typo on Key.Escape comment --- osu.Game/Graphics/UserInterface/FocusedTextBox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs index 62fbb3592c..0b183c0ec9 100644 --- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs +++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs @@ -61,7 +61,7 @@ namespace osu.Game.Graphics.UserInterface if (!HasFocus) return false; if (e.Key == Key.Escape) - return false; // disable the framework-level handling of escape key for confority (we use GlobalAction.Back). + return false; // disable the framework-level handling of escape key for conformity (we use GlobalAction.Back). return base.OnKeyDown(e); } From 2ac5e0bfa0b6961eb07e09f8995b5d041b449561 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Tue, 1 Oct 2019 17:39:01 +0200 Subject: [PATCH 1548/2815] Make use of SessionStatics --- osu.Game/Configuration/SessionStatics.cs | 2 ++ osu.Game/Screens/Play/PlayerLoader.cs | 12 ++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index 818a95c0be..40b2adb867 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.cs @@ -11,11 +11,13 @@ namespace osu.Game.Configuration protected override void InitialiseDefaults() { Set(Static.LoginOverlayDisplayed, false); + Set(Static.MutedAudioNotificationShownOnce, false); } } public enum Static { LoginOverlayDisplayed, + MutedAudioNotificationShownOnce } } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 157ff8fcd4..635201879b 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -15,6 +16,7 @@ using osu.Framework.Localisation; using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -71,8 +73,10 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader] - private void load() + private void load(SessionStatics sessionStatics) { + muteWarningShownOnce = sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce); + InternalChild = (content = new LogoTrackingContainer { Anchor = Anchor.Centre, @@ -157,18 +161,18 @@ namespace osu.Game.Screens.Play [Resolved] private AudioManager audioManager { get; set; } - private static bool muteWarningShownOnce; + private Bindable muteWarningShownOnce; private void checkVolume() { - if (muteWarningShownOnce) + if (muteWarningShownOnce.Value) return; //Checks if the notification has not been shown yet and also if master volume is muted, track/music volume is muted or if the whole game is muted. if (volumeOverlay?.IsMuted.Value == true || audioManager.Volume.Value <= audioManager.Volume.MinValue || audioManager.VolumeTrack.Value <= audioManager.VolumeTrack.MinValue) { notificationOverlay?.Post(new MutedNotification()); - muteWarningShownOnce = true; + muteWarningShownOnce.Value = true; } } From 5f399add82ece40b2f927982f0762d1d86369c04 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Tue, 1 Oct 2019 18:15:40 +0200 Subject: [PATCH 1549/2815] Resolve @iiSaLMaN 's suggested changes --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 6 +++++- osu.Game/Screens/Play/PlayerLoader.cs | 8 -------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 6866fd4c62..74ae641bfe 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.MathUtils; using osu.Framework.Screens; +using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; @@ -36,6 +37,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Resolved] private AudioManager audioManager { get; set; } + [Resolved] + private SessionStatics sessionStatics { get; set; } + /// /// Sets the input manager child to a new test player loader container instance. /// @@ -137,7 +141,7 @@ namespace osu.Game.Tests.Visual.Gameplay /// The function to be invoked and checked private void addVolumeSteps(string volumeName, Action beforeLoad, Action afterLoad, Func assert) { - AddStep("reset notification lock", PlayerLoader.ResetNotificationLock); + AddStep("reset notification lock", () => sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce).Value = false); AddStep("load player", () => ResetPlayer(false, beforeLoad, afterLoad)); AddUntilStep("wait for player", () => player.IsLoaded); diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 635201879b..7f8ef60a5d 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -536,13 +536,5 @@ namespace osu.Game.Screens.Play }; } } - - /// - /// Sets to false, reserved for testing. - /// - public static void ResetNotificationLock() - { - muteWarningShownOnce = false; - } } } From b6dd610af847b9d15c7a8fcb761110f7fd40c18f Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Tue, 1 Oct 2019 23:18:03 +0700 Subject: [PATCH 1550/2815] Apply reviews --- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 2 +- osu.Game/Overlays/Chat/ChatLine.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index 00defd5f34..6a94cec4fd 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -146,7 +146,7 @@ namespace osu.Game.Online.Chat protected override float HorizontalPadding => 10; protected override float MessagePadding => 120; - protected override float TimestampPadding => 130; + protected override float TimestampPadding => 50; public StandAloneMessage(Message message) : base(message) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 1f5f5d3ff6..db378bde73 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -96,7 +96,7 @@ namespace osu.Game.Overlays.Chat Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Bold, italics: true), Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - MaxWidth = default_message_padding - TimestampPadding + MaxWidth = MessagePadding - TimestampPadding }; if (hasBackground) @@ -151,7 +151,6 @@ namespace osu.Game.Overlays.Chat new MessageSender(message.Sender) { AutoSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = TimestampPadding }, Origin = Anchor.TopRight, Anchor = Anchor.TopRight, Child = effectedUsername, From faf8fe132ecc0fc9971406d25a09109088fa4b32 Mon Sep 17 00:00:00 2001 From: HDragonHR Date: Wed, 2 Oct 2019 12:26:46 +0800 Subject: [PATCH 1551/2815] Change bindable int to float --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- osu.Game/Graphics/Containers/HoldToConfirmContainer.cs | 4 ++-- .../Settings/Sections/Graphics/UserInterfaceSettings.cs | 6 +++--- osu.Game/Screens/Menu/MainMenu.cs | 4 ++-- osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 64b1f2d7bc..c0ce08ba08 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -114,7 +114,7 @@ namespace osu.Game.Configuration Set(OsuSetting.UIScale, 1f, 0.8f, 1.6f, 0.01f); - Set(OsuSetting.UIHoldActivationDelay, 200, 0, 500); + Set(OsuSetting.UIHoldActivationDelay, 200f, 0f, 500f, 50f); Set(OsuSetting.IntroSequence, IntroSequence.Triangles); } diff --git a/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs b/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs index 5d549ba217..fcf445a878 100644 --- a/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs +++ b/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs @@ -30,12 +30,12 @@ namespace osu.Game.Graphics.Containers public Bindable Progress = new BindableDouble(); - private Bindable holdActivationDelay; + private Bindable holdActivationDelay; [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - holdActivationDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); + holdActivationDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); } protected void BeginConfirm() diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs index a6956b7d9a..a8953ac3a2 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs @@ -27,16 +27,16 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics LabelText = "Parallax", Bindable = config.GetBindable(OsuSetting.MenuParallax) }, - new SettingsSlider + new SettingsSlider { LabelText = "Hold-to-confirm activation time", - Bindable = config.GetBindable(OsuSetting.UIHoldActivationDelay), + Bindable = config.GetBindable(OsuSetting.UIHoldActivationDelay), KeyboardStep = 50 }, }; } - private class TimeSlider : OsuSliderBar + private class TimeSlider : OsuSliderBar { public override string TooltipText => Current.Value.ToString("N0") + "ms"; } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 16e9d67cc3..c195ed6cb6 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -62,7 +62,7 @@ namespace osu.Game.Screens.Menu protected override BackgroundScreen CreateBackground() => background; - private Bindable holdDelay; + private Bindable holdDelay; private Bindable loginDisplayed; private ExitConfirmOverlay exitConfirmOverlay; @@ -70,7 +70,7 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader(true)] private void load(DirectOverlay direct, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics) { - holdDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); + holdDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); loginDisplayed = statics.GetBindable(Static.LoginOverlayDisplayed); if (host.CanExit) diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 2dc50326a8..a05937801c 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -63,11 +63,11 @@ namespace osu.Game.Screens.Play.HUD [Resolved] private OsuConfigManager config { get; set; } - private Bindable activationDelay; + private Bindable activationDelay; protected override void LoadComplete() { - activationDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); + activationDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); activationDelay.BindValueChanged(v => { text.Text = v.NewValue > 0 From dfaa9531f8f4fc6470a5a4bb60bea87fc44eed76 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 2 Oct 2019 18:48:50 +0900 Subject: [PATCH 1552/2815] Only lock the database for the duration of a deletion --- osu.Game/Database/ArchiveModelManager.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 17d1bd822e..2cc1e016d1 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -400,20 +400,17 @@ namespace osu.Game.Database int i = 0; - using (ContextFactory.GetForWrite()) + foreach (var b in items) { - foreach (var b in items) - { - if (notification.State == ProgressNotificationState.Cancelled) - // user requested abort - return; + if (notification.State == ProgressNotificationState.Cancelled) + // user requested abort + return; - notification.Text = $"Deleting {HumanisedModelName}s ({++i} of {items.Count})"; + notification.Text = $"Deleting {HumanisedModelName}s ({++i} of {items.Count})"; - Delete(b); + Delete(b); - notification.Progress = (float)i / items.Count; - } + notification.Progress = (float)i / items.Count; } notification.State = ProgressNotificationState.Completed; From 6929847b0870664c8f2568170cd08935cc876dd8 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Wed, 2 Oct 2019 17:22:34 +0200 Subject: [PATCH 1553/2815] Remove redundant override --- osu.Game/Screens/Play/PlayerLoader.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 7f8ef60a5d..cd4b331fce 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -516,8 +516,6 @@ namespace osu.Game.Screens.Play public override bool IsImportant => true; - public override bool RequestsFocus => true; - [BackgroundDependencyLoader] private void load(OsuColour colours, AudioManager audioManager, NotificationOverlay notificationOverlay, VolumeOverlay volumeOverlay) { From a69b9f1148bbee373e6243452291cbab0bab0e16 Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 2 Oct 2019 11:16:31 -0700 Subject: [PATCH 1554/2815] Fix alt-f4 being blocked during gameplay --- osu.Game/Screens/Play/Player.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 44be73b089..91d34c8056 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -299,7 +299,10 @@ namespace osu.Game.Screens.Play { if (!this.IsCurrentScreen()) return; - this.Exit(); + if (canPause) + Pause(); + else + this.Exit(); } public void Restart() @@ -508,12 +511,6 @@ namespace osu.Game.Screens.Play return true; } - if (canPause) - { - Pause(); - return true; - } - // ValidForResume is false when restarting if (ValidForResume) { From 752dd26a4f5a73823a9acbcbc5b518f9a9b6f4bf Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 2 Oct 2019 11:17:43 -0700 Subject: [PATCH 1555/2815] Fix alt-f4 being blocked in interface --- osu.Game/Screens/Menu/MainMenu.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index c195ed6cb6..df4803b2b6 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -81,8 +81,8 @@ namespace osu.Game.Screens.Menu { if (holdDelay.Value > 0) confirmAndExit(); - else - this.Exit(); + else if (!exitConfirmed && dialogOverlay != null && !(dialogOverlay.CurrentDialog is ConfirmExitDialog)) + dialogOverlay.Push(new ConfirmExitDialog(confirmAndExit, () => exitConfirmOverlay.Abort())); } }); } @@ -253,12 +253,6 @@ namespace osu.Game.Screens.Menu public override bool OnExiting(IScreen next) { - if (!exitConfirmed && dialogOverlay != null && !(dialogOverlay.CurrentDialog is ConfirmExitDialog)) - { - dialogOverlay.Push(new ConfirmExitDialog(confirmAndExit, () => exitConfirmOverlay.Abort())); - return true; - } - buttons.State = ButtonSystemState.Exit; this.FadeOut(3000); return base.OnExiting(next); From ff56453f1a259ed80d8ab6338ba4d8c9b93a27d3 Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 2 Oct 2019 12:07:07 -0700 Subject: [PATCH 1556/2815] Fix test regressions --- osu.Game.Tests/Visual/Gameplay/TestScenePause.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 50583e43c4..c67001c3d8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -132,9 +132,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("exit", () => Player.Exit()); - confirmPaused(); - - exitAndConfirm(); + confirmExited(); } [Test] From 38fe519c91f9836f46b6a9347e4ceb97a96f0d67 Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 2 Oct 2019 12:28:48 -0700 Subject: [PATCH 1557/2815] Remove unnecessary exitConfirmed condition check --- osu.Game/Screens/Menu/MainMenu.cs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index df4803b2b6..6deb29c2f2 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -80,9 +80,9 @@ namespace osu.Game.Screens.Menu Action = () => { if (holdDelay.Value > 0) - confirmAndExit(); - else if (!exitConfirmed && dialogOverlay != null && !(dialogOverlay.CurrentDialog is ConfirmExitDialog)) - dialogOverlay.Push(new ConfirmExitDialog(confirmAndExit, () => exitConfirmOverlay.Abort())); + this.Exit(); + else if (dialogOverlay != null && !(dialogOverlay.CurrentDialog is ConfirmExitDialog)) + dialogOverlay.Push(new ConfirmExitDialog(this.Exit, () => exitConfirmOverlay.Abort())); } }); } @@ -100,7 +100,7 @@ namespace osu.Game.Screens.Menu OnEdit = delegate { this.Push(new Editor()); }, OnSolo = onSolo, OnMulti = delegate { this.Push(new Multiplayer()); }, - OnExit = confirmAndExit, + OnExit = this.Exit, } } }, @@ -129,12 +129,6 @@ namespace osu.Game.Screens.Menu preloadSongSelect(); } - private void confirmAndExit() - { - exitConfirmed = true; - this.Exit(); - } - private void preloadSongSelect() { if (songSelect == null) @@ -172,8 +166,6 @@ namespace osu.Game.Screens.Menu Beatmap.ValueChanged += beatmap_ValueChanged; } - private bool exitConfirmed; - protected override void LogoArriving(OsuLogo logo, bool resuming) { base.LogoArriving(logo, resuming); From 148089f160240fc408f5a68b72caa2b84dc8731e Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 2 Oct 2019 16:08:19 -0700 Subject: [PATCH 1558/2815] Revert "Remove unnecessary exitConfirmed condition check" This reverts commit 38fe519c91f9836f46b6a9347e4ceb97a96f0d67. --- osu.Game/Screens/Menu/MainMenu.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 6deb29c2f2..df4803b2b6 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -80,9 +80,9 @@ namespace osu.Game.Screens.Menu Action = () => { if (holdDelay.Value > 0) - this.Exit(); - else if (dialogOverlay != null && !(dialogOverlay.CurrentDialog is ConfirmExitDialog)) - dialogOverlay.Push(new ConfirmExitDialog(this.Exit, () => exitConfirmOverlay.Abort())); + confirmAndExit(); + else if (!exitConfirmed && dialogOverlay != null && !(dialogOverlay.CurrentDialog is ConfirmExitDialog)) + dialogOverlay.Push(new ConfirmExitDialog(confirmAndExit, () => exitConfirmOverlay.Abort())); } }); } @@ -100,7 +100,7 @@ namespace osu.Game.Screens.Menu OnEdit = delegate { this.Push(new Editor()); }, OnSolo = onSolo, OnMulti = delegate { this.Push(new Multiplayer()); }, - OnExit = this.Exit, + OnExit = confirmAndExit, } } }, @@ -129,6 +129,12 @@ namespace osu.Game.Screens.Menu preloadSongSelect(); } + private void confirmAndExit() + { + exitConfirmed = true; + this.Exit(); + } + private void preloadSongSelect() { if (songSelect == null) @@ -166,6 +172,8 @@ namespace osu.Game.Screens.Menu Beatmap.ValueChanged += beatmap_ValueChanged; } + private bool exitConfirmed; + protected override void LogoArriving(OsuLogo logo, bool resuming) { base.LogoArriving(logo, resuming); From 80177885213d56d52f2386dc885784c0549a0db6 Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 2 Oct 2019 16:08:24 -0700 Subject: [PATCH 1559/2815] Revert "Fix alt-f4 being blocked in interface" This reverts commit 752dd26a4f5a73823a9acbcbc5b518f9a9b6f4bf. --- osu.Game/Screens/Menu/MainMenu.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index df4803b2b6..c195ed6cb6 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -81,8 +81,8 @@ namespace osu.Game.Screens.Menu { if (holdDelay.Value > 0) confirmAndExit(); - else if (!exitConfirmed && dialogOverlay != null && !(dialogOverlay.CurrentDialog is ConfirmExitDialog)) - dialogOverlay.Push(new ConfirmExitDialog(confirmAndExit, () => exitConfirmOverlay.Abort())); + else + this.Exit(); } }); } @@ -253,6 +253,12 @@ namespace osu.Game.Screens.Menu public override bool OnExiting(IScreen next) { + if (!exitConfirmed && dialogOverlay != null && !(dialogOverlay.CurrentDialog is ConfirmExitDialog)) + { + dialogOverlay.Push(new ConfirmExitDialog(confirmAndExit, () => exitConfirmOverlay.Abort())); + return true; + } + buttons.State = ButtonSystemState.Exit; this.FadeOut(3000); return base.OnExiting(next); From f8eb07b21116e9ae3f01eba30cd626e50f097a32 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Oct 2019 11:23:21 +0900 Subject: [PATCH 1560/2815] Only lock database for the duration of a model restoration --- osu.Game/Database/ArchiveModelManager.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 2cc1e016d1..b567f0c0e3 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -436,20 +436,17 @@ namespace osu.Game.Database int i = 0; - using (ContextFactory.GetForWrite()) + foreach (var item in items) { - foreach (var item in items) - { - if (notification.State == ProgressNotificationState.Cancelled) - // user requested abort - return; + if (notification.State == ProgressNotificationState.Cancelled) + // user requested abort + return; - notification.Text = $"Restoring ({++i} of {items.Count})"; + notification.Text = $"Restoring ({++i} of {items.Count})"; - Undelete(item); + Undelete(item); - notification.Progress = (float)i / items.Count; - } + notification.Progress = (float)i / items.Count; } notification.State = ProgressNotificationState.Completed; From 897b3233afdd196157db7891bc7ad05b8a173e0e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Oct 2019 14:23:48 +0900 Subject: [PATCH 1561/2815] Add start time tracking to EditorBeatmap --- osu.Game/Rulesets/Objects/HitObject.cs | 20 ++++++++++++++++- osu.Game/Screens/Edit/EditorBeatmap.cs | 31 +++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 6c5627c5d2..a99fac09cc 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using Newtonsoft.Json; +using osu.Framework.Bindables; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -27,10 +28,16 @@ namespace osu.Game.Rulesets.Objects /// private const double control_point_leniency = 1; + public readonly Bindable StartTimeBindable = new Bindable(); + /// /// The time at which the HitObject starts. /// - public virtual double StartTime { get; set; } + public virtual double StartTime + { + get => StartTimeBindable.Value; + set => StartTimeBindable.Value = value; + } private List samples; @@ -67,6 +74,17 @@ namespace osu.Game.Rulesets.Objects [JsonIgnore] public IReadOnlyList NestedHitObjects => nestedHitObjects; + public HitObject() + { + StartTimeBindable.ValueChanged += time => + { + double offset = time.NewValue - time.OldValue; + + foreach (var nested in NestedHitObjects) + nested.StartTime += offset; + }; + } + /// /// Applies default values to this HitObject. /// diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index f0b6c62154..22aa133de7 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; @@ -15,12 +16,17 @@ namespace osu.Game.Screens.Edit { public event Action HitObjectAdded; public event Action HitObjectRemoved; + public event Action HitObjectChanged; + private readonly Dictionary> startTimeBindables = new Dictionary>(); private readonly Beatmap beatmap; public EditorBeatmap(Beatmap beatmap) { this.beatmap = beatmap; + + foreach (var obj in HitObjects) + trackStartTime(obj); } public BeatmapInfo BeatmapInfo @@ -37,7 +43,7 @@ namespace osu.Game.Screens.Edit public double TotalBreakTime => beatmap.TotalBreakTime; - IReadOnlyList IBeatmap.HitObjects => beatmap.HitObjects; + public IReadOnlyList HitObjects => beatmap.HitObjects; IReadOnlyList IBeatmap.HitObjects => beatmap.HitObjects; @@ -51,6 +57,8 @@ namespace osu.Game.Screens.Edit /// The to add. public void Add(T hitObject) { + trackStartTime(hitObject); + // Preserve existing sorting order in the beatmap var insertionIndex = beatmap.HitObjects.FindLastIndex(h => h.StartTime <= hitObject.StartTime); beatmap.HitObjects.Insert(insertionIndex + 1, hitObject); @@ -65,7 +73,28 @@ namespace osu.Game.Screens.Edit public void Remove(T hitObject) { if (beatmap.HitObjects.Remove(hitObject)) + { + var bindable = startTimeBindables[hitObject]; + bindable.UnbindAll(); + + startTimeBindables.Remove(hitObject); HitObjectRemoved?.Invoke(hitObject); + } + } + + private void trackStartTime(T hitObject) + { + startTimeBindables[hitObject] = hitObject.StartTimeBindable.GetBoundCopy(); + startTimeBindables[hitObject].ValueChanged += _ => + { + // For now we'll remove and re-add the hitobject. This is not optimal and can be improved if required. + beatmap.HitObjects.Remove(hitObject); + + var insertionIndex = beatmap.HitObjects.FindLastIndex(h => h.StartTime <= hitObject.StartTime); + beatmap.HitObjects.Insert(insertionIndex + 1, hitObject); + + HitObjectChanged?.Invoke(hitObject); + }; } /// From f2719afd0e7f7e445659e413e03edd32125db3f0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Oct 2019 14:27:40 +0900 Subject: [PATCH 1562/2815] Add tests for Editorbeatmap --- osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs | 154 +++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs diff --git a/osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs b/osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs new file mode 100644 index 0000000000..88c9f9cb6c --- /dev/null +++ b/osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs @@ -0,0 +1,154 @@ +// 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 Microsoft.EntityFrameworkCore.Internal; +using NUnit.Framework; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit; + +namespace osu.Game.Tests.Beatmaps +{ + [TestFixture] + public class EditorBeatmapTest + { + /// + /// Tests that the addition event is correctly invoked after a hitobject is added. + /// + [Test] + public void TestHitObjectAddEvent() + { + var editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + + HitObject addedObject = null; + editorBeatmap.HitObjectAdded += h => addedObject = h; + + var hitCircle = new HitCircle(); + + editorBeatmap.Add(hitCircle); + Assert.That(addedObject, Is.EqualTo(hitCircle)); + } + + /// + /// Tests that the removal event is correctly invoked after a hitobject is removed. + /// + [Test] + public void HitObjectRemoveEvent() + { + var hitCircle = new HitCircle(); + var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } }); + + HitObject removedObject = null; + editorBeatmap.HitObjectRemoved += h => removedObject = h; + + editorBeatmap.Remove(hitCircle); + Assert.That(removedObject, Is.EqualTo(hitCircle)); + } + + /// + /// Tests that the changed event is correctly invoked after the start time of a hitobject is changed. + /// This tests for hitobjects which were already present before the editor beatmap was constructed. + /// + [Test] + public void TestInitialHitObjectStartTimeChangeEvent() + { + var hitCircle = new HitCircle(); + var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } }); + + HitObject changedObject = null; + editorBeatmap.HitObjectChanged += h => changedObject = h; + + hitCircle.StartTime = 1000; + Assert.That(changedObject, Is.EqualTo(hitCircle)); + } + + /// + /// Tests that the changed event is correctly invoked after the start time of a hitobject is changed. + /// This tests for hitobjects which were added to an existing editor beatmap. + /// + [Test] + public void TestAddedHitObjectStartTimeChangeEvent() + { + var editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + + HitObject changedObject = null; + editorBeatmap.HitObjectChanged += h => changedObject = h; + + var hitCircle = new HitCircle(); + + editorBeatmap.Add(hitCircle); + Assert.That(changedObject, Is.Null); + + hitCircle.StartTime = 1000; + Assert.That(changedObject, Is.EqualTo(hitCircle)); + } + + /// + /// Tests that the channged event is not invoked after a hitobject is removed from the beatmap/ + /// + [Test] + public void TestRemovedHitObjectStartTimeChangeEvent() + { + var hitCircle = new HitCircle(); + var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } }); + + HitObject changedObject = null; + editorBeatmap.HitObjectChanged += h => changedObject = h; + + editorBeatmap.Remove(hitCircle); + Assert.That(changedObject, Is.Null); + + hitCircle.StartTime = 1000; + Assert.That(changedObject, Is.Null); + } + + /// + /// Tests that an added hitobject is correctly inserted to preserve the sorting order of the beatmap. + /// + [Test] + public void TestAddHitObjectInMiddle() + { + var editorBeatmap = new EditorBeatmap(new OsuBeatmap + { + HitObjects = + { + new HitCircle(), + new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 2000 }, + } + }); + + var hitCircle = new HitCircle { StartTime = 1000 }; + editorBeatmap.Add(hitCircle); + Assert.That(editorBeatmap.HitObjects.Count(h => h == hitCircle), Is.EqualTo(1)); + Assert.That(editorBeatmap.HitObjects.IndexOf(hitCircle), Is.EqualTo(3)); + } + + /// + /// Tests that the beatmap remains correctly sorted after the start time of a hitobject is changed. + /// + [Test] + public void TestResortWhenStartTimeChanged() + { + var hitCircle = new HitCircle { StartTime = 1000 }; + var editorBeatmap = new EditorBeatmap(new OsuBeatmap + { + HitObjects = + { + new HitCircle(), + new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 1000 }, + hitCircle, + new HitCircle { StartTime = 2000 }, + } + }); + + hitCircle.StartTime = 0; + Assert.That(editorBeatmap.HitObjects.Count(h => h == hitCircle), Is.EqualTo(1)); + Assert.That(editorBeatmap.HitObjects.IndexOf(hitCircle), Is.EqualTo(1)); + } + } +} From 3fb0b0b66833d107ef2e5e17f9930959deefeff2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Oct 2019 14:37:16 +0900 Subject: [PATCH 1563/2815] Rename to StartTimeChanged and add xmldocs --- osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs | 6 +++--- osu.Game/Screens/Edit/EditorBeatmap.cs | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs b/osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs index 88c9f9cb6c..98e630abd2 100644 --- a/osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Beatmaps var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } }); HitObject changedObject = null; - editorBeatmap.HitObjectChanged += h => changedObject = h; + editorBeatmap.StartTimeChanged += h => changedObject = h; hitCircle.StartTime = 1000; Assert.That(changedObject, Is.EqualTo(hitCircle)); @@ -74,7 +74,7 @@ namespace osu.Game.Tests.Beatmaps var editorBeatmap = new EditorBeatmap(new OsuBeatmap()); HitObject changedObject = null; - editorBeatmap.HitObjectChanged += h => changedObject = h; + editorBeatmap.StartTimeChanged += h => changedObject = h; var hitCircle = new HitCircle(); @@ -95,7 +95,7 @@ namespace osu.Game.Tests.Beatmaps var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } }); HitObject changedObject = null; - editorBeatmap.HitObjectChanged += h => changedObject = h; + editorBeatmap.StartTimeChanged += h => changedObject = h; editorBeatmap.Remove(hitCircle); Assert.That(changedObject, Is.Null); diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 22aa133de7..c3a322ea36 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -14,9 +14,20 @@ namespace osu.Game.Screens.Edit public class EditorBeatmap : IEditorBeatmap where T : HitObject { + /// + /// Invoked when a is added to this . + /// public event Action HitObjectAdded; + + /// + /// Invoked when a is removed from this . + /// public event Action HitObjectRemoved; - public event Action HitObjectChanged; + + /// + /// Invoked when the start time of a in this was changed. + /// + public event Action StartTimeChanged; private readonly Dictionary> startTimeBindables = new Dictionary>(); private readonly Beatmap beatmap; @@ -93,7 +104,7 @@ namespace osu.Game.Screens.Edit var insertionIndex = beatmap.HitObjects.FindLastIndex(h => h.StartTime <= hitObject.StartTime); beatmap.HitObjects.Insert(insertionIndex + 1, hitObject); - HitObjectChanged?.Invoke(hitObject); + StartTimeChanged?.Invoke(hitObject); }; } From 2c13043c42b79fb166483862284fce652f3fb17f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Oct 2019 14:40:00 +0900 Subject: [PATCH 1564/2815] Hook up the event to HitObjectComposer --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index fc324d7021..a267d7c44d 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -132,6 +132,7 @@ namespace osu.Game.Rulesets.Edit editorBeatmap = new EditorBeatmap(playableBeatmap); editorBeatmap.HitObjectAdded += addHitObject; editorBeatmap.HitObjectRemoved += removeHitObject; + editorBeatmap.StartTimeChanged += updateHitObject; var dependencies = new DependencyContainer(parent); dependencies.CacheAs(editorBeatmap); @@ -162,12 +163,7 @@ namespace osu.Game.Rulesets.Edit }); } - private void addHitObject(HitObject hitObject) - { - beatmapProcessor?.PreProcess(); - hitObject.ApplyDefaults(playableBeatmap.ControlPointInfo, playableBeatmap.BeatmapInfo.BaseDifficulty); - beatmapProcessor?.PostProcess(); - } + private void addHitObject(HitObject hitObject) => updateHitObject(hitObject); private void removeHitObject(HitObject hitObject) { @@ -175,6 +171,13 @@ namespace osu.Game.Rulesets.Edit beatmapProcessor?.PostProcess(); } + private void updateHitObject(HitObject hitObject) + { + beatmapProcessor?.PreProcess(); + hitObject.ApplyDefaults(playableBeatmap.ControlPointInfo, playableBeatmap.BeatmapInfo.BaseDifficulty); + beatmapProcessor?.PostProcess(); + } + public override IEnumerable HitObjects => drawableRulesetWrapper.Playfield.AllHitObjects; public override bool CursorInPlacementArea => drawableRulesetWrapper.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); From b28689c774c8a0e4bc029eda00cb5a4083ad040e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Oct 2019 15:00:47 +0800 Subject: [PATCH 1565/2815] Fix key counters appearing negative on intense beatmaps When `FrameStabilityContainer` decides it needs multiple updates on the same frame, it ends up with an elapsed time of zero. This was interacting badly with the condition used in `RulesetInputManager` to govern playback direction. I have changed this to use `Rate` as exposed by the frame stable clock. - Closes #6198. --- .../Rulesets/UI/FrameStabilityContainer.cs | 28 ++++++++++--------- osu.Game/Rulesets/UI/RulesetInputManager.cs | 4 +-- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 05d3c02381..74d3439c59 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -47,6 +47,11 @@ namespace osu.Game.Rulesets.UI private IFrameBasedClock parentGameplayClock; + /// + /// The current direction of playback to be exposed to frame stable children. + /// + private int direction; + [BackgroundDependencyLoader(true)] private void load(GameplayClock clock) { @@ -111,16 +116,12 @@ namespace osu.Game.Rulesets.UI validState = true; - manualClock.Rate = parentGameplayClock.Rate; - manualClock.IsRunning = parentGameplayClock.IsRunning; - var newProposedTime = parentGameplayClock.CurrentTime; try { if (!FrameStablePlayback) { - manualClock.CurrentTime = newProposedTime; requireMoreUpdateLoops = false; return; } @@ -128,9 +129,9 @@ namespace osu.Game.Rulesets.UI { // On the first update, frame-stability seeking would result in unexpected/unwanted behaviour. // Instead we perform an initial seek to the proposed time. - manualClock.CurrentTime = newProposedTime; - // do a second process to clear out ElapsedTime + // process frame (in addition to finally clause) to clear out ElapsedTime + manualClock.CurrentTime = newProposedTime; framedClock.ProcessFrame(); firstConsumption = false; @@ -144,11 +145,7 @@ namespace osu.Game.Rulesets.UI : Math.Max(newProposedTime, manualClock.CurrentTime - sixty_frame_time); } - if (!isAttached) - { - manualClock.CurrentTime = newProposedTime; - } - else + if (isAttached) { double? newTime = ReplayInputHandler.SetFrameFromTime(newProposedTime); @@ -156,9 +153,7 @@ namespace osu.Game.Rulesets.UI { // we shouldn't execute for this time value. probably waiting on more replay data. validState = false; - requireMoreUpdateLoops = true; - manualClock.CurrentTime = newProposedTime; return; } @@ -169,6 +164,13 @@ namespace osu.Game.Rulesets.UI } finally { + if (newProposedTime != manualClock.CurrentTime) + direction = newProposedTime > manualClock.CurrentTime ? 1 : -1; + + manualClock.CurrentTime = newProposedTime; + manualClock.Rate = Math.Abs(parentGameplayClock.Rate) * direction; + manualClock.IsRunning = parentGameplayClock.IsRunning; + // The manual clock time has changed in the above code. The framed clock now needs to be updated // to ensure that the its time is valid for our children before input is processed framedClock.ProcessFrame(); diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 98e27240d3..5cc213be41 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -137,9 +137,9 @@ namespace osu.Game.Rulesets.UI { } - public bool OnPressed(T action) => Target.Children.OfType>().Any(c => c.OnPressed(action, Clock.ElapsedFrameTime > 0)); + public bool OnPressed(T action) => Target.Children.OfType>().Any(c => c.OnPressed(action, Clock.Rate >= 0)); - public bool OnReleased(T action) => Target.Children.OfType>().Any(c => c.OnReleased(action, Clock.ElapsedFrameTime > 0)); + public bool OnReleased(T action) => Target.Children.OfType>().Any(c => c.OnReleased(action, Clock.Rate >= 0)); } #endregion From 652acac87fa69d2debfd9ee18872e08379c4ad0e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Oct 2019 16:14:42 +0900 Subject: [PATCH 1566/2815] Move placement movement event to BlueprintContainer --- .../Blueprints/HoldNotePlacementBlueprint.cs | 11 ++++------ .../Blueprints/ManiaPlacementBlueprint.cs | 9 ++++---- .../HitCircles/HitCirclePlacementBlueprint.cs | 13 ++--------- .../Sliders/SliderPlacementBlueprint.cs | 20 +++++------------ .../Spinners/SpinnerPlacementBlueprint.cs | 5 +++++ osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 8 ++++++- .../Compose/Components/BlueprintContainer.cs | 22 ++++++++++++++++++- 7 files changed, 48 insertions(+), 40 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs index 26115311f7..bcbc1ee527 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -3,7 +3,6 @@ using System; using osu.Framework.Graphics; -using osu.Framework.Input.Events; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; using osuTK; @@ -49,13 +48,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints private double originalStartTime; - protected override bool OnMouseMove(MouseMoveEvent e) + public override void UpdatePosition(Vector2 screenSpacePosition) { - base.OnMouseMove(e); + base.UpdatePosition(screenSpacePosition); if (PlacementBegun) { - var endTime = TimeAt(e.ScreenSpaceMousePosition); + var endTime = TimeAt(screenSpacePosition); HitObject.StartTime = endTime < originalStartTime ? endTime : originalStartTime; HitObject.Duration = Math.Abs(endTime - originalStartTime); @@ -65,10 +64,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints headPiece.Width = tailPiece.Width = SnappedWidth; headPiece.X = tailPiece.X = SnappedMousePosition.X; - originalStartTime = HitObject.StartTime = TimeAt(e.ScreenSpaceMousePosition); + originalStartTime = HitObject.StartTime = TimeAt(screenSpacePosition); } - - return true; } } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index d3779e2e18..7ad38860dd 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -62,19 +62,18 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints return base.OnMouseUp(e); } - protected override bool OnMouseMove(MouseMoveEvent e) + public override void UpdatePosition(Vector2 screenSpacePosition) { if (!PlacementBegun) - Column = ColumnAt(e.ScreenSpaceMousePosition); + Column = ColumnAt(screenSpacePosition); - if (Column == null) return false; + if (Column == null) return; SnappedWidth = Column.DrawWidth; // Snap to the column var parentPos = Parent.ToLocalSpace(Column.ToScreenSpace(new Vector2(Column.DrawWidth / 2, 0))); - SnappedMousePosition = new Vector2(parentPos.X, e.MousePosition.Y); - return true; + SnappedMousePosition = new Vector2(parentPos.X, Parent.ToLocalSpace(screenSpacePosition).Y); } protected double TimeAt(Vector2 screenSpacePosition) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs index a4050f0c31..0f6bee19bb 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs @@ -19,14 +19,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles InternalChild = new HitCirclePiece(HitObject); } - protected override void LoadComplete() - { - base.LoadComplete(); - - // Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame - HitObject.Position = Parent?.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position) ?? Vector2.Zero; - } - protected override bool OnClick(ClickEvent e) { HitObject.StartTime = EditorClock.CurrentTime; @@ -34,10 +26,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles return true; } - protected override bool OnMouseMove(MouseMoveEvent e) + public override void UpdatePosition(Vector2 screenSpacePosition) { - HitObject.Position = e.MousePosition; - return true; + HitObject.Position = ToLocalSpace(screenSpacePosition); } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 55de626d7d..62c879b05e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -47,28 +47,18 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders setState(PlacementState.Initial); } - protected override void LoadComplete() - { - base.LoadComplete(); - - // Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame - HitObject.Position = Parent?.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position) ?? Vector2.Zero; - } - - protected override bool OnMouseMove(MouseMoveEvent e) + public override void UpdatePosition(Vector2 screenSpacePosition) { switch (state) { case PlacementState.Initial: - HitObject.Position = e.MousePosition; - return true; + HitObject.Position = ToLocalSpace(screenSpacePosition); + break; case PlacementState.Body: - cursor = e.MousePosition - HitObject.Position; - return true; + cursor = ToLocalSpace(screenSpacePosition) - HitObject.Position; + break; } - - return false; } protected override bool OnClick(ClickEvent e) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs index 03d761c67f..730b8448de 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs @@ -7,6 +7,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; +using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners { @@ -43,5 +44,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners return true; } + + public override void UpdatePosition(Vector2 screenSpacePosition) + { + } } } diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 757c269358..290fd8d27d 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -108,6 +108,12 @@ namespace osu.Game.Rulesets.Edit placementHandler.EndPlacement(HitObject); } + /// + /// Updates the position of this to a new screen-space position. + /// + /// The screen-space position. + public abstract void UpdatePosition(Vector2 screenSpacePosition); + /// /// Invokes , /// refreshing and parameters for the . @@ -125,7 +131,7 @@ namespace osu.Game.Rulesets.Edit case ScrollEvent _: return false; - case MouseEvent _: + case MouseButtonEvent _: return true; default: diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 7d25fd5283..d96d88c2b9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; +using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Framework.Input.States; using osu.Game.Rulesets.Edit; @@ -22,8 +23,8 @@ namespace osu.Game.Screens.Edit.Compose.Components private Container placementBlueprintContainer; private PlacementBlueprint currentPlacement; - private SelectionHandler selectionHandler; + private InputManager inputManager; private IEnumerable selections => selectionBlueprints.Children.Where(c => c.IsAlive); @@ -66,6 +67,8 @@ namespace osu.Game.Screens.Edit.Compose.Components beatmap.HitObjectAdded += addBlueprintFor; beatmap.HitObjectRemoved += removeBlueprintFor; + + inputManager = GetContainingInputManager(); } private HitObjectCompositionTool currentTool; @@ -136,6 +139,17 @@ namespace osu.Game.Screens.Edit.Compose.Components return true; } + protected override bool OnMouseMove(MouseMoveEvent e) + { + if (currentPlacement != null) + { + currentPlacement.UpdatePosition(e.ScreenSpaceMousePosition); + return true; + } + + return base.OnMouseMove(e); + } + protected override void Update() { base.Update(); @@ -158,8 +172,14 @@ namespace osu.Game.Screens.Edit.Compose.Components currentPlacement = null; var blueprint = CurrentTool?.CreatePlacementBlueprint(); + if (blueprint != null) + { placementBlueprintContainer.Child = currentPlacement = blueprint; + + // Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame + blueprint.UpdatePosition(inputManager.CurrentState.Mouse.Position); + } } /// From f2ba87a1d2a2f66fa423a682a0eb2ef0f7edef9c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Oct 2019 16:28:56 +0900 Subject: [PATCH 1567/2815] Fix placement blueprint test scenes not working --- .../Visual/PlacementBlueprintTestScene.cs | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs index 2b177e264f..0688620b8e 100644 --- a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs @@ -4,6 +4,8 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -18,16 +20,17 @@ namespace osu.Game.Tests.Visual protected Container HitObjectContainer; private PlacementBlueprint currentBlueprint; + private InputManager inputManager; + protected PlacementBlueprintTestScene() { - Add(HitObjectContainer = CreateHitObjectContainer()); + Add(HitObjectContainer = CreateHitObjectContainer().With(c => c.Clock = new FramedClock(new StopwatchClock()))); } [BackgroundDependencyLoader] private void load() { Beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize = 2; - Add(currentBlueprint = CreateBlueprint()); } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -38,6 +41,14 @@ namespace osu.Game.Tests.Visual return dependencies; } + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + Add(currentBlueprint = CreateBlueprint()); + } + public void BeginPlacement(HitObject hitObject) { } @@ -54,10 +65,27 @@ namespace osu.Game.Tests.Visual { } - protected virtual Container CreateHitObjectContainer() => new Container { RelativeSizeAxes = Axes.Both }; + protected override bool OnMouseMove(MouseMoveEvent e) + { + currentBlueprint.UpdatePosition(e.ScreenSpaceMousePosition); + return true; + } + + public override void Add(Drawable drawable) + { + base.Add(drawable); + + if (drawable is PlacementBlueprint blueprint) + { + blueprint.Show(); + blueprint.UpdatePosition(inputManager.CurrentState.Mouse.Position); + } + } protected virtual void AddHitObject(DrawableHitObject hitObject) => HitObjectContainer.Add(hitObject); + protected virtual Container CreateHitObjectContainer() => new Container { RelativeSizeAxes = Axes.Both }; + protected abstract DrawableHitObject CreateHitObject(HitObject hitObject); protected abstract PlacementBlueprint CreateBlueprint(); } From e9c73ce30fae825f2ed79150f3b4a098d346eb6c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Oct 2019 16:21:14 +0800 Subject: [PATCH 1568/2815] Fix random failures on BeatmapCarousel filter test The "un-filter" step causes a `SelectNextRandom` invocation. If this happens to select a difficulty in set 3 other than the previously buffered difficulty #2, the subsequent test would fail. I've split this test out to remove the random element, but added a new assert to ensure buffered (previously visited?) difficulty is re-selected on return to the same set. --- .../SongSelect/TestSceneBeatmapCarousel.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 90c6c9065c..6bdd94db21 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -239,6 +239,18 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("Selection is non-null", () => currentSelection != null); setSelected(1, 3); + } + + [Test] + public void TestFilterRange() + { + loadBeatmaps(); + + // buffer the selection + setSelected(3, 2); + + setSelected(1, 3); + AddStep("Apply a range filter", () => carousel.Filter(new FilterCriteria { SearchText = "#3", @@ -249,9 +261,9 @@ namespace osu.Game.Tests.Visual.SongSelect IsLowerInclusive = true } }, false)); - waitForSelection(3, 2); - AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false)); + // should reselect the buffered selection. + waitForSelection(3, 2); } /// From ee34c5ccb457e582868ced659a6d0f952cb98665 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Oct 2019 18:21:00 +0900 Subject: [PATCH 1569/2815] Add a flip step to mania placement test scenes --- .../ManiaPlacementBlueprintTestScene.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs index 4b3786c30a..afde1c9521 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs @@ -27,8 +27,13 @@ namespace osu.Game.Rulesets.Mania.Tests [Cached(typeof(IReadOnlyList))] private IReadOnlyList mods { get; set; } = Array.Empty(); + [Cached(typeof(IScrollingInfo))] + private IScrollingInfo scrollingInfo; + protected ManiaPlacementBlueprintTestScene() { + scrollingInfo = ((ScrollingTestContainer)HitObjectContainer).ScrollingInfo; + Add(column = new Column(0) { Anchor = Anchor.Centre, @@ -36,15 +41,8 @@ namespace osu.Game.Rulesets.Mania.Tests AccentColour = Color4.OrangeRed, Clock = new FramedClock(new StopwatchClock()), // No scroll }); - } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - - dependencies.CacheAs(((ScrollingTestContainer)HitObjectContainer).ScrollingInfo); - - return dependencies; + AddStep("change direction", () => ((ScrollingTestContainer)HitObjectContainer).Flip()); } protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both }; From 754fbc59e1a96f2dc7da030dce850a0cb5231e78 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Oct 2019 18:21:36 +0900 Subject: [PATCH 1570/2815] Fix note placement being offset --- .../Edit/Blueprints/ManiaPlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index d3779e2e18..f7d21ddf55 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints // If we're scrolling downwards, a position of 0 is actually further away from the hit target // so we need to flip the vertical coordinate in the hitobject container's space - var hitObjectPos = Column.HitObjectContainer.ToLocalSpace(applyPositionOffset(screenSpacePosition, false)).Y; + var hitObjectPos = applyPositionOffset(Column.HitObjectContainer.ToLocalSpace(screenSpacePosition), false).Y; if (scrollingInfo.Direction.Value == ScrollingDirection.Down) hitObjectPos = hitObjectContainer.DrawHeight - hitObjectPos; From 0a409075be251fbc1acbebfdfa80f81204d8f0a6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Oct 2019 18:21:50 +0900 Subject: [PATCH 1571/2815] Fix note placement offset not working for down-scroll --- .../Blueprints/ManiaPlacementBlueprint.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index f7d21ddf55..83282f9990 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -111,8 +111,23 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints private Vector2 applyPositionOffset(Vector2 position, bool reverse) { - position.Y += (scrollingInfo.Direction.Value == ScrollingDirection.Up && !reverse ? -1 : 1) * NotePiece.NOTE_HEIGHT / 2; - return position; + float offset = 0; + + switch (scrollingInfo.Direction.Value) + { + case ScrollingDirection.Up: + offset = -NotePiece.NOTE_HEIGHT / 2; + break; + + case ScrollingDirection.Down: + offset = NotePiece.NOTE_HEIGHT / 2; + break; + } + + if (reverse) + offset = -offset; + + return new Vector2(position.X, position.Y + offset); } } } From 39369620fa530000cf22dc6b1643b932b738dda1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Oct 2019 18:22:06 +0900 Subject: [PATCH 1572/2815] Remove position offset from ColumnAt --- .../Edit/Blueprints/ManiaPlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index 83282f9990..a0feecee3b 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints } protected Column ColumnAt(Vector2 screenSpacePosition) - => composer.ColumnAt(applyPositionOffset(screenSpacePosition, false)); + => composer.ColumnAt(screenSpacePosition); private Vector2 applyPositionOffset(Vector2 position, bool reverse) { From f1ff22cf8bc1612aef947df2bdafd4e0e0d2cafd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Oct 2019 18:23:13 +0900 Subject: [PATCH 1573/2815] Fix hold note blueprint placing in the wrong direction --- .../Edit/Blueprints/ManiaPlacementBlueprint.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index a0feecee3b..4a187b1942 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -103,6 +103,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints scrollingInfo.TimeRange.Value, Column.HitObjectContainer.DrawHeight); + if (scrollingInfo.Direction.Value == ScrollingDirection.Down) + pos = Column.HitObjectContainer.DrawHeight - pos; + return applyPositionOffset(Column.HitObjectContainer.ToSpaceOfOtherDrawable(new Vector2(0, pos), Parent), true).Y; } From 80585d446c108268e3261933caf9ef2f3d56f1c6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Oct 2019 18:27:39 +0900 Subject: [PATCH 1574/2815] Split applyPositionOffset into two methods and add xmldocs --- .../Blueprints/ManiaPlacementBlueprint.cs | 44 ++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index 4a187b1942..e83c85b40e 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints // If we're scrolling downwards, a position of 0 is actually further away from the hit target // so we need to flip the vertical coordinate in the hitobject container's space - var hitObjectPos = applyPositionOffset(Column.HitObjectContainer.ToLocalSpace(screenSpacePosition), false).Y; + var hitObjectPos = mouseToHitObjectPosition(Column.HitObjectContainer.ToLocalSpace(screenSpacePosition)).Y; if (scrollingInfo.Direction.Value == ScrollingDirection.Down) hitObjectPos = hitObjectContainer.DrawHeight - hitObjectPos; @@ -106,31 +106,55 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints if (scrollingInfo.Direction.Value == ScrollingDirection.Down) pos = Column.HitObjectContainer.DrawHeight - pos; - return applyPositionOffset(Column.HitObjectContainer.ToSpaceOfOtherDrawable(new Vector2(0, pos), Parent), true).Y; + return hitObjectToMousePosition(Column.HitObjectContainer.ToSpaceOfOtherDrawable(new Vector2(0, pos), Parent)).Y; } protected Column ColumnAt(Vector2 screenSpacePosition) => composer.ColumnAt(screenSpacePosition); - private Vector2 applyPositionOffset(Vector2 position, bool reverse) + /// + /// Converts a mouse position to a hitobject position. + /// + /// + /// Blueprints are centred on the mouse position, such that the hitobject position is anchored at the top or bottom of the blueprint depending on the scroll direction. + /// + /// The mouse position. + /// The resulting hitobject position, acnhored at the top or bottom of the blueprint depending on the scroll direction. + private Vector2 mouseToHitObjectPosition(Vector2 mousePosition) { - float offset = 0; - switch (scrollingInfo.Direction.Value) { case ScrollingDirection.Up: - offset = -NotePiece.NOTE_HEIGHT / 2; + mousePosition.Y -= NotePiece.NOTE_HEIGHT / 2; break; case ScrollingDirection.Down: - offset = NotePiece.NOTE_HEIGHT / 2; + mousePosition.Y += NotePiece.NOTE_HEIGHT / 2; break; } - if (reverse) - offset = -offset; + return mousePosition; + } - return new Vector2(position.X, position.Y + offset); + /// + /// Converts a hitobject position to a mouse position. + /// + /// The hitobject position. + /// The resulting mouse position, anchored at the centre of the hitobject. + private Vector2 hitObjectToMousePosition(Vector2 hitObjectPosition) + { + switch (scrollingInfo.Direction.Value) + { + case ScrollingDirection.Up: + hitObjectPosition.Y += NotePiece.NOTE_HEIGHT / 2; + break; + + case ScrollingDirection.Down: + hitObjectPosition.Y -= NotePiece.NOTE_HEIGHT / 2; + break; + } + + return hitObjectPosition; } } } From 2d0c52239893526a28be0e01eacf74b18bab0ee3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Oct 2019 18:43:46 +0900 Subject: [PATCH 1575/2815] Remove unused method --- .../TestSceneHoldNoteSelectionBlueprint.cs | 3 --- .../TestSceneNoteSelectionBlueprint.cs | 3 --- .../TestSceneHitCircleSelectionBlueprint.cs | 3 --- .../TestSceneSliderSelectionBlueprint.cs | 3 --- .../TestSceneSpinnerSelectionBlueprint.cs | 3 --- osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs | 2 -- 6 files changed, 17 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs index 5507ca2ba0..90394f3d1b 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; -using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; @@ -53,7 +52,5 @@ namespace osu.Game.Rulesets.Mania.Tests nested.Y = (float)(-finalPosition * content.DrawHeight); } } - - protected override SelectionBlueprint CreateBlueprint() => new HoldNoteSelectionBlueprint(null); } } diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNoteSelectionBlueprint.cs index c0482e2150..1514bdf0bd 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNoteSelectionBlueprint.cs @@ -5,7 +5,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; @@ -37,7 +36,5 @@ namespace osu.Game.Rulesets.Mania.Tests AddBlueprint(new NoteSelectionBlueprint(drawableObject)); } - - protected override SelectionBlueprint CreateBlueprint() => new NoteSelectionBlueprint(null); } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs index 7278d923c1..d4cdabdb07 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs @@ -4,7 +4,6 @@ using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Objects; @@ -53,8 +52,6 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("blueprint positioned over hitobject", () => blueprint.CirclePiece.Position == hitCircle.Position); } - protected override SelectionBlueprint CreateBlueprint() => new HitCircleSelectionBlueprint(drawableObject); - private class TestBlueprint : HitCircleSelectionBlueprint { public new HitCirclePiece CirclePiece => base.CirclePiece; diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs index 61c8316ed9..ec23ec31b2 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs @@ -7,7 +7,6 @@ using NUnit.Framework; using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; @@ -98,8 +97,6 @@ namespace osu.Game.Rulesets.Osu.Tests () => Precision.AlmostEquals(blueprint.TailBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre)); } - protected override SelectionBlueprint CreateBlueprint() => new SliderSelectionBlueprint(drawableObject); - private class TestSliderBlueprint : SliderSelectionBlueprint { public new SliderBodyPiece BodyPiece => base.BodyPiece; diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSelectionBlueprint.cs index 1c195311a4..d777ca3610 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSelectionBlueprint.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners; using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components; using osu.Game.Rulesets.Osu.Objects; @@ -47,7 +46,5 @@ namespace osu.Game.Rulesets.Osu.Tests AddBlueprint(new SpinnerSelectionBlueprint(drawableSpinner) { Size = new Vector2(0.5f) }); } - - protected override SelectionBlueprint CreateBlueprint() => new SpinnerSelectionBlueprint(null) { Size = new Vector2(0.5f) }; } } diff --git a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs index 55dda03b16..f53c12b047 100644 --- a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs @@ -30,7 +30,5 @@ namespace osu.Game.Tests.Visual d.Select(); })); } - - protected abstract SelectionBlueprint CreateBlueprint(); } } From bcf0b2752e75f40b046e6447cbfce2b705dfb0ce Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Oct 2019 18:48:44 +0900 Subject: [PATCH 1576/2815] Fix possible MusicController nullref --- osu.Game/Overlays/MusicController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 49d16a4f3e..51afac4b8c 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -75,7 +75,7 @@ namespace osu.Game.Overlays /// /// Returns whether the current beatmap track is playing. /// - public bool IsPlaying => beatmap.Value?.Track.IsRunning ?? false; + public bool IsPlaying => beatmap?.Value?.Track.IsRunning ?? false; private void handleBeatmapAdded(BeatmapSetInfo set) => Schedule(() => beatmapSets.Add(set)); From 662a1a9c2cb8af2b6e151a9b1bed43d32491ca3b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Oct 2019 18:55:53 +0900 Subject: [PATCH 1577/2815] Use current --- osu.Game/Overlays/MusicController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 51afac4b8c..f5c36a9cac 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -75,7 +75,7 @@ namespace osu.Game.Overlays /// /// Returns whether the current beatmap track is playing. /// - public bool IsPlaying => beatmap?.Value?.Track.IsRunning ?? false; + public bool IsPlaying => current?.Track.IsRunning ?? false; private void handleBeatmapAdded(BeatmapSetInfo set) => Schedule(() => beatmapSets.Add(set)); From 6c878cb167544addc3a6af83efad12558a7b230d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Oct 2019 19:11:50 +0900 Subject: [PATCH 1578/2815] Prevent nullrefs --- osu.Game/Overlays/VolumeOverlay.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index 0c08e0eb6e..27e2eef200 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -32,7 +32,8 @@ namespace osu.Game.Overlays private readonly BindableDouble muteAdjustment = new BindableDouble(); - public Bindable IsMuted => muteButton.Current; + private readonly Bindable isMuted = new Bindable(); + public Bindable IsMuted => isMuted; [BackgroundDependencyLoader] private void load(AudioManager audio, OsuColour colours) @@ -66,7 +67,8 @@ namespace osu.Game.Overlays volumeMeterMusic = new VolumeMeter("MUSIC", 125, colours.BlueDarker), muteButton = new MuteButton { - Margin = new MarginPadding { Top = 100 } + Margin = new MarginPadding { Top = 100 }, + Current = { BindTarget = isMuted } } } }, @@ -76,13 +78,13 @@ namespace osu.Game.Overlays volumeMeterEffect.Bindable.BindTo(audio.VolumeSample); volumeMeterMusic.Bindable.BindTo(audio.VolumeTrack); - muteButton.Current.ValueChanged += muted => + isMuted.BindValueChanged(muted => { if (muted.NewValue) audio.AddAdjustment(AdjustableProperty.Volume, muteAdjustment); else audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment); - }; + }); } protected override void LoadComplete() From 636913a4a6a62319f23f3b17f52552bfa3905409 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 3 Oct 2019 19:16:31 +0900 Subject: [PATCH 1579/2815] Refactor PlayerLoader changes --- osu.Game/Screens/Play/PlayerLoader.cs | 63 ++++++++++++--------------- 1 file changed, 28 insertions(+), 35 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index cd4b331fce..87d902b547 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -58,9 +58,19 @@ namespace osu.Game.Screens.Play private Task loadTask; private InputManager inputManager; - private IdleTracker idleTracker; + [Resolved(CanBeNull = true)] + private NotificationOverlay notificationOverlay { get; set; } + + [Resolved(CanBeNull = true)] + private VolumeOverlay volumeOverlay { get; set; } + + [Resolved] + private AudioManager audioManager { get; set; } + + private Bindable muteWarningShownOnce; + public PlayerLoader(Func createPlayer) { this.createPlayer = createPlayer; @@ -110,7 +120,22 @@ namespace osu.Game.Screens.Play loadNewPlayer(); } - private void playerLoaded(Player player) => info.Loading = false; + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + + if (!muteWarningShownOnce.Value) + { + //Checks if the notification has not been shown yet and also if master volume is muted, track/music volume is muted or if the whole game is muted. + if (volumeOverlay?.IsMuted.Value == true || audioManager.Volume.Value <= audioManager.Volume.MinValue || audioManager.VolumeTrack.Value <= audioManager.VolumeTrack.MinValue) + { + notificationOverlay?.Post(new MutedNotification()); + muteWarningShownOnce.Value = true; + } + } + } public override void OnResuming(IScreen last) { @@ -134,7 +159,7 @@ namespace osu.Game.Screens.Play player.RestartCount = restartCount; player.RestartRequested = restartRequested; - loadTask = LoadComponentAsync(player, playerLoaded); + loadTask = LoadComponentAsync(player, _ => info.Loading = false); } private void contentIn() @@ -152,30 +177,6 @@ namespace osu.Game.Screens.Play content.FadeOut(250); } - [Resolved(CanBeNull = true)] - private NotificationOverlay notificationOverlay { get; set; } - - [Resolved(CanBeNull = true)] - private VolumeOverlay volumeOverlay { get; set; } - - [Resolved] - private AudioManager audioManager { get; set; } - - private Bindable muteWarningShownOnce; - - private void checkVolume() - { - if (muteWarningShownOnce.Value) - return; - - //Checks if the notification has not been shown yet and also if master volume is muted, track/music volume is muted or if the whole game is muted. - if (volumeOverlay?.IsMuted.Value == true || audioManager.Volume.Value <= audioManager.Volume.MinValue || audioManager.VolumeTrack.Value <= audioManager.VolumeTrack.MinValue) - { - notificationOverlay?.Post(new MutedNotification()); - muteWarningShownOnce.Value = true; - } - } - public override void OnEntering(IScreen last) { base.OnEntering(last); @@ -216,14 +217,6 @@ namespace osu.Game.Screens.Play content.StopTracking(); } - protected override void LoadComplete() - { - inputManager = GetContainingInputManager(); - base.LoadComplete(); - - checkVolume(); - } - private ScheduledDelegate pushDebounce; protected VisualSettings VisualSettings; From d2c9a29c0d4bf8f805dbead933156a57ea466d75 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Oct 2019 10:45:18 +0800 Subject: [PATCH 1580/2815] Remove unnecessary local assign --- osu.Game/Screens/Select/SongSelect.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 5ab49fa2b9..e97fac96c5 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -66,11 +66,7 @@ namespace osu.Game.Screens.Select /// protected readonly Container FooterPanels; - protected override BackgroundScreen CreateBackground() - { - var background = new BackgroundScreenBeatmap(); - return background; - } + protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(); protected readonly BeatmapCarousel Carousel; private readonly BeatmapInfoWedge beatmapInfoWedge; From 925615320e4d8ae455738b5dc3fa1de8a170f71f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Oct 2019 10:46:48 +0800 Subject: [PATCH 1581/2815] Update lazer default combo colours to match stable --- osu.Game/Skinning/DefaultSkinConfiguration.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/DefaultSkinConfiguration.cs b/osu.Game/Skinning/DefaultSkinConfiguration.cs index f52fac6077..cd5975edac 100644 --- a/osu.Game/Skinning/DefaultSkinConfiguration.cs +++ b/osu.Game/Skinning/DefaultSkinConfiguration.cs @@ -14,10 +14,10 @@ namespace osu.Game.Skinning { ComboColours.AddRange(new[] { - new Color4(17, 136, 170, 255), - new Color4(102, 136, 0, 255), - new Color4(204, 102, 0, 255), - new Color4(121, 9, 13, 255) + new Color4(255, 192, 0, 255), + new Color4(0, 202, 0, 255), + new Color4(18, 124, 255, 255), + new Color4(242, 24, 57, 255), }); } } From 71985c7ef13913af2d3c6acf07c17ac4d11986c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Oct 2019 11:23:42 +0800 Subject: [PATCH 1582/2815] Update fail logic to match --- 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 91d34c8056..5560baa7d1 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -299,6 +299,12 @@ namespace osu.Game.Screens.Play { if (!this.IsCurrentScreen()) return; + if (HasFailed && !FailOverlay.IsPresent) + { + failAnimation.FinishTransforms(true); + return; + } + if (canPause) Pause(); else @@ -517,12 +523,6 @@ namespace osu.Game.Screens.Play if (pauseCooldownActive && !GameplayClockContainer.IsPaused.Value) // still want to block if we are within the cooldown period and not already paused. return true; - - if (HasFailed && !FailOverlay.IsPresent) - { - failAnimation.FinishTransforms(true); - return true; - } } GameplayClockContainer.ResetLocalAdjustments(); From e646b2677ca0b07267690783cf8894676e437dc4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Oct 2019 11:25:23 +0800 Subject: [PATCH 1583/2815] Add test coverage --- osu.Game.Tests/Visual/Gameplay/TestScenePause.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index c67001c3d8..48159aa5e0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -127,6 +127,15 @@ namespace osu.Game.Tests.Visual.Gameplay exitAndConfirm(); } + [Test] + public void TestExitFromFailedGameplay() + { + AddUntilStep("wait for fail", () => Player.HasFailed); + AddStep("exit", () => Player.Exit()); + + confirmExited(); + } + [Test] public void TestExitFromGameplay() { From 47c1f36f9d91f54eb68b824badad248b69c169be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Oct 2019 11:41:53 +0800 Subject: [PATCH 1584/2815] Add ValidForResume check --- 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 5560baa7d1..0b363eac4d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -299,7 +299,7 @@ namespace osu.Game.Screens.Play { if (!this.IsCurrentScreen()) return; - if (HasFailed && !FailOverlay.IsPresent) + if (ValidForResume && HasFailed && !FailOverlay.IsPresent) { failAnimation.FinishTransforms(true); return; From 626f7388c8fdc8dd34935d2a8cf86cfaf702ac4a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Oct 2019 12:23:01 +0800 Subject: [PATCH 1585/2815] Add tests for quick retry and quick exit scenarios --- .../Visual/Gameplay/TestScenePause.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 48159aa5e0..2df22df659 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -136,6 +136,24 @@ namespace osu.Game.Tests.Visual.Gameplay confirmExited(); } + [Test] + public void TestQuickRetryFromFailedGameplay() + { + AddUntilStep("wait for fail", () => Player.HasFailed); + AddStep("quick retry", () => Player.GameplayClockContainer.OfType().First().Action?.Invoke()); + + confirmExited(); + } + + [Test] + public void TestQuickExitFromFailedGameplay() + { + AddUntilStep("wait for fail", () => Player.HasFailed); + AddStep("quick exit", () => Player.GameplayClockContainer.OfType().First().Action?.Invoke()); + + confirmExited(); + } + [Test] public void TestExitFromGameplay() { @@ -144,6 +162,14 @@ namespace osu.Game.Tests.Visual.Gameplay confirmExited(); } + [Test] + public void TestQuickExitFromGameplay() + { + AddStep("quick exit", () => Player.GameplayClockContainer.OfType().First().Action?.Invoke()); + + confirmExited(); + } + [Test] public void TestExitViaHoldToExit() { From ffde389641a15f0b0faceb8e1e900fa186509bda Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 4 Oct 2019 13:28:32 +0900 Subject: [PATCH 1586/2815] Add difficulty calculator beatmap decoder fallback --- .../Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs index 2c493254e0..238187bf8f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs @@ -24,6 +24,7 @@ namespace osu.Game.Beatmaps.Formats public new static void Register() { AddDecoder(@"osu file format v", m => new LegacyDifficultyCalculatorBeatmapDecoder(int.Parse(m.Split('v').Last()))); + SetFallbackDecoder(() => new LegacyDifficultyCalculatorBeatmapDecoder()); } protected override TimingControlPoint CreateTimingControlPoint() From ddef7fa3ba67d47ff90d62446d7ed5bbf294c46d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Oct 2019 13:32:47 +0800 Subject: [PATCH 1587/2815] Repair behavioural change --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 74d3439c59..c6d812aee3 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -157,7 +157,7 @@ namespace osu.Game.Rulesets.UI return; } - manualClock.CurrentTime = newTime.Value; + newProposedTime = newTime.Value; } requireMoreUpdateLoops = manualClock.CurrentTime != parentGameplayClock.CurrentTime; From aeb62825cdb0c0727fca76b6ae21541133aeb1e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Oct 2019 13:42:06 +0800 Subject: [PATCH 1588/2815] Move out requireMoreUpdateLoops for better consistency --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index c6d812aee3..e569bb8459 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -115,17 +115,16 @@ namespace osu.Game.Rulesets.UI setClock(); // LoadComplete may not be run yet, but we still want the clock. validState = true; + requireMoreUpdateLoops = false; var newProposedTime = parentGameplayClock.CurrentTime; try { if (!FrameStablePlayback) - { - requireMoreUpdateLoops = false; return; - } - else if (firstConsumption) + + if (firstConsumption) { // On the first update, frame-stability seeking would result in unexpected/unwanted behaviour. // Instead we perform an initial seek to the proposed time. @@ -159,8 +158,6 @@ namespace osu.Game.Rulesets.UI newProposedTime = newTime.Value; } - - requireMoreUpdateLoops = manualClock.CurrentTime != parentGameplayClock.CurrentTime; } finally { @@ -171,6 +168,8 @@ namespace osu.Game.Rulesets.UI manualClock.Rate = Math.Abs(parentGameplayClock.Rate) * direction; manualClock.IsRunning = parentGameplayClock.IsRunning; + requireMoreUpdateLoops |= manualClock.CurrentTime != parentGameplayClock.CurrentTime; + // The manual clock time has changed in the above code. The framed clock now needs to be updated // to ensure that the its time is valid for our children before input is processed framedClock.ProcessFrame(); From 7b414092afd261c953fd9f51f9bdf93632021523 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 4 Oct 2019 17:35:41 +0300 Subject: [PATCH 1589/2815] Implement beatmap ruleset selector --- .../BeatmapSet/BeatmapRulesetSelector.cs | 50 ++++++ .../BeatmapSet/BeatmapRulesetTabItem.cs | 148 ++++++++++++++++++ 2 files changed, 198 insertions(+) create mode 100644 osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs create mode 100644 osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs new file mode 100644 index 0000000000..0847272e1f --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.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 osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osuTK; +using System.Linq; + +namespace osu.Game.Overlays.BeatmapSet +{ + public class BeatmapRulesetSelector : RulesetSelector + { + private BeatmapSetInfo beatmapSet; + + public BeatmapSetInfo BeatmapSet + { + get => beatmapSet; + set + { + if (value == beatmapSet) + return; + + beatmapSet = value; + + foreach (BeatmapRulesetTabItem tab in TabContainer.TabItems) + tab.SetBeatmaps(beatmapSet?.Beatmaps.FindAll(b => b.Ruleset.Equals(tab.Value))); + + var firstRuleset = beatmapSet?.Beatmaps.OrderBy(b => b.Ruleset.ID).FirstOrDefault()?.Ruleset; + SelectTab(TabContainer.TabItems.FirstOrDefault(t => t.Value.Equals(firstRuleset))); + } + } + + public BeatmapRulesetSelector() + { + AutoSizeAxes = Axes.Both; + TabContainer.Spacing = new Vector2(10, 0); + } + + protected override TabItem CreateTabItem(RulesetInfo value) => new BeatmapRulesetTabItem(value); + + protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + }; + } +} diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs new file mode 100644 index 0000000000..19c9af13d5 --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs @@ -0,0 +1,148 @@ +// 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.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; +using osuTK; +using osuTK.Graphics; +using System.Collections.Generic; +using System.Diagnostics; + +namespace osu.Game.Overlays.BeatmapSet +{ + public class BeatmapRulesetTabItem : TabItem + { + private readonly OsuSpriteText name, count; + private readonly Box bar; + + public override bool PropagatePositionalInputSubTree => Enabled.Value && !Active.Value && base.PropagatePositionalInputSubTree; + + public BeatmapRulesetTabItem(RulesetInfo value) + : base(value) + { + AutoSizeAxes = Axes.Both; + Masking = true; + + FillFlowContainer nameContainer; + + Children = new Drawable[] + { + nameContainer = new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { Bottom = 7.5f }, + Spacing = new Vector2(2.5f), + Children = new Drawable[] + { + name = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = value.Name, + Font = OsuFont.Default.With(size: 18), + }, + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 4f, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.5f), + }, + count = new OsuSpriteText + { + Alpha = 0, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Horizontal = 5f }, + Font = OsuFont.Default.With(weight: FontWeight.SemiBold), + } + } + } + } + }, + bar = new Box + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.X, + Height = 4f, + }, + new HoverClickSounds(), + }; + + Enabled.BindValueChanged(v => nameContainer.Alpha = v.NewValue ? 1f : 0.5f, true); + } + + [Resolved] + private OsuColour colour { get; set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + count.Colour = colour.Gray9; + bar.Colour = colour.Blue; + + updateState(); + } + + public void SetBeatmaps(List beatmaps) + { + Trace.Assert(beatmaps?.TrueForAll(b => b.Ruleset.Equals(Value)) ?? true, "A beatmap has a ruleset not of this tab value"); + + count.Text = beatmaps?.Count.ToString(); + + var hasBeatmaps = (beatmaps?.Count ?? 0) > 0; + count.Alpha = hasBeatmaps ? 1f : 0f; + Enabled.Value = hasBeatmaps; + } + + private void updateState() + { + var isHoveredOrActive = IsHovered || Active.Value; + name.Colour = isHoveredOrActive ? colour.GrayE : colour.GrayC; + bar.MoveToY(isHoveredOrActive ? 0f : bar.Height, 120); + + name.Font = name.Font.With(weight: Active.Value ? FontWeight.Bold : FontWeight.Regular); + } + + #region Hovering and activation logic + + protected override void OnActivated() => updateState(); + + protected override void OnDeactivated() => updateState(); + + protected override bool OnHover(HoverEvent e) + { + updateState(); + return false; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateState(); + } + + #endregion + } +} From edddbdb7845a250002ee5dd111750b3290a29401 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 4 Oct 2019 17:37:09 +0300 Subject: [PATCH 1590/2815] Add tests for beatmap ruleset selector --- .../Online/TestSceneBeatmapRulesetSelector.cs | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneBeatmapRulesetSelector.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapRulesetSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapRulesetSelector.cs new file mode 100644 index 0000000000..1f8df438fb --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapRulesetSelector.cs @@ -0,0 +1,102 @@ +// 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.Graphics.UserInterface; +using osu.Game.Beatmaps; +using osu.Game.Overlays.BeatmapSet; +using osu.Game.Rulesets; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneBeatmapRulesetSelector : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(BeatmapRulesetSelector), + typeof(BeatmapRulesetTabItem), + }; + + private readonly TestRulesetSelector selector; + + public TestSceneBeatmapRulesetSelector() + { + Add(selector = new TestRulesetSelector()); + } + + [Resolved] + private RulesetStore rulesets { get; set; } + + [Test] + public void TestMultipleRulesetsBeatmapSet() + { + var enabledRulesets = rulesets.AvailableRulesets.Skip(1).Take(2); + + AddStep("load multiple rulesets beatmapset", () => + { + selector.BeatmapSet = new BeatmapSetInfo + { + Beatmaps = enabledRulesets.Select(r => new BeatmapInfo { Ruleset = r }).ToList() + }; + }); + + var tabItems = selector.TabContainer.TabItems; + AddAssert("other rulesets disabled", () => tabItems.Except(tabItems.Where(t => enabledRulesets.Any(r => r.Equals(t.Value)))).All(t => !t.Enabled.Value)); + AddAssert("left-most ruleset selected", () => tabItems.First(t => t.Enabled.Value).Active.Value); + } + + [Test] + public void TestSingleRulesetBeatmapSet() + { + var enabledRuleset = rulesets.AvailableRulesets.Last(); + + AddStep("load single ruleset beatmapset", () => + { + selector.BeatmapSet = new BeatmapSetInfo + { + Beatmaps = new List + { + new BeatmapInfo + { + Ruleset = enabledRuleset + } + } + }; + }); + + AddAssert("single ruleset selected", () => selector.SelectedTab.Value.Equals(enabledRuleset)); + } + + [Test] + public void TestEmptyBeatmapSet() + { + AddStep("load empty beatmapset", () => selector.BeatmapSet = new BeatmapSetInfo + { + Beatmaps = new List() + }); + + AddAssert("no ruleset selected", () => selector.SelectedTab == null); + AddAssert("all rulesets disabled", () => selector.TabContainer.TabItems.All(t => !t.Enabled.Value)); + } + + [Test] + public void TestNullBeatmapSet() + { + AddStep("load null beatmapset", () => selector.BeatmapSet = null); + + AddAssert("no ruleset selected", () => selector.SelectedTab == null); + AddAssert("all rulesets disabled", () => selector.TabContainer.TabItems.All(t => !t.Enabled.Value)); + } + + private class TestRulesetSelector : BeatmapRulesetSelector + { + public new TabItem SelectedTab => base.SelectedTab; + + public new TabFillFlowContainer TabContainer => base.TabContainer; + } + } +} From 7d5f5d2fd9272e207bc26860e76f613ce01e38ec Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 4 Oct 2019 17:55:33 +0300 Subject: [PATCH 1591/2815] Add ruleset selector to the beatmap overlay header --- osu.Game/Overlays/BeatmapSet/Header.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index 260a989628..0e3d29c25b 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -3,6 +3,7 @@ using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -16,6 +17,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Overlays.BeatmapSet.Buttons; using osu.Game.Overlays.Direct; +using osu.Game.Rulesets; using osuTK; using osuTK.Graphics; @@ -39,6 +41,7 @@ namespace osu.Game.Overlays.BeatmapSet public bool DownloadButtonsVisible => downloadButtonsContainer.Any(); + public readonly BeatmapRulesetSelector RulesetSelector; public readonly BeatmapPicker Picker; private readonly FavouriteButton favouriteButton; @@ -69,12 +72,17 @@ namespace osu.Game.Overlays.BeatmapSet { RelativeSizeAxes = Axes.X, Height = tabs_height, - Children = new[] + Children = new Drawable[] { tabsBg = new Box { RelativeSizeAxes = Axes.Both, }, + RulesetSelector = new BeatmapRulesetSelector + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + } }, }, new Container @@ -214,6 +222,13 @@ namespace osu.Game.Overlays.BeatmapSet }; } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.CacheAs>(RulesetSelector.Current); + return dependencies; + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -223,7 +238,7 @@ namespace osu.Game.Overlays.BeatmapSet BeatmapSet.BindValueChanged(setInfo => { - Picker.BeatmapSet = author.BeatmapSet = beatmapAvailability.BeatmapSet = Details.BeatmapSet = setInfo.NewValue; + Picker.BeatmapSet = RulesetSelector.BeatmapSet = author.BeatmapSet = beatmapAvailability.BeatmapSet = Details.BeatmapSet = setInfo.NewValue; cover.BeatmapSet = setInfo.NewValue; if (setInfo.NewValue == null) From 555c82e9c9077f8e363aebb12e73ff07127aa97f Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 4 Oct 2019 17:56:42 +0300 Subject: [PATCH 1592/2815] Filter beatmap difficulties by current ruleset --- osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index 28947b6f22..e7a3d15401 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -17,6 +17,7 @@ using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; using osuTK; using osuTK.Graphics; @@ -27,10 +28,11 @@ namespace osu.Game.Overlays.BeatmapSet private const float tile_icon_padding = 7; private const float tile_spacing = 2; - private readonly DifficultiesContainer difficulties; private readonly OsuSpriteText version, starRating; private readonly Statistic plays, favourites; + public readonly DifficultiesContainer Difficulties; + public readonly Bindable Beatmap = new Bindable(); private BeatmapSetInfo beatmapSet; @@ -50,11 +52,11 @@ namespace osu.Game.Overlays.BeatmapSet private void updateDisplay() { - difficulties.Clear(); + Difficulties.Clear(); if (BeatmapSet != null) { - difficulties.ChildrenEnumerable = BeatmapSet.Beatmaps.OrderBy(beatmap => beatmap.StarDifficulty).Select(b => new DifficultySelectorButton(b) + Difficulties.ChildrenEnumerable = BeatmapSet.Beatmaps.FindAll(b => b.Ruleset.Equals(ruleset.Value)).OrderBy(b => b.StarDifficulty).Select(b => new DifficultySelectorButton(b) { State = DifficultySelectorState.NotSelected, OnHovered = beatmap => @@ -68,7 +70,7 @@ namespace osu.Game.Overlays.BeatmapSet } starRating.FadeOut(100); - Beatmap.Value = BeatmapSet?.Beatmaps.FirstOrDefault(); + Beatmap.Value = Difficulties.FirstOrDefault()?.Beatmap; plays.Value = BeatmapSet?.OnlineInfo.PlayCount ?? 0; favourites.Value = BeatmapSet?.OnlineInfo.FavouriteCount ?? 0; @@ -89,7 +91,7 @@ namespace osu.Game.Overlays.BeatmapSet Direction = FillDirection.Vertical, Children = new Drawable[] { - difficulties = new DifficultiesContainer + Difficulties = new DifficultiesContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -147,6 +149,9 @@ namespace osu.Game.Overlays.BeatmapSet }; } + [Resolved] + private IBindable ruleset { get; set; } + [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -158,6 +163,8 @@ namespace osu.Game.Overlays.BeatmapSet { base.LoadComplete(); + ruleset.ValueChanged += r => updateDisplay(); + // done here so everything can bind in intialization and get the first trigger Beatmap.TriggerChange(); } @@ -169,10 +176,10 @@ namespace osu.Game.Overlays.BeatmapSet private void updateDifficultyButtons() { - difficulties.Children.ToList().ForEach(diff => diff.State = diff.Beatmap == Beatmap.Value ? DifficultySelectorState.Selected : DifficultySelectorState.NotSelected); + Difficulties.Children.ToList().ForEach(diff => diff.State = diff.Beatmap == Beatmap.Value ? DifficultySelectorState.Selected : DifficultySelectorState.NotSelected); } - private class DifficultiesContainer : FillFlowContainer + public class DifficultiesContainer : FillFlowContainer { public Action OnLostHover; @@ -183,7 +190,7 @@ namespace osu.Game.Overlays.BeatmapSet } } - private class DifficultySelectorButton : OsuClickableContainer, IStateful + public class DifficultySelectorButton : OsuClickableContainer, IStateful { private const float transition_duration = 100; private const float size = 52; @@ -320,7 +327,7 @@ namespace osu.Game.Overlays.BeatmapSet } } - private enum DifficultySelectorState + public enum DifficultySelectorState { Selected, NotSelected, From 4f40a044258b392f8c0d42a672d5d492ab11779e Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 4 Oct 2019 17:57:39 +0300 Subject: [PATCH 1593/2815] Add tests ensuring correct behaviour with ruleset selection --- .../Online/TestSceneBeatmapSetOverlay.cs | 70 +++++++++++++++---- 1 file changed, 57 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index 9f03d947b9..6c7a3e4108 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet; @@ -40,24 +41,19 @@ namespace osu.Game.Tests.Visual.Online typeof(PreviewButton), typeof(SuccessRate), typeof(BeatmapAvailability), + typeof(BeatmapRulesetSelector), + typeof(BeatmapRulesetTabItem), }; protected override bool UseOnlineAPI => true; - private RulesetInfo taikoRuleset; - private RulesetInfo maniaRuleset; - public TestSceneBeatmapSetOverlay() { Add(overlay = new TestBeatmapSetOverlay()); } - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - taikoRuleset = rulesets.GetRuleset(1); - maniaRuleset = rulesets.GetRuleset(3); - } + [Resolved] + private RulesetStore rulesets { get; set; } [Test] public void TestLoading() @@ -111,7 +107,7 @@ namespace osu.Game.Tests.Visual.Online StarDifficulty = 9.99, Version = @"TEST", Length = 456000, - Ruleset = maniaRuleset, + Ruleset = rulesets.GetRuleset(3), BaseDifficulty = new BeatmapDifficulty { CircleSize = 1, @@ -189,7 +185,7 @@ namespace osu.Game.Tests.Visual.Online StarDifficulty = 5.67, Version = @"ANOTHER TEST", Length = 123000, - Ruleset = taikoRuleset, + Ruleset = rulesets.GetRuleset(1), BaseDifficulty = new BeatmapDifficulty { CircleSize = 9, @@ -217,6 +213,54 @@ namespace osu.Game.Tests.Visual.Online downloadAssert(false); } + [Test] + public void TestMultipleRulesets() + { + AddStep("show multiple rulesets beatmap", () => + { + var beatmaps = new List(); + + foreach (var ruleset in rulesets.AvailableRulesets.Skip(1)) + { + beatmaps.Add(new BeatmapInfo + { + Version = ruleset.Name, + Ruleset = ruleset, + BaseDifficulty = new BeatmapDifficulty(), + OnlineInfo = new BeatmapOnlineInfo(), + Metrics = new BeatmapMetrics + { + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), + }, + }); + } + + overlay.ShowBeatmapSet(new BeatmapSetInfo + { + Metadata = new BeatmapMetadata + { + Title = @"multiple rulesets beatmap", + Artist = @"none", + Author = new User + { + Username = "BanchoBot", + Id = 3, + } + }, + OnlineInfo = new BeatmapSetOnlineInfo + { + Covers = new BeatmapSetOnlineCovers(), + }, + Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }, + Beatmaps = beatmaps + }); + }); + + AddAssert("shown beatmaps of current ruleset", () => overlay.Header.Picker.Difficulties.All(b => b.Beatmap.Ruleset.Equals(overlay.Header.RulesetSelector.Current.Value))); + AddAssert("left-most beatmap selected", () => overlay.Header.Picker.Difficulties.First().State == BeatmapPicker.DifficultySelectorState.Selected); + } + [Test] public void TestHide() { @@ -281,12 +325,12 @@ namespace osu.Game.Tests.Visual.Online private void downloadAssert(bool shown) { - AddAssert($"is download button {(shown ? "shown" : "hidden")}", () => overlay.DownloadButtonsVisible == shown); + AddAssert($"is download button {(shown ? "shown" : "hidden")}", () => overlay.Header.DownloadButtonsVisible == shown); } private class TestBeatmapSetOverlay : BeatmapSetOverlay { - public bool DownloadButtonsVisible => Header.DownloadButtonsVisible; + public new Header Header => base.Header; } } } From 76c74719a44f2aa461cebf93cd2b71e476663632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 4 Oct 2019 17:00:51 +0200 Subject: [PATCH 1594/2815] Add test for fallback decoder overwrite LegacyDifficultyCalculatorBeatmapDecoder was registered as a fallback decoder in commit ffde389 for future use in the server-side difficulty calculation components. Due to the pre-existing fallback registrations this causes a runtime crash when the diffcalc components are started. Add a test reproducing this scenario to prevent the issue from resurfacing in the future. --- .../Formats/LegacyBeatmapDecoderTest.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index f6c0dbbecf..de516d3142 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -591,5 +591,27 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.Throws(() => Decoder.GetDecoder(stream)); } } + + [Test] + public void TestAllowFallbackDecoderOverwrite() + { + Decoder decoder = null; + + using (var resStream = TestResources.OpenResource("corrupted-header.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder(stream)); + Assert.IsInstanceOf(decoder); + } + + Assert.DoesNotThrow(LegacyDifficultyCalculatorBeatmapDecoder.Register); + + using (var resStream = TestResources.OpenResource("corrupted-header.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder(stream)); + Assert.IsInstanceOf(decoder); + } + } } } From 0c4f24825969c64775705b8feb9297af2f4fc9ac Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 4 Oct 2019 18:02:13 +0300 Subject: [PATCH 1595/2815] Fix CI issues --- osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs index 0847272e1f..1abe103fc8 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.BeatmapSet beatmapSet = value; - foreach (BeatmapRulesetTabItem tab in TabContainer.TabItems) + foreach (var tab in TabContainer.TabItems.OfType()) tab.SetBeatmaps(beatmapSet?.Beatmaps.FindAll(b => b.Ruleset.Equals(tab.Value))); var firstRuleset = beatmapSet?.Beatmaps.OrderBy(b => b.Ruleset.ID).FirstOrDefault()?.Ruleset; From 7c2c537bc9cca82740ea9df51f65c3f8047de372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 4 Oct 2019 17:23:33 +0200 Subject: [PATCH 1596/2815] Allow fallback decoder overwrite To fix the runtime crashes in difficulty calculation components, remove the check for pre-existing fallback registration along with the exception. The xmldoc for the registration function has been extended to make users aware of possible consequences of calling it. --- osu.Game/Beatmaps/Formats/Decoder.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs index 045eb2d14d..40c329eb7e 100644 --- a/osu.Game/Beatmaps/Formats/Decoder.cs +++ b/osu.Game/Beatmaps/Formats/Decoder.cs @@ -93,14 +93,12 @@ namespace osu.Game.Beatmaps.Formats /// /// Registers a fallback decoder instantiation function. /// The fallback will be returned if the first non-empty line of the decoded stream does not match any known magic. + /// Calling this method will overwrite any existing global fallback registration for type - use with caution. /// /// Type of object being decoded. /// A function that constructs the fallback. protected static void SetFallbackDecoder(Func constructor) { - if (fallback_decoders.ContainsKey(typeof(T))) - throw new InvalidOperationException($"A fallback decoder was already added for type {typeof(T)}."); - fallback_decoders[typeof(T)] = constructor; } } From 6985249d90e63e8e18ca92dd31c056eed82f35af Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 4 Oct 2019 21:18:03 +0300 Subject: [PATCH 1597/2815] Simplify properties --- osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs index 1abe103fc8..bfb188a83b 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs @@ -36,15 +36,15 @@ namespace osu.Game.Overlays.BeatmapSet public BeatmapRulesetSelector() { AutoSizeAxes = Axes.Both; - TabContainer.Spacing = new Vector2(10, 0); } protected override TabItem CreateTabItem(RulesetInfo value) => new BeatmapRulesetTabItem(value); protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer { - Direction = FillDirection.Horizontal, AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), }; } } From e7ba6ef5c43fcf1c55d93a86cb0773ea4d8070f2 Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 4 Oct 2019 14:32:43 -0700 Subject: [PATCH 1598/2815] Fix keybinding order of beatmap options --- osu.Game/Screens/Select/PlaySongSelect.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 4df6e6a3f3..9368bac69f 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Select { ValidForResume = false; Edit(); - }, Key.Number3); + }, Key.Number4); } public override void OnResuming(IScreen last) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 452c63a18c..ee2e40dcd9 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -230,9 +230,9 @@ namespace osu.Game.Screens.Select Footer.AddButton(new FooterButtonRandom { Action = triggerRandom }); Footer.AddButton(new FooterButtonOptions(), BeatmapOptions); - BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue); - BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, colours.Purple, null, Key.Number1); - BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, colours.Purple, () => clearScores(Beatmap.Value.BeatmapInfo), Key.Number2); + BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo), Key.Number1, float.MaxValue); + BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, colours.Purple, null, Key.Number2); + BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, colours.Purple, () => clearScores(Beatmap.Value.BeatmapInfo), Key.Number3); } if (this.beatmaps == null) From 5d460eaf6b8a9c05d54c61f2cd01cfddb2636386 Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 4 Oct 2019 17:14:19 -0700 Subject: [PATCH 1599/2815] Remove depth specification and button order regression --- osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs | 5 ++--- osu.Game/Screens/Select/SongSelect.cs | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs index ede526f9da..b831ae274c 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs @@ -93,7 +93,7 @@ namespace osu.Game.Screens.Select.Options /// Lower depth to be put on the left, and higher to be put on the right. /// Notice this is different to ! /// - public void AddButton(string firstLine, string secondLine, IconUsage icon, Color4 colour, Action action, Key? hotkey = null, float depth = 0) + public void AddButton(string firstLine, string secondLine, IconUsage icon, Color4 colour, Action action, Key? hotkey = null) { var button = new BeatmapOptionsButton { @@ -101,7 +101,6 @@ namespace osu.Game.Screens.Select.Options SecondLineText = secondLine, Icon = icon, ButtonColour = colour, - Depth = depth, Action = () => { Hide(); @@ -110,7 +109,7 @@ namespace osu.Game.Screens.Select.Options HotKey = hotkey }; - buttonsContainer.Insert((int)depth, button); + buttonsContainer.Add(button); } } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index ee2e40dcd9..59a143728c 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -230,9 +230,9 @@ namespace osu.Game.Screens.Select Footer.AddButton(new FooterButtonRandom { Action = triggerRandom }); Footer.AddButton(new FooterButtonOptions(), BeatmapOptions); - BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo), Key.Number1, float.MaxValue); - BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, colours.Purple, null, Key.Number2); - BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, colours.Purple, () => clearScores(Beatmap.Value.BeatmapInfo), Key.Number3); + BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, colours.Purple, null, Key.Number1); + BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, colours.Purple, () => clearScores(Beatmap.Value.BeatmapInfo), Key.Number2); + BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo), Key.Number3); } if (this.beatmaps == null) From de658c932e6ef23ce6511d15cabf28b836f94416 Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 4 Oct 2019 17:22:42 -0700 Subject: [PATCH 1600/2815] Fix test regression --- .../Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs index ecdc484887..f55c099d83 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs @@ -18,8 +18,8 @@ namespace osu.Game.Tests.Visual.SongSelect overlay.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, Color4.Purple, null, Key.Number1); overlay.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, Color4.Purple, null, Key.Number2); - overlay.AddButton(@"Edit", @"Beatmap", FontAwesome.Solid.PencilAlt, Color4.Yellow, null, Key.Number3); - overlay.AddButton(@"Delete", @"Beatmap", FontAwesome.Solid.Trash, Color4.Pink, null, Key.Number4, float.MaxValue); + overlay.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, Color4.Pink, null, Key.Number3); + overlay.AddButton(@"Edit", @"beatmap", FontAwesome.Solid.PencilAlt, Color4.Yellow, null, Key.Number4); Add(overlay); From a71db11ea544cacf62fdd22da93a9f89a6523f5c Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 4 Oct 2019 18:38:44 -0700 Subject: [PATCH 1601/2815] Remove depth parameter description --- osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs index b831ae274c..c01970f536 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs @@ -89,10 +89,6 @@ namespace osu.Game.Screens.Select.Options /// Icon of the button. /// Hotkey of the button. /// Binding the button does. - /// - /// Lower depth to be put on the left, and higher to be put on the right. - /// Notice this is different to ! - /// public void AddButton(string firstLine, string secondLine, IconUsage icon, Color4 colour, Action action, Key? hotkey = null) { var button = new BeatmapOptionsButton From ba1a8547011d721f61ef1798b6dab6ed6d0e083e Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sat, 5 Oct 2019 10:30:32 +0300 Subject: [PATCH 1602/2815] Use IEnumerable.Where<>() rather than List.FindAll() Saves a whole list allocation --- osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index e7a3d15401..bffe779da1 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -56,7 +56,7 @@ namespace osu.Game.Overlays.BeatmapSet if (BeatmapSet != null) { - Difficulties.ChildrenEnumerable = BeatmapSet.Beatmaps.FindAll(b => b.Ruleset.Equals(ruleset.Value)).OrderBy(b => b.StarDifficulty).Select(b => new DifficultySelectorButton(b) + Difficulties.ChildrenEnumerable = BeatmapSet.Beatmaps.Where(b => b.Ruleset.Equals(ruleset.Value)).OrderBy(b => b.StarDifficulty).Select(b => new DifficultySelectorButton(b) { State = DifficultySelectorState.NotSelected, OnHovered = beatmap => From e257f4ca04481e6b43ef8ec52337c7c364218b56 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sat, 5 Oct 2019 10:31:44 -0700 Subject: [PATCH 1603/2815] Resume music to same position when exiting gameplay --- osu.Game/Screens/Select/SongSelect.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 59a143728c..d9ddfa2a94 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -413,7 +413,7 @@ namespace osu.Game.Screens.Select Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, previous); if (this.IsCurrentScreen() && Beatmap.Value?.Track != previous?.Track) - ensurePlayingSelected(); + ensurePlayingSelected(true); if (beatmap != null) { @@ -585,18 +585,14 @@ namespace osu.Game.Screens.Select { Track track = Beatmap.Value.Track; - if (!track.IsRunning || restart) + if (!track.IsRunning) { track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; - if (music != null) - { - // use the global music controller (when available) to cancel a potential local user paused state. - music.SeekTo(track.RestartPoint); - music.Play(); - } - else + if (restart) track.Restart(); + else + track.Start(); } } From bdea75b99584c2624cfc9acff6fc811f332ebc07 Mon Sep 17 00:00:00 2001 From: Roman Kapustin Date: Sat, 5 Oct 2019 23:53:05 +0300 Subject: [PATCH 1604/2815] Autoscroll playlist on song change --- osu.Game/Configuration/OsuConfigManager.cs | 5 +++- osu.Game/Overlays/Music/PlaylistList.cs | 11 +++++++- .../Sections/Audio/PlaylistSettings.cs | 27 +++++++++++++++++++ .../Settings/Sections/AudioSection.cs | 1 + 4 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Overlays/Settings/Sections/Audio/PlaylistSettings.cs diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index c0ce08ba08..75cc961a9e 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -117,6 +117,8 @@ namespace osu.Game.Configuration Set(OsuSetting.UIHoldActivationDelay, 200f, 0f, 500f, 50f); Set(OsuSetting.IntroSequence, IntroSequence.Triangles); + + Set(OsuSetting.FollowPlayback, true); } public OsuConfigManager(Storage storage) @@ -186,6 +188,7 @@ namespace osu.Game.Configuration UIScale, IntroSequence, UIHoldActivationDelay, - HitLighting + HitLighting, + FollowPlayback } } diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index 5b528c5ab2..95b4a28125 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osuTK; @@ -52,6 +53,8 @@ namespace osu.Game.Overlays.Music private IBindableList beatmaps; + private IBindable followPlayback; + [Resolved] private MusicController musicController { get; set; } @@ -76,8 +79,10 @@ namespace osu.Game.Overlays.Music } [BackgroundDependencyLoader] - private void load(IBindable beatmap) + private void load(IBindable beatmap, OsuConfigManager configManager) { + followPlayback = configManager.GetBindable(OsuSetting.FollowPlayback); + beatmaps = musicController.BeatmapSets.GetBoundCopy(); beatmaps.ItemsAdded += i => i.ForEach(addBeatmapSet); beatmaps.ItemsRemoved += i => i.ForEach(removeBeatmapSet); @@ -109,7 +114,11 @@ namespace osu.Game.Overlays.Music private void updateSelectedSet() { foreach (PlaylistItem s in items.Children) + { s.Selected = s.BeatmapSetInfo.ID == beatmapBacking.Value.BeatmapSetInfo?.ID; + if (s.Selected && followPlayback.Value) + ScrollIntoView(s); + } } public string SearchTerm diff --git a/osu.Game/Overlays/Settings/Sections/Audio/PlaylistSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/PlaylistSettings.cs new file mode 100644 index 0000000000..5de99e24af --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Audio/PlaylistSettings.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.Framework.Graphics; +using osu.Game.Configuration; + +namespace osu.Game.Overlays.Settings.Sections.Audio +{ + public class PlaylistSettings : SettingsSubsection + { + protected override string Header => "Playlist"; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = "Follow playback", + Bindable = config.GetBindable(OsuSetting.FollowPlayback) + } + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/AudioSection.cs b/osu.Game/Overlays/Settings/Sections/AudioSection.cs index 772f5c2039..dd67493a75 100644 --- a/osu.Game/Overlays/Settings/Sections/AudioSection.cs +++ b/osu.Game/Overlays/Settings/Sections/AudioSection.cs @@ -20,6 +20,7 @@ namespace osu.Game.Overlays.Settings.Sections new VolumeSettings(), new OffsetSettings(), new MainMenuSettings(), + new PlaylistSettings() }; } } From eda4a27b45e66c8231c7aeea7c9c2915bbfca74b Mon Sep 17 00:00:00 2001 From: Roman Kapustin Date: Sun, 6 Oct 2019 15:06:25 +0300 Subject: [PATCH 1605/2815] Move FollowPlayback setting to User Interface subsection --- .../Sections/Audio/PlaylistSettings.cs | 27 ------------------- .../Settings/Sections/AudioSection.cs | 3 +-- .../Graphics/UserInterfaceSettings.cs | 5 ++++ 3 files changed, 6 insertions(+), 29 deletions(-) delete mode 100644 osu.Game/Overlays/Settings/Sections/Audio/PlaylistSettings.cs diff --git a/osu.Game/Overlays/Settings/Sections/Audio/PlaylistSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/PlaylistSettings.cs deleted file mode 100644 index 5de99e24af..0000000000 --- a/osu.Game/Overlays/Settings/Sections/Audio/PlaylistSettings.cs +++ /dev/null @@ -1,27 +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.Game.Configuration; - -namespace osu.Game.Overlays.Settings.Sections.Audio -{ - public class PlaylistSettings : SettingsSubsection - { - protected override string Header => "Playlist"; - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - Children = new Drawable[] - { - new SettingsCheckbox - { - LabelText = "Follow playback", - Bindable = config.GetBindable(OsuSetting.FollowPlayback) - } - }; - } - } -} diff --git a/osu.Game/Overlays/Settings/Sections/AudioSection.cs b/osu.Game/Overlays/Settings/Sections/AudioSection.cs index dd67493a75..7ca313a751 100644 --- a/osu.Game/Overlays/Settings/Sections/AudioSection.cs +++ b/osu.Game/Overlays/Settings/Sections/AudioSection.cs @@ -19,8 +19,7 @@ namespace osu.Game.Overlays.Settings.Sections new AudioDevicesSettings(), new VolumeSettings(), new OffsetSettings(), - new MainMenuSettings(), - new PlaylistSettings() + new MainMenuSettings() }; } } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs index a8953ac3a2..2678d4c11a 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs @@ -17,6 +17,11 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { Children = new Drawable[] { + new SettingsCheckbox + { + LabelText = "Scroll playlist on song change", + Bindable = config.GetBindable(OsuSetting.FollowPlayback) + }, new SettingsCheckbox { LabelText = "Rotate cursor when dragging", From 38c1cee5fdbb913c771ddbf373ca3fea3d062235 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 6 Oct 2019 10:22:55 -0700 Subject: [PATCH 1606/2815] Fix tab controls overflowing --- .../Overlays/Chat/Tabs/ChannelTabControl.cs | 13 ++----------- osu.Game/Overlays/ChatOverlay.cs | 9 +++++++++ .../SearchableListFilterControl.cs | 10 +++++++++- .../Select/BeatmapDetailAreaTabControl.cs | 18 +++++++++++++++--- 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs index 612379d339..8b88d81b88 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs @@ -9,7 +9,6 @@ using osuTK; using System; using System.Linq; using osu.Framework.Bindables; -using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Chat.Tabs { @@ -25,19 +24,11 @@ namespace osu.Game.Overlays.Chat.Tabs public ChannelTabControl() { - TabContainer.Margin = new MarginPadding { Left = 50 }; + Padding = new MarginPadding { Left = 50 }; + TabContainer.Spacing = new Vector2(-SHEAR_WIDTH, 0); TabContainer.Masking = false; - AddInternal(new SpriteIcon - { - Icon = FontAwesome.Solid.Comments, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(20), - Margin = new MarginPadding(10), - }); - AddTabItem(selectorTab = new ChannelSelectorTabItem()); ChannelSelectorActive.BindTo(selectorTab.Active); diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 0cadbdfd31..33bcc4c139 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -21,6 +21,7 @@ using osu.Game.Overlays.Chat; using osu.Game.Overlays.Chat.Selection; using osu.Game.Overlays.Chat.Tabs; using osuTK.Input; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays { @@ -156,6 +157,14 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both, Colour = Color4.Black, }, + new SpriteIcon + { + Icon = FontAwesome.Solid.Comments, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(20), + Margin = new MarginPadding(10), + }, ChannelTabControl = CreateChannelTabControl().With(d => { d.Anchor = Anchor.BottomLeft; diff --git a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs index a0c4e9a080..d72e99289e 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs @@ -18,6 +18,7 @@ namespace osu.Game.Overlays.SearchableList private const float padding = 10; private readonly Container filterContainer; + private readonly Container tabsContainer; private readonly Box tabStrip; public readonly SearchTextBox Search; @@ -85,9 +86,14 @@ namespace osu.Game.Overlays.SearchableList AutoSizeAxes = Axes.Y, Margin = new MarginPadding { Top = controls != null ? padding : 0 }, }, - Tabs = new PageTabControl + tabsContainer = new Container { RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = Tabs = new PageTabControl + { + RelativeSizeAxes = Axes.X, + }, }, new Box //keep the tab strip part of autosize, but don't put it in the flow container { @@ -127,6 +133,8 @@ namespace osu.Game.Overlays.SearchableList Height = filterContainer.Height; DisplayStyleControl.Margin = new MarginPadding { Top = filterContainer.Height - 35, Right = SearchableListOverlay.WIDTH_PADDING }; + + tabsContainer.Padding = new MarginPadding { Right = DisplayStyleControl.Width }; } private class FilterSearchTextBox : SearchTextBox diff --git a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs index 6caef8e2aa..4ca629fee9 100644 --- a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs +++ b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs @@ -20,6 +20,7 @@ namespace osu.Game.Screens.Select public static readonly float HEIGHT = 24; private readonly OsuTabControlCheckbox modsCheckbox; private readonly OsuTabControl tabs; + private readonly Container tabsContainer; public Action OnFilter; //passed the selected tab and if mods is checked @@ -39,11 +40,15 @@ namespace osu.Game.Screens.Select Height = 1, Colour = Color4.White.Opacity(0.2f), }, - tabs = new OsuTabControl + tabsContainer = new Container { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, RelativeSizeAxes = Axes.Both, + Child = tabs = new OsuTabControl + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Both, + }, }, modsCheckbox = new OsuTabControlCheckbox { @@ -69,6 +74,13 @@ namespace osu.Game.Screens.Select tabs.Current.TriggerChange(); } + protected override void Update() + { + base.Update(); + + tabsContainer.Padding = new MarginPadding { Right = modsCheckbox.Width }; + } + private void invokeOnFilter() { OnFilter?.Invoke(tabs.Current.Value, modsCheckbox.Current.Value); From 11d937beab1708fb1354994b017e08e98cb5cc78 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 6 Oct 2019 10:24:33 -0700 Subject: [PATCH 1607/2815] Fix beatmap detail area tab dropdown being blocked by content --- osu.Game/Screens/Select/BeatmapDetailArea.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs index 5348de68d6..71733c9f06 100644 --- a/osu.Game/Screens/Select/BeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs @@ -36,6 +36,11 @@ namespace osu.Game.Screens.Select { AddRangeInternal(new Drawable[] { + content = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = BeatmapDetailAreaTabControl.HEIGHT }, + }, new BeatmapDetailAreaTabControl { RelativeSizeAxes = Axes.X, @@ -58,11 +63,6 @@ namespace osu.Game.Screens.Select } }, }, - content = new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = BeatmapDetailAreaTabControl.HEIGHT }, - }, }); AddRange(new Drawable[] From 62c4c1266ef8c1dc71324021bd4808abbb7b6205 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Oct 2019 14:45:26 +0900 Subject: [PATCH 1608/2815] Move private functions to bottom --- .../SongSelect/TestSceneBeatmapCarousel.cs | 202 +++++++++--------- 1 file changed, 101 insertions(+), 101 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 6bdd94db21..527367fdb8 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -58,107 +58,6 @@ namespace osu.Game.Tests.Visual.SongSelect }); } - private void loadBeatmaps(List beatmapSets = null) - { - if (beatmapSets == null) - { - beatmapSets = new List(); - - for (int i = 1; i <= set_count; i++) - beatmapSets.Add(createTestBeatmapSet(i)); - } - - bool changed = false; - AddStep($"Load {beatmapSets.Count} Beatmaps", () => - { - carousel.Filter(new FilterCriteria()); - carousel.BeatmapSetsChanged = () => changed = true; - carousel.BeatmapSets = beatmapSets; - }); - - AddUntilStep("Wait for load", () => changed); - } - - private void ensureRandomFetchSuccess() => - AddAssert("ensure prev random fetch worked", () => selectedSets.Peek() == carousel.SelectedBeatmapSet); - - private void waitForSelection(int set, int? diff = null) => - AddUntilStep($"selected is set{set}{(diff.HasValue ? $" diff{diff.Value}" : "")}", () => - { - if (diff != null) - return carousel.SelectedBeatmap == carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff.Value - 1).First(); - - return carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Contains(carousel.SelectedBeatmap); - }); - - private void setSelected(int set, int diff) => - AddStep($"select set{set} diff{diff}", () => - carousel.SelectBeatmap(carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff - 1).First())); - - private void advanceSelection(bool diff, int direction = 1, int count = 1) - { - if (count == 1) - AddStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () => - carousel.SelectNext(direction, !diff)); - else - { - AddRepeatStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () => - carousel.SelectNext(direction, !diff), count); - } - } - - private void checkVisibleItemCount(bool diff, int count) => - AddAssert($"{count} {(diff ? "diffs" : "sets")} visible", () => - carousel.Items.Count(s => (diff ? s.Item is CarouselBeatmap : s.Item is CarouselBeatmapSet) && s.Item.Visible) == count); - - private void checkNoSelection() => AddAssert("Selection is null", () => currentSelection == null); - - private void nextRandom() => - AddStep("select random next", () => - { - carousel.RandomAlgorithm.Value = RandomSelectAlgorithm.RandomPermutation; - - if (!selectedSets.Any() && carousel.SelectedBeatmap != null) - selectedSets.Push(carousel.SelectedBeatmapSet); - - carousel.SelectNextRandom(); - selectedSets.Push(carousel.SelectedBeatmapSet); - }); - - private void ensureRandomDidntRepeat() => - AddAssert("ensure no repeats", () => selectedSets.Distinct().Count() == selectedSets.Count); - - private void prevRandom() => AddStep("select random last", () => - { - carousel.SelectPreviousRandom(); - selectedSets.Pop(); - }); - - private bool selectedBeatmapVisible() - { - var currentlySelected = carousel.Items.Find(s => s.Item is CarouselBeatmap && s.Item.State.Value == CarouselItemState.Selected); - if (currentlySelected == null) - return true; - - return currentlySelected.Item.Visible; - } - - private void checkInvisibleDifficultiesUnselectable() - { - nextRandom(); - AddAssert("Selection is visible", selectedBeatmapVisible); - } - - private void checkNonmatchingFilter() - { - AddStep("Toggle non-matching filter", () => - { - carousel.Filter(new FilterCriteria { SearchText = "Dingo" }, false); - carousel.Filter(new FilterCriteria(), false); - eagerSelectedIDs.Add(carousel.SelectedBeatmapSet.ID); - }); - } - /// /// Test keyboard traversal /// @@ -482,6 +381,107 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("Selection was random", () => eagerSelectedIDs.Count > 1); } + private void loadBeatmaps(List beatmapSets = null) + { + if (beatmapSets == null) + { + beatmapSets = new List(); + + for (int i = 1; i <= set_count; i++) + beatmapSets.Add(createTestBeatmapSet(i)); + } + + bool changed = false; + AddStep($"Load {beatmapSets.Count} Beatmaps", () => + { + carousel.Filter(new FilterCriteria()); + carousel.BeatmapSetsChanged = () => changed = true; + carousel.BeatmapSets = beatmapSets; + }); + + AddUntilStep("Wait for load", () => changed); + } + + private void ensureRandomFetchSuccess() => + AddAssert("ensure prev random fetch worked", () => selectedSets.Peek() == carousel.SelectedBeatmapSet); + + private void waitForSelection(int set, int? diff = null) => + AddUntilStep($"selected is set{set}{(diff.HasValue ? $" diff{diff.Value}" : "")}", () => + { + if (diff != null) + return carousel.SelectedBeatmap == carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff.Value - 1).First(); + + return carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Contains(carousel.SelectedBeatmap); + }); + + private void setSelected(int set, int diff) => + AddStep($"select set{set} diff{diff}", () => + carousel.SelectBeatmap(carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff - 1).First())); + + private void advanceSelection(bool diff, int direction = 1, int count = 1) + { + if (count == 1) + AddStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () => + carousel.SelectNext(direction, !diff)); + else + { + AddRepeatStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () => + carousel.SelectNext(direction, !diff), count); + } + } + + private void checkVisibleItemCount(bool diff, int count) => + AddAssert($"{count} {(diff ? "diffs" : "sets")} visible", () => + carousel.Items.Count(s => (diff ? s.Item is CarouselBeatmap : s.Item is CarouselBeatmapSet) && s.Item.Visible) == count); + + private void checkNoSelection() => AddAssert("Selection is null", () => currentSelection == null); + + private void nextRandom() => + AddStep("select random next", () => + { + carousel.RandomAlgorithm.Value = RandomSelectAlgorithm.RandomPermutation; + + if (!selectedSets.Any() && carousel.SelectedBeatmap != null) + selectedSets.Push(carousel.SelectedBeatmapSet); + + carousel.SelectNextRandom(); + selectedSets.Push(carousel.SelectedBeatmapSet); + }); + + private void ensureRandomDidntRepeat() => + AddAssert("ensure no repeats", () => selectedSets.Distinct().Count() == selectedSets.Count); + + private void prevRandom() => AddStep("select random last", () => + { + carousel.SelectPreviousRandom(); + selectedSets.Pop(); + }); + + private bool selectedBeatmapVisible() + { + var currentlySelected = carousel.Items.Find(s => s.Item is CarouselBeatmap && s.Item.State.Value == CarouselItemState.Selected); + if (currentlySelected == null) + return true; + + return currentlySelected.Item.Visible; + } + + private void checkInvisibleDifficultiesUnselectable() + { + nextRandom(); + AddAssert("Selection is visible", selectedBeatmapVisible); + } + + private void checkNonmatchingFilter() + { + AddStep("Toggle non-matching filter", () => + { + carousel.Filter(new FilterCriteria { SearchText = "Dingo" }, false); + carousel.Filter(new FilterCriteria(), false); + eagerSelectedIDs.Add(carousel.SelectedBeatmapSet.ID); + }); + } + private BeatmapSetInfo createTestBeatmapSet(int id) { return new BeatmapSetInfo From 46d6c5ec3b6832d353f033d9284d761f423943db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Oct 2019 15:13:58 +0900 Subject: [PATCH 1609/2815] Add failing test --- .../SongSelect/TestSceneBeatmapCarousel.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 527367fdb8..f87d6ebebb 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -245,6 +245,30 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert($"Check #{set_count} is at bottom", () => carousel.BeatmapSets.Last().Metadata.Title.EndsWith($"#{set_count}!")); } + [Test] + public void TestSortingWithFiltered() + { + List sets = new List(); + + for (int i = 0; i < 3; i++) + { + var set = createTestBeatmapSet(i); + set.Beatmaps[0].StarDifficulty = 3 - i; + set.Beatmaps[2].StarDifficulty = 6 + i; + sets.Add(set); + } + + loadBeatmaps(sets); + + AddStep("Filter to normal", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Normal" }, false)); + AddAssert("Check first set at end", () => carousel.BeatmapSets.First() == sets.Last()); + AddAssert("Check last set at start", () => carousel.BeatmapSets.Last() == sets.First()); + + AddStep("Filter to insane", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Insane" }, false)); + AddAssert("Check first set at start", () => carousel.BeatmapSets.First() == sets.First()); + AddAssert("Check last set at end", () => carousel.BeatmapSets.Last() == sets.Last()); + } + [Test] public void TestRemoveAll() { From f15953d65ce00c38a2544234c426c33f55429cb0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Oct 2019 15:16:26 +0900 Subject: [PATCH 1610/2815] Fix carousel including filtered difficulties in sort comparisons --- .../Select/Carousel/CarouselBeatmapSet.cs | 23 ++++++++++++++++--- .../Screens/Select/Carousel/CarouselGroup.cs | 2 +- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 5a3996bb49..35816fe620 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -49,16 +49,33 @@ namespace osu.Game.Screens.Select.Carousel return otherSet.BeatmapSet.DateAdded.CompareTo(BeatmapSet.DateAdded); case SortMode.BPM: - return BeatmapSet.MaxBPM.CompareTo(otherSet.BeatmapSet.MaxBPM); + return compareUsingAggregateMax(otherSet, b => b.BPM); case SortMode.Length: - return BeatmapSet.MaxLength.CompareTo(otherSet.BeatmapSet.MaxLength); + return compareUsingAggregateMax(otherSet, b => b.Length); case SortMode.Difficulty: - return BeatmapSet.MaxStarDifficulty.CompareTo(otherSet.BeatmapSet.MaxStarDifficulty); + return compareUsingAggregateMax(otherSet, b => b.StarDifficulty); } } + /// + /// All beatmaps which are not filtered and valid for display. + /// + protected IEnumerable ValidBeatmaps => Beatmaps.Where(b => !b.Filtered.Value).Select(b => b.Beatmap); + + private int compareUsingAggregateMax(CarouselBeatmapSet other, Func func) + { + var ourBeatmaps = ValidBeatmaps.Any(); + var otherBeatmaps = other.ValidBeatmaps.Any(); + + if (!ourBeatmaps && !otherBeatmaps) return 0; + if (!ourBeatmaps) return -1; + if (!otherBeatmaps) return 1; + + return ValidBeatmaps.Max(func).CompareTo(other.ValidBeatmaps.Max(func)); + } + public override void Filter(FilterCriteria criteria) { base.Filter(criteria); diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index 6ebd2d41cc..09b728abeb 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -83,8 +83,8 @@ namespace osu.Game.Screens.Select.Carousel var children = new List(InternalChildren); - children.Sort((x, y) => x.CompareTo(criteria, y)); children.ForEach(c => c.Filter(criteria)); + children.Sort((x, y) => x.CompareTo(criteria, y)); InternalChildren = children; } From e265beb289a3d1df5c5d0645526884e85c410ac9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 7 Oct 2019 18:49:59 +0900 Subject: [PATCH 1611/2815] Fix merge error --- .../Blueprints/HitCircles/HitCirclePlacementBlueprint.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs index 38584ce898..6c08990ad6 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs @@ -21,6 +21,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles InternalChild = circlePiece = new HitCirclePiece(); } + protected override void Update() + { + base.Update(); + + circlePiece.UpdateFrom(HitObject); + } + protected override bool OnClick(ClickEvent e) { HitObject.StartTime = EditorClock.CurrentTime; From 2d707b2b65bb4baa189e4cb68c3e9d3bbbfeff54 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 7 Oct 2019 15:36:23 +0300 Subject: [PATCH 1612/2815] Implement PostBeatmapFavouriteRequest --- .../Requests/PostBeatmapFavouriteRequest.cs | 36 +++++++++++++++++++ .../BeatmapSet/Buttons/FavouriteButton.cs | 24 ++++++++++++- 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Online/API/Requests/PostBeatmapFavouriteRequest.cs diff --git a/osu.Game/Online/API/Requests/PostBeatmapFavouriteRequest.cs b/osu.Game/Online/API/Requests/PostBeatmapFavouriteRequest.cs new file mode 100644 index 0000000000..f3724230cb --- /dev/null +++ b/osu.Game/Online/API/Requests/PostBeatmapFavouriteRequest.cs @@ -0,0 +1,36 @@ +// 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.IO.Network; +using System.Net.Http; + +namespace osu.Game.Online.API.Requests +{ + public class PostBeatmapFavouriteRequest : APIRequest + { + private readonly int id; + private readonly BeatmapFavouriteAction action; + + public PostBeatmapFavouriteRequest(int id, BeatmapFavouriteAction action) + { + this.id = id; + this.action = action; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + req.Method = HttpMethod.Post; + req.AddParameter(@"action", action.ToString().ToLowerInvariant()); + return req; + } + + protected override string Target => $@"beatmapsets/{id}/favourites"; + } + + public enum BeatmapFavouriteAction + { + Favourite, + UnFavourite + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index 11f56bc163..23325b8765 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs @@ -10,6 +10,9 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osuTK; namespace osu.Game.Overlays.BeatmapSet.Buttons @@ -20,8 +23,11 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons private readonly Bindable favourited = new Bindable(); + private PostBeatmapFavouriteRequest request; + private DimmedLoadingLayer loading; + [BackgroundDependencyLoader] - private void load() + private void load(IAPIProvider api) { Container pink; SpriteIcon icon; @@ -55,6 +61,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons Size = new Vector2(18), Shadow = false, }, + loading = new DimmedLoadingLayer(), }); BeatmapSet.BindValueChanged(setInfo => @@ -67,6 +74,8 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons favourited.ValueChanged += favourited => { + loading.Hide(); + if (favourited.NewValue) { pink.FadeIn(200); @@ -78,6 +87,19 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons icon.Icon = FontAwesome.Regular.Heart; } }; + + Action = () => + { + if (loading.State.Value == Visibility.Visible) + return; + + loading.Show(); + + request?.Cancel(); + request = new PostBeatmapFavouriteRequest(BeatmapSet.Value?.OnlineBeatmapSetID ?? 0, favourited.Value ? BeatmapFavouriteAction.UnFavourite : BeatmapFavouriteAction.Favourite); + request.Success += () => favourited.Value = !favourited.Value; + api.Queue(request); + }; } protected override void UpdateAfterChildren() From a7dc9bb582682c0b87ea047f1da9aa6b40a45ea0 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 7 Oct 2019 15:41:05 +0300 Subject: [PATCH 1613/2815] Add tooltip and remove pink layer --- .../BeatmapSet/Buttons/FavouriteButton.cs | 40 +++---------------- 1 file changed, 5 insertions(+), 35 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index 23325b8765..fcea20ef11 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs @@ -5,11 +5,9 @@ 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.Cursor; using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -17,7 +15,7 @@ using osuTK; namespace osu.Game.Overlays.BeatmapSet.Buttons { - public class FavouriteButton : HeaderButton + public class FavouriteButton : HeaderButton, IHasTooltip { public readonly Bindable BeatmapSet = new Bindable(); @@ -26,33 +24,14 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons private PostBeatmapFavouriteRequest request; private DimmedLoadingLayer loading; + public string TooltipText => (favourited.Value ? "Unfavourite" : "Favourite") + " this beatmapset"; + [BackgroundDependencyLoader] private void load(IAPIProvider api) { - Container pink; SpriteIcon icon; AddRange(new Drawable[] { - pink = new Container - { - RelativeSizeAxes = Axes.Both, - Alpha = 0f, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex(@"9f015f"), - }, - new Triangles - { - RelativeSizeAxes = Axes.Both, - ColourLight = OsuColour.FromHex(@"cb2187"), - ColourDark = OsuColour.FromHex(@"9f015f"), - TriangleScale = 1.5f, - }, - }, - }, icon = new SpriteIcon { Anchor = Anchor.Centre, @@ -76,16 +55,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons { loading.Hide(); - if (favourited.NewValue) - { - pink.FadeIn(200); - icon.Icon = FontAwesome.Solid.Heart; - } - else - { - pink.FadeOut(200); - icon.Icon = FontAwesome.Regular.Heart; - } + icon.Icon = favourited.NewValue ? FontAwesome.Solid.Heart : FontAwesome.Regular.Heart; }; Action = () => From 76db200bd3efd8f3f1a93820e852cbdb9e54fdc8 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 7 Oct 2019 16:48:05 +0300 Subject: [PATCH 1614/2815] Implement GetCommentsRequest --- .../Online/API/Requests/GetCommentsRequest.cs | 53 ++++++++++++++++++ .../API/Requests/Responses/APIComments.cs | 36 ++++++++++++ .../Online/API/Requests/Responses/Comment.cs | 56 +++++++++++++++++++ 3 files changed, 145 insertions(+) create mode 100644 osu.Game/Online/API/Requests/GetCommentsRequest.cs create mode 100644 osu.Game/Online/API/Requests/Responses/APIComments.cs create mode 100644 osu.Game/Online/API/Requests/Responses/Comment.cs diff --git a/osu.Game/Online/API/Requests/GetCommentsRequest.cs b/osu.Game/Online/API/Requests/GetCommentsRequest.cs new file mode 100644 index 0000000000..279a1905da --- /dev/null +++ b/osu.Game/Online/API/Requests/GetCommentsRequest.cs @@ -0,0 +1,53 @@ +// 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.IO.Network; +using Humanizer; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Online.API.Requests +{ + public class GetCommentsRequest : APIRequest + { + private readonly long id; + private readonly int page; + private readonly CommentableType type; + private readonly SortCommentsBy sort; + + public GetCommentsRequest(CommentableType type, long id, SortCommentsBy sort = SortCommentsBy.New, int page = 1) + { + this.type = type; + this.sort = sort; + this.id = id; + this.page = page; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + + req.AddParameter("commentable_type", type.ToString().Underscore().ToLowerInvariant()); + req.AddParameter("commentable_id", id.ToString()); + req.AddParameter("sort", sort.ToString()); + req.AddParameter("page", page.ToString()); + + return req; + } + + protected override string Target => "comments"; + } + + public enum CommentableType + { + Build, + Beatmapset, + NewsPost + } + + public enum SortCommentsBy + { + New, + Old, + Top + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIComments.cs b/osu.Game/Online/API/Requests/Responses/APIComments.cs new file mode 100644 index 0000000000..158430a5b6 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIComments.cs @@ -0,0 +1,36 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; +using osu.Game.Users; +using System.Collections.Generic; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIComments + { + [JsonProperty(@"comments")] + public List Comments { get; set; } + + [JsonProperty(@"has_more")] + public bool HasMore { get; set; } + + [JsonProperty(@"has_more_id")] + public long HasMoreId { get; set; } + + [JsonProperty(@"user_follow")] + public bool UserFollow { get; set; } + + [JsonProperty(@"included_comments")] + public List IncludedComments { get; set; } + + [JsonProperty(@"users")] + public List Users { get; set; } + + [JsonProperty(@"total")] + public int Total { get; set; } + + [JsonProperty(@"top_level_count")] + public int TopLevelCount { get; set; } + } +} diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs new file mode 100644 index 0000000000..e157a10f8a --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -0,0 +1,56 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; +using System; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class Comment + { + [JsonProperty(@"id")] + public long Id { get; set; } + + [JsonProperty(@"parent_id")] + public long ParentId { get; set; } + + [JsonProperty(@"user_id")] + public long UserId { get; set; } + + [JsonProperty(@"message")] + public string Message { get; set; } + + [JsonProperty(@"message_html")] + public string MessageHTML { get; set; } + + [JsonProperty(@"replies_count")] + public int RepliesCount { get; set; } + + [JsonProperty(@"votes_count")] + public int VotesCount { get; set; } + + [JsonProperty(@"commenatble_type")] + public string CommentableType { get; set; } + + [JsonProperty(@"commentable_id")] + public int CommentableId { get; set; } + + [JsonProperty(@"legacy_name")] + public string LegacyName { get; set; } + + [JsonProperty(@"created_at")] + public DateTimeOffset CreatedAt { get; set; } + + [JsonProperty(@"updated_at")] + public DateTimeOffset UpdatedAt { get; set; } + + [JsonProperty(@"deleted_at")] + public DateTimeOffset DeletedAt { get; set; } + + [JsonProperty(@"edited_at")] + public DateTimeOffset EditedAt { get; set; } + + [JsonProperty(@"edited_by_id")] + public long EditedById { get; set; } + } +} From 738580ec617a23789236575f2525606a6a4d227e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 7 Oct 2019 16:58:24 +0300 Subject: [PATCH 1615/2815] Add IsTopLevel property --- osu.Game/Online/API/Requests/Responses/Comment.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs index e157a10f8a..6edf13d2da 100644 --- a/osu.Game/Online/API/Requests/Responses/Comment.cs +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -11,8 +11,18 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"id")] public long Id { get; set; } + private long? parentId; + [JsonProperty(@"parent_id")] - public long ParentId { get; set; } + public long? ParentId + { + get => parentId; + set + { + parentId = value; + IsTopLevel = value != null; + } + } [JsonProperty(@"user_id")] public long UserId { get; set; } @@ -52,5 +62,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"edited_by_id")] public long EditedById { get; set; } + + public bool IsTopLevel { get; set; } } } From e772822bd5ee46afddc5bc14398e73f84ff84564 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 7 Oct 2019 17:49:20 +0300 Subject: [PATCH 1616/2815] Basic implementation --- .../Online/TestSceneCommentsContainer.cs | 29 +++++ osu.Game/Overlays/CommentsContainer.cs | 110 ++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs create mode 100644 osu.Game/Overlays/CommentsContainer.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs new file mode 100644 index 0000000000..c99062d59b --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -0,0 +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 System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Overlays; +using osu.Game.Online.API.Requests; + +namespace osu.Game.Tests.Visual.Online +{ + [TestFixture] + public class TestSceneCommentsContainer : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(CommentsContainer), + }; + + public TestSceneCommentsContainer() + { + AddStep("Big Black comments", () => + { + Clear(); + Add(new CommentsContainer(CommentableType.Beatmapset, 41823)); + }); + } + } +} diff --git a/osu.Game/Overlays/CommentsContainer.cs b/osu.Game/Overlays/CommentsContainer.cs new file mode 100644 index 0000000000..8ed6fd0878 --- /dev/null +++ b/osu.Game/Overlays/CommentsContainer.cs @@ -0,0 +1,110 @@ +// 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.Containers; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Framework.Graphics; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Overlays +{ + public class CommentsContainer : CompositeDrawable + { + private readonly CommentableType type; + private readonly long id; + + public readonly Bindable Sort = new Bindable(); + + [Resolved] + private IAPIProvider api { get; set; } + + private readonly CommentsHeader header; + private readonly Box background; + + public CommentsContainer(CommentableType type, long id) + { + this.type = type; + this.id = id; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + AddRangeInternal(new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + header = new CommentsHeader + { + Sort = { BindTarget = Sort } + } + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Gray3; + } + + private class CommentsHeader : CompositeDrawable + { + private const int height = 40; + private const int spacing = 10; + private const int padding = 50; + + public readonly Bindable Sort = new Bindable(); + + private readonly Box background; + + public CommentsHeader() + { + RelativeSizeAxes = Axes.X; + Height = height; + AddRangeInternal(new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = padding }, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(spacing, 0), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Children = new Drawable[] + { + new SpriteText + { + Font = OsuFont.GetFont(size: 14), + Text = @"Sort by" + } + } + } + } + } + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Gray4; + } + } + } +} From aa8df0fa20426b0beec6997a2f2a07895cdddbaf Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 7 Oct 2019 18:26:07 +0300 Subject: [PATCH 1617/2815] Hook up api and implement some visual comments representation --- .../Online/TestSceneCommentsContainer.cs | 15 +++- .../API/Requests/Responses/APIComments.cs | 2 +- .../Online/API/Requests/Responses/Comment.cs | 8 +- osu.Game/Overlays/CommentsContainer.cs | 84 ++++++++++++++++++- 4 files changed, 99 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs index c99062d59b..bf4117189a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -6,6 +6,8 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Game.Overlays; using osu.Game.Online.API.Requests; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; namespace osu.Game.Tests.Visual.Online { @@ -17,12 +19,21 @@ namespace osu.Game.Tests.Visual.Online typeof(CommentsContainer), }; + protected override bool UseOnlineAPI => true; + public TestSceneCommentsContainer() { + BasicScrollContainer scrollFlow; + + Add(scrollFlow = new BasicScrollContainer + { + RelativeSizeAxes = Axes.Both, + }); + AddStep("Big Black comments", () => { - Clear(); - Add(new CommentsContainer(CommentableType.Beatmapset, 41823)); + scrollFlow.Clear(); + scrollFlow.Add(new CommentsContainer(CommentableType.Beatmapset, 41823)); }); } } diff --git a/osu.Game/Online/API/Requests/Responses/APIComments.cs b/osu.Game/Online/API/Requests/Responses/APIComments.cs index 158430a5b6..af7650e512 100644 --- a/osu.Game/Online/API/Requests/Responses/APIComments.cs +++ b/osu.Game/Online/API/Requests/Responses/APIComments.cs @@ -16,7 +16,7 @@ namespace osu.Game.Online.API.Requests.Responses public bool HasMore { get; set; } [JsonProperty(@"has_more_id")] - public long HasMoreId { get; set; } + public long? HasMoreId { get; set; } [JsonProperty(@"user_follow")] public bool UserFollow { get; set; } diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs index 6edf13d2da..df5c812fa0 100644 --- a/osu.Game/Online/API/Requests/Responses/Comment.cs +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -52,16 +52,16 @@ namespace osu.Game.Online.API.Requests.Responses public DateTimeOffset CreatedAt { get; set; } [JsonProperty(@"updated_at")] - public DateTimeOffset UpdatedAt { get; set; } + public DateTimeOffset? UpdatedAt { get; set; } [JsonProperty(@"deleted_at")] - public DateTimeOffset DeletedAt { get; set; } + public DateTimeOffset? DeletedAt { get; set; } [JsonProperty(@"edited_at")] - public DateTimeOffset EditedAt { get; set; } + public DateTimeOffset? EditedAt { get; set; } [JsonProperty(@"edited_by_id")] - public long EditedById { get; set; } + public long? EditedById { get; set; } public bool IsTopLevel { get; set; } } diff --git a/osu.Game/Overlays/CommentsContainer.cs b/osu.Game/Overlays/CommentsContainer.cs index 8ed6fd0878..fce9b3f03b 100644 --- a/osu.Game/Overlays/CommentsContainer.cs +++ b/osu.Game/Overlays/CommentsContainer.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Framework.Graphics.Sprites; using osuTK; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays { @@ -24,8 +25,14 @@ namespace osu.Game.Overlays [Resolved] private IAPIProvider api { get; set; } + [Resolved] + private OsuColour colours { get; set; } + + private GetCommentsRequest request; + private readonly CommentsHeader header; private readonly Box background; + private readonly FillFlowContainer content; public CommentsContainer(CommentableType type, long id) { @@ -40,15 +47,86 @@ namespace osu.Game.Overlays { RelativeSizeAxes = Axes.Both, }, - header = new CommentsHeader + new FillFlowContainer { - Sort = { BindTarget = Sort } + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + header = new CommentsHeader + { + Sort = { BindTarget = Sort } + }, + content = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + } + } + } + }); + } + + protected override void LoadComplete() + { + Sort.BindValueChanged(onSortChanged, true); + base.LoadComplete(); + } + + private void onSortChanged(ValueChangedEvent sort) => getComments(); + + private void getComments() + { + request?.Cancel(); + request = new GetCommentsRequest(type, id, Sort.Value); + request.Success += onSuccess; + api.Queue(request); + } + + private void onSuccess(APIComments response) + { + content.Clear(); + + foreach (var c in response.Comments) + { + createDrawableComment(c); + } + } + + private void createDrawableComment(Comment comment) + { + content.Add(new Container + { + RelativeSizeAxes = Axes.X, + Height = 70, + Children = new Drawable[] + { + new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = comment.MessageHTML, + }, + new Container + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.X, + Height = 1, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Gray1, + } + } } }); } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { background.Colour = colours.Gray3; } From cc6bf2f173b4d53c9fb568963c4685e133417254 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 7 Oct 2019 18:45:22 +0300 Subject: [PATCH 1618/2815] Add IsDeleted property --- .../Online/API/Requests/Responses/Comment.cs | 16 ++++++++++++++-- osu.Game/Overlays/CommentsContainer.cs | 6 +++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs index df5c812fa0..e4b66ddeee 100644 --- a/osu.Game/Online/API/Requests/Responses/Comment.cs +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -20,7 +20,7 @@ namespace osu.Game.Online.API.Requests.Responses set { parentId = value; - IsTopLevel = value != null; + IsTopLevel = value == null; } } @@ -54,8 +54,18 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"updated_at")] public DateTimeOffset? UpdatedAt { get; set; } + private DateTimeOffset? deletedAt; + [JsonProperty(@"deleted_at")] - public DateTimeOffset? DeletedAt { get; set; } + public DateTimeOffset? DeletedAt + { + get => deletedAt; + set + { + deletedAt = value; + IsDeleted = value != null; + } + } [JsonProperty(@"edited_at")] public DateTimeOffset? EditedAt { get; set; } @@ -64,5 +74,7 @@ namespace osu.Game.Online.API.Requests.Responses public long? EditedById { get; set; } public bool IsTopLevel { get; set; } + + public bool IsDeleted { get; set; } } } diff --git a/osu.Game/Overlays/CommentsContainer.cs b/osu.Game/Overlays/CommentsContainer.cs index fce9b3f03b..b22fefb3de 100644 --- a/osu.Game/Overlays/CommentsContainer.cs +++ b/osu.Game/Overlays/CommentsContainer.cs @@ -30,7 +30,6 @@ namespace osu.Game.Overlays private GetCommentsRequest request; - private readonly CommentsHeader header; private readonly Box background; private readonly FillFlowContainer content; @@ -54,7 +53,7 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - header = new CommentsHeader + new CommentsHeader { Sort = { BindTarget = Sort } }, @@ -91,7 +90,8 @@ namespace osu.Game.Overlays foreach (var c in response.Comments) { - createDrawableComment(c); + if (!c.IsDeleted) + createDrawableComment(c); } } From e00992dfd8f18cc9e99724099e75eae42d0a1424 Mon Sep 17 00:00:00 2001 From: Roman Kapustin Date: Mon, 7 Oct 2019 19:44:22 +0300 Subject: [PATCH 1619/2815] Remove FollowPlayback setting --- osu.Game/Configuration/OsuConfigManager.cs | 5 +---- osu.Game/Overlays/Music/PlaylistList.cs | 9 ++------- .../Settings/Sections/Graphics/UserInterfaceSettings.cs | 5 ----- 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 75cc961a9e..c0ce08ba08 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -117,8 +117,6 @@ namespace osu.Game.Configuration Set(OsuSetting.UIHoldActivationDelay, 200f, 0f, 500f, 50f); Set(OsuSetting.IntroSequence, IntroSequence.Triangles); - - Set(OsuSetting.FollowPlayback, true); } public OsuConfigManager(Storage storage) @@ -188,7 +186,6 @@ namespace osu.Game.Configuration UIScale, IntroSequence, UIHoldActivationDelay, - HitLighting, - FollowPlayback + HitLighting } } diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index 95b4a28125..f497bd5b8d 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Beatmaps; -using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osuTK; @@ -53,8 +52,6 @@ namespace osu.Game.Overlays.Music private IBindableList beatmaps; - private IBindable followPlayback; - [Resolved] private MusicController musicController { get; set; } @@ -79,10 +76,8 @@ namespace osu.Game.Overlays.Music } [BackgroundDependencyLoader] - private void load(IBindable beatmap, OsuConfigManager configManager) + private void load(IBindable beatmap) { - followPlayback = configManager.GetBindable(OsuSetting.FollowPlayback); - beatmaps = musicController.BeatmapSets.GetBoundCopy(); beatmaps.ItemsAdded += i => i.ForEach(addBeatmapSet); beatmaps.ItemsRemoved += i => i.ForEach(removeBeatmapSet); @@ -116,7 +111,7 @@ namespace osu.Game.Overlays.Music foreach (PlaylistItem s in items.Children) { s.Selected = s.BeatmapSetInfo.ID == beatmapBacking.Value.BeatmapSetInfo?.ID; - if (s.Selected && followPlayback.Value) + if (s.Selected) ScrollIntoView(s); } } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs index 2678d4c11a..a8953ac3a2 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/UserInterfaceSettings.cs @@ -17,11 +17,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { Children = new Drawable[] { - new SettingsCheckbox - { - LabelText = "Scroll playlist on song change", - Bindable = config.GetBindable(OsuSetting.FollowPlayback) - }, new SettingsCheckbox { LabelText = "Rotate cursor when dragging", From 8e6e90eaecf467b2764f3b2497acac12d4977c30 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 7 Oct 2019 16:11:40 -0700 Subject: [PATCH 1620/2815] Use fixed numbers for padding instead --- .../SearchableList/SearchableListFilterControl.cs | 6 ++---- .../Screens/Select/BeatmapDetailAreaTabControl.cs | 11 ++--------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs index d72e99289e..372da94b37 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs @@ -18,7 +18,6 @@ namespace osu.Game.Overlays.SearchableList private const float padding = 10; private readonly Container filterContainer; - private readonly Container tabsContainer; private readonly Box tabStrip; public readonly SearchTextBox Search; @@ -86,10 +85,11 @@ namespace osu.Game.Overlays.SearchableList AutoSizeAxes = Axes.Y, Margin = new MarginPadding { Top = controls != null ? padding : 0 }, }, - tabsContainer = new Container + new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Right = 225 }, Child = Tabs = new PageTabControl { RelativeSizeAxes = Axes.X, @@ -133,8 +133,6 @@ namespace osu.Game.Overlays.SearchableList Height = filterContainer.Height; DisplayStyleControl.Margin = new MarginPadding { Top = filterContainer.Height - 35, Right = SearchableListOverlay.WIDTH_PADDING }; - - tabsContainer.Padding = new MarginPadding { Right = DisplayStyleControl.Width }; } private class FilterSearchTextBox : SearchTextBox diff --git a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs index 4ca629fee9..38b1c6411b 100644 --- a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs +++ b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs @@ -20,7 +20,6 @@ namespace osu.Game.Screens.Select public static readonly float HEIGHT = 24; private readonly OsuTabControlCheckbox modsCheckbox; private readonly OsuTabControl tabs; - private readonly Container tabsContainer; public Action OnFilter; //passed the selected tab and if mods is checked @@ -40,9 +39,10 @@ namespace osu.Game.Screens.Select Height = 1, Colour = Color4.White.Opacity(0.2f), }, - tabsContainer = new Container + new Container { RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = 100 }, Child = tabs = new OsuTabControl { Anchor = Anchor.BottomLeft, @@ -74,13 +74,6 @@ namespace osu.Game.Screens.Select tabs.Current.TriggerChange(); } - protected override void Update() - { - base.Update(); - - tabsContainer.Padding = new MarginPadding { Right = modsCheckbox.Width }; - } - private void invokeOnFilter() { OnFilter?.Invoke(tabs.Current.Value, modsCheckbox.Current.Value); From 9fdbe583262411aa5f863aec924459420e373c26 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 7 Oct 2019 16:17:58 -0700 Subject: [PATCH 1621/2815] Fix dropdown header padding when selected mod filter is hidden --- osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs index 38b1c6411b..bba72c7ee1 100644 --- a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs +++ b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs @@ -20,6 +20,7 @@ namespace osu.Game.Screens.Select public static readonly float HEIGHT = 24; private readonly OsuTabControlCheckbox modsCheckbox; private readonly OsuTabControl tabs; + private readonly Container tabsContainer; public Action OnFilter; //passed the selected tab and if mods is checked @@ -39,10 +40,9 @@ namespace osu.Game.Screens.Select Height = 1, Colour = Color4.White.Opacity(0.2f), }, - new Container + tabsContainer = new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = 100 }, Child = tabs = new OsuTabControl { Anchor = Anchor.BottomLeft, @@ -79,6 +79,8 @@ namespace osu.Game.Screens.Select OnFilter?.Invoke(tabs.Current.Value, modsCheckbox.Current.Value); modsCheckbox.FadeTo(tabs.Current.Value == BeatmapDetailTab.Details ? 0 : 1, 200, Easing.OutQuint); + + tabsContainer.Padding = new MarginPadding { Right = tabs.Current.Value == BeatmapDetailTab.Details ? 0 : 100 }; } } From 3ec78388e896e6b5bf1fe1f2fa3869545c6759c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Oct 2019 11:02:54 +0900 Subject: [PATCH 1622/2815] Avoid excess background updates in playlist overlay --- osu.Game/Overlays/Music/PlaylistList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index 5b528c5ab2..e8c2537c43 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -84,7 +84,7 @@ namespace osu.Game.Overlays.Music beatmaps.ForEach(addBeatmapSet); beatmapBacking.BindTo(beatmap); - beatmapBacking.ValueChanged += _ => updateSelectedSet(); + beatmapBacking.ValueChanged += _ => Scheduler.AddOnce(updateSelectedSet); } private void addBeatmapSet(BeatmapSetInfo obj) From e7fc5e556c79f154b334545b2d367e712c0154e7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Oct 2019 10:40:52 +0900 Subject: [PATCH 1623/2815] Fix song select not correctly playing tracks in some cases --- osu.Game/Screens/Select/SongSelect.cs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index d9ddfa2a94..04a686f481 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -412,9 +412,6 @@ namespace osu.Game.Screens.Select WorkingBeatmap previous = Beatmap.Value; Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, previous); - if (this.IsCurrentScreen() && Beatmap.Value?.Track != previous?.Track) - ensurePlayingSelected(true); - if (beatmap != null) { if (beatmap.BeatmapSetInfoID == beatmapNoDebounce?.BeatmapSetInfoID) @@ -424,6 +421,9 @@ namespace osu.Game.Screens.Select } } + if (this.IsCurrentScreen()) + ensurePlayingSelected(); + UpdateBeatmap(Beatmap.Value); } } @@ -581,19 +581,14 @@ namespace osu.Game.Screens.Select beatmap.Track.Looping = true; } - private void ensurePlayingSelected(bool restart = false) + private void ensurePlayingSelected() { Track track = Beatmap.Value.Track; - if (!track.IsRunning) - { - track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; + track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; - if (restart) - track.Restart(); - else - track.Start(); - } + if (!track.IsRunning) + track.Restart(); } private void onBeatmapSetAdded(BeatmapSetInfo s) => Carousel.UpdateBeatmapSet(s); From 3c0b1be7f48c32db7714b04ce6fda76ffeefdcb8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Oct 2019 11:52:16 +0900 Subject: [PATCH 1624/2815] Add xmldoc where applicable --- osu.Game/Screens/Menu/IntroScreen.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index c81fef6436..1052ebdd5b 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -21,8 +21,15 @@ namespace osu.Game.Screens.Menu { public abstract class IntroScreen : StartupScreen { + /// + /// A hash used to find the associated beatmap if already imported. + /// protected abstract string BeatmapHash { get; } + /// + /// A source file to use as an import source if the intro beatmap is not yet present. + /// Should be within the "Tracks" namespace of game resources. + /// protected abstract string BeatmapFile { get; } private readonly BindableDouble exitingVolumeFade = new BindableDouble(1); From c3d56088d87d8f12fe7abbe2912b8a6f89e73bc1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Oct 2019 11:54:39 +0900 Subject: [PATCH 1625/2815] Make constant private --- osu.Game/Screens/Menu/IntroScreen.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 1052ebdd5b..5d560c0c2a 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Menu private readonly BindableDouble exitingVolumeFade = new BindableDouble(1); - public const int EXIT_DELAY = 3000; + private const int exit_delay = 3000; [Resolved] private AudioManager audio { get; set; } @@ -110,7 +110,7 @@ namespace osu.Game.Screens.Menu { this.FadeIn(300); - double fadeOutTime = EXIT_DELAY; + double fadeOutTime = exit_delay; //we also handle the exit transition. if (MenuVoice.Value) seeya.Play(); @@ -151,8 +151,8 @@ namespace osu.Game.Screens.Menu .ScaleTo(1, initialMovementTime, Easing.OutQuint) .FadeIn(quick_appear, Easing.OutQuint) .Then() - .RotateTo(20, EXIT_DELAY * 1.5f) - .FadeOut(EXIT_DELAY); + .RotateTo(20, exit_delay * 1.5f) + .FadeOut(exit_delay); } } From 449e53ee6d5c57db401884b9924eb4d5158dda20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Oct 2019 12:03:42 +0900 Subject: [PATCH 1626/2815] Centralise track handling --- osu.Game/Screens/Menu/IntroCircles.cs | 12 +----------- osu.Game/Screens/Menu/IntroScreen.cs | 18 +++++++++++++++++- osu.Game/Screens/Menu/IntroTriangles.cs | 13 +------------ 3 files changed, 19 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroCircles.cs b/osu.Game/Screens/Menu/IntroCircles.cs index 6c643860a0..8f3b4f43c5 100644 --- a/osu.Game/Screens/Menu/IntroCircles.cs +++ b/osu.Game/Screens/Menu/IntroCircles.cs @@ -30,19 +30,11 @@ namespace osu.Game.Screens.Menu if (!resuming) { - Beatmap.Value = IntroBeatmap; - IntroBeatmap = null; - Welcome?.Play(); Scheduler.AddDelayed(delegate { - // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu. - if (MenuMusic.Value) - { - Track.Restart(); - Track = null; - } + StartTrack(); PrepareMenuLoad(); @@ -57,8 +49,6 @@ namespace osu.Game.Screens.Menu public override void OnSuspending(IScreen next) { - Track = null; - this.FadeOut(300); base.OnSuspending(next); } diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 5d560c0c2a..c00d105e12 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -47,7 +47,7 @@ namespace osu.Game.Screens.Menu protected Bindable MenuMusic; - protected Track Track; + protected Track Track { get; private set; } protected WorkingBeatmap IntroBeatmap; @@ -57,6 +57,13 @@ namespace osu.Game.Screens.Menu protected override BackgroundScreen CreateBackground() => new BackgroundScreenBlack(); + protected void StartTrack() + { + // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu. + if (MenuMusic.Value) + Track.Restart(); + } + [BackgroundDependencyLoader] private void load(OsuConfigManager config, SkinManager skinManager, BeatmapManager beatmaps, Framework.Game game) { @@ -136,6 +143,9 @@ namespace osu.Game.Screens.Menu if (!resuming) { + Beatmap.Value = IntroBeatmap; + IntroBeatmap = null; + logo.MoveTo(new Vector2(0.5f)); logo.ScaleTo(Vector2.One); logo.Hide(); @@ -156,6 +166,12 @@ namespace osu.Game.Screens.Menu } } + public override void OnSuspending(IScreen next) + { + base.OnSuspending(next); + Track = null; + } + private MainMenu mainMenu; protected void PrepareMenuLoad() diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 590069ab43..08941eca37 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -55,9 +55,6 @@ namespace osu.Game.Screens.Menu if (!resuming) { - Beatmap.Value = IntroBeatmap; - IntroBeatmap = null; - PrepareMenuLoad(); LoadComponentAsync(new TrianglesIntroSequence(logo, background) @@ -70,9 +67,7 @@ namespace osu.Game.Screens.Menu AddInternal(t); Welcome?.Play(); - // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu. - if (MenuMusic.Value) - Track.Start(); + StartTrack(); }); } } @@ -83,12 +78,6 @@ namespace osu.Game.Screens.Menu background.FadeOut(100); } - public override void OnSuspending(IScreen next) - { - Track = null; - base.OnSuspending(next); - } - private class TrianglesIntroSequence : CompositeDrawable { private readonly OsuLogo logo; From a0bb19334219edf1f37282a95aa11893cb2fd954 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Oct 2019 12:04:13 +0900 Subject: [PATCH 1627/2815] Remove unnecessary beatmap storage --- osu.Game/Screens/Menu/IntroScreen.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index c00d105e12..ccb7b8ef3d 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -53,8 +53,6 @@ namespace osu.Game.Screens.Menu private LeasedBindable beatmap; - public new Bindable Beatmap => beatmap; - protected override BackgroundScreen CreateBackground() => new BackgroundScreenBlack(); protected void StartTrack() @@ -68,7 +66,7 @@ namespace osu.Game.Screens.Menu private void load(OsuConfigManager config, SkinManager skinManager, BeatmapManager beatmaps, Framework.Game game) { // prevent user from changing beatmap while the intro is still runnning. - beatmap = base.Beatmap.BeginLease(false); + beatmap = Beatmap.BeginLease(false); MenuVoice = config.GetBindable(OsuSetting.MenuVoice); MenuMusic = config.GetBindable(OsuSetting.MenuMusic); From 4ba2dccde30caac207687d4bff7bbb62a2d770cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Oct 2019 12:05:52 +0900 Subject: [PATCH 1628/2815] Reorder file contents --- osu.Game/Screens/Menu/IntroScreen.cs | 73 +++++++++++++--------------- 1 file changed, 33 insertions(+), 40 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index ccb7b8ef3d..c638d4f3c9 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -21,6 +21,11 @@ namespace osu.Game.Screens.Menu { public abstract class IntroScreen : StartupScreen { + /// + /// Whether we have loaded the menu previously. + /// + public bool DidLoadMenu { get; private set; } + /// /// A hash used to find the associated beatmap if already imported. /// @@ -32,35 +37,28 @@ namespace osu.Game.Screens.Menu /// protected abstract string BeatmapFile { get; } - private readonly BindableDouble exitingVolumeFade = new BindableDouble(1); - - private const int exit_delay = 3000; - - [Resolved] - private AudioManager audio { get; set; } - protected SampleChannel Welcome; - private SampleChannel seeya; - protected Bindable MenuVoice; protected Bindable MenuMusic; + protected WorkingBeatmap IntroBeatmap; + protected Track Track { get; private set; } - protected WorkingBeatmap IntroBeatmap; + private readonly BindableDouble exitingVolumeFade = new BindableDouble(1); + + private const int exit_delay = 3000; + + private SampleChannel seeya; private LeasedBindable beatmap; - protected override BackgroundScreen CreateBackground() => new BackgroundScreenBlack(); + private MainMenu mainMenu; - protected void StartTrack() - { - // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu. - if (MenuMusic.Value) - Track.Restart(); - } + [Resolved] + private AudioManager audio { get; set; } [BackgroundDependencyLoader] private void load(OsuConfigManager config, SkinManager skinManager, BeatmapManager beatmaps, Framework.Game game) @@ -100,16 +98,7 @@ namespace osu.Game.Screens.Menu Track = IntroBeatmap.Track; } - /// - /// Whether we have loaded the menu previously. - /// - public bool DidLoadMenu { get; private set; } - - public override bool OnExiting(IScreen next) - { - //cancel exiting if we haven't loaded the menu yet. - return !DidLoadMenu; - } + public override bool OnExiting(IScreen next) => !DidLoadMenu; public override void OnResuming(IScreen last) { @@ -131,6 +120,21 @@ namespace osu.Game.Screens.Menu base.OnResuming(last); } + public override void OnSuspending(IScreen next) + { + base.OnSuspending(next); + Track = null; + } + + protected override BackgroundScreen CreateBackground() => new BackgroundScreenBlack(); + + protected void StartTrack() + { + // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu. + if (MenuMusic.Value) + Track.Restart(); + } + protected override void LogoArriving(OsuLogo logo, bool resuming) { base.LogoArriving(logo, resuming); @@ -151,7 +155,7 @@ namespace osu.Game.Screens.Menu else { const int quick_appear = 350; - int initialMovementTime = logo.Alpha > 0.2f ? quick_appear : 0; + var initialMovementTime = logo.Alpha > 0.2f ? quick_appear : 0; logo.MoveTo(new Vector2(0.5f), initialMovementTime, Easing.OutQuint); @@ -164,18 +168,7 @@ namespace osu.Game.Screens.Menu } } - public override void OnSuspending(IScreen next) - { - base.OnSuspending(next); - Track = null; - } - - private MainMenu mainMenu; - - protected void PrepareMenuLoad() - { - LoadComponentAsync(mainMenu = new MainMenu()); - } + protected void PrepareMenuLoad() => LoadComponentAsync(mainMenu = new MainMenu()); protected void LoadMenu() { From b8b2ff2674001e9bc3334a7799a8931b5004e7d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Oct 2019 12:07:59 +0900 Subject: [PATCH 1629/2815] Move welcome to local usages --- osu.Game/Screens/Menu/IntroCircles.cs | 7 +++++-- osu.Game/Screens/Menu/IntroScreen.cs | 2 -- osu.Game/Screens/Menu/IntroTriangles.cs | 7 +++++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroCircles.cs b/osu.Game/Screens/Menu/IntroCircles.cs index 8f3b4f43c5..aa9cee969c 100644 --- a/osu.Game/Screens/Menu/IntroCircles.cs +++ b/osu.Game/Screens/Menu/IntroCircles.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Screens; using osu.Framework.Graphics; @@ -17,11 +18,13 @@ namespace osu.Game.Screens.Menu private const double delay_step_one = 2300; private const double delay_step_two = 600; + private SampleChannel welcome; + [BackgroundDependencyLoader] private void load(AudioManager audio) { if (MenuVoice.Value) - Welcome = audio.Samples.Get(@"welcome"); + welcome = audio.Samples.Get(@"welcome"); } protected override void LogoArriving(OsuLogo logo, bool resuming) @@ -30,7 +33,7 @@ namespace osu.Game.Screens.Menu if (!resuming) { - Welcome?.Play(); + welcome?.Play(); Scheduler.AddDelayed(delegate { diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index c638d4f3c9..0c192552eb 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -37,8 +37,6 @@ namespace osu.Game.Screens.Menu /// protected abstract string BeatmapFile { get; } - protected SampleChannel Welcome; - protected Bindable MenuVoice; protected Bindable MenuMusic; diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 08941eca37..c86f1393a4 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Screens; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -40,11 +41,13 @@ namespace osu.Game.Screens.Menu private BackgroundScreenDefault background; + private SampleChannel welcome; + [BackgroundDependencyLoader] private void load(AudioManager audio) { if (MenuVoice.Value && !MenuMusic.Value) - Welcome = audio.Samples.Get(@"welcome"); + welcome = audio.Samples.Get(@"welcome"); } protected override void LogoArriving(OsuLogo logo, bool resuming) @@ -65,7 +68,7 @@ namespace osu.Game.Screens.Menu }, t => { AddInternal(t); - Welcome?.Play(); + welcome?.Play(); StartTrack(); }); From c280bee894df7e9c01c88332df579d2964f401e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Oct 2019 12:08:47 +0900 Subject: [PATCH 1630/2815] Protect configuration bindables --- osu.Game/Screens/Menu/IntroScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 0c192552eb..feb472ba88 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -37,9 +37,9 @@ namespace osu.Game.Screens.Menu /// protected abstract string BeatmapFile { get; } - protected Bindable MenuVoice; + protected IBindable MenuVoice { get; private set; } - protected Bindable MenuMusic; + protected IBindable MenuMusic { get; private set; } protected WorkingBeatmap IntroBeatmap; From cbb120cd3874044673e3197a9fc78942433a3edd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Oct 2019 12:09:42 +0900 Subject: [PATCH 1631/2815] Switch beatmap to private --- osu.Game/Screens/Menu/IntroScreen.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index feb472ba88..f5ef839b06 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Menu protected IBindable MenuMusic { get; private set; } - protected WorkingBeatmap IntroBeatmap; + private WorkingBeatmap introBeatmap; protected Track Track { get; private set; } @@ -92,8 +92,8 @@ namespace osu.Game.Screens.Menu } } - IntroBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); - Track = IntroBeatmap.Track; + introBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); + Track = introBeatmap.Track; } public override bool OnExiting(IScreen next) => !DidLoadMenu; @@ -143,8 +143,8 @@ namespace osu.Game.Screens.Menu if (!resuming) { - Beatmap.Value = IntroBeatmap; - IntroBeatmap = null; + Beatmap.Value = introBeatmap; + introBeatmap = null; logo.MoveTo(new Vector2(0.5f)); logo.ScaleTo(Vector2.One); From 52770f803d8844c135c3cfb05a4abb1d119e087c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Oct 2019 12:14:53 +0900 Subject: [PATCH 1632/2815] Fix incorrect beatmap usage --- osu.Game/Screens/Menu/IntroScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index f5ef839b06..df83e98494 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -143,7 +143,7 @@ namespace osu.Game.Screens.Menu if (!resuming) { - Beatmap.Value = introBeatmap; + beatmap.Value = introBeatmap; introBeatmap = null; logo.MoveTo(new Vector2(0.5f)); From 5472029ffed6681a327eeacad42577da0306e0e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Oct 2019 12:27:51 +0900 Subject: [PATCH 1633/2815] Fix editor defaulting to 0.5x playback speed --- osu.Game/Screens/Edit/Components/PlaybackControl.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index 8d4ad0efa9..62d6c4648b 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.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.Linq; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; @@ -105,6 +106,8 @@ namespace osu.Game.Screens.Edit.Components TabContainer.Spacing = Vector2.Zero; tempo_values.ForEach(AddItem); + + Current.Value = tempo_values.Last(); } public class PlaybackTabItem : TabItem From 24269c0384f6ebaa5c09ce52c6a4d404bf23fb7e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Oct 2019 12:52:34 +0900 Subject: [PATCH 1634/2815] Fix skins not being displayed correctly in the editor --- osu.Game/Screens/Edit/Compose/ComposeScreen.cs | 13 ++++++++++++- osu.Game/Skinning/SkinProvidingContainer.cs | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index ec4dda5c23..bd7e8e44e5 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -11,6 +11,7 @@ using osu.Framework.Logging; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osu.Game.Skinning; using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose @@ -115,7 +116,17 @@ namespace osu.Game.Screens.Edit.Compose return; } - composerContainer.Child = composer; + var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.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)); + + // 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. + composerContainer.Add( + beatmapSkinProvider.WithChild( + rulesetSkinProvider.WithChild(composer))); } } } diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index ef7f5f381b..1c01bbf1ab 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -53,7 +53,7 @@ namespace osu.Game.Skinning if (AllowTextureLookup(componentName) && (sourceTexture = skin?.GetTexture(componentName)) != null) return sourceTexture; - return fallbackSource.GetTexture(componentName); + return fallbackSource?.GetTexture(componentName); } public SampleChannel GetSample(ISampleInfo sampleInfo) From 63bf8ff832ba04886bb2b60f83247f4b30e3073d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Oct 2019 14:23:13 +0900 Subject: [PATCH 1635/2815] Better signify under construction screens in editor --- .../Screens/Edit/Compose/ComposeScreen.cs | 41 ++-- osu.Game/Screens/Edit/Design/DesignScreen.cs | 41 +--- osu.Game/Screens/Edit/Editor.cs | 10 + osu.Game/Screens/Edit/Setup/SetupScreen.cs | 13 ++ osu.Game/Screens/Edit/Timing/SetupScreen.cs | 13 ++ osu.Game/Screens/ScreenWhiteBox.cs | 200 ++++++++++-------- 6 files changed, 169 insertions(+), 149 deletions(-) create mode 100644 osu.Game/Screens/Edit/Setup/SetupScreen.cs create mode 100644 osu.Game/Screens/Edit/Timing/SetupScreen.cs diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index bd7e8e44e5..2a99d81516 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Logging; -using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Skinning; @@ -23,8 +22,6 @@ namespace osu.Game.Screens.Edit.Compose private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); - private HitObjectComposer composer; - [BackgroundDependencyLoader(true)] private void load([CanBeNull] BindableBeatDivisor beatDivisor) { @@ -107,26 +104,32 @@ namespace osu.Game.Screens.Edit.Compose return; } - composer = ruleset.CreateHitObjectComposer(); + var composer = ruleset.CreateHitObjectComposer(); - if (composer == null) + Drawable content; + + if (composer != null) { - Logger.Log($"Ruleset {ruleset.Description} doesn't support hitobject composition."); - // ExitRequested?.Invoke(); - return; + var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.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)); + + // 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. + content = beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(ruleset.CreateHitObjectComposer())); + } + else + { + content = new ScreenWhiteBox.UnderConstructionMessage($"{ruleset.Description}'s composer"); } - var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.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)); - - // 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. - composerContainer.Add( - beatmapSkinProvider.WithChild( - rulesetSkinProvider.WithChild(composer))); + LoadComponentAsync(content, _ => + { + composerContainer.Add(content); + content.FadeInFromZero(300, Easing.OutQuint); + }); } } } diff --git a/osu.Game/Screens/Edit/Design/DesignScreen.cs b/osu.Game/Screens/Edit/Design/DesignScreen.cs index 2a334e1b30..9f1fcf55b2 100644 --- a/osu.Game/Screens/Edit/Design/DesignScreen.cs +++ b/osu.Game/Screens/Edit/Design/DesignScreen.cs @@ -1,52 +1,13 @@ // 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.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.Sprites; -using osuTK.Graphics; - namespace osu.Game.Screens.Edit.Design { public class DesignScreen : EditorScreen { public DesignScreen() { - Add(new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.35f - }, - new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.5f - }, - new Container - { - AutoSizeAxes = Axes.Both, - Padding = new MarginPadding(20), - Child = new OsuSpriteText { Text = "Design screen" } - } - } - } - } - }); + Child = new ScreenWhiteBox.UnderConstructionMessage("Design mode"); } } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 8cc227d9be..9ebe3bc26a 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -26,6 +26,8 @@ using System.Collections.Generic; using osu.Framework; using osu.Framework.Input.Bindings; using osu.Game.Input.Bindings; +using osu.Game.Screens.Edit.Setup; +using osu.Game.Screens.Edit.Timing; using osu.Game.Users; namespace osu.Game.Screens.Edit @@ -258,6 +260,10 @@ namespace osu.Game.Screens.Edit switch (e.NewValue) { + case EditorScreenMode.SongSetup: + currentScreen = new SetupScreen(); + break; + case EditorScreenMode.Compose: currentScreen = new ComposeScreen(); break; @@ -266,6 +272,10 @@ namespace osu.Game.Screens.Edit currentScreen = new DesignScreen(); break; + case EditorScreenMode.Timing: + currentScreen = new TimingScreen(); + break; + default: currentScreen = new EditorScreen(); break; diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs new file mode 100644 index 0000000000..758dbc6e16 --- /dev/null +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -0,0 +1,13 @@ +// 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.Screens.Edit.Setup +{ + public class SetupScreen : EditorScreen + { + public SetupScreen() + { + Child = new ScreenWhiteBox.UnderConstructionMessage("Setup mode"); + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/SetupScreen.cs b/osu.Game/Screens/Edit/Timing/SetupScreen.cs new file mode 100644 index 0000000000..9ded4207e5 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/SetupScreen.cs @@ -0,0 +1,13 @@ +// 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.Screens.Edit.Timing +{ + public class TimingScreen : EditorScreen + { + public TimingScreen() + { + Child = new ScreenWhiteBox.UnderConstructionMessage("Timing mode"); + } + } +} diff --git a/osu.Game/Screens/ScreenWhiteBox.cs b/osu.Game/Screens/ScreenWhiteBox.cs index 6c5854d17e..e4971221c4 100644 --- a/osu.Game/Screens/ScreenWhiteBox.cs +++ b/osu.Game/Screens/ScreenWhiteBox.cs @@ -20,38 +20,17 @@ namespace osu.Game.Screens { public class ScreenWhiteBox : OsuScreen { + private readonly UnderConstructionMessage message; + private const double transition_time = 1000; protected virtual IEnumerable PossibleChildren => null; - private readonly FillFlowContainer textContainer; - private readonly Container boxContainer; - protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg2"); - public override void OnEntering(IScreen last) - { - base.OnEntering(last); - - Alpha = 0; - textContainer.Position = new Vector2(DrawSize.X / 16, 0); - - boxContainer.ScaleTo(0.2f); - boxContainer.RotateTo(-20); - - using (BeginDelayedSequence(300, true)) - { - boxContainer.ScaleTo(1, transition_time, Easing.OutElastic); - boxContainer.RotateTo(0, transition_time / 2, Easing.OutQuint); - - textContainer.MoveTo(Vector2.Zero, transition_time, Easing.OutExpo); - this.FadeIn(transition_time, Easing.OutExpo); - } - } - public override bool OnExiting(IScreen next) { - textContainer.MoveTo(new Vector2(DrawSize.X / 16, 0), transition_time, Easing.OutExpo); + message.TextContainer.MoveTo(new Vector2(DrawSize.X / 16, 0), transition_time, Easing.OutExpo); this.FadeOut(transition_time, Easing.OutExpo); return base.OnExiting(next); @@ -61,7 +40,7 @@ namespace osu.Game.Screens { base.OnSuspending(next); - textContainer.MoveTo(new Vector2(-(DrawSize.X / 16), 0), transition_time, Easing.OutExpo); + message.TextContainer.MoveTo(new Vector2(-(DrawSize.X / 16), 0), transition_time, Easing.OutExpo); this.FadeOut(transition_time, Easing.OutExpo); } @@ -69,7 +48,7 @@ namespace osu.Game.Screens { base.OnResuming(last); - textContainer.MoveTo(Vector2.Zero, transition_time, Easing.OutExpo); + message.TextContainer.MoveTo(Vector2.Zero, transition_time, Easing.OutExpo); this.FadeIn(transition_time, Easing.OutExpo); } @@ -79,65 +58,7 @@ namespace osu.Game.Screens InternalChildren = new Drawable[] { - boxContainer = new Container - { - Size = new Vector2(0.3f), - RelativeSizeAxes = Axes.Both, - CornerRadius = 20, - Masking = true, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - - Colour = getColourFor(GetType()), - Alpha = 0.2f, - Blending = BlendingParameters.Additive, - }, - textContainer = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new SpriteIcon - { - Icon = FontAwesome.Solid.UniversalAccess, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Size = new Vector2(50), - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = GetType().Name, - Colour = getColourFor(GetType()).Lighten(0.8f), - Font = OsuFont.GetFont(size: 50), - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = "is not yet ready for use!", - Font = OsuFont.GetFont(size: 20), - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = "please check back a bit later.", - Font = OsuFont.GetFont(size: 14), - }, - } - }, - } - }, + message = new UnderConstructionMessage(GetType().Name), childModeButtons = new FillFlowContainer { Direction = FillDirection.Vertical, @@ -155,24 +76,24 @@ namespace osu.Game.Screens childModeButtons.Add(new ChildModeButton { Text = $@"{t.Name}", - BackgroundColour = getColourFor(t), - HoverColour = getColourFor(t).Lighten(0.2f), + BackgroundColour = getColourFor(t.Name), + HoverColour = getColourFor(t.Name).Lighten(0.2f), Action = delegate { this.Push(Activator.CreateInstance(t) as Screen); } }); } } } - private Color4 getColourFor(Type type) + private static Color4 getColourFor(object type) { - int hash = type.Name.GetHashCode(); + int hash = type.GetHashCode(); byte r = (byte)MathHelper.Clamp(((hash & 0xFF0000) >> 16) * 0.8f, 20, 255); byte g = (byte)MathHelper.Clamp(((hash & 0x00FF00) >> 8) * 0.8f, 20, 255); byte b = (byte)MathHelper.Clamp((hash & 0x0000FF) * 0.8f, 20, 255); return new Color4(r, g, b, 255); } - public class ChildModeButton : TwoLayerButton + private class ChildModeButton : TwoLayerButton { public ChildModeButton() { @@ -181,5 +102,104 @@ namespace osu.Game.Screens Origin = Anchor.BottomRight; } } + + public class UnderConstructionMessage : CompositeDrawable + { + public FillFlowContainer TextContainer { get; } + + private readonly Container boxContainer; + + public UnderConstructionMessage(string name) + { + RelativeSizeAxes = Axes.Both; + Size = new Vector2(0.3f); + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + var colour = getColourFor(name); + + InternalChildren = new Drawable[] + { + boxContainer = new Container + { + CornerRadius = 20, + Masking = true, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + + Colour = colour, + Alpha = 0.2f, + Blending = BlendingParameters.Additive, + }, + TextContainer = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new SpriteIcon + { + Icon = FontAwesome.Solid.UniversalAccess, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Size = new Vector2(50), + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = name, + Colour = colour.Lighten(0.8f), + Font = OsuFont.GetFont(size: 36), + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = "is not yet ready for use!", + Font = OsuFont.GetFont(size: 20), + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = "please check back a bit later.", + Font = OsuFont.GetFont(size: 14), + }, + } + }, + } + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + TextContainer.Position = new Vector2(DrawSize.X / 16, 0); + + boxContainer.Hide(); + boxContainer.ScaleTo(0.2f); + boxContainer.RotateTo(-20); + + using (BeginDelayedSequence(300, true)) + { + boxContainer.ScaleTo(1, transition_time, Easing.OutElastic); + boxContainer.RotateTo(0, transition_time / 2, Easing.OutQuint); + + TextContainer.MoveTo(Vector2.Zero, transition_time, Easing.OutExpo); + boxContainer.FadeIn(transition_time, Easing.OutExpo); + } + } + } } } From 3e904b4838b9169b80b0f6fc2381f0de6a47a6cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Oct 2019 14:37:56 +0900 Subject: [PATCH 1636/2815] Fix naming of file --- osu.Game/Screens/Edit/Timing/{SetupScreen.cs => TimingScreen.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game/Screens/Edit/Timing/{SetupScreen.cs => TimingScreen.cs} (100%) diff --git a/osu.Game/Screens/Edit/Timing/SetupScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs similarity index 100% rename from osu.Game/Screens/Edit/Timing/SetupScreen.cs rename to osu.Game/Screens/Edit/Timing/TimingScreen.cs From 4e026b163ccd15556945a8d769cad3b5c0a8ac34 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Oct 2019 15:03:48 +0900 Subject: [PATCH 1637/2815] Don't resume playback when user has paused and track hasn't changed --- osu.Game/Screens/Select/SongSelect.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 04a686f481..6c5f64ed6c 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -581,14 +581,24 @@ namespace osu.Game.Screens.Select beatmap.Track.Looping = true; } + private readonly WeakReference lastTrack = new WeakReference(null); + + /// + /// Ensures some music is playing for the current track. + /// Will resume playback from a manual user pause if the track has changed. + /// private void ensurePlayingSelected() { Track track = Beatmap.Value.Track; + bool isNewTrack = !lastTrack.TryGetTarget(out var last) || last != track; + track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; - if (!track.IsRunning) + if (!track.IsRunning && (music?.IsUserPaused != true || isNewTrack)) track.Restart(); + + lastTrack.SetTarget(track); } private void onBeatmapSetAdded(BeatmapSetInfo s) => Carousel.UpdateBeatmapSet(s); From f284d096b75fc63349f08fa0385ce8709e79f214 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Oct 2019 16:37:47 +0900 Subject: [PATCH 1638/2815] Fix ignored song select test --- osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 263eada07c..be054495cc 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -128,12 +128,10 @@ namespace osu.Game.Tests.Visual.SongSelect } [Test] - [Ignore("needs fixing")] public void TestImportUnderDifferentRuleset() { createSongSelect(); - changeRuleset(2); - addRulesetImportStep(0); + addRulesetImportStep(2); AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmap == null); } From 8ebccfe31fe098807ed355bf171c990557d6642f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Oct 2019 16:37:34 +0900 Subject: [PATCH 1639/2815] Add comprehensive audio state tests --- .../SongSelect/TestScenePlaySongSelect.cs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 263eada07c..5584de5484 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -16,6 +16,7 @@ using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; @@ -34,6 +35,8 @@ namespace osu.Game.Tests.Visual.SongSelect private RulesetStore rulesets; + private MusicController music; + private WorkingBeatmap defaultBeatmap; public override IReadOnlyList RequiredTypes => new[] @@ -79,6 +82,11 @@ namespace osu.Game.Tests.Visual.SongSelect Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, defaultBeatmap = Beatmap.Default)); + Dependencies.Cache(music = new MusicController()); + + // required to get bindables attached + Add(music); + Beatmap.SetDefault(); Dependencies.Cache(config = new OsuConfigManager(LocalStorage)); @@ -93,6 +101,57 @@ namespace osu.Game.Tests.Visual.SongSelect manager?.Delete(manager.GetAllUsableBeatmapSets()); }); + [Test] + public void TestAudioResuming() + { + createSongSelect(); + + addRulesetImportStep(0); + addRulesetImportStep(0); + + checkMusicPlaying(true); + AddStep("select first", () => songSelect.Carousel.SelectBeatmap(songSelect.Carousel.BeatmapSets.First().Beatmaps.First())); + checkMusicPlaying(true); + + AddStep("manual pause", () => music.TogglePause()); + checkMusicPlaying(false); + AddStep("select next difficulty", () => songSelect.Carousel.SelectNext(skipDifficulties: false)); + checkMusicPlaying(false); + + AddStep("select next set", () => songSelect.Carousel.SelectNext()); + checkMusicPlaying(true); + } + + [TestCase(false)] + [TestCase(true)] + public void TestAudioRemainsCorrectOnRulesetChange(bool rulesetsInSameBeatmap) + { + createSongSelect(); + + // start with non-osu! to avoid convert confusion + changeRuleset(1); + + if (rulesetsInSameBeatmap) + AddStep("import multi-ruleset map", () => + { + var usableRulesets = rulesets.AvailableRulesets.Where(r => r.ID != 2).ToArray(); + manager.Import(createTestBeatmapSet(0, usableRulesets)).Wait(); + }); + else + { + addRulesetImportStep(1); + addRulesetImportStep(0); + } + + checkMusicPlaying(true); + + AddStep("manual pause", () => music.TogglePause()); + checkMusicPlaying(false); + + changeRuleset(0); + checkMusicPlaying(!rulesetsInSameBeatmap); + } + [Test] public void TestDummy() { @@ -224,6 +283,9 @@ namespace osu.Game.Tests.Visual.SongSelect private static int importId; private int getImportId() => ++importId; + private void checkMusicPlaying(bool playing) => + AddUntilStep($"music {(playing ? "" : "not ")}playing", () => music.IsPlaying == playing); + private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.Acronym))}", () => Mods.Value = mods); private void changeRuleset(int id) => AddStep($"change ruleset to {id}", () => Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == id)); From b09d9b7e1f761e9070c0f3aa3629928729c1c7a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Oct 2019 17:56:56 +0900 Subject: [PATCH 1640/2815] Add todo in slider tail to avoid confusion --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 23c5494cf5..42bf5e4d21 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -40,6 +40,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables positionBindable.BindValueChanged(_ => updatePosition()); pathBindable.BindValueChanged(_ => updatePosition(), true); + + // TODO: This has no drawable content. Support for skins should be added. } protected override void CheckForResult(bool userTriggered, double timeOffset) From 4446a2972c7c36e48099b99fc597587156cf50c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Oct 2019 18:08:05 +0900 Subject: [PATCH 1641/2815] Move WaveContainer test out of editor namespace --- .../Visual/{Editor => UserInterface}/TestSceneWaveContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Tests/Visual/{Editor => UserInterface}/TestSceneWaveContainer.cs (97%) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneWaveContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneWaveContainer.cs similarity index 97% rename from osu.Game.Tests/Visual/Editor/TestSceneWaveContainer.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneWaveContainer.cs index de19727251..5b130b9224 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneWaveContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneWaveContainer.cs @@ -12,7 +12,7 @@ using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual.Editor +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestSceneWaveContainer : OsuTestScene From 08d043f44715b02f1f65ecdc59fe21ce6190ec2a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Oct 2019 18:57:03 +0900 Subject: [PATCH 1642/2815] Move selection relative to the hitobject start positions --- .../Blueprints/ManiaSelectionBlueprint.cs | 2 +- .../Edit/ManiaSelectionHandler.cs | 23 ++++---- .../Edit/OsuSelectionHandler.cs | 5 +- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 11 +++- .../Compose/Components/BlueprintContainer.cs | 9 +++- .../Compose/Components/SelectionDragEvent.cs | 53 +++++++++++++++++++ .../Compose/Components/SelectionHandler.cs | 2 +- 7 files changed, 87 insertions(+), 18 deletions(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/SelectionDragEvent.cs diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs index d3c12b1944..cc50459a0c 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints public Vector2 ScreenSpaceDragPosition { get; private set; } public Vector2 DragPosition { get; private set; } - protected new DrawableManiaHitObject HitObject => (DrawableManiaHitObject)base.HitObject; + public new DrawableManiaHitObject HitObject => (DrawableManiaHitObject)base.HitObject; protected IClock EditorClock { get; private set; } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 6f49c7f0c4..389837b059 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -3,7 +3,6 @@ using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints; @@ -31,11 +30,14 @@ namespace osu.Game.Rulesets.Mania.Edit editorClock = clock; } - public override void HandleDrag(SelectionBlueprint blueprint, DragEvent dragEvent) + public override void HandleDrag(SelectionBlueprint blueprint, SelectionDragEvent dragEvent) { - adjustOrigins((ManiaSelectionBlueprint)blueprint); + var maniaBlueprint = (ManiaSelectionBlueprint)blueprint; + int lastColumn = maniaBlueprint.HitObject.HitObject.Column; + + adjustOrigins(maniaBlueprint); performDragMovement(dragEvent); - performColumnMovement(dragEvent); + performColumnMovement(lastColumn, dragEvent); base.HandleDrag(blueprint, dragEvent); } @@ -62,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.Edit b.HitObject.Y += movementDelta; } - private void performDragMovement(DragEvent dragEvent) + private void performDragMovement(SelectionDragEvent dragEvent) { foreach (var b in SelectedBlueprints) { @@ -72,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Edit // Using the hitobject position is required since AdjustPosition can be invoked multiple times per frame // without the position having been updated by the parenting ScrollingHitObjectContainer - hitObject.Y += dragEvent.Delta.Y; + hitObject.Y += dragEvent.InstantDragDelta.Y; float targetPosition; @@ -94,14 +96,13 @@ namespace osu.Game.Rulesets.Mania.Edit } } - private void performColumnMovement(DragEvent dragEvent) + private void performColumnMovement(int lastColumn, SelectionDragEvent dragEvent) { - var lastColumn = composer.ColumnAt(dragEvent.ScreenSpaceLastMousePosition); - var currentColumn = composer.ColumnAt(dragEvent.ScreenSpaceMousePosition); - if (lastColumn == null || currentColumn == null) + var currentColumn = composer.ColumnAt(dragEvent.ScreenSpaceDragPosition); + if (currentColumn == null) return; - int columnDelta = currentColumn.Index - lastColumn.Index; + int columnDelta = currentColumn.Index - lastColumn; if (columnDelta == 0) return; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 1ab1219ab0..4a0e88889b 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using osu.Framework.Input.Events; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; @@ -11,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Edit { public class OsuSelectionHandler : SelectionHandler { - public override void HandleDrag(SelectionBlueprint blueprint, DragEvent dragEvent) + public override void HandleDrag(SelectionBlueprint blueprint, SelectionDragEvent dragEvent) { foreach (var h in SelectedHitObjects.OfType()) { @@ -21,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Edit continue; } - h.Position += dragEvent.Delta; + h.Position += dragEvent.InstantDragDelta; } base.HandleDrag(blueprint, dragEvent); diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 0f77b8d584..aa0dd1cc25 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -45,6 +45,11 @@ namespace osu.Game.Rulesets.Edit /// public readonly DrawableHitObject HitObject; + /// + /// The screen-space position of when a drag was started. + /// + public Vector2 ScreenSpaceDragStartPosition { get; private set; } + protected override bool ShouldBeAlive => (HitObject.IsAlive && HitObject.IsPresent) || State == SelectionState.Selected; public override bool HandlePositionalInput => ShouldBeAlive; public override bool RemoveWhenNotAlive => false; @@ -131,7 +136,11 @@ namespace osu.Game.Rulesets.Edit return base.OnClick(e); } - protected override bool OnDragStart(DragStartEvent e) => true; + protected override bool OnDragStart(DragStartEvent e) + { + ScreenSpaceDragStartPosition = HitObject.ToScreenSpace(HitObject.OriginPosition); + return true; + } protected override bool OnDrag(DragEvent e) { diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index d96d88c2b9..3286be4be6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -216,7 +216,14 @@ namespace osu.Game.Screens.Edit.Compose.Components private void onSelectionRequested(SelectionBlueprint blueprint, InputState state) => selectionHandler.HandleSelectionRequested(blueprint, state); - private void onDragRequested(SelectionBlueprint blueprint, DragEvent dragEvent) => selectionHandler.HandleDrag(blueprint, dragEvent); + private void onDragRequested(SelectionBlueprint blueprint, DragEvent dragEvent) + { + var dragPosition = blueprint.ScreenSpaceDragStartPosition + dragEvent.ScreenSpaceMousePosition - dragEvent.ScreenSpaceMouseDownPosition; + + // Todo: Snap dragPosition + + selectionHandler.HandleDrag(blueprint, new SelectionDragEvent(blueprint, blueprint.ScreenSpaceDragStartPosition, dragPosition)); + } protected override void Dispose(bool isDisposing) { diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionDragEvent.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionDragEvent.cs new file mode 100644 index 0000000000..8e00e8c30c --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionDragEvent.cs @@ -0,0 +1,53 @@ +// 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.Edit; +using osuTK; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + /// + /// An event which occurs when a is dragged. + /// + public class SelectionDragEvent + { + /// + /// The dragged . + /// + public readonly SelectionBlueprint DraggedBlueprint; + + /// + /// The screen-space position of the hitobject at the start of the drag. + /// + public readonly Vector2 ScreenSpaceDragStartPosition; + + /// + /// The new screen-space position of the hitobject at the current drag point. + /// + public readonly Vector2 ScreenSpaceDragPosition; + + /// + /// The distance between and the hitobject's current position, in the coordinate-space of the hitobject's parent. + /// + /// + /// This does not use and does not represent the cumulative drag distance. + /// + public readonly Vector2 InstantDragDelta; + + public SelectionDragEvent(SelectionBlueprint blueprint, Vector2 screenSpaceDragStartPosition, Vector2 screenSpaceDragPosition) + { + DraggedBlueprint = blueprint; + ScreenSpaceDragStartPosition = screenSpaceDragStartPosition; + ScreenSpaceDragPosition = screenSpaceDragPosition; + + InstantDragDelta = toLocalSpace(ScreenSpaceDragPosition) - DraggedBlueprint.HitObject.Position; + } + + /// + /// Converts a screen-space position into the coordinate space of the hitobject's parents. + /// + /// The screen-space position. + /// The position in the coordinate space of the hitobject's parent. + private Vector2 toLocalSpace(Vector2 screenSpacePosition) => DraggedBlueprint.HitObject.Parent.ToLocalSpace(screenSpacePosition); + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 11e649168f..3fb06c8ee8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -69,7 +69,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// The that received the drag event. /// The drag event. - public virtual void HandleDrag(SelectionBlueprint blueprint, DragEvent dragEvent) + public virtual void HandleDrag(SelectionBlueprint blueprint, SelectionDragEvent dragEvent) { } From 8b661e624d25334b7c1b67db6b6c83c138a51ec1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Oct 2019 19:08:23 +0900 Subject: [PATCH 1643/2815] Remove drag from class/method namings + refactor --- .../Edit/ManiaSelectionHandler.cs | 19 ++++--- .../Edit/OsuSelectionHandler.cs | 7 ++- .../Compose/Components/BlueprintContainer.cs | 2 +- .../Compose/Components/MoveSelectionEvent.cs | 53 +++++++++++++++++++ .../Compose/Components/SelectionDragEvent.cs | 53 ------------------- .../Compose/Components/SelectionHandler.cs | 7 ++- 6 files changed, 69 insertions(+), 72 deletions(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs delete mode 100644 osu.Game/Screens/Edit/Compose/Components/SelectionDragEvent.cs diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 389837b059..f576c43e52 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -4,7 +4,6 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Timing; -using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.UI; @@ -30,16 +29,16 @@ namespace osu.Game.Rulesets.Mania.Edit editorClock = clock; } - public override void HandleDrag(SelectionBlueprint blueprint, SelectionDragEvent dragEvent) + public override void HandleMovement(MoveSelectionEvent moveEvent) { - var maniaBlueprint = (ManiaSelectionBlueprint)blueprint; + var maniaBlueprint = (ManiaSelectionBlueprint)moveEvent.Blueprint; int lastColumn = maniaBlueprint.HitObject.HitObject.Column; adjustOrigins(maniaBlueprint); - performDragMovement(dragEvent); - performColumnMovement(lastColumn, dragEvent); + performDragMovement(moveEvent); + performColumnMovement(lastColumn, moveEvent); - base.HandleDrag(blueprint, dragEvent); + base.HandleMovement(moveEvent); } /// @@ -64,7 +63,7 @@ namespace osu.Game.Rulesets.Mania.Edit b.HitObject.Y += movementDelta; } - private void performDragMovement(SelectionDragEvent dragEvent) + private void performDragMovement(MoveSelectionEvent moveEvent) { foreach (var b in SelectedBlueprints) { @@ -74,7 +73,7 @@ namespace osu.Game.Rulesets.Mania.Edit // Using the hitobject position is required since AdjustPosition can be invoked multiple times per frame // without the position having been updated by the parenting ScrollingHitObjectContainer - hitObject.Y += dragEvent.InstantDragDelta.Y; + hitObject.Y += moveEvent.InstantDelta.Y; float targetPosition; @@ -96,9 +95,9 @@ namespace osu.Game.Rulesets.Mania.Edit } } - private void performColumnMovement(int lastColumn, SelectionDragEvent dragEvent) + private void performColumnMovement(int lastColumn, MoveSelectionEvent moveEvent) { - var currentColumn = composer.ColumnAt(dragEvent.ScreenSpaceDragPosition); + var currentColumn = composer.ColumnAt(moveEvent.ScreenSpacePosition); if (currentColumn == null) return; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 4a0e88889b..472267eb66 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; @@ -10,7 +9,7 @@ namespace osu.Game.Rulesets.Osu.Edit { public class OsuSelectionHandler : SelectionHandler { - public override void HandleDrag(SelectionBlueprint blueprint, SelectionDragEvent dragEvent) + public override void HandleMovement(MoveSelectionEvent moveEvent) { foreach (var h in SelectedHitObjects.OfType()) { @@ -20,10 +19,10 @@ namespace osu.Game.Rulesets.Osu.Edit continue; } - h.Position += dragEvent.InstantDragDelta; + h.Position += moveEvent.InstantDelta; } - base.HandleDrag(blueprint, dragEvent); + base.HandleMovement(moveEvent); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 3286be4be6..593d70b24f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -222,7 +222,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // Todo: Snap dragPosition - selectionHandler.HandleDrag(blueprint, new SelectionDragEvent(blueprint, blueprint.ScreenSpaceDragStartPosition, dragPosition)); + selectionHandler.HandleMovement(new MoveSelectionEvent(blueprint, blueprint.ScreenSpaceDragStartPosition, dragPosition)); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs new file mode 100644 index 0000000000..a75226d2bc --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs @@ -0,0 +1,53 @@ +// 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.Edit; +using osuTK; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + /// + /// An event which occurs when a is moved. + /// + public class MoveSelectionEvent + { + /// + /// The that triggered this . + /// + public readonly SelectionBlueprint Blueprint; + + /// + /// The starting screen-space position of the hitobject. + /// + public readonly Vector2 ScreenSpaceStartPosition; + + /// + /// The expected screen-space position of the hitobject at the current cursor position. + /// + public readonly Vector2 ScreenSpacePosition; + + /// + /// The distance between and the hitobject's current position, in the coordinate-space of the hitobject's parent. + /// + /// + /// This does not use and does not represent the cumulative movement distance. + /// + public readonly Vector2 InstantDelta; + + public MoveSelectionEvent(SelectionBlueprint blueprint, Vector2 screenSpaceStartPosition, Vector2 screenSpacePosition) + { + Blueprint = blueprint; + ScreenSpaceStartPosition = screenSpaceStartPosition; + ScreenSpacePosition = screenSpacePosition; + + InstantDelta = toLocalSpace(ScreenSpacePosition) - Blueprint.HitObject.Position; + } + + /// + /// Converts a screen-space position into the coordinate space of the hitobject's parents. + /// + /// The screen-space position. + /// The position in the coordinate space of the hitobject's parent. + private Vector2 toLocalSpace(Vector2 screenSpacePosition) => Blueprint.HitObject.Parent.ToLocalSpace(screenSpacePosition); + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionDragEvent.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionDragEvent.cs deleted file mode 100644 index 8e00e8c30c..0000000000 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionDragEvent.cs +++ /dev/null @@ -1,53 +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.Game.Rulesets.Edit; -using osuTK; - -namespace osu.Game.Screens.Edit.Compose.Components -{ - /// - /// An event which occurs when a is dragged. - /// - public class SelectionDragEvent - { - /// - /// The dragged . - /// - public readonly SelectionBlueprint DraggedBlueprint; - - /// - /// The screen-space position of the hitobject at the start of the drag. - /// - public readonly Vector2 ScreenSpaceDragStartPosition; - - /// - /// The new screen-space position of the hitobject at the current drag point. - /// - public readonly Vector2 ScreenSpaceDragPosition; - - /// - /// The distance between and the hitobject's current position, in the coordinate-space of the hitobject's parent. - /// - /// - /// This does not use and does not represent the cumulative drag distance. - /// - public readonly Vector2 InstantDragDelta; - - public SelectionDragEvent(SelectionBlueprint blueprint, Vector2 screenSpaceDragStartPosition, Vector2 screenSpaceDragPosition) - { - DraggedBlueprint = blueprint; - ScreenSpaceDragStartPosition = screenSpaceDragStartPosition; - ScreenSpaceDragPosition = screenSpaceDragPosition; - - InstantDragDelta = toLocalSpace(ScreenSpaceDragPosition) - DraggedBlueprint.HitObject.Position; - } - - /// - /// Converts a screen-space position into the coordinate space of the hitobject's parents. - /// - /// The screen-space position. - /// The position in the coordinate space of the hitobject's parent. - private Vector2 toLocalSpace(Vector2 screenSpacePosition) => DraggedBlueprint.HitObject.Parent.ToLocalSpace(screenSpacePosition); - } -} diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 3fb06c8ee8..c9e862d99e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -65,11 +65,10 @@ namespace osu.Game.Screens.Edit.Compose.Components #region User Input Handling /// - /// Handles the selected s being dragged. + /// Handles the selected s being moved. /// - /// The that received the drag event. - /// The drag event. - public virtual void HandleDrag(SelectionBlueprint blueprint, SelectionDragEvent dragEvent) + /// The move event. + public virtual void HandleMovement(MoveSelectionEvent moveEvent) { } From c1db11fa0612622cce5182a987847dc96701e66b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Oct 2019 19:24:58 +0900 Subject: [PATCH 1644/2815] More removal of "drag" --- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 6 +++--- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 6 ++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index aa0dd1cc25..838984b223 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -46,9 +46,9 @@ namespace osu.Game.Rulesets.Edit public readonly DrawableHitObject HitObject; /// - /// The screen-space position of when a drag was started. + /// The screen-space position of prior to handling a movement event. /// - public Vector2 ScreenSpaceDragStartPosition { get; private set; } + internal Vector2 ScreenSpaceMovementStartPosition { get; private set; } protected override bool ShouldBeAlive => (HitObject.IsAlive && HitObject.IsPresent) || State == SelectionState.Selected; public override bool HandlePositionalInput => ShouldBeAlive; @@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Edit protected override bool OnDragStart(DragStartEvent e) { - ScreenSpaceDragStartPosition = HitObject.ToScreenSpace(HitObject.OriginPosition); + ScreenSpaceMovementStartPosition = HitObject.ToScreenSpace(HitObject.OriginPosition); return true; } diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 593d70b24f..2de5ecf633 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -218,11 +218,9 @@ namespace osu.Game.Screens.Edit.Compose.Components private void onDragRequested(SelectionBlueprint blueprint, DragEvent dragEvent) { - var dragPosition = blueprint.ScreenSpaceDragStartPosition + dragEvent.ScreenSpaceMousePosition - dragEvent.ScreenSpaceMouseDownPosition; + var movePosition = blueprint.ScreenSpaceMovementStartPosition + dragEvent.ScreenSpaceMousePosition - dragEvent.ScreenSpaceMouseDownPosition; - // Todo: Snap dragPosition - - selectionHandler.HandleMovement(new MoveSelectionEvent(blueprint, blueprint.ScreenSpaceDragStartPosition, dragPosition)); + selectionHandler.HandleMovement(new MoveSelectionEvent(blueprint, blueprint.ScreenSpaceMovementStartPosition, movePosition)); } protected override void Dispose(bool isDisposing) From 179656788dd87caae95c35c3e8e4b0372106bcbd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 8 Oct 2019 19:27:59 +0900 Subject: [PATCH 1645/2815] Remove unnecessary method --- .../Edit/Compose/Components/MoveSelectionEvent.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs index a75226d2bc..13945381bb 100644 --- a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs +++ b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs @@ -40,14 +40,7 @@ namespace osu.Game.Screens.Edit.Compose.Components ScreenSpaceStartPosition = screenSpaceStartPosition; ScreenSpacePosition = screenSpacePosition; - InstantDelta = toLocalSpace(ScreenSpacePosition) - Blueprint.HitObject.Position; + InstantDelta = Blueprint.HitObject.Parent.ToLocalSpace(ScreenSpacePosition) - Blueprint.HitObject.Position; } - - /// - /// Converts a screen-space position into the coordinate space of the hitobject's parents. - /// - /// The screen-space position. - /// The position in the coordinate space of the hitobject's parent. - private Vector2 toLocalSpace(Vector2 screenSpacePosition) => Blueprint.HitObject.Parent.ToLocalSpace(screenSpacePosition); } } From 4b1a40dabaebadb49d07cbc077d71709714fe6d8 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 8 Oct 2019 13:31:49 +0300 Subject: [PATCH 1646/2815] Implement temp fix to get the actual message --- osu.Game/Online/API/Requests/Responses/Comment.cs | 6 ++++++ osu.Game/Overlays/CommentsContainer.cs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs index e4b66ddeee..ba71faa843 100644 --- a/osu.Game/Online/API/Requests/Responses/Comment.cs +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -76,5 +76,11 @@ namespace osu.Game.Online.API.Requests.Responses public bool IsTopLevel { get; set; } public bool IsDeleted { get; set; } + + public string GetMessage() + { + //temporary fix until HTML parsing will be implemented + return MessageHTML.Remove(MessageHTML.LastIndexOf("

")).Substring(65); + } } } diff --git a/osu.Game/Overlays/CommentsContainer.cs b/osu.Game/Overlays/CommentsContainer.cs index b22fefb3de..1b4bbee6a1 100644 --- a/osu.Game/Overlays/CommentsContainer.cs +++ b/osu.Game/Overlays/CommentsContainer.cs @@ -107,7 +107,7 @@ namespace osu.Game.Overlays { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Text = comment.MessageHTML, + Text = comment.GetMessage(), }, new Container { From 59b2f028289521f6eef68456662ca9255913287d Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Tue, 8 Oct 2019 18:34:09 +0800 Subject: [PATCH 1647/2815] initial implementation of customizable mods --- osu.Game/Overlays/Mods/ModControlSection.cs | 56 ++++++++++++++++++++ osu.Game/Overlays/Mods/ModSelectOverlay.cs | 58 +++++++++++++++++++++ osu.Game/Rulesets/Mods/IModHasSettings.cs | 15 ++++++ osu.Game/Rulesets/Mods/Mod.cs | 2 +- 4 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Overlays/Mods/ModControlSection.cs create mode 100644 osu.Game/Rulesets/Mods/IModHasSettings.cs diff --git a/osu.Game/Overlays/Mods/ModControlSection.cs b/osu.Game/Overlays/Mods/ModControlSection.cs new file mode 100644 index 0000000000..df6f4d15b0 --- /dev/null +++ b/osu.Game/Overlays/Mods/ModControlSection.cs @@ -0,0 +1,56 @@ +// 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.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osuTK; + +namespace osu.Game.Overlays.Mods +{ + public class ModControlSection : Container + { + protected FillFlowContainer FlowContent; + protected override Container Content => FlowContent; + + public readonly Mod Mod; + + public ModControlSection(Mod mod) + { + Mod = mod; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + FlowContent = new FillFlowContainer + { + Margin = new MarginPadding { Top = 30 }, + Spacing = new Vector2(0, 5), + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + }; + + if (Mod is IModHasSettings modHasSettings) + AddRange(modHasSettings.CreateControls()); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AddRangeInternal(new Drawable[] + { + new OsuSpriteText + { + Text = Mod.Name, + Font = OsuFont.GetFont(weight: FontWeight.Bold), + Colour = colours.Yellow, + }, + FlowContent + }); + } + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 9ff320841a..5493080964 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -31,6 +31,7 @@ namespace osu.Game.Overlays.Mods public class ModSelectOverlay : WaveOverlayContainer { protected readonly TriangleButton DeselectAllButton; + protected readonly TriangleButton CustomizeButton; protected readonly TriangleButton CloseButton; protected readonly OsuSpriteText MultiplierLabel; @@ -42,6 +43,10 @@ namespace osu.Game.Overlays.Mods protected readonly FillFlowContainer ModSectionsContainer; + protected readonly FillFlowContainer ModSettingsContent; + + protected readonly Container ModSettingsContainer; + protected readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); protected readonly IBindable Ruleset = new Bindable(); @@ -226,6 +231,16 @@ namespace osu.Game.Overlays.Mods Right = 20 } }, + CustomizeButton = new TriangleButton + { + Width = 180, + Text = "Customization", + Action = () => ModSettingsContainer.Alpha = ModSettingsContainer.Alpha == 1 ? 0 : 1, + Margin = new MarginPadding + { + Right = 20 + } + }, CloseButton = new TriangleButton { Width = 180, @@ -271,6 +286,36 @@ namespace osu.Game.Overlays.Mods }, }, }, + ModSettingsContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Width = 0.25f, + Alpha = 0, + X = -100, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new Color4(0, 0, 0, 192) + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = ModSettingsContent = new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0f, 10f), + Padding = new MarginPadding(20), + } + } + } + } }; } @@ -284,10 +329,23 @@ namespace osu.Game.Overlays.Mods Ruleset.BindTo(ruleset); if (mods != null) SelectedMods.BindTo(mods); + SelectedMods.ValueChanged += updateModSettings; + sampleOn = audio.Samples.Get(@"UI/check-on"); sampleOff = audio.Samples.Get(@"UI/check-off"); } + private void updateModSettings(ValueChangedEvent> selectedMods) + { + var added = selectedMods.NewValue.Except(selectedMods.OldValue).FirstOrDefault(); + var removed = selectedMods.OldValue.Except(selectedMods.NewValue).FirstOrDefault(); + + if (added is IModHasSettings) + ModSettingsContent.Add(new ModControlSection(added)); + else if (removed is IModHasSettings) + ModSettingsContent.Remove(ModSettingsContent.Children.Where(section => section.Mod == removed).FirstOrDefault()); + } + public void DeselectAll() { foreach (var section in ModSectionsContainer.Children) diff --git a/osu.Game/Rulesets/Mods/IModHasSettings.cs b/osu.Game/Rulesets/Mods/IModHasSettings.cs new file mode 100644 index 0000000000..004279f71d --- /dev/null +++ b/osu.Game/Rulesets/Mods/IModHasSettings.cs @@ -0,0 +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 osu.Framework.Graphics; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// An interface for mods that allows user control over it's properties. + /// + public interface IModHasSettings + { + Drawable[] CreateControls(); + } +} diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 023d37497a..1c280c820d 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Mods /// /// Creates a copy of this initialised to a default state. /// - public virtual Mod CreateCopy() => (Mod)Activator.CreateInstance(GetType()); + public virtual Mod CreateCopy() => (Mod)MemberwiseClone(); public bool Equals(IMod other) => GetType() == other?.GetType(); } From 801b5b474e300343bafc0defc0a8818e3516919d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 8 Oct 2019 13:45:13 +0300 Subject: [PATCH 1648/2815] Add a User property to the comment for easy access --- .../API/Requests/Responses/APIComments.cs | 20 ++++++++++++++++- .../Online/API/Requests/Responses/Comment.cs | 3 +++ osu.Game/Overlays/CommentsContainer.cs | 22 +++++++++++++++++-- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIComments.cs b/osu.Game/Online/API/Requests/Responses/APIComments.cs index af7650e512..86bbf0358a 100644 --- a/osu.Game/Online/API/Requests/Responses/APIComments.cs +++ b/osu.Game/Online/API/Requests/Responses/APIComments.cs @@ -24,8 +24,26 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"included_comments")] public List IncludedComments { get; set; } + private List users; + [JsonProperty(@"users")] - public List Users { get; set; } + public List Users + { + get => users; + set + { + users = value; + + value.ForEach(u => + { + Comments.ForEach(c => + { + if (c.UserId == u.Id) + c.User = u; + }); + }); + } + } [JsonProperty(@"total")] public int Total { get; set; } diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs index ba71faa843..46212dd50f 100644 --- a/osu.Game/Online/API/Requests/Responses/Comment.cs +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using Newtonsoft.Json; +using osu.Game.Users; using System; namespace osu.Game.Online.API.Requests.Responses @@ -27,6 +28,8 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"user_id")] public long UserId { get; set; } + public User User { get; set; } + [JsonProperty(@"message")] public string Message { get; set; } diff --git a/osu.Game/Overlays/CommentsContainer.cs b/osu.Game/Overlays/CommentsContainer.cs index 1b4bbee6a1..c871810d8a 100644 --- a/osu.Game/Overlays/CommentsContainer.cs +++ b/osu.Game/Overlays/CommentsContainer.cs @@ -103,11 +103,29 @@ namespace osu.Game.Overlays Height = 70, Children = new Drawable[] { - new SpriteText + new FillFlowContainer { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Text = comment.GetMessage(), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 3), + Children = new[] + { + new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = $"user: {comment.User.Username}", + }, + new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = $"message: {comment.GetMessage()}", + }, + } }, new Container { From 9375ef5eeae16c551b9e950becbb148a1a782dfd Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Tue, 8 Oct 2019 19:42:15 +0800 Subject: [PATCH 1649/2815] clear settings controls when changing rulesets --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 5493080964..a344bcc254 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -330,6 +330,7 @@ namespace osu.Game.Overlays.Mods if (mods != null) SelectedMods.BindTo(mods); SelectedMods.ValueChanged += updateModSettings; + Ruleset.ValueChanged += _ => ModSettingsContent.Clear(); sampleOn = audio.Samples.Get(@"UI/check-on"); sampleOff = audio.Samples.Get(@"UI/check-off"); From 1c89841949e025fe693aab73a97dd9a93c78b85b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 8 Oct 2019 14:51:12 +0300 Subject: [PATCH 1650/2815] Move all the logic to it's own namespace --- .../Online/TestSceneCommentsContainer.cs | 2 +- .../{ => Comments}/CommentsContainer.cs | 67 +++---------- osu.Game/Overlays/Comments/DrawableComment.cs | 99 +++++++++++++++++++ 3 files changed, 116 insertions(+), 52 deletions(-) rename osu.Game/Overlays/{ => Comments}/CommentsContainer.cs (73%) create mode 100644 osu.Game/Overlays/Comments/DrawableComment.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs index bf4117189a..e6f9582910 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -4,10 +4,10 @@ using System; using System.Collections.Generic; using NUnit.Framework; -using osu.Game.Overlays; using osu.Game.Online.API.Requests; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; +using osu.Game.Overlays.Comments; namespace osu.Game.Tests.Visual.Online { diff --git a/osu.Game/Overlays/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs similarity index 73% rename from osu.Game/Overlays/CommentsContainer.cs rename to osu.Game/Overlays/Comments/CommentsContainer.cs index c871810d8a..60b22428f0 100644 --- a/osu.Game/Overlays/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -13,7 +13,7 @@ using osu.Framework.Graphics.Sprites; using osuTK; using osu.Game.Online.API.Requests.Responses; -namespace osu.Game.Overlays +namespace osu.Game.Overlays.Comments { public class CommentsContainer : CompositeDrawable { @@ -90,59 +90,24 @@ namespace osu.Game.Overlays foreach (var c in response.Comments) { - if (!c.IsDeleted) - createDrawableComment(c); + if (!c.IsDeleted && c.IsTopLevel) + content.AddRange(new Drawable[] + { + new DrawableComment(c), + new Container + { + RelativeSizeAxes = Axes.X, + Height = 1, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Gray1, + } + } + }); } } - private void createDrawableComment(Comment comment) - { - content.Add(new Container - { - RelativeSizeAxes = Axes.X, - Height = 70, - Children = new Drawable[] - { - new FillFlowContainer - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 3), - Children = new[] - { - new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = $"user: {comment.User.Username}", - }, - new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = $"message: {comment.GetMessage()}", - }, - } - }, - new Container - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.X, - Height = 1, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Gray1, - } - } - } - }); - } - [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs new file mode 100644 index 0000000000..bbb804dc5b --- /dev/null +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -0,0 +1,99 @@ +// 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.Containers; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; +using osuTK; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Users.Drawables; +using osu.Game.Graphics.Containers; +using osu.Game.Utils; + +namespace osu.Game.Overlays.Comments +{ + public class DrawableComment : CompositeDrawable + { + private const int avatar_size = 40; + private const int margin = 10; + + public DrawableComment(Comment comment) + { + LinkFlowContainer username; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding(margin), + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] + { + new UpdateableAvatar(comment.User) + { + Size = new Vector2(avatar_size), + Margin = new MarginPadding { Horizontal = margin }, + Masking = true, + CornerRadius = avatar_size / 2, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Spacing = new Vector2(0, 2), + Children = new Drawable[] + { + username = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true)) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + }, + new TextFlowContainer(s => s.Font = OsuFont.GetFont(size: 18)) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Text = comment.GetMessage() + } + } + } + }, + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + }, + new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: 12), + Text = HumanizerUtils.Humanize(comment.CreatedAt) + } + } + } + }; + + username.AddUserLink(comment.User); + } + } +} From 2564214a72b70007368fa3c5763c407fab2192ac Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 8 Oct 2019 15:01:18 +0300 Subject: [PATCH 1651/2815] Fix some padding issues with the big comments --- osu.Game/Overlays/Comments/DrawableComment.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index bbb804dc5b..6f3d92c7fd 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -54,21 +54,16 @@ namespace osu.Game.Overlays.Comments { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Top = margin / 2 }, Spacing = new Vector2(0, 2), Children = new Drawable[] { username = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true)) { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, }, - new TextFlowContainer(s => s.Font = OsuFont.GetFont(size: 18)) + new TextFlowContainer(s => s.Font = OsuFont.GetFont(size: 14)) { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Text = comment.GetMessage() From 451a7342ce7459ae42562521e549af8e7e8a1d5b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 8 Oct 2019 15:39:03 +0300 Subject: [PATCH 1652/2815] Parse child comments --- .../API/Requests/Responses/APIComments.cs | 22 +++- .../Online/API/Requests/Responses/Comment.cs | 3 + .../Overlays/Comments/CommentsContainer.cs | 4 +- osu.Game/Overlays/Comments/DrawableComment.cs | 106 +++++++++++------- 4 files changed, 92 insertions(+), 43 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIComments.cs b/osu.Game/Online/API/Requests/Responses/APIComments.cs index 86bbf0358a..9a12fb613e 100644 --- a/osu.Game/Online/API/Requests/Responses/APIComments.cs +++ b/osu.Game/Online/API/Requests/Responses/APIComments.cs @@ -9,8 +9,28 @@ namespace osu.Game.Online.API.Requests.Responses { public class APIComments { + private List comments; + [JsonProperty(@"comments")] - public List Comments { get; set; } + public List Comments + { + get => comments; + set + { + comments = value; + comments.ForEach(child => + { + if (child.ParentId != null) + { + comments.ForEach(parent => + { + if (parent.Id == child.ParentId) + parent.ChildComments.Add(child); + }); + } + }); + } + } [JsonProperty(@"has_more")] public bool HasMore { get; set; } diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs index 46212dd50f..cdc3c3204b 100644 --- a/osu.Game/Online/API/Requests/Responses/Comment.cs +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -4,6 +4,7 @@ using Newtonsoft.Json; using osu.Game.Users; using System; +using System.Collections.Generic; namespace osu.Game.Online.API.Requests.Responses { @@ -25,6 +26,8 @@ namespace osu.Game.Online.API.Requests.Responses } } + public List ChildComments = new List(); + [JsonProperty(@"user_id")] public long UserId { get; set; } diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 60b22428f0..d02e74a5a4 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -17,6 +17,8 @@ namespace osu.Game.Overlays.Comments { public class CommentsContainer : CompositeDrawable { + private const float separator_height = 1.5f; + private readonly CommentableType type; private readonly long id; @@ -97,7 +99,7 @@ namespace osu.Game.Overlays.Comments new Container { RelativeSizeAxes = Axes.X, - Height = 1, + Height = separator_height, Child = new Box { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 6f3d92c7fd..53366be878 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -17,78 +17,102 @@ namespace osu.Game.Overlays.Comments { private const int avatar_size = 40; private const int margin = 10; + private const int child_margin = 20; public DrawableComment(Comment comment) { LinkFlowContainer username; + FillFlowContainer childCommentsContainer; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - InternalChild = new GridContainer + InternalChild = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Margin = new MarginPadding(margin), - ColumnDimensions = new[] + Direction = FillDirection.Vertical, + Children = new Drawable[] { - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new Drawable[] + new GridContainer { - new UpdateableAvatar(comment.User) + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding(margin), + ColumnDimensions = new[] { - Size = new Vector2(avatar_size), - Margin = new MarginPadding { Horizontal = margin }, - Masking = true, - CornerRadius = avatar_size / 2, + new Dimension(GridSizeMode.AutoSize), + new Dimension(), }, - new FillFlowContainer + RowDimensions = new[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Top = margin / 2 }, - Spacing = new Vector2(0, 2), - Children = new Drawable[] + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] { - username = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true)) + new UpdateableAvatar(comment.User) { - AutoSizeAxes = Axes.Both, + Size = new Vector2(avatar_size), + Margin = new MarginPadding { Horizontal = margin }, + Masking = true, + CornerRadius = avatar_size / 2, }, - new TextFlowContainer(s => s.Font = OsuFont.GetFont(size: 14)) + new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Text = comment.GetMessage() + Margin = new MarginPadding { Top = margin / 2 }, + Spacing = new Vector2(0, 2), + Children = new Drawable[] + { + username = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true)) + { + AutoSizeAxes = Axes.Both, + }, + new TextFlowContainer(s => s.Font = OsuFont.GetFont(size: 14)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Text = comment.GetMessage() + } + } + } + }, + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + }, + new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: 12), + Text = HumanizerUtils.Humanize(comment.CreatedAt) } } } }, - new Drawable[] + childCommentsContainer = new FillFlowContainer { - new Container - { - RelativeSizeAxes = Axes.Both, - }, - new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: 12), - Text = HumanizerUtils.Humanize(comment.CreatedAt) - } + Margin = new MarginPadding { Left = child_margin }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical } } }; username.AddUserLink(comment.User); + + comment.ChildComments.ForEach(c => + { + if (!c.IsDeleted) + childCommentsContainer.Add(new DrawableComment(c)); + }); } } } From 9c7e403cf8ca62b83ded5505c030b77f8ee0a81b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 8 Oct 2019 16:00:34 +0300 Subject: [PATCH 1653/2815] Implement replies button --- osu.Game/Overlays/Comments/DrawableComment.cs | 98 +++++++++++++++++-- 1 file changed, 90 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 53366be878..9879995b00 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -10,6 +10,8 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Users.Drawables; using osu.Game.Graphics.Containers; using osu.Game.Utils; +using osu.Framework.Input.Events; +using System; namespace osu.Game.Overlays.Comments { @@ -18,11 +20,39 @@ namespace osu.Game.Overlays.Comments private const int avatar_size = 40; private const int margin = 10; private const int child_margin = 20; + private const int duration = 200; + + private bool childExpanded = true; + + public bool ChildExpanded + { + get => childExpanded; + set + { + if (childExpanded == value) + return; + + childExpanded = value; + + childCommentsVisibilityContainer.ClearTransforms(); + + if (childExpanded) + childCommentsVisibilityContainer.AutoSizeAxes = Axes.Y; + else + { + childCommentsVisibilityContainer.AutoSizeAxes = Axes.None; + childCommentsVisibilityContainer.ResizeHeightTo(0, duration, Easing.OutQuint); + } + } + } + + private readonly Container childCommentsVisibilityContainer; public DrawableComment(Comment comment) { LinkFlowContainer username; FillFlowContainer childCommentsContainer; + RepliesButton replies; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -86,22 +116,40 @@ namespace osu.Game.Overlays.Comments { RelativeSizeAxes = Axes.Both, }, - new SpriteText + new FillFlowContainer { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: 12), - Text = HumanizerUtils.Humanize(comment.CreatedAt) + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), + Children = new Drawable[] + { + new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: 12), + Text = HumanizerUtils.Humanize(comment.CreatedAt) + }, + replies = new RepliesButton(comment.RepliesCount), + } } } } }, - childCommentsContainer = new FillFlowContainer + childCommentsVisibilityContainer = new Container { - Margin = new MarginPadding { Left = child_margin }, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical + AutoSizeDuration = duration, + AutoSizeEasing = Easing.OutQuint, + Masking = true, + Child = childCommentsContainer = new FillFlowContainer + { + Margin = new MarginPadding { Left = child_margin }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical + } } } }; @@ -113,6 +161,40 @@ namespace osu.Game.Overlays.Comments if (!c.IsDeleted) childCommentsContainer.Add(new DrawableComment(c)); }); + + replies.Action += expanded => ChildExpanded = expanded; + } + + private class RepliesButton : Container + { + private readonly SpriteText text; + private bool expanded; + private readonly int count; + + public Action Action; + + public RepliesButton(int count) + { + this.count = count; + + AutoSizeAxes = Axes.Both; + Alpha = count == 0 ? 0 : 1; + Child = text = new SpriteText + { + Font = OsuFont.GetFont(size: 12), + Text = $@"[-] replies ({count})" + }; + + expanded = true; + } + + protected override bool OnClick(ClickEvent e) + { + text.Text = $@"{(expanded ? "[+]" : "[-]")} replies ({count})"; + expanded = !expanded; + Action?.Invoke(expanded); + return base.OnClick(e); + } } } } From 3f8fecbc5034d2b601ea6e6bc3643e25b8e13fc6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 8 Oct 2019 16:04:52 +0300 Subject: [PATCH 1654/2815] Adjust spacing --- osu.Game/Overlays/Comments/DrawableComment.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 9879995b00..f3d8d2f577 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -120,7 +120,7 @@ namespace osu.Game.Overlays.Comments { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Spacing = new Vector2(5, 0), + Spacing = new Vector2(10, 0), Children = new Drawable[] { new SpriteText From 000e4a563cf4389d9fc86683838ece2e8251230b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 8 Oct 2019 19:09:02 +0300 Subject: [PATCH 1655/2815] Parse parent comments --- .../API/Requests/Responses/APIComments.cs | 3 ++ .../Online/API/Requests/Responses/Comment.cs | 2 + osu.Game/Overlays/Comments/DrawableComment.cs | 45 ++++++++++++++++++- 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIComments.cs b/osu.Game/Online/API/Requests/Responses/APIComments.cs index 9a12fb613e..f454d4052f 100644 --- a/osu.Game/Online/API/Requests/Responses/APIComments.cs +++ b/osu.Game/Online/API/Requests/Responses/APIComments.cs @@ -25,7 +25,10 @@ namespace osu.Game.Online.API.Requests.Responses comments.ForEach(parent => { if (parent.Id == child.ParentId) + { parent.ChildComments.Add(child); + child.ParentComment = parent; + } }); } }); diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs index cdc3c3204b..e5d3f14d27 100644 --- a/osu.Game/Online/API/Requests/Responses/Comment.cs +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -28,6 +28,8 @@ namespace osu.Game.Online.API.Requests.Responses public List ChildComments = new List(); + public Comment ParentComment { get; set; } + [JsonProperty(@"user_id")] public long UserId { get; set; } diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index f3d8d2f577..dea14f22c8 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Utils; using osu.Framework.Input.Events; using System; +using osu.Framework.Graphics.Cursor; namespace osu.Game.Overlays.Comments { @@ -97,9 +98,19 @@ namespace osu.Game.Overlays.Comments Spacing = new Vector2(0, 2), Children = new Drawable[] { - username = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true)) + new FillFlowContainer { AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(7, 0), + Children = new Drawable[] + { + username = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true)) + { + AutoSizeAxes = Axes.Both, + }, + new ParentUsername(comment) + } }, new TextFlowContainer(s => s.Font = OsuFont.GetFont(size: 14)) { @@ -196,5 +207,37 @@ namespace osu.Game.Overlays.Comments return base.OnClick(e); } } + + private class ParentUsername : FillFlowContainer, IHasTooltip + { + private const int spacing = 3; + + public string TooltipText => comment.ParentComment?.GetMessage() ?? ""; + + private readonly Comment comment; + + public ParentUsername(Comment comment) + { + this.comment = comment; + + AutoSizeAxes = Axes.Both; + Direction = FillDirection.Horizontal; + Spacing = new Vector2(spacing, 0); + Alpha = comment.ParentId == null ? 0 : 1; + Children = new Drawable[] + { + new SpriteIcon + { + Icon = FontAwesome.Solid.Reply, + Size = new Vector2(14), + }, + new SpriteText + { + Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true), + Text = comment.ParentComment?.User?.Username + } + }; + } + } } } From 341702b91d084207316711e6f43e33afa3fadcc6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 8 Oct 2019 19:18:46 +0300 Subject: [PATCH 1656/2815] Use Bindable for expansion logic --- osu.Game/Overlays/Comments/DrawableComment.cs | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index dea14f22c8..bf24cbb70d 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -11,8 +11,8 @@ using osu.Game.Users.Drawables; using osu.Game.Graphics.Containers; using osu.Game.Utils; using osu.Framework.Input.Events; -using System; using osu.Framework.Graphics.Cursor; +using osu.Framework.Bindables; namespace osu.Game.Overlays.Comments { @@ -23,29 +23,7 @@ namespace osu.Game.Overlays.Comments private const int child_margin = 20; private const int duration = 200; - private bool childExpanded = true; - - public bool ChildExpanded - { - get => childExpanded; - set - { - if (childExpanded == value) - return; - - childExpanded = value; - - childCommentsVisibilityContainer.ClearTransforms(); - - if (childExpanded) - childCommentsVisibilityContainer.AutoSizeAxes = Axes.Y; - else - { - childCommentsVisibilityContainer.AutoSizeAxes = Axes.None; - childCommentsVisibilityContainer.ResizeHeightTo(0, duration, Easing.OutQuint); - } - } - } + private readonly BindableBool childExpanded = new BindableBool(true); private readonly Container childCommentsVisibilityContainer; @@ -53,7 +31,6 @@ namespace osu.Game.Overlays.Comments { LinkFlowContainer username; FillFlowContainer childCommentsContainer; - RepliesButton replies; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -141,7 +118,8 @@ namespace osu.Game.Overlays.Comments Font = OsuFont.GetFont(size: 12), Text = HumanizerUtils.Humanize(comment.CreatedAt) }, - replies = new RepliesButton(comment.RepliesCount), + new RepliesButton(comment.RepliesCount) + { Expanded = { BindTarget = childExpanded } }, } } } @@ -172,17 +150,33 @@ namespace osu.Game.Overlays.Comments if (!c.IsDeleted) childCommentsContainer.Add(new DrawableComment(c)); }); + } - replies.Action += expanded => ChildExpanded = expanded; + protected override void LoadComplete() + { + childExpanded.BindValueChanged(onChildExpandedChanged, true); + base.LoadComplete(); + } + + private void onChildExpandedChanged(ValueChangedEvent expanded) + { + childCommentsVisibilityContainer.ClearTransforms(); + + if (expanded.NewValue) + childCommentsVisibilityContainer.AutoSizeAxes = Axes.Y; + else + { + childCommentsVisibilityContainer.AutoSizeAxes = Axes.None; + childCommentsVisibilityContainer.ResizeHeightTo(0, duration, Easing.OutQuint); + } } private class RepliesButton : Container { private readonly SpriteText text; - private bool expanded; private readonly int count; - public Action Action; + public readonly BindableBool Expanded = new BindableBool(true); public RepliesButton(int count) { @@ -193,17 +187,23 @@ namespace osu.Game.Overlays.Comments Child = text = new SpriteText { Font = OsuFont.GetFont(size: 12), - Text = $@"[-] replies ({count})" }; + } - expanded = true; + protected override void LoadComplete() + { + Expanded.BindValueChanged(onExpandedChanged, true); + base.LoadComplete(); + } + + private void onExpandedChanged(ValueChangedEvent expanded) + { + text.Text = $@"{(expanded.NewValue ? "[+]" : "[-]")} replies ({count})"; } protected override bool OnClick(ClickEvent e) { - text.Text = $@"{(expanded ? "[+]" : "[-]")} replies ({count})"; - expanded = !expanded; - Action?.Invoke(expanded); + Expanded.Value = !Expanded.Value; return base.OnClick(e); } } From 4230b00110768023bd1cf9d0e1fc098d5b273224 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 8 Oct 2019 19:22:23 +0300 Subject: [PATCH 1657/2815] Rename APIComments to APICommentsController --- osu.Game/Online/API/Requests/GetCommentsRequest.cs | 2 +- .../Responses/{APIComments.cs => APICommentsController.cs} | 2 +- osu.Game/Overlays/Comments/CommentsContainer.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename osu.Game/Online/API/Requests/Responses/{APIComments.cs => APICommentsController.cs} (98%) diff --git a/osu.Game/Online/API/Requests/GetCommentsRequest.cs b/osu.Game/Online/API/Requests/GetCommentsRequest.cs index 279a1905da..5a2b61b4d0 100644 --- a/osu.Game/Online/API/Requests/GetCommentsRequest.cs +++ b/osu.Game/Online/API/Requests/GetCommentsRequest.cs @@ -7,7 +7,7 @@ using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { - public class GetCommentsRequest : APIRequest + public class GetCommentsRequest : APIRequest { private readonly long id; private readonly int page; diff --git a/osu.Game/Online/API/Requests/Responses/APIComments.cs b/osu.Game/Online/API/Requests/Responses/APICommentsController.cs similarity index 98% rename from osu.Game/Online/API/Requests/Responses/APIComments.cs rename to osu.Game/Online/API/Requests/Responses/APICommentsController.cs index f454d4052f..cf2e5e8a35 100644 --- a/osu.Game/Online/API/Requests/Responses/APIComments.cs +++ b/osu.Game/Online/API/Requests/Responses/APICommentsController.cs @@ -7,7 +7,7 @@ using System.Collections.Generic; namespace osu.Game.Online.API.Requests.Responses { - public class APIComments + public class APICommentsController { private List comments; diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index d02e74a5a4..abbd2cdbde 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -86,7 +86,7 @@ namespace osu.Game.Overlays.Comments api.Queue(request); } - private void onSuccess(APIComments response) + private void onSuccess(APICommentsController response) { content.Clear(); From 35cfb16c8d21594237251fbf63d4fd69491efc3f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 8 Oct 2019 19:56:43 +0300 Subject: [PATCH 1658/2815] Implement VotePill component --- osu.Game/Overlays/Comments/DrawableComment.cs | 55 +++++++++++++++++-- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index bf24cbb70d..ce60d905ad 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -13,6 +13,8 @@ using osu.Game.Utils; using osu.Framework.Input.Events; using osu.Framework.Graphics.Cursor; using osu.Framework.Bindables; +using osu.Framework.Graphics.Shapes; +using osuTK.Graphics; namespace osu.Game.Overlays.Comments { @@ -60,12 +62,28 @@ namespace osu.Game.Overlays.Comments { new Drawable[] { - new UpdateableAvatar(comment.User) + new FillFlowContainer { - Size = new Vector2(avatar_size), + AutoSizeAxes = Axes.Both, Margin = new MarginPadding { Horizontal = margin }, - Masking = true, - CornerRadius = avatar_size / 2, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), + Children = new Drawable[] + { + new VotePill(comment.VotesCount) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new UpdateableAvatar(comment.User) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(avatar_size), + Masking = true, + CornerRadius = avatar_size / 2, + }, + } }, new FillFlowContainer { @@ -239,5 +257,34 @@ namespace osu.Game.Overlays.Comments }; } } + + private class VotePill : CircularContainer + { + private const int height = 20; + private const int margin = 10; + + public VotePill(int count) + { + AutoSizeAxes = Axes.X; + Height = height; + Masking = true; + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black + }, + new SpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Horizontal = margin }, + Font = OsuFont.GetFont(size: 14), + Text = $"+{count}" + } + }; + } + } } } From b6047e46135cd099a1d21ebcd492d38cac7f5a3b Mon Sep 17 00:00:00 2001 From: HoLLy-HaCKeR Date: Tue, 8 Oct 2019 19:39:54 +0200 Subject: [PATCH 1659/2815] Move OsuCursor resize logic to OsuCursorContainer --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 36 ++----------------- .../UI/Cursor/OsuCursorContainer.cs | 34 ++++++++++++++++-- 2 files changed, 34 insertions(+), 36 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index 41a02deaca..0aa8661fd3 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -2,14 +2,11 @@ // 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.Beatmaps; -using osu.Game.Configuration; using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Skinning; using osuTK; @@ -23,12 +20,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private bool cursorExpand; - private Bindable cursorScale; - private Bindable autoCursorScale; - private readonly IBindable beatmap = new Bindable(); - private Container expandTarget; - private Drawable scaleTarget; public OsuCursor() { @@ -43,43 +35,19 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor } [BackgroundDependencyLoader] - private void load(OsuConfigManager config, IBindable beatmap) + private void load() { InternalChild = expandTarget = new Container { RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, Anchor = Anchor.Centre, - Child = scaleTarget = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling) + Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling) { Origin = Anchor.Centre, Anchor = Anchor.Centre, } }; - - this.beatmap.BindTo(beatmap); - this.beatmap.ValueChanged += _ => calculateScale(); - - cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize); - cursorScale.ValueChanged += _ => calculateScale(); - - autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize); - autoCursorScale.ValueChanged += _ => calculateScale(); - - calculateScale(); - } - - private void calculateScale() - { - float scale = cursorScale.Value; - - if (autoCursorScale.Value && beatmap.Value != null) - { - // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier. - scale *= 1f - 0.7f * (1f + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY; - } - - scaleTarget.Scale = new Vector2(scale); } private const float pressed_scale = 1.2f; diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index 6dbdf0114d..e24ab3a7cb 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -8,6 +8,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; +using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.UI; using osu.Game.Skinning; @@ -27,6 +29,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private readonly Drawable cursorTrail; + private Bindable cursorScale; + private Bindable autoCursorScale; + private readonly IBindable beatmap = new Bindable(); + public OsuCursorContainer() { InternalChild = fadeContainer = new Container @@ -37,9 +43,33 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor } [BackgroundDependencyLoader(true)] - private void load(OsuRulesetConfigManager config) + private void load(OsuConfigManager config, OsuRulesetConfigManager rulesetConfig, IBindable beatmap) { - config?.BindWith(OsuRulesetSetting.ShowCursorTrail, showTrail); + rulesetConfig?.BindWith(OsuRulesetSetting.ShowCursorTrail, showTrail); + + this.beatmap.BindTo(beatmap); + this.beatmap.ValueChanged += _ => calculateScale(); + + cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize); + cursorScale.ValueChanged += _ => calculateScale(); + + autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize); + autoCursorScale.ValueChanged += _ => calculateScale(); + + calculateScale(); + } + + private void calculateScale() + { + float scale = cursorScale.Value; + + if (autoCursorScale.Value && beatmap.Value != null) + { + // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier. + scale *= 1f - 0.7f * (1f + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY; + } + + ActiveCursor.Scale = new Vector2(scale); } protected override void LoadComplete() From 1c22fb485fa2cc8b75f230c5a5ddcd40fa090534 Mon Sep 17 00:00:00 2001 From: HoLLy-HaCKeR Date: Tue, 8 Oct 2019 19:40:46 +0200 Subject: [PATCH 1660/2815] Scale cursortrail along with cursor --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index e24ab3a7cb..371c2983fc 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor scale *= 1f - 0.7f * (1f + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY; } - ActiveCursor.Scale = new Vector2(scale); + ActiveCursor.Scale = cursorTrail.Scale = new Vector2(scale); } protected override void LoadComplete() From 9ab309fc0ef387e2226991c7f010e75f52fe1a19 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 8 Oct 2019 20:44:01 +0300 Subject: [PATCH 1661/2815] Use bold font for replies button --- osu.Game/Overlays/Comments/DrawableComment.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index ce60d905ad..ce78dfec9f 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -204,7 +204,7 @@ namespace osu.Game.Overlays.Comments Alpha = count == 0 ? 0 : 1; Child = text = new SpriteText { - Font = OsuFont.GetFont(size: 12), + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), }; } From b9ad079bf8b077ae7a9aa53bdb2da1e6dcc0993e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 8 Oct 2019 20:57:55 +0300 Subject: [PATCH 1662/2815] Move CommentsHeader to it's own file --- .../Online/TestSceneCommentsContainer.cs | 2 + .../Overlays/Comments/CommentsContainer.cs | 56 --------------- osu.Game/Overlays/Comments/CommentsHeader.cs | 69 +++++++++++++++++++ 3 files changed, 71 insertions(+), 56 deletions(-) create mode 100644 osu.Game/Overlays/Comments/CommentsHeader.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs index e6f9582910..c8d16bdf21 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -17,6 +17,8 @@ namespace osu.Game.Tests.Visual.Online public override IReadOnlyList RequiredTypes => new[] { typeof(CommentsContainer), + typeof(CommentsHeader), + typeof(DrawableComment), }; protected override bool UseOnlineAPI => true; diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index abbd2cdbde..f8783952d1 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -9,8 +9,6 @@ using osu.Framework.Graphics; using osu.Framework.Bindables; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Framework.Graphics.Sprites; -using osuTK; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.Comments @@ -115,59 +113,5 @@ namespace osu.Game.Overlays.Comments { background.Colour = colours.Gray3; } - - private class CommentsHeader : CompositeDrawable - { - private const int height = 40; - private const int spacing = 10; - private const int padding = 50; - - public readonly Bindable Sort = new Bindable(); - - private readonly Box background; - - public CommentsHeader() - { - RelativeSizeAxes = Axes.X; - Height = height; - AddRangeInternal(new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = padding }, - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(spacing, 0), - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Children = new Drawable[] - { - new SpriteText - { - Font = OsuFont.GetFont(size: 14), - Text = @"Sort by" - } - } - } - } - } - }); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - background.Colour = colours.Gray4; - } - } } } diff --git a/osu.Game/Overlays/Comments/CommentsHeader.cs b/osu.Game/Overlays/Comments/CommentsHeader.cs new file mode 100644 index 0000000000..9ebd257a5d --- /dev/null +++ b/osu.Game/Overlays/Comments/CommentsHeader.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.Graphics.Containers; +using osu.Game.Online.API.Requests; +using osu.Framework.Graphics; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Overlays.Comments +{ + public class CommentsHeader : CompositeDrawable + { + private const int height = 40; + private const int spacing = 10; + private const int padding = 50; + + public readonly Bindable Sort = new Bindable(); + + private readonly Box background; + + public CommentsHeader() + { + RelativeSizeAxes = Axes.X; + Height = height; + AddRangeInternal(new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = padding }, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(spacing, 0), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Children = new Drawable[] + { + new SpriteText + { + Font = OsuFont.GetFont(size: 14), + Text = @"Sort by" + } + } + } + } + } + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Gray4; + } + } +} From 574170124cff05070833415e324f77a2e5fe88ef Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 8 Oct 2019 21:38:19 +0300 Subject: [PATCH 1663/2815] Implement HeaderButton component --- .../Online/TestSceneCommentsContainer.cs | 1 + osu.Game/Overlays/Comments/CommentsHeader.cs | 63 ++++++++++++++++- osu.Game/Overlays/Comments/HeaderButton.cs | 68 +++++++++++++++++++ 3 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Overlays/Comments/HeaderButton.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs index c8d16bdf21..f5205552e0 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -19,6 +19,7 @@ namespace osu.Game.Tests.Visual.Online typeof(CommentsContainer), typeof(CommentsHeader), typeof(DrawableComment), + typeof(HeaderButton), }; protected override bool UseOnlineAPI => true; diff --git a/osu.Game/Overlays/Comments/CommentsHeader.cs b/osu.Game/Overlays/Comments/CommentsHeader.cs index 9ebd257a5d..b9dee7935e 100644 --- a/osu.Game/Overlays/Comments/CommentsHeader.cs +++ b/osu.Game/Overlays/Comments/CommentsHeader.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Framework.Graphics.Sprites; using osuTK; +using osu.Framework.Input.Events; namespace osu.Game.Overlays.Comments { @@ -18,8 +19,10 @@ namespace osu.Game.Overlays.Comments private const int height = 40; private const int spacing = 10; private const int padding = 50; + private const int text_size = 14; public readonly Bindable Sort = new Bindable(); + public readonly BindableBool ShowDeleted = new BindableBool(); private readonly Box background; @@ -50,10 +53,16 @@ namespace osu.Game.Overlays.Comments { new SpriteText { - Font = OsuFont.GetFont(size: 14), + Font = OsuFont.GetFont(size: text_size), Text = @"Sort by" } } + }, + new ShowDeletedButton + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Checked = { BindTarget = ShowDeleted } } } } @@ -65,5 +74,57 @@ namespace osu.Game.Overlays.Comments { background.Colour = colours.Gray4; } + + private class ShowDeletedButton : HeaderButton + { + private const int spacing = 5; + + public readonly BindableBool Checked = new BindableBool(); + + private readonly SpriteIcon checkboxIcon; + + public ShowDeletedButton() + { + Add(new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(spacing, 0), + Children = new Drawable[] + { + checkboxIcon = new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(10), + }, + new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: text_size), + Text = @"Show deleted" + } + }, + }); + } + + protected override void LoadComplete() + { + Checked.BindValueChanged(onCheckedChanged, true); + base.LoadComplete(); + } + + private void onCheckedChanged(ValueChangedEvent isChecked) + { + checkboxIcon.Icon = isChecked.NewValue ? FontAwesome.Solid.CheckSquare : FontAwesome.Regular.Square; + } + + protected override bool OnClick(ClickEvent e) + { + Checked.Value = !Checked.Value; + return base.OnClick(e); + } + } } } diff --git a/osu.Game/Overlays/Comments/HeaderButton.cs b/osu.Game/Overlays/Comments/HeaderButton.cs new file mode 100644 index 0000000000..db8ea92a1a --- /dev/null +++ b/osu.Game/Overlays/Comments/HeaderButton.cs @@ -0,0 +1,68 @@ +// 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.Containers; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Framework.Input.Events; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Comments +{ + public class HeaderButton : Container + { + private const int height = 20; + private const int corner_radius = 3; + private const int margin = 10; + private const int duration = 200; + + protected override Container Content => content; + + private readonly Box background; + private readonly Container content; + + public HeaderButton() + { + AutoSizeAxes = Axes.X; + Height = height; + Masking = true; + CornerRadius = corner_radius; + AddRangeInternal(new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + content = new Container + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Horizontal = margin } + }, + new HoverClickSounds(), + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Gray6; + } + + protected override bool OnHover(HoverEvent e) + { + background.FadeIn(duration, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + background.FadeOut(duration, Easing.OutQuint); + } + } +} From 29b0eacc821b5091c0251d6066e2e52296d8c279 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 8 Oct 2019 22:46:42 +0300 Subject: [PATCH 1664/2815] Implement SortSelector component --- .../Online/TestSceneCommentsContainer.cs | 1 + .../Online/API/Requests/GetCommentsRequest.cs | 2 +- .../Online/API/Requests/Responses/Comment.cs | 2 +- .../Overlays/Comments/CommentsContainer.cs | 3 +- osu.Game/Overlays/Comments/CommentsHeader.cs | 8 ++ osu.Game/Overlays/Comments/DrawableComment.cs | 9 ++- osu.Game/Overlays/Comments/HeaderButton.cs | 8 +- osu.Game/Overlays/Comments/SortSelector.cs | 75 +++++++++++++++++++ 8 files changed, 101 insertions(+), 7 deletions(-) create mode 100644 osu.Game/Overlays/Comments/SortSelector.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs index f5205552e0..7fbe9d7e8b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -20,6 +20,7 @@ namespace osu.Game.Tests.Visual.Online typeof(CommentsHeader), typeof(DrawableComment), typeof(HeaderButton), + typeof(SortSelector) }; protected override bool UseOnlineAPI => true; diff --git a/osu.Game/Online/API/Requests/GetCommentsRequest.cs b/osu.Game/Online/API/Requests/GetCommentsRequest.cs index 5a2b61b4d0..02a36f7aa2 100644 --- a/osu.Game/Online/API/Requests/GetCommentsRequest.cs +++ b/osu.Game/Online/API/Requests/GetCommentsRequest.cs @@ -28,7 +28,7 @@ namespace osu.Game.Online.API.Requests req.AddParameter("commentable_type", type.ToString().Underscore().ToLowerInvariant()); req.AddParameter("commentable_id", id.ToString()); - req.AddParameter("sort", sort.ToString()); + req.AddParameter("sort", sort.ToString().ToLowerInvariant()); req.AddParameter("page", page.ToString()); return req; diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs index e5d3f14d27..30aca3c2ea 100644 --- a/osu.Game/Online/API/Requests/Responses/Comment.cs +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -31,7 +31,7 @@ namespace osu.Game.Online.API.Requests.Responses public Comment ParentComment { get; set; } [JsonProperty(@"user_id")] - public long UserId { get; set; } + public long? UserId { get; set; } public User User { get; set; } diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index f8783952d1..5be1b6c1c4 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -79,6 +79,7 @@ namespace osu.Game.Overlays.Comments private void getComments() { request?.Cancel(); + content.Clear(); request = new GetCommentsRequest(type, id, Sort.Value); request.Success += onSuccess; api.Queue(request); @@ -86,8 +87,6 @@ namespace osu.Game.Overlays.Comments private void onSuccess(APICommentsController response) { - content.Clear(); - foreach (var c in response.Comments) { if (!c.IsDeleted && c.IsTopLevel) diff --git a/osu.Game/Overlays/Comments/CommentsHeader.cs b/osu.Game/Overlays/Comments/CommentsHeader.cs index b9dee7935e..90a6f44d6b 100644 --- a/osu.Game/Overlays/Comments/CommentsHeader.cs +++ b/osu.Game/Overlays/Comments/CommentsHeader.cs @@ -53,8 +53,16 @@ namespace osu.Game.Overlays.Comments { new SpriteText { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Font = OsuFont.GetFont(size: text_size), Text = @"Sort by" + }, + new SortSelector + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Current = { BindTarget = Sort } } } }, diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index ce78dfec9f..41b30513a1 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -161,7 +161,14 @@ namespace osu.Game.Overlays.Comments } }; - username.AddUserLink(comment.User); + if (comment.UserId == null) + { + username.AddText(comment.LegacyName); + } + else + { + username.AddUserLink(comment.User); + } comment.ChildComments.ForEach(c => { diff --git a/osu.Game/Overlays/Comments/HeaderButton.cs b/osu.Game/Overlays/Comments/HeaderButton.cs index db8ea92a1a..e3729b3b05 100644 --- a/osu.Game/Overlays/Comments/HeaderButton.cs +++ b/osu.Game/Overlays/Comments/HeaderButton.cs @@ -55,14 +55,18 @@ namespace osu.Game.Overlays.Comments protected override bool OnHover(HoverEvent e) { - background.FadeIn(duration, Easing.OutQuint); + FadeInBackground(); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { base.OnHoverLost(e); - background.FadeOut(duration, Easing.OutQuint); + FadeOutBackground(); } + + public void FadeInBackground() => background.FadeIn(duration, Easing.OutQuint); + + public void FadeOutBackground() => background.FadeOut(duration, Easing.OutQuint); } } diff --git a/osu.Game/Overlays/Comments/SortSelector.cs b/osu.Game/Overlays/Comments/SortSelector.cs new file mode 100644 index 0000000000..4425145c3e --- /dev/null +++ b/osu.Game/Overlays/Comments/SortSelector.cs @@ -0,0 +1,75 @@ +// 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.Online.API.Requests; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osuTK; +using osu.Game.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Framework.Bindables; + +namespace osu.Game.Overlays.Comments +{ + public class SortSelector : OsuTabControl + { + private const int spacing = 5; + + protected override Dropdown CreateDropdown() => null; + + protected override TabItem CreateTabItem(SortCommentsBy value) => new SortTabItem(value); + + protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(spacing, 0), + }; + + public SortSelector() + { + AutoSizeAxes = Axes.Both; + } + + private class SortTabItem : TabItem + { + private readonly TabContent content; + + public SortTabItem(SortCommentsBy value) + : base(value) + { + AutoSizeAxes = Axes.Both; + Child = content = new TabContent(value) + { Active = { BindTarget = Active } }; + } + + protected override void OnActivated() => content.FadeInBackground(); + + protected override void OnDeactivated() => content.FadeOutBackground(); + + private class TabContent : HeaderButton + { + private const int text_size = 14; + + public readonly BindableBool Active = new BindableBool(); + + public TabContent(SortCommentsBy value) + { + Add(new SpriteText + { + Font = OsuFont.GetFont(size: text_size), + Text = value.ToString() + }); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + if (!Active.Value) base.OnHoverLost(e); + } + } + } + } +} From 3dd2b18ff0d32cb717bddd6c982e42ca3a7a723f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Oct 2019 16:04:58 +0900 Subject: [PATCH 1665/2815] Make EditorScreen abstract --- osu.Game/Screens/Edit/Editor.cs | 6 +----- osu.Game/Screens/Edit/EditorScreen.cs | 15 +++++---------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 9ebe3bc26a..7f08c2f8b9 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -19,13 +19,13 @@ using osu.Framework.Timing; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; -using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Design; using osuTK.Input; using System.Collections.Generic; using osu.Framework; using osu.Framework.Input.Bindings; using osu.Game.Input.Bindings; +using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Edit.Timing; using osu.Game.Users; @@ -275,10 +275,6 @@ namespace osu.Game.Screens.Edit case EditorScreenMode.Timing: currentScreen = new TimingScreen(); break; - - default: - currentScreen = new EditorScreen(); - break; } LoadComponentAsync(currentScreen, screenContainer.Add); diff --git a/osu.Game/Screens/Edit/EditorScreen.cs b/osu.Game/Screens/Edit/EditorScreen.cs index 045e5a1226..1b57c703ae 100644 --- a/osu.Game/Screens/Edit/EditorScreen.cs +++ b/osu.Game/Screens/Edit/EditorScreen.cs @@ -10,16 +10,17 @@ using osu.Game.Beatmaps; namespace osu.Game.Screens.Edit { /// - /// TODO: eventually make this inherit Screen and add a local scren stack inside the Editor. + /// TODO: eventually make this inherit Screen and add a local screen stack inside the Editor. /// - public class EditorScreen : Container + public abstract class EditorScreen : Container { - protected readonly IBindable Beatmap = new Bindable(); + [Resolved] + protected IBindable Beatmap { get; private set; } protected override Container Content => content; private readonly Container content; - public EditorScreen() + protected EditorScreen() { Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -28,12 +29,6 @@ namespace osu.Game.Screens.Edit InternalChild = content = new Container { RelativeSizeAxes = Axes.Both }; } - [BackgroundDependencyLoader] - private void load(IBindable beatmap) - { - Beatmap.BindTo(beatmap); - } - protected override void LoadComplete() { base.LoadComplete(); From f2adae8fd167195b667989d30d7bc99b09e12ba3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Oct 2019 16:05:38 +0900 Subject: [PATCH 1666/2815] Rename test case to better match underlying class --- ...{TestSceneEditorCompose.cs => TestSceneComposeScreen.cs} | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) rename osu.Game.Tests/Visual/Editor/{TestSceneEditorCompose.cs => TestSceneComposeScreen.cs} (72%) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorCompose.cs b/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs similarity index 72% rename from osu.Game.Tests/Visual/Editor/TestSceneEditorCompose.cs rename to osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs index 608df1965e..9f16e1d781 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorCompose.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.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 System; -using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Game.Rulesets.Osu; @@ -11,10 +9,8 @@ using osu.Game.Screens.Edit.Compose; namespace osu.Game.Tests.Visual.Editor { [TestFixture] - public class TestSceneEditorCompose : EditorClockTestScene + public class TestSceneComposeScreen : EditorClockTestScene { - public override IReadOnlyList RequiredTypes => new[] { typeof(ComposeScreen) }; - [BackgroundDependencyLoader] private void load() { From 2d8e5615e4970fc1f47a94ed2690d6f2c0cb8a99 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Oct 2019 16:05:55 +0900 Subject: [PATCH 1667/2815] Extract timeline and layout logic from ComposeScreen --- .../Screens/Edit/Compose/ComposeScreen.cs | 111 +----------------- .../Screens/Edit/EditorScreenWithTimeline.cs | 107 +++++++++++++++++ 2 files changed, 112 insertions(+), 106 deletions(-) create mode 100644 osu.Game/Screens/Edit/EditorScreenWithTimeline.cs diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 2a99d81516..1d93c6d4a4 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -1,112 +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 JetBrains.Annotations; -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Logging; -using osu.Game.Screens.Edit.Compose.Components; -using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Skinning; -using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose { - public class ComposeScreen : EditorScreen + public class ComposeScreen : EditorScreenWithTimeline { - private const float vertical_margins = 10; - private const float horizontal_margins = 20; - - private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); - - [BackgroundDependencyLoader(true)] - private void load([CanBeNull] BindableBeatDivisor beatDivisor) + protected override Drawable CreateMainContent() { - if (beatDivisor != null) - this.beatDivisor.BindTo(beatDivisor); - - Container composerContainer; - - Children = new Drawable[] - { - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] - { - new Container - { - Name = "Timeline", - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.5f) - }, - new Container - { - Name = "Timeline content", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins }, - Child = new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = 5 }, - Child = new TimelineArea { RelativeSizeAxes = Axes.Both } - }, - new BeatDivisorControl(beatDivisor) { RelativeSizeAxes = Axes.Both } - }, - }, - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 90), - } - }, - } - } - } - }, - new Drawable[] - { - composerContainer = new Container - { - Name = "Composer content", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins }, - } - } - }, - RowDimensions = new[] { new Dimension(GridSizeMode.Absolute, 110) } - }, - }; - var ruleset = Beatmap.Value.BeatmapInfo.Ruleset?.CreateInstance(); - if (ruleset == null) - { - Logger.Log("Beatmap doesn't have a ruleset assigned."); - // ExitRequested?.Invoke(); - return; - } - - var composer = ruleset.CreateHitObjectComposer(); - - Drawable content; + var composer = ruleset?.CreateHitObjectComposer(); if (composer != null) { @@ -118,18 +25,10 @@ namespace osu.Game.Screens.Edit.Compose // 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. - content = beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(ruleset.CreateHitObjectComposer())); - } - else - { - content = new ScreenWhiteBox.UnderConstructionMessage($"{ruleset.Description}'s composer"); + return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(ruleset.CreateHitObjectComposer())); } - LoadComponentAsync(content, _ => - { - composerContainer.Add(content); - content.FadeInFromZero(300, Easing.OutQuint); - }); + return new ScreenWhiteBox.UnderConstructionMessage($"{ruleset.Description}'s composer"); } } } diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs new file mode 100644 index 0000000000..752356e8c4 --- /dev/null +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -0,0 +1,107 @@ +// 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.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit +{ + public abstract class EditorScreenWithTimeline : EditorScreen + { + private const float vertical_margins = 10; + private const float horizontal_margins = 20; + + private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); + + [BackgroundDependencyLoader(true)] + private void load([CanBeNull] BindableBeatDivisor beatDivisor) + { + if (beatDivisor != null) + this.beatDivisor.BindTo(beatDivisor); + + Container mainContent; + + Children = new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + new Container + { + Name = "Timeline", + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.5f) + }, + new Container + { + Name = "Timeline content", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins }, + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = 5 }, + Child = CreateTimeline() + }, + new BeatDivisorControl(beatDivisor) { RelativeSizeAxes = Axes.Both } + }, + }, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 90), + } + }, + } + } + } + }, + new Drawable[] + { + mainContent = new Container + { + Name = "Main content", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins }, + } + } + }, + RowDimensions = new[] { new Dimension(GridSizeMode.Absolute, 110) } + }, + }; + + LoadComponentAsync(CreateMainContent(), content => + { + mainContent.Add(content); + content.FadeInFromZero(300, Easing.OutQuint); + }); + } + + protected abstract Drawable CreateMainContent(); + + protected virtual Drawable CreateTimeline() => new TimelineArea { RelativeSizeAxes = Axes.Both }; + } +} From faef4d932d08cc7ef5fde89d88953b3c21cd7ac6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 9 Oct 2019 10:17:14 +0300 Subject: [PATCH 1668/2815] Improve message parsing --- osu.Game/Online/API/Requests/Responses/Comment.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs index 30aca3c2ea..fb0aad0e0b 100644 --- a/osu.Game/Online/API/Requests/Responses/Comment.cs +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -87,8 +87,7 @@ namespace osu.Game.Online.API.Requests.Responses public string GetMessage() { - //temporary fix until HTML parsing will be implemented - return MessageHTML.Remove(MessageHTML.LastIndexOf("

")).Substring(65); + return MessageHTML.Replace("
", "").Replace("

", "").Replace("
", "").Replace("

", "").Replace("
", ""); } } } From 4462d454e87fba91a25b92b3bc4fef0a9ba6007c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 9 Oct 2019 10:34:17 +0300 Subject: [PATCH 1669/2815] Message padding improvements --- osu.Game/Overlays/Comments/CommentsContainer.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 5be1b6c1c4..b9effb39e8 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -16,6 +16,7 @@ namespace osu.Game.Overlays.Comments public class CommentsContainer : CompositeDrawable { private const float separator_height = 1.5f; + private const int padding = 40; private readonly CommentableType type; private readonly long id; @@ -92,7 +93,13 @@ namespace osu.Game.Overlays.Comments if (!c.IsDeleted && c.IsTopLevel) content.AddRange(new Drawable[] { - new DrawableComment(c), + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Right = padding }, + Child = new DrawableComment(c) + }, new Container { RelativeSizeAxes = Axes.X, From 0681bb9a2b27f063ae0153ca204b25a895334db1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Oct 2019 16:49:10 +0900 Subject: [PATCH 1670/2815] Fix potential nullref --- osu.Game/Screens/Edit/Compose/ComposeScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 1d93c6d4a4..2e9094ebe6 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Edit.Compose return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(ruleset.CreateHitObjectComposer())); } - return new ScreenWhiteBox.UnderConstructionMessage($"{ruleset.Description}'s composer"); + return new ScreenWhiteBox.UnderConstructionMessage(ruleset == null ? "This beatmap" : $"{ruleset.Description}'s composer"); } } } From 0a56b041fdf7116ceb72d8a7251383f912961f5f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 9 Oct 2019 11:07:56 +0300 Subject: [PATCH 1671/2815] Implement ShowChildsButton --- .../Online/TestSceneCommentsContainer.cs | 3 +- .../Online/API/Requests/Responses/Comment.cs | 2 +- .../Overlays/Comments/CommentsContainer.cs | 9 +-- osu.Game/Overlays/Comments/DrawableComment.cs | 72 ++++++++++++------- .../Overlays/Comments/ShowChildsButton.cs | 34 +++++++++ 5 files changed, 85 insertions(+), 35 deletions(-) create mode 100644 osu.Game/Overlays/Comments/ShowChildsButton.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs index 7fbe9d7e8b..4187771963 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -20,7 +20,8 @@ namespace osu.Game.Tests.Visual.Online typeof(CommentsHeader), typeof(DrawableComment), typeof(HeaderButton), - typeof(SortSelector) + typeof(SortSelector), + typeof(ShowChildsButton) }; protected override bool UseOnlineAPI => true; diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs index fb0aad0e0b..9a3dee30b4 100644 --- a/osu.Game/Online/API/Requests/Responses/Comment.cs +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -87,7 +87,7 @@ namespace osu.Game.Online.API.Requests.Responses public string GetMessage() { - return MessageHTML.Replace("
", "").Replace("

", "").Replace("
", "").Replace("

", "").Replace("
", ""); + return MessageHTML.Replace("
", "").Replace("

", "").Replace("
", "").Replace("

", "").Replace("
", "").Replace(""", "\""); } } } diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index b9effb39e8..5be1b6c1c4 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -16,7 +16,6 @@ namespace osu.Game.Overlays.Comments public class CommentsContainer : CompositeDrawable { private const float separator_height = 1.5f; - private const int padding = 40; private readonly CommentableType type; private readonly long id; @@ -93,13 +92,7 @@ namespace osu.Game.Overlays.Comments if (!c.IsDeleted && c.IsTopLevel) content.AddRange(new Drawable[] { - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Right = padding }, - Child = new DrawableComment(c) - }, + new DrawableComment(c), new Container { RelativeSizeAxes = Axes.X, diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 41b30513a1..a66b8fcd44 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -10,11 +10,11 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Users.Drawables; using osu.Game.Graphics.Containers; using osu.Game.Utils; -using osu.Framework.Input.Events; using osu.Framework.Graphics.Cursor; using osu.Framework.Bindables; using osu.Framework.Graphics.Shapes; using osuTK.Graphics; +using System.Linq; namespace osu.Game.Overlays.Comments { @@ -23,6 +23,8 @@ namespace osu.Game.Overlays.Comments private const int avatar_size = 40; private const int margin = 10; private const int child_margin = 20; + private const int chevron_margin = 30; + private const int message_padding = 40; private const int duration = 200; private readonly BindableBool childExpanded = new BindableBool(true); @@ -93,25 +95,41 @@ namespace osu.Game.Overlays.Comments Spacing = new Vector2(0, 2), Children = new Drawable[] { - new FillFlowContainer + new Container { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(7, 0), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Children = new Drawable[] { - username = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true)) + new FillFlowContainer { AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(7, 0), + Children = new Drawable[] + { + username = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true)) + { + AutoSizeAxes = Axes.Both, + }, + new ParentUsername(comment) + } }, - new ParentUsername(comment) + new ChevronButton(comment) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Margin = new MarginPadding { Right = chevron_margin }, + Expanded = { BindTarget = childExpanded } + } } }, new TextFlowContainer(s => s.Font = OsuFont.GetFont(size: 14)) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Text = comment.GetMessage() + Text = comment.GetMessage(), + Padding = new MarginPadding { Right = message_padding } } } } @@ -196,18 +214,34 @@ namespace osu.Game.Overlays.Comments } } - private class RepliesButton : Container + private class ChevronButton : ShowChildsButton + { + private readonly SpriteIcon icon; + + public ChevronButton(Comment comment) + { + Alpha = comment.IsTopLevel && comment.ChildComments.Any() ? 1 : 0; + Child = icon = new SpriteIcon + { + Size = new Vector2(12), + }; + } + + protected override void OnExpandedChanged(ValueChangedEvent expanded) + { + icon.Icon = expanded.NewValue ? FontAwesome.Solid.ChevronUp : FontAwesome.Solid.ChevronDown; + } + } + + private class RepliesButton : ShowChildsButton { private readonly SpriteText text; private readonly int count; - public readonly BindableBool Expanded = new BindableBool(true); - public RepliesButton(int count) { this.count = count; - AutoSizeAxes = Axes.Both; Alpha = count == 0 ? 0 : 1; Child = text = new SpriteText { @@ -215,22 +249,10 @@ namespace osu.Game.Overlays.Comments }; } - protected override void LoadComplete() - { - Expanded.BindValueChanged(onExpandedChanged, true); - base.LoadComplete(); - } - - private void onExpandedChanged(ValueChangedEvent expanded) + protected override void OnExpandedChanged(ValueChangedEvent expanded) { text.Text = $@"{(expanded.NewValue ? "[+]" : "[-]")} replies ({count})"; } - - protected override bool OnClick(ClickEvent e) - { - Expanded.Value = !Expanded.Value; - return base.OnClick(e); - } } private class ParentUsername : FillFlowContainer, IHasTooltip diff --git a/osu.Game/Overlays/Comments/ShowChildsButton.cs b/osu.Game/Overlays/Comments/ShowChildsButton.cs new file mode 100644 index 0000000000..81280a71b5 --- /dev/null +++ b/osu.Game/Overlays/Comments/ShowChildsButton.cs @@ -0,0 +1,34 @@ +// 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.Graphics.Containers; +using osu.Framework.Input.Events; +using osu.Framework.Bindables; + +namespace osu.Game.Overlays.Comments +{ + public abstract class ShowChildsButton : OsuHoverContainer + { + public readonly BindableBool Expanded = new BindableBool(true); + + public ShowChildsButton() + { + AutoSizeAxes = Axes.Both; + } + + protected override void LoadComplete() + { + Expanded.BindValueChanged(OnExpandedChanged, true); + base.LoadComplete(); + } + + protected abstract void OnExpandedChanged(ValueChangedEvent expanded); + + protected override bool OnClick(ClickEvent e) + { + Expanded.Value = !Expanded.Value; + return base.OnClick(e); + } + } +} From a0dfbfe1488e9cbb66ad068828fdbf88069b24ec Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 9 Oct 2019 11:18:26 +0300 Subject: [PATCH 1672/2815] Handle parent usernames for legacy comments --- osu.Game/Overlays/Comments/DrawableComment.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index a66b8fcd44..6215f5e108 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -281,7 +281,7 @@ namespace osu.Game.Overlays.Comments new SpriteText { Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true), - Text = comment.ParentComment?.User?.Username + Text = comment.ParentComment?.User?.Username ?? comment.ParentComment?.LegacyName } }; } From ad99a3236f052d2f2130a8c3559093f5c3c3699d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 9 Oct 2019 11:32:17 +0300 Subject: [PATCH 1673/2815] Handle edited comments --- .../Requests/Responses/APICommentsController.cs | 3 +++ osu.Game/Online/API/Requests/Responses/Comment.cs | 2 ++ osu.Game/Overlays/Comments/DrawableComment.cs | 14 +++++++++++++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APICommentsController.cs b/osu.Game/Online/API/Requests/Responses/APICommentsController.cs index cf2e5e8a35..ca6062e371 100644 --- a/osu.Game/Online/API/Requests/Responses/APICommentsController.cs +++ b/osu.Game/Online/API/Requests/Responses/APICommentsController.cs @@ -63,6 +63,9 @@ namespace osu.Game.Online.API.Requests.Responses { if (c.UserId == u.Id) c.User = u; + + if (c.EditedById == u.Id) + c.EditedUser = u; }); }); } diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs index 9a3dee30b4..e420a585fe 100644 --- a/osu.Game/Online/API/Requests/Responses/Comment.cs +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -81,6 +81,8 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"edited_by_id")] public long? EditedById { get; set; } + public User EditedUser { get; set; } + public bool IsTopLevel { get; set; } public bool IsDeleted { get; set; } diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 6215f5e108..92b59a985b 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -35,6 +35,7 @@ namespace osu.Game.Overlays.Comments { LinkFlowContainer username; FillFlowContainer childCommentsContainer; + FillFlowContainer info; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -140,7 +141,7 @@ namespace osu.Game.Overlays.Comments { RelativeSizeAxes = Axes.Both, }, - new FillFlowContainer + info = new FillFlowContainer { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, @@ -188,6 +189,17 @@ namespace osu.Game.Overlays.Comments username.AddUserLink(comment.User); } + if (comment.EditedAt.HasValue) + { + info.Add(new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: 12), + Text = $@"edited {HumanizerUtils.Humanize(comment.EditedAt.Value)} by {comment.EditedUser.Username}" + }); + } + comment.ChildComments.ForEach(c => { if (!c.IsDeleted) From b2bd78308dc4473d89d89244b873e5bd97b9f54f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 9 Oct 2019 12:18:49 +0300 Subject: [PATCH 1674/2815] Handle deleted comments --- .../Online/API/Requests/Responses/Comment.cs | 2 +- .../Overlays/Comments/CommentsContainer.cs | 24 +-- osu.Game/Overlays/Comments/DrawableComment.cs | 150 +++++++++++------- 3 files changed, 98 insertions(+), 78 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs index e420a585fe..76a322e5c9 100644 --- a/osu.Game/Online/API/Requests/Responses/Comment.cs +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -89,7 +89,7 @@ namespace osu.Game.Online.API.Requests.Responses public string GetMessage() { - return MessageHTML.Replace("
", "").Replace("

", "").Replace("
", "").Replace("

", "").Replace("
", "").Replace(""", "\""); + return IsDeleted ? @"deleted" : MessageHTML.Replace("
", "").Replace("

", "").Replace("
", "").Replace("

", "").Replace("
", "").Replace(""", "\""); } } } diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 5be1b6c1c4..fb97f08a6e 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -15,12 +15,11 @@ namespace osu.Game.Overlays.Comments { public class CommentsContainer : CompositeDrawable { - private const float separator_height = 1.5f; - private readonly CommentableType type; private readonly long id; public readonly Bindable Sort = new Bindable(); + public readonly BindableBool ShowDeleted = new BindableBool(); [Resolved] private IAPIProvider api { get; set; } @@ -55,7 +54,8 @@ namespace osu.Game.Overlays.Comments { new CommentsHeader { - Sort = { BindTarget = Sort } + Sort = { BindTarget = Sort }, + ShowDeleted = { BindTarget = ShowDeleted } }, content = new FillFlowContainer { @@ -89,21 +89,9 @@ namespace osu.Game.Overlays.Comments { foreach (var c in response.Comments) { - if (!c.IsDeleted && c.IsTopLevel) - content.AddRange(new Drawable[] - { - new DrawableComment(c), - new Container - { - RelativeSizeAxes = Axes.X, - Height = separator_height, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Gray1, - } - } - }); + if (c.IsTopLevel) + content.Add(new DrawableComment(c) + { ShowDeleted = { BindTarget = ShowDeleted } }); } } diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 92b59a985b..4092cbb177 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -26,19 +26,29 @@ namespace osu.Game.Overlays.Comments private const int chevron_margin = 30; private const int message_padding = 40; private const int duration = 200; + private const float separator_height = 1.5f; + + public readonly BindableBool ShowDeleted = new BindableBool(); private readonly BindableBool childExpanded = new BindableBool(true); private readonly Container childCommentsVisibilityContainer; + private readonly Comment comment; public DrawableComment(Comment comment) { LinkFlowContainer username; FillFlowContainer childCommentsContainer; FillFlowContainer info; + TextFlowContainer message; + GridContainer content; + VotePill votePill; + + this.comment = comment; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; + Masking = true; InternalChild = new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -46,7 +56,7 @@ namespace osu.Game.Overlays.Comments Direction = FillDirection.Vertical, Children = new Drawable[] { - new GridContainer + content = new GridContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -58,7 +68,6 @@ namespace osu.Game.Overlays.Comments }, RowDimensions = new[] { - new Dimension(GridSizeMode.AutoSize), new Dimension(GridSizeMode.AutoSize) }, Content = new[] @@ -73,10 +82,11 @@ namespace osu.Game.Overlays.Comments Spacing = new Vector2(5, 0), Children = new Drawable[] { - new VotePill(comment.VotesCount) + votePill = new VotePill(comment.VotesCount) { Anchor = Anchor.Centre, Origin = Anchor.Centre, + AlwaysPresent = true, }, new UpdateableAvatar(comment.User) { @@ -92,71 +102,53 @@ namespace osu.Game.Overlays.Comments { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Top = margin / 2 }, - Spacing = new Vector2(0, 2), + Spacing = new Vector2(0, 3), Children = new Drawable[] { - new Container + new FillFlowContainer { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(7, 0), Children = new Drawable[] { - new FillFlowContainer + username = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true)) { AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(7, 0), - Children = new Drawable[] - { - username = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true)) - { - AutoSizeAxes = Axes.Both, - }, - new ParentUsername(comment) - } }, - new ChevronButton(comment) + new ParentUsername(comment), + new SpriteText { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Margin = new MarginPadding { Right = chevron_margin }, - Expanded = { BindTarget = childExpanded } + Alpha = comment.IsDeleted? 1 : 0, + Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true), + Text = @"deleted", } } }, - new TextFlowContainer(s => s.Font = OsuFont.GetFont(size: 14)) + message = new TextFlowContainer(s => s.Font = OsuFont.GetFont(size: 14)) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Text = comment.GetMessage(), Padding = new MarginPadding { Right = message_padding } - } - } - } - }, - new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - }, - info = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), - Children = new Drawable[] - { - new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: 12), - Text = HumanizerUtils.Humanize(comment.CreatedAt) }, - new RepliesButton(comment.RepliesCount) - { Expanded = { BindTarget = childExpanded } }, + info = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: 12), + Text = HumanizerUtils.Humanize(comment.CreatedAt) + }, + new RepliesButton(comment.RepliesCount) + { Expanded = { BindTarget = childExpanded } }, + } + } } } } @@ -181,13 +173,9 @@ namespace osu.Game.Overlays.Comments }; if (comment.UserId == null) - { username.AddText(comment.LegacyName); - } else - { username.AddUserLink(comment.User); - } if (comment.EditedAt.HasValue) { @@ -200,15 +188,45 @@ namespace osu.Game.Overlays.Comments }); } - comment.ChildComments.ForEach(c => + if (!comment.IsDeleted) + message.Text = comment.GetMessage(); + else { - if (!c.IsDeleted) - childCommentsContainer.Add(new DrawableComment(c)); - }); + content.FadeColour(OsuColour.Gray(0.5f)); + votePill.Hide(); + } + + if (comment.IsTopLevel) + { + AddInternal(new Container + { + RelativeSizeAxes = Axes.X, + Height = separator_height, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.1f) + } + }); + + if (comment.ChildComments.Any()) + { + AddInternal(new ChevronButton(comment) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Margin = new MarginPadding { Right = chevron_margin, Top = margin }, + Expanded = { BindTarget = childExpanded } + }); + } + } + + comment.ChildComments.ForEach(c => childCommentsContainer.Add(new DrawableComment(c))); } protected override void LoadComplete() { + ShowDeleted.BindValueChanged(onShowDeletedChanged, true); childExpanded.BindValueChanged(onChildExpandedChanged, true); base.LoadComplete(); } @@ -226,6 +244,20 @@ namespace osu.Game.Overlays.Comments } } + private void onShowDeletedChanged(ValueChangedEvent show) + { + if (comment.IsDeleted) + { + if (show.NewValue) + AutoSizeAxes = Axes.Y; + else + { + AutoSizeAxes = Axes.None; + this.ResizeHeightTo(0); + } + } + } + private class ChevronButton : ShowChildsButton { private readonly SpriteIcon icon; From 93d2c3d7a1c43cb9871f1bcb779b51cddcc086e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Oct 2019 19:03:20 +0900 Subject: [PATCH 1675/2815] Warn on incorrect null usage --- osu.sln.DotSettings | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index ed162eed6e..ae08a1666f 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -17,7 +17,8 @@ WARNING WARNING HINT - HINT + + True HINT HINT WARNING @@ -738,6 +739,7 @@ See the LICENCE file in the repository root for full licence text. <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True True True From 51bf600ea7a830d736a748342bd987e6cc4dc878 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Oct 2019 19:08:31 +0900 Subject: [PATCH 1676/2815] Use empty hitwindows instead of null --- .../Objects/CatchHitObject.cs | 2 +- osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 2 +- .../Objects/HoldNoteTick.cs | 2 +- osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- .../Objects/SliderTailCircle.cs | 2 +- osu.Game.Rulesets.Osu/Objects/SliderTick.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 2 +- .../Objects/DrumRollTick.cs | 2 +- .../Objects/StrongHitObject.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/Swell.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/SwellTick.cs | 2 +- osu.Game/Rulesets/Objects/HitObject.cs | 6 +--- .../Objects/Legacy/Mania/ConvertHit.cs | 2 +- .../Objects/Legacy/Mania/ConvertHold.cs | 2 +- .../Objects/Legacy/Mania/ConvertSlider.cs | 2 +- .../Objects/Legacy/Mania/ConvertSpinner.cs | 2 +- .../Rulesets/Objects/Legacy/Osu/ConvertHit.cs | 2 +- .../Objects/Legacy/Osu/ConvertSlider.cs | 2 +- .../Objects/Legacy/Osu/ConvertSpinner.cs | 2 +- .../Objects/Legacy/Taiko/ConvertHit.cs | 2 +- .../Objects/Legacy/Taiko/ConvertSlider.cs | 2 +- .../Objects/Legacy/Taiko/ConvertSpinner.cs | 2 +- osu.Game/Rulesets/Scoring/HitWindows.cs | 35 +++++++++++++++++++ osu.Game/Rulesets/UI/DrawableRuleset.cs | 4 +-- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 2 +- 27 files changed, 62 insertions(+), 31 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index 77d7de989a..e4ad49ea50 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Catch.Objects Scale = 1.0f - 0.7f * (difficulty.CircleSize - 5) / 5; } - protected override HitWindows CreateHitWindows() => null; + protected override HitWindows CreateHitWindows() => HitWindows.Empty; } public enum FruitVisualRepresentation diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index 0c82cf7bbc..bdba813eed 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -101,6 +101,6 @@ namespace osu.Game.Rulesets.Mania.Objects public override Judgement CreateJudgement() => new HoldNoteJudgement(); - protected override HitWindows CreateHitWindows() => null; + protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs index d0125f8793..ac6697a6dc 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs @@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Mania.Objects { public override Judgement CreateJudgement() => new HoldNoteTickJudgement(); - protected override HitWindows CreateHitWindows() => null; + protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs index a794e57c9e..a277517f9f 100644 --- a/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs @@ -30,6 +30,6 @@ namespace osu.Game.Rulesets.Osu.Objects public override Judgement CreateJudgement() => new OsuJudgement(); - protected override HitWindows CreateHitWindows() => null; + protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 3ed1f2cdde..9bed123465 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -205,6 +205,6 @@ namespace osu.Game.Rulesets.Osu.Objects public override Judgement CreateJudgement() => new OsuJudgement(); - protected override HitWindows CreateHitWindows() => null; + protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index 7e540a577b..14c3369967 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -25,6 +25,6 @@ namespace osu.Game.Rulesets.Osu.Objects public override Judgement CreateJudgement() => new OsuSliderTailJudgement(); - protected override HitWindows CreateHitWindows() => null; + protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs index af7cf5b144..a49f4cef8b 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs @@ -32,6 +32,6 @@ namespace osu.Game.Rulesets.Osu.Objects public override Judgement CreateJudgement() => new OsuJudgement(); - protected override HitWindows CreateHitWindows() => null; + protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 2e7b763966..2441a1449d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -33,6 +33,6 @@ namespace osu.Game.Rulesets.Osu.Objects public override Judgement CreateJudgement() => new OsuJudgement(); - protected override HitWindows CreateHitWindows() => null; + protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 4e02c76a8b..8956ca9c19 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -88,6 +88,6 @@ namespace osu.Game.Rulesets.Taiko.Objects public override Judgement CreateJudgement() => new TaikoDrumRollJudgement(); - protected override HitWindows CreateHitWindows() => null; + protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs index c466ca7c8a..8a8be3e38d 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs @@ -27,6 +27,6 @@ namespace osu.Game.Rulesets.Taiko.Objects public override Judgement CreateJudgement() => new TaikoDrumRollTickJudgement(); - protected override HitWindows CreateHitWindows() => null; + protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs index d660149528..72a04698be 100644 --- a/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs @@ -11,6 +11,6 @@ namespace osu.Game.Rulesets.Taiko.Objects { public override Judgement CreateJudgement() => new TaikoStrongJudgement(); - protected override HitWindows CreateHitWindows() => null; + protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs index f96c033dce..e60984596d 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs @@ -35,6 +35,6 @@ namespace osu.Game.Rulesets.Taiko.Objects public override Judgement CreateJudgement() => new TaikoSwellJudgement(); - protected override HitWindows CreateHitWindows() => null; + protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs index 68212e8f12..91f4d3dbe7 100644 --- a/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs @@ -11,6 +11,6 @@ namespace osu.Game.Rulesets.Taiko.Objects { public override Judgement CreateJudgement() => new TaikoSwellTickJudgement(); - protected override HitWindows CreateHitWindows() => null; + protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index a99fac09cc..eb8652443f 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -66,7 +66,6 @@ namespace osu.Game.Rulesets.Objects /// /// The hit windows for this . /// - [CanBeNull] public HitWindows HitWindows { get; set; } private readonly List nestedHitObjects = new List(); @@ -113,10 +112,7 @@ namespace osu.Game.Rulesets.Objects nestedHitObjects.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime)); foreach (var h in nestedHitObjects) - { - h.HitWindows = HitWindows; h.ApplyDefaults(controlPointInfo, difficulty); - } } protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) @@ -147,7 +143,7 @@ namespace osu.Game.Rulesets.Objects /// This will only be invoked if hasn't been set externally (e.g. from a . /// ///
- [CanBeNull] + [NotNull] protected virtual HitWindows CreateHitWindows() => new HitWindows(); } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs index 609bdd571a..883ef55df0 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs @@ -13,6 +13,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania { public float X { get; set; } - protected override HitWindows CreateHitWindows() => null; + protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs index 350ee3185d..69e6f8379d 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs @@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania public double Duration => EndTime - StartTime; - protected override HitWindows CreateHitWindows() => null; + protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs index e372fbd273..4486c5d906 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs @@ -13,6 +13,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania { public float X { get; set; } - protected override HitWindows CreateHitWindows() => null; + protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs index 067377d300..c6d1f1922c 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs @@ -17,6 +17,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania public float X { get; set; } - protected override HitWindows CreateHitWindows() => null; + protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs index c9851a0074..e40b5b4505 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs @@ -22,6 +22,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu public int ComboOffset { get; set; } - protected override HitWindows CreateHitWindows() => null; + protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs index 1c1180702b..a163329d47 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs @@ -22,6 +22,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu public int ComboOffset { get; set; } - protected override HitWindows CreateHitWindows() => null; + protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs index bc94ea1803..5d96a61633 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu public float Y => Position.Y; - protected override HitWindows CreateHitWindows() => null; + protected override HitWindows CreateHitWindows() => HitWindows.Empty; public bool NewCombo { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs index 709345170f..efb9810927 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs @@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko ///
internal sealed class ConvertHit : HitObject { - protected override HitWindows CreateHitWindows() => null; + protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs index c173b3e11a..b365fd34ae 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs @@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko ///
internal sealed class ConvertSlider : Legacy.ConvertSlider { - protected override HitWindows CreateHitWindows() => null; + protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs index 9a35ad2776..840ba51ac2 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs @@ -15,6 +15,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko public double Duration => EndTime - StartTime; - protected override HitWindows CreateHitWindows() => null; + protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game/Rulesets/Scoring/HitWindows.cs b/osu.Game/Rulesets/Scoring/HitWindows.cs index efc4cd9f5c..9e7e594104 100644 --- a/osu.Game/Rulesets/Scoring/HitWindows.cs +++ b/osu.Game/Rulesets/Scoring/HitWindows.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; @@ -30,6 +31,17 @@ namespace osu.Game.Rulesets.Scoring private double meh; private double miss; + /// + /// An empty with only and . + /// No time values are provided (meaning instantaneous hit or miss). + /// + public static HitWindows Empty => new EmptyHitWindows(); + + public HitWindows() + { + Debug.Assert(GetRanges().Length >= 1, $"{nameof(HitWindows)}"); + } + /// /// Retrieves the with the largest hit window that produces a successful hit. /// @@ -168,6 +180,29 @@ namespace osu.Game.Rulesets.Scoring /// Defaults are provided but can be overridden to customise for a ruleset. ///
protected virtual DifficultyRange[] GetRanges() => base_ranges; + + public class EmptyHitWindows : HitWindows + { + private static readonly DifficultyRange[] ranges = + { + new DifficultyRange(HitResult.Perfect, 0, 0, 0), + new DifficultyRange(HitResult.Miss, 0, 0, 0), + }; + + public override bool IsHitResultAllowed(HitResult result) + { + switch (result) + { + case HitResult.Great: + case HitResult.Miss: + return true; + } + + return false; + } + + protected override DifficultyRange[] GetRanges() => ranges; + } } public struct DifficultyRange diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index d68b0e94c5..d5b3df27df 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -426,11 +426,11 @@ namespace osu.Game.Rulesets.UI { foreach (var h in Objects) { - if (h.HitWindows != null) + if (h.HitWindows.WindowFor(HitResult.Miss) > 0) return h.HitWindows; foreach (var n in h.NestedHitObjects) - if (n.HitWindows != null) + if (h.HitWindows.WindowFor(HitResult.Miss) > 0) return n.HitWindows; } diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 920d11c910..54556f8648 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Play.HUD private void onNewJudgement(JudgementResult result) { - if (result.HitObject.HitWindows == null) + if (result.HitObject.HitWindows.WindowFor(HitResult.Miss) == 0) return; foreach (var c in Children) From ad6b8d3e04eccee5eaabf496b7b3df2d77c79659 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Oct 2019 19:08:55 +0900 Subject: [PATCH 1677/2815] Add result offset bounding to result itself, rather than just transforms --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index f8bc74b2a6..7f3bfd3b5c 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -173,7 +173,7 @@ namespace osu.Game.Rulesets.Objects.Drawables { UpdateInitialTransforms(); - var judgementOffset = Math.Min(HitObject.HitWindows?.WindowFor(HitResult.Miss) ?? double.MaxValue, Result?.TimeOffset ?? 0); + var judgementOffset = Result?.TimeOffset ?? 0; using (BeginDelayedSequence(InitialLifetimeOffset + judgementOffset, true)) { @@ -379,7 +379,8 @@ namespace osu.Game.Rulesets.Objects.Drawables // Ensure that the judgement is given a valid time offset, because this may not get set by the caller var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; - Result.TimeOffset = Time.Current - endTime; + + Result.TimeOffset = Math.Min(HitObject.HitWindows.WindowFor(HitResult.Miss), Time.Current - endTime); switch (Result.Type) { From 9f2a64843291fbe2e8dae6f86f8004559b75991b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Oct 2019 19:23:37 +0900 Subject: [PATCH 1678/2815] Add full asserts --- osu.Game/Rulesets/Scoring/HitWindows.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/HitWindows.cs b/osu.Game/Rulesets/Scoring/HitWindows.cs index 9e7e594104..93e4dfc5fa 100644 --- a/osu.Game/Rulesets/Scoring/HitWindows.cs +++ b/osu.Game/Rulesets/Scoring/HitWindows.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; @@ -39,7 +40,8 @@ namespace osu.Game.Rulesets.Scoring public HitWindows() { - Debug.Assert(GetRanges().Length >= 1, $"{nameof(HitWindows)}"); + Debug.Assert(GetRanges().Any(r => r.Result == HitResult.Miss), $"{nameof(GetRanges)} should always contain {nameof(HitResult.Miss)}"); + Debug.Assert(GetRanges().Any(r => r.Result != HitResult.Miss), $"{nameof(GetRanges)} should always contain at least one result type other than {nameof(HitResult.Miss)}."); } /// From 7e3c97f4962e0503b16527d13cc2fb545a3dc659 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 9 Oct 2019 13:37:07 +0300 Subject: [PATCH 1679/2815] Implement DeletedChildsPlaceholder component --- .../Online/TestSceneCommentsContainer.cs | 6 ++ .../Online/API/Requests/Responses/Comment.cs | 18 +++++ osu.Game/Overlays/Comments/DrawableComment.cs | 66 +++++++++++++++++-- 3 files changed, 86 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs index 4187771963..8a6ec81d8e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -40,6 +40,12 @@ namespace osu.Game.Tests.Visual.Online scrollFlow.Clear(); scrollFlow.Add(new CommentsContainer(CommentableType.Beatmapset, 41823)); }); + + AddStep("Airman comments", () => + { + scrollFlow.Clear(); + scrollFlow.Add(new CommentsContainer(CommentableType.Beatmapset, 24313)); + }); } } } diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs index 76a322e5c9..6fea994cb9 100644 --- a/osu.Game/Online/API/Requests/Responses/Comment.cs +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -5,6 +5,7 @@ using Newtonsoft.Json; using osu.Game.Users; using System; using System.Collections.Generic; +using System.Linq; namespace osu.Game.Online.API.Requests.Responses { @@ -91,5 +92,22 @@ namespace osu.Game.Online.API.Requests.Responses { return IsDeleted ? @"deleted" : MessageHTML.Replace("
", "").Replace("

", "").Replace("
", "").Replace("

", "").Replace("
", "").Replace(""", "\""); } + + public int GetDeletedChildsCount() + { + int count = 0; + + if (ChildComments.Any()) + ChildComments.ForEach(child => + { + if (child.IsDeleted) + count++; + }); + + if (IsDeleted) + count++; + + return count; + } } } diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 4092cbb177..60cae8c62a 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -27,6 +27,7 @@ namespace osu.Game.Overlays.Comments private const int message_padding = 40; private const int duration = 200; private const float separator_height = 1.5f; + private const int deleted_placeholder_margin = 80; public readonly BindableBool ShowDeleted = new BindableBool(); @@ -161,12 +162,26 @@ namespace osu.Game.Overlays.Comments AutoSizeDuration = duration, AutoSizeEasing = Easing.OutQuint, Masking = true, - Child = childCommentsContainer = new FillFlowContainer + Child = new FillFlowContainer { - Margin = new MarginPadding { Left = child_margin }, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + childCommentsContainer = new FillFlowContainer + { + Margin = new MarginPadding { Left = child_margin }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical + }, + new DeletedChildsPlaceholder(comment.GetDeletedChildsCount()) + { + Margin = new MarginPadding { Bottom = margin, Left = deleted_placeholder_margin }, + ShowDeleted = { BindTarget = ShowDeleted } + } + } } } } @@ -221,7 +236,8 @@ namespace osu.Game.Overlays.Comments } } - comment.ChildComments.ForEach(c => childCommentsContainer.Add(new DrawableComment(c))); + comment.ChildComments.ForEach(c => childCommentsContainer.Add(new DrawableComment(c) + { ShowDeleted = { BindTarget = ShowDeleted } })); } protected override void LoadComplete() @@ -258,6 +274,48 @@ namespace osu.Game.Overlays.Comments } } + private class DeletedChildsPlaceholder : FillFlowContainer + { + public readonly BindableBool ShowDeleted = new BindableBool(); + + private readonly bool canBeVisible; + + public DeletedChildsPlaceholder(int count) + { + canBeVisible = count != 0; + + AutoSizeAxes = Axes.Both; + Direction = FillDirection.Horizontal; + Spacing = new Vector2(3, 0); + Alpha = 0; + Children = new Drawable[] + { + new SpriteIcon + { + Icon = FontAwesome.Solid.Trash, + Size = new Vector2(14), + }, + new SpriteText + { + Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true), + Text = $@"{count} deleted comments" + } + }; + } + + protected override void LoadComplete() + { + ShowDeleted.BindValueChanged(onShowDeletedChanged, true); + base.LoadComplete(); + } + + private void onShowDeletedChanged(ValueChangedEvent showDeleted) + { + if (canBeVisible) + this.FadeTo(showDeleted.NewValue ? 0 : 1); + } + } + private class ChevronButton : ShowChildsButton { private readonly SpriteIcon icon; From e6f857d0d8db175f6cae7b70df070227fb474387 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Oct 2019 19:03:20 +0900 Subject: [PATCH 1680/2815] Revert "Warn on incorrect null usage" This reverts commit 93d2c3d7a1c43cb9871f1bcb779b51cddcc086e9. --- osu.sln.DotSettings | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index ae08a1666f..ed162eed6e 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -17,8 +17,7 @@ WARNING WARNING HINT - - True + HINT HINT HINT WARNING @@ -739,7 +738,6 @@ See the LICENCE file in the repository root for full licence text. <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - True True True From c9d5bea0f1bd1a8a01c4fe2f08242e8292dffe22 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 9 Oct 2019 13:45:14 +0300 Subject: [PATCH 1681/2815] Remove animations --- osu.Game/Overlays/Comments/DrawableComment.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 60cae8c62a..94ec7a861a 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -25,7 +25,6 @@ namespace osu.Game.Overlays.Comments private const int child_margin = 20; private const int chevron_margin = 30; private const int message_padding = 40; - private const int duration = 200; private const float separator_height = 1.5f; private const int deleted_placeholder_margin = 80; @@ -159,8 +158,6 @@ namespace osu.Game.Overlays.Comments { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - AutoSizeDuration = duration, - AutoSizeEasing = Easing.OutQuint, Masking = true, Child = new FillFlowContainer { @@ -249,14 +246,12 @@ namespace osu.Game.Overlays.Comments private void onChildExpandedChanged(ValueChangedEvent expanded) { - childCommentsVisibilityContainer.ClearTransforms(); - if (expanded.NewValue) childCommentsVisibilityContainer.AutoSizeAxes = Axes.Y; else { childCommentsVisibilityContainer.AutoSizeAxes = Axes.None; - childCommentsVisibilityContainer.ResizeHeightTo(0, duration, Easing.OutQuint); + childCommentsVisibilityContainer.ResizeHeightTo(0); } } From 4e273fc628d2bea152dc93f65e9cdbf144c9c610 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Oct 2019 19:50:05 +0900 Subject: [PATCH 1682/2815] Return correct allowed value for Perfect Co-Authored-By: Salman Ahmed --- osu.Game/Rulesets/Scoring/HitWindows.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/HitWindows.cs b/osu.Game/Rulesets/Scoring/HitWindows.cs index 93e4dfc5fa..39d67f1071 100644 --- a/osu.Game/Rulesets/Scoring/HitWindows.cs +++ b/osu.Game/Rulesets/Scoring/HitWindows.cs @@ -195,7 +195,7 @@ namespace osu.Game.Rulesets.Scoring { switch (result) { - case HitResult.Great: + case HitResult.Perfect: case HitResult.Miss: return true; } From 107d39c3e97edb17e56bc9377d7e6cf76574ed43 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 9 Oct 2019 14:10:05 +0300 Subject: [PATCH 1683/2815] Add DeletedChildsPlaceholder to the bottom of the comments container --- .../Online/TestSceneCommentsContainer.cs | 3 +- .../Online/API/Requests/Responses/Comment.cs | 3 - .../Overlays/Comments/CommentsContainer.cs | 13 +++++ .../Comments/DeletedChildsPlaceholder.cs | 58 +++++++++++++++++++ osu.Game/Overlays/Comments/DrawableComment.cs | 46 +-------------- 5 files changed, 75 insertions(+), 48 deletions(-) create mode 100644 osu.Game/Overlays/Comments/DeletedChildsPlaceholder.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs index 8a6ec81d8e..342ba487f0 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -21,7 +21,8 @@ namespace osu.Game.Tests.Visual.Online typeof(DrawableComment), typeof(HeaderButton), typeof(SortSelector), - typeof(ShowChildsButton) + typeof(ShowChildsButton), + typeof(DeletedChildsPlaceholder) }; protected override bool UseOnlineAPI => true; diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs index 6fea994cb9..2334c86519 100644 --- a/osu.Game/Online/API/Requests/Responses/Comment.cs +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -104,9 +104,6 @@ namespace osu.Game.Online.API.Requests.Responses count++; }); - if (IsDeleted) - count++; - return count; } } diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index fb97f08a6e..bf68457988 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -93,6 +93,19 @@ namespace osu.Game.Overlays.Comments content.Add(new DrawableComment(c) { ShowDeleted = { BindTarget = ShowDeleted } }); } + + int deletedComments = 0; + + response.Comments.ForEach(comment => + { + if (comment.IsDeleted && comment.IsTopLevel) + deletedComments++; + }); + + content.Add(new DeletedChildsPlaceholder(deletedComments) + { + ShowDeleted = { BindTarget = ShowDeleted } + }); } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/Comments/DeletedChildsPlaceholder.cs b/osu.Game/Overlays/Comments/DeletedChildsPlaceholder.cs new file mode 100644 index 0000000000..d0e6c17ccb --- /dev/null +++ b/osu.Game/Overlays/Comments/DeletedChildsPlaceholder.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 osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; +using osuTK; +using osu.Framework.Bindables; + +namespace osu.Game.Overlays.Comments +{ + public class DeletedChildsPlaceholder : FillFlowContainer + { + private const int deleted_placeholder_margin = 80; + private const int margin = 10; + + public readonly BindableBool ShowDeleted = new BindableBool(); + + private readonly bool canBeVisible; + + public DeletedChildsPlaceholder(int count) + { + canBeVisible = count != 0; + + AutoSizeAxes = Axes.Both; + Direction = FillDirection.Horizontal; + Spacing = new Vector2(3, 0); + Margin = new MarginPadding { Vertical = margin, Left = deleted_placeholder_margin }; + Alpha = 0; + Children = new Drawable[] + { + new SpriteIcon + { + Icon = FontAwesome.Solid.Trash, + Size = new Vector2(14), + }, + new SpriteText + { + Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true), + Text = $@"{count} deleted comments" + } + }; + } + + protected override void LoadComplete() + { + ShowDeleted.BindValueChanged(onShowDeletedChanged, true); + base.LoadComplete(); + } + + private void onShowDeletedChanged(ValueChangedEvent showDeleted) + { + if (canBeVisible) + this.FadeTo(showDeleted.NewValue ? 0 : 1); + } + } +} diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 94ec7a861a..4af2e07227 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -26,7 +26,6 @@ namespace osu.Game.Overlays.Comments private const int chevron_margin = 30; private const int message_padding = 40; private const float separator_height = 1.5f; - private const int deleted_placeholder_margin = 80; public readonly BindableBool ShowDeleted = new BindableBool(); @@ -175,7 +174,6 @@ namespace osu.Game.Overlays.Comments }, new DeletedChildsPlaceholder(comment.GetDeletedChildsCount()) { - Margin = new MarginPadding { Bottom = margin, Left = deleted_placeholder_margin }, ShowDeleted = { BindTarget = ShowDeleted } } } @@ -214,6 +212,8 @@ namespace osu.Game.Overlays.Comments { RelativeSizeAxes = Axes.X, Height = separator_height, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, Child = new Box { RelativeSizeAxes = Axes.Both, @@ -269,48 +269,6 @@ namespace osu.Game.Overlays.Comments } } - private class DeletedChildsPlaceholder : FillFlowContainer - { - public readonly BindableBool ShowDeleted = new BindableBool(); - - private readonly bool canBeVisible; - - public DeletedChildsPlaceholder(int count) - { - canBeVisible = count != 0; - - AutoSizeAxes = Axes.Both; - Direction = FillDirection.Horizontal; - Spacing = new Vector2(3, 0); - Alpha = 0; - Children = new Drawable[] - { - new SpriteIcon - { - Icon = FontAwesome.Solid.Trash, - Size = new Vector2(14), - }, - new SpriteText - { - Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true), - Text = $@"{count} deleted comments" - } - }; - } - - protected override void LoadComplete() - { - ShowDeleted.BindValueChanged(onShowDeletedChanged, true); - base.LoadComplete(); - } - - private void onShowDeletedChanged(ValueChangedEvent showDeleted) - { - if (canBeVisible) - this.FadeTo(showDeleted.NewValue ? 0 : 1); - } - } - private class ChevronButton : ShowChildsButton { private readonly SpriteIcon icon; From 003af19e3f039985ec73565cee3d3ad7345e0e9b Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 9 Oct 2019 23:04:34 +0300 Subject: [PATCH 1684/2815] Introduce legacy skin configuration --- osu.Game/Skinning/LegacySkin.cs | 18 ++++++++++++++++-- osu.Game/Skinning/LegacySkinConfiguration.cs | 20 ++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Skinning/LegacySkinConfiguration.cs diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index fea15458e4..25703b30dc 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -26,6 +26,8 @@ namespace osu.Game.Skinning [CanBeNull] protected IResourceStore Samples; + protected new LegacySkinConfiguration Configuration => (LegacySkinConfiguration)base.Configuration; + public LegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager) : this(skin, new LegacySkinResourceStore(skin, storage), audioManager, "skin.ini") { @@ -37,9 +39,9 @@ namespace osu.Game.Skinning Stream stream = storage?.GetStream(filename); if (stream != null) using (LineBufferedReader reader = new LineBufferedReader(stream)) - Configuration = new LegacySkinDecoder().Decode(reader); + base.Configuration = new LegacySkinDecoder().Decode(reader); else - Configuration = new DefaultSkinConfiguration(); + base.Configuration = new LegacySkinConfiguration { LegacyVersion = LegacySkinConfiguration.LATEST_VERSION }; if (storage != null) { @@ -71,6 +73,18 @@ namespace osu.Game.Skinning case GlobalSkinColour colour: return SkinUtils.As(getCustomColour(colour.ToString())); + case LegacySkinConfigurations legacy: + switch (legacy) + { + case LegacySkinConfigurations.Version: + if (Configuration.LegacyVersion.HasValue) + return SkinUtils.As(new BindableDouble(Configuration.LegacyVersion.Value)); + + break; + } + + break; + case SkinCustomColourLookup customColour: return SkinUtils.As(getCustomColour(customColour.Lookup.ToString())); diff --git a/osu.Game/Skinning/LegacySkinConfiguration.cs b/osu.Game/Skinning/LegacySkinConfiguration.cs new file mode 100644 index 0000000000..022f613562 --- /dev/null +++ b/osu.Game/Skinning/LegacySkinConfiguration.cs @@ -0,0 +1,20 @@ +// 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 LegacySkinConfiguration : DefaultSkinConfiguration + { + public const double LATEST_VERSION = 2.5; + + /// + /// Legacy version of this skin. + /// + public double? LegacyVersion { get; internal set; } + } + + public enum LegacySkinConfigurations + { + Version, + } +} From 7f6541672c9c3d3e30673e47c7d7a1b0ab630f1b Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 9 Oct 2019 23:05:50 +0300 Subject: [PATCH 1685/2815] Parse legacy version of decoded skin to numerical --- osu.Game/Skinning/LegacySkinDecoder.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs index e97664e75e..75cd0a666d 100644 --- a/osu.Game/Skinning/LegacySkinDecoder.cs +++ b/osu.Game/Skinning/LegacySkinDecoder.cs @@ -5,14 +5,14 @@ using osu.Game.Beatmaps.Formats; namespace osu.Game.Skinning { - public class LegacySkinDecoder : LegacyDecoder + public class LegacySkinDecoder : LegacyDecoder { public LegacySkinDecoder() : base(1) { } - protected override void ParseLine(DefaultSkinConfiguration skin, Section section, string line) + protected override void ParseLine(LegacySkinConfiguration skin, Section section, string line) { if (section != Section.Colours) { @@ -32,6 +32,14 @@ namespace osu.Game.Skinning case @"Author": skin.SkinInfo.Creator = pair.Value; return; + + case @"Version": + if (pair.Value == "latest" || pair.Value == "User") + skin.LegacyVersion = LegacySkinConfiguration.LATEST_VERSION; + else if (double.TryParse(pair.Value, out var version)) + skin.LegacyVersion = version; + + return; } break; From 01ac19fdbb96cea3f36da3ce4b5ed39d1994658a Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 9 Oct 2019 23:06:32 +0300 Subject: [PATCH 1686/2815] Set legacy version of osu!classic skin to 2.0 --- osu.Game/Skinning/DefaultLegacySkin.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs index 4b6eea6b6e..4c68ee938f 100644 --- a/osu.Game/Skinning/DefaultLegacySkin.cs +++ b/osu.Game/Skinning/DefaultLegacySkin.cs @@ -20,6 +20,8 @@ namespace osu.Game.Skinning new Color4(18, 124, 255, 255), new Color4(242, 24, 57, 255), }); + + Configuration.LegacyVersion = 2.0; } public static SkinInfo Info { get; } = new SkinInfo From d15db378ce2598a66f5bdad3958d964dbd1b869b Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 9 Oct 2019 23:06:53 +0300 Subject: [PATCH 1687/2815] Add tests for legacy skin decoding --- osu.Game.Tests/Resources/skin-20.ini | 2 ++ osu.Game.Tests/Resources/skin-latest.ini | 2 ++ osu.Game.Tests/Skins/LegacySkinDecoderTest.cs | 11 +++++++++++ 3 files changed, 15 insertions(+) create mode 100644 osu.Game.Tests/Resources/skin-20.ini create mode 100644 osu.Game.Tests/Resources/skin-latest.ini diff --git a/osu.Game.Tests/Resources/skin-20.ini b/osu.Game.Tests/Resources/skin-20.ini new file mode 100644 index 0000000000..947b56b2f9 --- /dev/null +++ b/osu.Game.Tests/Resources/skin-20.ini @@ -0,0 +1,2 @@ +[General] +Version: 2 \ No newline at end of file diff --git a/osu.Game.Tests/Resources/skin-latest.ini b/osu.Game.Tests/Resources/skin-latest.ini new file mode 100644 index 0000000000..32f500263f --- /dev/null +++ b/osu.Game.Tests/Resources/skin-latest.ini @@ -0,0 +1,2 @@ +[General] +Version: latest \ No newline at end of file diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs index 0d96dd08da..8c85074456 100644 --- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs +++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs @@ -56,5 +56,16 @@ namespace osu.Game.Tests.Skins Assert.AreEqual("TestValue", config.ConfigDictionary["TestLookup"]); } } + + [TestCase("skin-20.ini", 2.0)] + [TestCase("skin-latest.ini", LegacySkinConfiguration.LATEST_VERSION)] + [TestCase("skin-empty.ini", null)] + public void TestDecodeVersion(string filename, double? expected) + { + var decoder = new LegacySkinDecoder(); + using (var resStream = TestResources.OpenResource(filename)) + using (var stream = new LineBufferedReader(resStream)) + Assert.AreEqual(expected, decoder.Decode(stream).LegacyVersion); + } } } From 2cf17e0bf38455d465d2f59e7c9552255dde2766 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 9 Oct 2019 23:33:25 +0300 Subject: [PATCH 1688/2815] Use decimal data type instead --- osu.Game.Tests/Skins/LegacySkinDecoderTest.cs | 28 +++++++++++++++---- osu.Game/Skinning/DefaultLegacySkin.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 2 +- osu.Game/Skinning/LegacySkinConfiguration.cs | 4 +-- osu.Game/Skinning/LegacySkinDecoder.cs | 2 +- 5 files changed, 27 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs index 8c85074456..4fee6942d0 100644 --- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs +++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs @@ -57,15 +57,31 @@ namespace osu.Game.Tests.Skins } } - [TestCase("skin-20.ini", 2.0)] - [TestCase("skin-latest.ini", LegacySkinConfiguration.LATEST_VERSION)] - [TestCase("skin-empty.ini", null)] - public void TestDecodeVersion(string filename, double? expected) + [Test] + public void TestDecodeSpecifiedVersion() { var decoder = new LegacySkinDecoder(); - using (var resStream = TestResources.OpenResource(filename)) + using (var resStream = TestResources.OpenResource("skin-20.ini")) using (var stream = new LineBufferedReader(resStream)) - Assert.AreEqual(expected, decoder.Decode(stream).LegacyVersion); + Assert.AreEqual(2.0m, decoder.Decode(stream).LegacyVersion); + } + + [Test] + public void TestDecodeLatestVersion() + { + var decoder = new LegacySkinDecoder(); + using (var resStream = TestResources.OpenResource("skin-latest.ini")) + using (var stream = new LineBufferedReader(resStream)) + Assert.AreEqual(LegacySkinConfiguration.LATEST_VERSION, decoder.Decode(stream).LegacyVersion); + } + + [Test] + public void TestDecodeNoVersion() + { + var decoder = new LegacySkinDecoder(); + using (var resStream = TestResources.OpenResource("skin-empty.ini")) + using (var stream = new LineBufferedReader(resStream)) + Assert.IsNull(decoder.Decode(stream).LegacyVersion); } } } diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs index 4c68ee938f..0caf2d19e9 100644 --- a/osu.Game/Skinning/DefaultLegacySkin.cs +++ b/osu.Game/Skinning/DefaultLegacySkin.cs @@ -21,7 +21,7 @@ namespace osu.Game.Skinning new Color4(242, 24, 57, 255), }); - Configuration.LegacyVersion = 2.0; + Configuration.LegacyVersion = 2.0m; } public static SkinInfo Info { get; } = new SkinInfo diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 25703b30dc..f6a366b21d 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -78,7 +78,7 @@ namespace osu.Game.Skinning { case LegacySkinConfigurations.Version: if (Configuration.LegacyVersion.HasValue) - return SkinUtils.As(new BindableDouble(Configuration.LegacyVersion.Value)); + return SkinUtils.As(new Bindable(Configuration.LegacyVersion.Value)); break; } diff --git a/osu.Game/Skinning/LegacySkinConfiguration.cs b/osu.Game/Skinning/LegacySkinConfiguration.cs index 022f613562..051d10747b 100644 --- a/osu.Game/Skinning/LegacySkinConfiguration.cs +++ b/osu.Game/Skinning/LegacySkinConfiguration.cs @@ -5,12 +5,12 @@ namespace osu.Game.Skinning { public class LegacySkinConfiguration : DefaultSkinConfiguration { - public const double LATEST_VERSION = 2.5; + public const decimal LATEST_VERSION = 2.5m; /// /// Legacy version of this skin. /// - public double? LegacyVersion { get; internal set; } + public decimal? LegacyVersion { get; internal set; } } public enum LegacySkinConfigurations diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs index 75cd0a666d..ea087353a0 100644 --- a/osu.Game/Skinning/LegacySkinDecoder.cs +++ b/osu.Game/Skinning/LegacySkinDecoder.cs @@ -36,7 +36,7 @@ namespace osu.Game.Skinning case @"Version": if (pair.Value == "latest" || pair.Value == "User") skin.LegacyVersion = LegacySkinConfiguration.LATEST_VERSION; - else if (double.TryParse(pair.Value, out var version)) + else if (decimal.TryParse(pair.Value, out var version)) skin.LegacyVersion = version; return; From f74c79c2b85422fda49a147c2ee0d9441bbd49df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Oct 2019 11:58:43 +0900 Subject: [PATCH 1689/2815] Fix audio playback position being reset after resuming to song select --- .../Visual/Menus/TestSceneScreenNavigation.cs | 80 ++++++++++++++----- osu.Game/Screens/Select/SongSelect.cs | 12 ++- 2 files changed, 68 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs index 17535cae98..124d3bb453 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs @@ -5,12 +5,15 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; @@ -18,7 +21,9 @@ using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Screens; using osu.Game.Screens.Menu; +using osu.Game.Screens.Play; using osu.Game.Screens.Select; +using osu.Game.Tests.Beatmaps.IO; using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -31,11 +36,11 @@ namespace osu.Game.Tests.Visual.Menus private const float click_padding = 25; private GameHost host; - private TestOsuGame osuGame; + private TestOsuGame game; - private Vector2 backButtonPosition => osuGame.ToScreenSpace(new Vector2(click_padding, osuGame.LayoutRectangle.Bottom - click_padding)); + private Vector2 backButtonPosition => game.ToScreenSpace(new Vector2(click_padding, game.LayoutRectangle.Bottom - click_padding)); - private Vector2 optionsButtonPosition => osuGame.ToScreenSpace(new Vector2(click_padding, click_padding)); + private Vector2 optionsButtonPosition => game.ToScreenSpace(new Vector2(click_padding, click_padding)); [BackgroundDependencyLoader] private void load(GameHost host) @@ -54,23 +59,23 @@ namespace osu.Game.Tests.Visual.Menus { AddStep("Create new game instance", () => { - if (osuGame != null) + if (game != null) { - Remove(osuGame); - osuGame.Dispose(); + Remove(game); + game.Dispose(); } - osuGame = new TestOsuGame(LocalStorage, API); - osuGame.SetHost(host); + game = new TestOsuGame(LocalStorage, API); + game.SetHost(host); // todo: this can be removed once we can run audio trakcs without a device present // see https://github.com/ppy/osu/issues/1302 - osuGame.LocalConfig.Set(OsuSetting.IntroSequence, IntroSequence.Circles); + game.LocalConfig.Set(OsuSetting.IntroSequence, IntroSequence.Circles); - Add(osuGame); + Add(game); }); - AddUntilStep("Wait for load", () => osuGame.IsLoaded); - AddUntilStep("Wait for intro", () => osuGame.ScreenStack.CurrentScreen is IntroScreen); + AddUntilStep("Wait for load", () => game.IsLoaded); + AddUntilStep("Wait for intro", () => game.ScreenStack.CurrentScreen is IntroScreen); confirmAtMainMenu(); } @@ -82,11 +87,39 @@ namespace osu.Game.Tests.Visual.Menus pushAndConfirm(() => songSelect = new TestSongSelect()); AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show()); AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible); - AddStep("Press escape", () => pressAndRelease(Key.Escape)); + pushEscape(); AddAssert("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden); exitViaEscapeAndConfirm(); } + [Test] + public void TestSongContinuesAfterExitPlayer() + { + Player player = null; + + WorkingBeatmap beatmap() => game.Beatmap.Value; + Track track() => beatmap().Track; + + pushAndConfirm(() => new TestSongSelect()); + + AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(game).Wait()); + + AddUntilStep("wait for selected", () => !(track() is TrackVirtual)); + + AddStep("press enter", () => pressAndRelease(Key.Enter)); + + AddUntilStep("wait for player", () => (player = game.ScreenStack.CurrentScreen as Player) != null); + AddUntilStep("wait for fail", () => player.HasFailed); + + AddUntilStep("wait for track stop", () => !track().IsRunning); + AddAssert("Ensure time before preview point", () => track().CurrentTime < beatmap().Metadata.PreviewTime); + + pushEscape(); + + AddUntilStep("wait for track playing", () => track().IsRunning); + AddAssert("Ensure time wasn't reset to preview point", () => track().CurrentTime < beatmap().Metadata.PreviewTime); + } + [Test] public void TestExitSongSelectWithClick() { @@ -98,7 +131,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition)); // BackButton handles hover using its child button, so this checks whether or not any of BackButton's children are hovered. - AddUntilStep("Back button is hovered", () => InputManager.HoveredDrawables.Any(d => d.Parent == osuGame.BackButton)); + AddUntilStep("Back button is hovered", () => InputManager.HoveredDrawables.Any(d => d.Parent == game.BackButton)); AddStep("Click back button", () => InputManager.Click(MouseButton.Left)); AddUntilStep("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden); @@ -122,25 +155,28 @@ namespace osu.Game.Tests.Visual.Menus [Test] public void TestOpenOptionsAndExitWithEscape() { - AddUntilStep("Wait for options to load", () => osuGame.Settings.IsLoaded); + AddUntilStep("Wait for options to load", () => game.Settings.IsLoaded); AddStep("Enter menu", () => pressAndRelease(Key.Enter)); AddStep("Move mouse to options overlay", () => InputManager.MoveMouseTo(optionsButtonPosition)); AddStep("Click options overlay", () => InputManager.Click(MouseButton.Left)); - AddAssert("Options overlay was opened", () => osuGame.Settings.State.Value == Visibility.Visible); + AddAssert("Options overlay was opened", () => game.Settings.State.Value == Visibility.Visible); AddStep("Hide options overlay using escape", () => pressAndRelease(Key.Escape)); - AddAssert("Options overlay was closed", () => osuGame.Settings.State.Value == Visibility.Hidden); + AddAssert("Options overlay was closed", () => game.Settings.State.Value == Visibility.Hidden); } private void pushAndConfirm(Func newScreen) { Screen screen = null; - AddStep("Push new screen", () => osuGame.ScreenStack.Push(screen = newScreen())); - AddUntilStep("Wait for new screen", () => osuGame.ScreenStack.CurrentScreen == screen && screen.IsLoaded); + AddStep("Push new screen", () => game.ScreenStack.Push(screen = newScreen())); + AddUntilStep("Wait for new screen", () => game.ScreenStack.CurrentScreen == screen && screen.IsLoaded); } + private void pushEscape() => + AddStep("Press escape", () => pressAndRelease(Key.Escape)); + private void exitViaEscapeAndConfirm() { - AddStep("Press escape", () => pressAndRelease(Key.Escape)); + pushEscape(); confirmAtMainMenu(); } @@ -151,7 +187,7 @@ namespace osu.Game.Tests.Visual.Menus confirmAtMainMenu(); } - private void confirmAtMainMenu() => AddUntilStep("Wait for main menu", () => osuGame.ScreenStack.CurrentScreen is MainMenu menu && menu.IsLoaded); + private void confirmAtMainMenu() => AddUntilStep("Wait for main menu", () => game.ScreenStack.CurrentScreen is MainMenu menu && menu.IsLoaded); private void pressAndRelease(Key key) { @@ -169,6 +205,8 @@ namespace osu.Game.Tests.Visual.Menus public new OsuConfigManager LocalConfig => base.LocalConfig; + public new Bindable Beatmap => base.Beatmap; + protected override Loader CreateLoader() => new TestLoader(); public TestOsuGame(Storage storage, IAPIProvider api) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 6c5f64ed6c..0025188ad4 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -490,7 +490,7 @@ namespace osu.Game.Screens.Select if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending) { UpdateBeatmap(Beatmap.Value); - ensurePlayingSelected(); + ensurePlayingSelected(false); } base.OnResuming(last); @@ -587,7 +587,8 @@ namespace osu.Game.Screens.Select /// Ensures some music is playing for the current track. /// Will resume playback from a manual user pause if the track has changed. ///
- private void ensurePlayingSelected() + /// Whether to restart from the preview point, rather than resuming from previous location. + private void ensurePlayingSelected(bool fromPreviewPoint = true) { Track track = Beatmap.Value.Track; @@ -596,7 +597,12 @@ namespace osu.Game.Screens.Select track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; if (!track.IsRunning && (music?.IsUserPaused != true || isNewTrack)) - track.Restart(); + { + if (fromPreviewPoint) + track.Restart(); + else + track.Start(); + } lastTrack.SetTarget(track); } From e66f9adb8653a68b570d7fbee47f8854a7f61084 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Oct 2019 16:52:51 +0900 Subject: [PATCH 1690/2815] Fix user pause not being cancelled when playing audio --- osu.Game/Overlays/MusicController.cs | 45 ++++++++++++++++++--------- osu.Game/Screens/Select/SongSelect.cs | 7 +---- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index f5c36a9cac..2547d7515e 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -98,20 +98,13 @@ namespace osu.Game.Overlays /// /// Start playing the current track (if not already playing). /// - public void Play() - { - if (!IsPlaying) - TogglePause(); - } - - /// - /// Toggle pause / play. - /// /// Whether the operation was successful. - public bool TogglePause() + public bool Play(bool restart = false) { var track = current?.Track; + IsUserPaused = false; + if (track == null) { if (beatmap.Disabled) @@ -121,16 +114,40 @@ namespace osu.Game.Overlays return true; } + if (restart) + track.Restart(); + else if (!IsPlaying) + track.Start(); + + return true; + } + + /// + /// Stop playing the current track and pause at the current position. + /// + public void Stop() + { + var track = current?.Track; + if (track.IsRunning) { IsUserPaused = true; track.Stop(); } + } + + /// + /// Toggle pause / play. + /// + /// Whether the operation was successful. + public bool TogglePause() + { + var track = current?.Track; + + if (track?.IsRunning == true) + Stop(); else - { - track.Start(); - IsUserPaused = false; - } + Play(); return true; } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 0025188ad4..8187dd04d1 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -597,12 +597,7 @@ namespace osu.Game.Screens.Select track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; if (!track.IsRunning && (music?.IsUserPaused != true || isNewTrack)) - { - if (fromPreviewPoint) - track.Restart(); - else - track.Start(); - } + music?.Play(fromPreviewPoint); lastTrack.SetTarget(track); } From 65df7902f3f933baa68df1df3211f0077147b40f Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2019 08:15:47 +0000 Subject: [PATCH 1691/2815] Bump ppy.osu.Framework.NativeLibs from 2019.813.0 to 2019.1010.0 Bumps [ppy.osu.Framework.NativeLibs](https://github.com/ppy/osu-framework) from 2019.813.0 to 2019.1010.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.813.0...2019.1010.0) Signed-off-by: dependabot-preview[bot] --- osu.iOS.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.iOS.props b/osu.iOS.props index a15cae55c4..e59f613c98 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -124,6 +124,6 @@ - +
From efa5cedf4fef1c3393fdb6ef5926873072c8e6c5 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2019 08:16:07 +0000 Subject: [PATCH 1692/2815] Bump ppy.osu.Framework.Android from 2019.930.0 to 2019.1010.0 Bumps [ppy.osu.Framework.Android](https://github.com/ppy/osu-framework) from 2019.930.0 to 2019.1010.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.930.0...2019.1010.0) Signed-off-by: dependabot-preview[bot] --- osu.Android.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Android.props b/osu.Android.props index 51245351b6..fa940a7c89 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -62,6 +62,6 @@ - + From bc4c1a237140da50f9958fcaf3ee7b0535e945db Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2019 08:16:28 +0000 Subject: [PATCH 1693/2815] Bump ppy.osu.Framework from 2019.930.0 to 2019.1010.0 Bumps [ppy.osu.Framework](https://github.com/ppy/osu-framework) from 2019.930.0 to 2019.1010.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.930.0...2019.1010.0) 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 8cbc8b0af3..4e3130b64c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index a15cae55c4..8a9dc01f38 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,7 +118,7 @@ - + From e50d8419fda384b0f57a11e0bf4d838ab66d8783 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2019 08:30:37 +0000 Subject: [PATCH 1694/2815] Bump ppy.osu.Framework.iOS from 2019.930.0 to 2019.1010.0 Bumps [ppy.osu.Framework.iOS](https://github.com/ppy/osu-framework) from 2019.930.0 to 2019.1010.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.930.0...2019.1010.0) Signed-off-by: dependabot-preview[bot] --- osu.iOS.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.iOS.props b/osu.iOS.props index a15cae55c4..7069960ce1 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -119,7 +119,7 @@ - + From 8df2e359c48d458cebaa3923e9d7342428259be2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Oct 2019 17:39:41 +0900 Subject: [PATCH 1695/2815] Fix tests on CI --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 4 ++-- osu.Game.Tests/Resources/TestResources.cs | 6 +++--- osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 6da8d8cb71..b24171a231 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -456,9 +456,9 @@ namespace osu.Game.Tests.Beatmaps.IO } } - public static async Task LoadOszIntoOsu(OsuGameBase osu, string path = null) + public static async Task LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false) { - var temp = path ?? TestResources.GetTestBeatmapForImport(); + var temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack); var manager = osu.Dependencies.Get(); diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index 9cb85a63bf..66084a3204 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -11,13 +11,13 @@ namespace osu.Game.Tests.Resources { public static Stream OpenResource(string name) => new DllResourceStore("osu.Game.Tests.dll").GetStream($"Resources/{name}"); - public static Stream GetTestBeatmapStream() => new DllResourceStore("osu.Game.Resources.dll").GetStream("Beatmaps/241526 Soleily - Renatus.osz"); + public static Stream GetTestBeatmapStream(bool virtualTrack = false) => new DllResourceStore("osu.Game.Resources.dll").GetStream($"Beatmaps/241526 Soleily - Renatus{(virtualTrack ? "_virtual" : "")}.osz"); - public static string GetTestBeatmapForImport() + public static string GetTestBeatmapForImport(bool virtualTrack = false) { var temp = Path.GetTempFileName() + ".osz"; - using (var stream = GetTestBeatmapStream()) + using (var stream = GetTestBeatmapStream(virtualTrack)) using (var newFile = File.Create(temp)) stream.CopyTo(newFile); diff --git a/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs index 124d3bb453..e74992e37a 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs @@ -102,9 +102,9 @@ namespace osu.Game.Tests.Visual.Menus pushAndConfirm(() => new TestSongSelect()); - AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(game).Wait()); + AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Wait()); - AddUntilStep("wait for selected", () => !(track() is TrackVirtual)); + AddUntilStep("wait for selected", () => !game.Beatmap.IsDefault); AddStep("press enter", () => pressAndRelease(Key.Enter)); From f6b78ad6617508f49654c86696585b934c83a8d2 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 10 Oct 2019 11:43:45 +0300 Subject: [PATCH 1696/2815] Overall cleanups --- .../Online/API/Requests/GetCommentsRequest.cs | 8 +-- .../Overlays/Comments/CommentsContainer.cs | 4 +- osu.Game/Overlays/Comments/CommentsHeader.cs | 3 +- .../Comments/DeletedChildsPlaceholder.cs | 3 +- osu.Game/Overlays/Comments/DrawableComment.cs | 59 +++++++------------ osu.Game/Overlays/Comments/SortSelector.cs | 8 ++- 6 files changed, 35 insertions(+), 50 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetCommentsRequest.cs b/osu.Game/Online/API/Requests/GetCommentsRequest.cs index 02a36f7aa2..fb30130ee9 100644 --- a/osu.Game/Online/API/Requests/GetCommentsRequest.cs +++ b/osu.Game/Online/API/Requests/GetCommentsRequest.cs @@ -4,6 +4,7 @@ using osu.Framework.IO.Network; using Humanizer; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays.Comments; namespace osu.Game.Online.API.Requests { @@ -43,11 +44,4 @@ namespace osu.Game.Online.API.Requests Beatmapset, NewsPost } - - public enum SortCommentsBy - { - New, - Old, - Top - } } diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index bf68457988..314376f5ff 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -91,7 +91,9 @@ namespace osu.Game.Overlays.Comments { if (c.IsTopLevel) content.Add(new DrawableComment(c) - { ShowDeleted = { BindTarget = ShowDeleted } }); + { + ShowDeleted = { BindTarget = ShowDeleted } + }); } int deletedComments = 0; diff --git a/osu.Game/Overlays/Comments/CommentsHeader.cs b/osu.Game/Overlays/Comments/CommentsHeader.cs index 90a6f44d6b..6e9864f153 100644 --- a/osu.Game/Overlays/Comments/CommentsHeader.cs +++ b/osu.Game/Overlays/Comments/CommentsHeader.cs @@ -3,7 +3,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; -using osu.Game.Online.API.Requests; using osu.Framework.Graphics; using osu.Framework.Bindables; using osu.Framework.Graphics.Shapes; @@ -62,7 +61,7 @@ namespace osu.Game.Overlays.Comments { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Current = { BindTarget = Sort } + Current = Sort } } }, diff --git a/osu.Game/Overlays/Comments/DeletedChildsPlaceholder.cs b/osu.Game/Overlays/Comments/DeletedChildsPlaceholder.cs index d0e6c17ccb..7aae42908e 100644 --- a/osu.Game/Overlays/Comments/DeletedChildsPlaceholder.cs +++ b/osu.Game/Overlays/Comments/DeletedChildsPlaceholder.cs @@ -7,6 +7,7 @@ using osu.Game.Graphics; using osu.Framework.Graphics.Sprites; using osuTK; using osu.Framework.Bindables; +using System.Linq; namespace osu.Game.Overlays.Comments { @@ -38,7 +39,7 @@ namespace osu.Game.Overlays.Comments new SpriteText { Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true), - Text = $@"{count} deleted comments" + Text = $@"{count} deleted comment{(count.ToString().ToCharArray().Last() == '1' ? "" : "s")}" } }; } diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 4af2e07227..fd7f874304 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Comments private readonly BindableBool childExpanded = new BindableBool(true); - private readonly Container childCommentsVisibilityContainer; + private readonly FillFlowContainer childCommentsVisibilityContainer; private readonly Comment comment; public DrawableComment(Comment comment) @@ -47,7 +47,6 @@ namespace osu.Game.Overlays.Comments RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - Masking = true; InternalChild = new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -145,7 +144,9 @@ namespace osu.Game.Overlays.Comments Text = HumanizerUtils.Humanize(comment.CreatedAt) }, new RepliesButton(comment.RepliesCount) - { Expanded = { BindTarget = childExpanded } }, + { + Expanded = { BindTarget = childExpanded } + }, } } } @@ -153,29 +154,23 @@ namespace osu.Game.Overlays.Comments } } }, - childCommentsVisibilityContainer = new Container + childCommentsVisibilityContainer = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Masking = true, - Child = new FillFlowContainer + Direction = FillDirection.Vertical, + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] + childCommentsContainer = new FillFlowContainer { - childCommentsContainer = new FillFlowContainer - { - Margin = new MarginPadding { Left = child_margin }, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical - }, - new DeletedChildsPlaceholder(comment.GetDeletedChildsCount()) - { - ShowDeleted = { BindTarget = ShowDeleted } - } + Margin = new MarginPadding { Left = child_margin }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical + }, + new DeletedChildsPlaceholder(comment.GetDeletedChildsCount()) + { + ShowDeleted = { BindTarget = ShowDeleted } } } } @@ -234,7 +229,9 @@ namespace osu.Game.Overlays.Comments } comment.ChildComments.ForEach(c => childCommentsContainer.Add(new DrawableComment(c) - { ShowDeleted = { BindTarget = ShowDeleted } })); + { + ShowDeleted = { BindTarget = ShowDeleted } + })); } protected override void LoadComplete() @@ -246,27 +243,13 @@ namespace osu.Game.Overlays.Comments private void onChildExpandedChanged(ValueChangedEvent expanded) { - if (expanded.NewValue) - childCommentsVisibilityContainer.AutoSizeAxes = Axes.Y; - else - { - childCommentsVisibilityContainer.AutoSizeAxes = Axes.None; - childCommentsVisibilityContainer.ResizeHeightTo(0); - } + childCommentsVisibilityContainer.FadeTo(expanded.NewValue ? 1 : 0); } private void onShowDeletedChanged(ValueChangedEvent show) { if (comment.IsDeleted) - { - if (show.NewValue) - AutoSizeAxes = Axes.Y; - else - { - AutoSizeAxes = Axes.None; - this.ResizeHeightTo(0); - } - } + this.FadeTo(show.NewValue ? 1 : 0); } private class ChevronButton : ShowChildsButton diff --git a/osu.Game/Overlays/Comments/SortSelector.cs b/osu.Game/Overlays/Comments/SortSelector.cs index 4425145c3e..cb95a758ff 100644 --- a/osu.Game/Overlays/Comments/SortSelector.cs +++ b/osu.Game/Overlays/Comments/SortSelector.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 osu.Game.Online.API.Requests; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; @@ -72,4 +71,11 @@ namespace osu.Game.Overlays.Comments } } } + + public enum SortCommentsBy + { + New, + Old, + Top + } } From 5d6648d9c95df7f5e05b5008e280f525410ccfe7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Oct 2019 17:45:38 +0900 Subject: [PATCH 1697/2815] Update 2.2 references --- .../CatchRuleset__Tests_.xml | 4 +-- .../ManiaRuleset__Tests_.xml | 4 +-- .../runConfigurations/OsuRuleset__Tests_.xml | 4 +-- .../TaikoRuleset__Tests_.xml | 4 +-- .../.idea/runConfigurations/Tournament.xml | 4 +-- .../.idea/runConfigurations/osu_.xml | 4 +-- .../.idea/runConfigurations/osu___Tests_.xml | 4 +-- .vscode/launch.json | 32 +++++++++---------- .vscode/tasks.json | 2 +- README.md | 4 +-- osu.Desktop/osu.Desktop.csproj | 2 +- .../.vscode/launch.json | 4 +-- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../.vscode/launch.json | 4 +-- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- .../.vscode/launch.json | 4 +-- .../osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../.vscode/launch.json | 4 +-- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- .../osu.Game.Tournament.Tests.csproj | 2 +- 21 files changed, 48 insertions(+), 48 deletions(-) diff --git a/.idea/.idea.osu/.idea/runConfigurations/CatchRuleset__Tests_.xml b/.idea/.idea.osu/.idea/runConfigurations/CatchRuleset__Tests_.xml index 6463dd6ea5..5372b6f28a 100644 --- a/.idea/.idea.osu/.idea/runConfigurations/CatchRuleset__Tests_.xml +++ b/.idea/.idea.osu/.idea/runConfigurations/CatchRuleset__Tests_.xml @@ -1,6 +1,6 @@ -
/// The snapped position. /// The time at the snapped position. - public double GetSnapTime(Vector2 snappedPosition) => startTime + (ToLocalSpace(snappedPosition) - startPosition).Length / Velocity; + public double GetSnapTime(Vector2 snappedPosition) => startTime + (ToLocalSpace(snappedPosition) - StartPosition).Length / Velocity; /// /// Retrieves the applicable colour for a beat index. From 8fb2628f9e07185dbdf1b3211a753839c3561ac1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Oct 2019 15:41:01 +0900 Subject: [PATCH 1724/2815] Improve xmldocs --- .../Edit/Compose/Components/BeatSnapGrid.cs | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs index d44d53dca7..175d61fd32 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs @@ -17,8 +17,14 @@ namespace osu.Game.Screens.Edit.Compose.Components { public abstract class BeatSnapGrid : CompositeDrawable { + /// + /// The velocity of the beatmap at the point of placement in pixels per millisecond. + /// protected double Velocity { get; private set; } + /// + /// The spacing between each tick of the beat snapping grid. + /// protected float DistanceSpacing { get; private set; } protected readonly Vector2 StartPosition; @@ -88,32 +94,32 @@ namespace osu.Game.Screens.Edit.Compose.Components } /// - /// Draws the grid. + /// Creates the content which visualises the grid ticks. /// protected abstract void CreateGrid(Vector2 startPosition); /// - /// Retrieves the velocity of gameplay at a time. + /// Retrieves the velocity of gameplay at a point in time in pixels per millisecond. /// /// The time to retrieve the velocity at. - /// The beatmap's at the requested time. - /// The beatmap's at the requested time. - /// The velocity in pixels per millisecond. + /// The beatmap's at the point in time. + /// The beatmap's at the point in time. + /// The velocity. protected abstract float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty); /// - /// Snaps a position to this grid. + /// Snaps a screen-space position to this grid. /// /// The original screen-space position. - /// The snapped position. + /// The snapped screen-space position. public abstract Vector2 GetSnapPosition(Vector2 screenSpacePosition); /// - /// Retrieves the time at a snapped position. + /// Retrieves the time at a snapped screen-space position. /// - /// The snapped position. + /// The snapped screen-space position. /// The time at the snapped position. - public double GetSnapTime(Vector2 snappedPosition) => startTime + (ToLocalSpace(snappedPosition) - StartPosition).Length / Velocity; + public double GetSnapTime(Vector2 screenSpacePosition) => startTime + (ToLocalSpace(screenSpacePosition) - CentrePosition).Length / Velocity; /// /// Retrieves the applicable colour for a beat index. From 5f0cd356d764ffab48ab4396c18f4a017600e4f0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Oct 2019 15:41:21 +0900 Subject: [PATCH 1725/2815] Rename startPosition to centrePosition --- osu.Game.Tests/Visual/Editor/TestSceneBeatSnapGrid.cs | 4 ++-- .../Screens/Edit/Compose/Components/BeatSnapGrid.cs | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneBeatSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneBeatSnapGrid.cs index 6419b0c2e2..9f9b884cdd 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneBeatSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneBeatSnapGrid.cs @@ -133,8 +133,8 @@ namespace osu.Game.Tests.Visual.Editor public new float DistanceSpacing => base.DistanceSpacing; - public TestBeatSnapGrid(HitObject hitObject, Vector2 startPosition) - : base(hitObject, startPosition) + public TestBeatSnapGrid(HitObject hitObject, Vector2 centrePosition) + : base(hitObject, centrePosition) { } diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs index 175d61fd32..24926995e6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs @@ -27,7 +27,11 @@ namespace osu.Game.Screens.Edit.Compose.Components /// protected float DistanceSpacing { get; private set; } - protected readonly Vector2 StartPosition; + /// + /// The position which the grid is centred on. + /// The first beat snapping tick is located at + in the desired direction. + /// + protected readonly Vector2 CentrePosition; [Resolved] private IEditorBeatmap beatmap { get; set; } @@ -44,10 +48,10 @@ namespace osu.Game.Screens.Edit.Compose.Components private double startTime; private double beatLength; - protected BeatSnapGrid(HitObject hitObject, Vector2 startPosition) + protected BeatSnapGrid(HitObject hitObject, Vector2 centrePosition) { this.hitObject = hitObject; - this.StartPosition = startPosition; + this.CentrePosition = centrePosition; RelativeSizeAxes = Axes.Both; } From 9ecec806c2343d90c03eb48b71b264e1f82dba0e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Oct 2019 15:41:40 +0900 Subject: [PATCH 1726/2815] Rename grid creation method + parameter --- .../Visual/Editor/TestSceneBeatSnapGrid.cs | 20 +++++++++---------- .../Edit/Compose/Components/BeatSnapGrid.cs | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneBeatSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneBeatSnapGrid.cs index 9f9b884cdd..345fe245fe 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneBeatSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneBeatSnapGrid.cs @@ -138,63 +138,63 @@ namespace osu.Game.Tests.Visual.Editor { } - protected override void CreateGrid(Vector2 startPosition) + protected override void CreateContent(Vector2 centrePosition) { AddInternal(new Circle { Origin = Anchor.Centre, Size = new Vector2(5), - Position = startPosition + Position = centrePosition }); int beatIndex = 0; - for (float s = startPosition.X + DistanceSpacing; s <= DrawWidth; s += DistanceSpacing, beatIndex++) + for (float s = centrePosition.X + DistanceSpacing; s <= DrawWidth; s += DistanceSpacing, beatIndex++) { AddInternal(new Circle { Origin = Anchor.Centre, Size = new Vector2(5, 10), - Position = new Vector2(s, startPosition.Y), + Position = new Vector2(s, centrePosition.Y), Colour = GetColourForBeatIndex(beatIndex) }); } beatIndex = 0; - for (float s = startPosition.X - DistanceSpacing; s >= 0; s -= DistanceSpacing, beatIndex++) + for (float s = centrePosition.X - DistanceSpacing; s >= 0; s -= DistanceSpacing, beatIndex++) { AddInternal(new Circle { Origin = Anchor.Centre, Size = new Vector2(5, 10), - Position = new Vector2(s, startPosition.Y), + Position = new Vector2(s, centrePosition.Y), Colour = GetColourForBeatIndex(beatIndex) }); } beatIndex = 0; - for (float s = startPosition.Y + DistanceSpacing; s <= DrawHeight; s += DistanceSpacing, beatIndex++) + for (float s = centrePosition.Y + DistanceSpacing; s <= DrawHeight; s += DistanceSpacing, beatIndex++) { AddInternal(new Circle { Origin = Anchor.Centre, Size = new Vector2(10, 5), - Position = new Vector2(startPosition.X, s), + Position = new Vector2(centrePosition.X, s), Colour = GetColourForBeatIndex(beatIndex) }); } beatIndex = 0; - for (float s = startPosition.Y - DistanceSpacing; s >= 0; s -= DistanceSpacing, beatIndex++) + for (float s = centrePosition.Y - DistanceSpacing; s >= 0; s -= DistanceSpacing, beatIndex++) { AddInternal(new Circle { Origin = Anchor.Centre, Size = new Vector2(10, 5), - Position = new Vector2(startPosition.X, s), + Position = new Vector2(centrePosition.X, s), Colour = GetColourForBeatIndex(beatIndex) }); } diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs index 24926995e6..98ad0dd3e8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs @@ -92,7 +92,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (!gridCache.IsValid) { ClearInternal(); - CreateGrid(StartPosition); + CreateContent(CentrePosition); gridCache.Validate(); } } @@ -100,7 +100,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Creates the content which visualises the grid ticks. /// - protected abstract void CreateGrid(Vector2 startPosition); + protected abstract void CreateContent(Vector2 centrePosition); /// /// Retrieves the velocity of gameplay at a point in time in pixels per millisecond. From 050d86a741b5908d45ea588d7d25d3f8ae9c079b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Oct 2019 15:46:11 +0900 Subject: [PATCH 1727/2815] Always use the local coordinate space --- .../Visual/Editor/TestSceneBeatSnapGrid.cs | 8 ++++---- .../Edit/Compose/Components/BeatSnapGrid.cs | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneBeatSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneBeatSnapGrid.cs index 345fe245fe..d6ddd4cc86 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneBeatSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneBeatSnapGrid.cs @@ -101,12 +101,12 @@ namespace osu.Game.Tests.Visual.Editor { createGrid(); - Vector2 screenSpacePosition = Vector2.Zero; - AddStep("get first tick position", () => screenSpacePosition = grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0))); - AddAssert("snap time is 1 beat away", () => Precision.AlmostEquals(beat_length, grid.GetSnapTime(screenSpacePosition), 0.01)); + Vector2 snapPosition = Vector2.Zero; + AddStep("get first tick position", () => snapPosition = grid_position + new Vector2((float)beat_length, 0)); + AddAssert("snap time is 1 beat away", () => Precision.AlmostEquals(beat_length, grid.GetSnapTime(snapPosition), 0.01)); createGrid(g => g.Velocity = 2, "with velocity = 2"); - AddAssert("snap time is now 0.5 beats away", () => Precision.AlmostEquals(beat_length / 2, grid.GetSnapTime(screenSpacePosition), 0.01)); + AddAssert("snap time is now 0.5 beats away", () => Precision.AlmostEquals(beat_length / 2, grid.GetSnapTime(snapPosition), 0.01)); } private void createGrid(Action func = null, string description = null) diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs index 98ad0dd3e8..9040843144 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs @@ -112,18 +112,18 @@ namespace osu.Game.Screens.Edit.Compose.Components protected abstract float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty); /// - /// Snaps a screen-space position to this grid. + /// Snaps a position to this grid. /// - /// The original screen-space position. - /// The snapped screen-space position. - public abstract Vector2 GetSnapPosition(Vector2 screenSpacePosition); + /// The original position in coordinate space local to this . + /// The snapped position in coordinate space local to this . + public abstract Vector2 GetSnapPosition(Vector2 position); /// - /// Retrieves the time at a snapped screen-space position. + /// Retrieves the time at a snapped position. /// - /// The snapped screen-space position. + /// The snapped position in coordinate space local to this . /// The time at the snapped position. - public double GetSnapTime(Vector2 screenSpacePosition) => startTime + (ToLocalSpace(screenSpacePosition) - CentrePosition).Length / Velocity; + public double GetSnapTime(Vector2 position) => startTime + (position - CentrePosition).Length / Velocity; /// /// Retrieves the applicable colour for a beat index. From 631f1555547581f0383a864e174683dc54ff9692 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Oct 2019 17:17:08 +0900 Subject: [PATCH 1728/2815] Add grid to make the test not appear empty --- osu.Game.Tests/Visual/Editor/TestSceneBeatSnapGrid.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneBeatSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneBeatSnapGrid.cs index d6ddd4cc86..073cec7315 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneBeatSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneBeatSnapGrid.cs @@ -32,6 +32,9 @@ namespace osu.Game.Tests.Visual.Editor public TestSceneBeatSnapGrid() { editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); + + createGrid(); } [SetUp] From 6301f837e0b17a8216f2af8a3ee55e064249b16d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Oct 2019 15:27:23 +0900 Subject: [PATCH 1729/2815] Initial implementation of osu! beat snapping grid --- .../TestSceneOsuBeatSnapGrid.cs | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs new file mode 100644 index 0000000000..9f0d59afab --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs @@ -0,0 +1,170 @@ +// 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneOsuBeatSnapGrid : ManualInputManagerTestScene + { + private const double beat_length = 100; + private static readonly Vector2 grid_position = new Vector2(512, 384); + + [Cached(typeof(IEditorBeatmap))] + private readonly EditorBeatmap editorBeatmap; + + [Cached] + private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); + + private OsuBeatSnapGrid grid; + private Drawable cursor; + + public TestSceneOsuBeatSnapGrid() + { + editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + } + + [SetUp] + public void Setup() => Schedule(() => + { + Clear(); + + editorBeatmap.ControlPointInfo.TimingPoints.Clear(); + editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); + + beatDivisor.Value = 1; + }); + + protected override bool OnMouseMove(MouseMoveEvent e) + { + base.OnMouseMove(e); + + if (cursor != null) + cursor.Position = grid?.GetSnapPosition(grid.ToLocalSpace(e.ScreenSpaceMousePosition)) ?? e.ScreenSpaceMousePosition; + + return true; + } + + [TestCase(1)] + [TestCase(2)] + [TestCase(3)] + [TestCase(4)] + [TestCase(6)] + [TestCase(8)] + [TestCase(12)] + [TestCase(16)] + public void TestBeatDivisor(int divisor) + { + AddStep($"set beat divisor = {divisor}", () => beatDivisor.Value = divisor); + createGrid(); + } + + private void createGrid() + { + AddStep("create grid", () => + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.SlateGray + }, + grid = new OsuBeatSnapGrid(new HitCircle { Position = grid_position }), + cursor = new Circle + { + Origin = Anchor.Centre, + Size = new Vector2(50), + Colour = Color4.Red + } + }; + }); + } + + private abstract class CircularBeatSnapGrid : BeatSnapGrid + { + protected override void CreateGrid(Vector2 startPosition) + { + float maxDistance = Math.Max( + Vector2.Distance(startPosition, Vector2.Zero), + Math.Max( + Vector2.Distance(startPosition, new Vector2(DrawWidth, 0)), + Math.Max( + Vector2.Distance(startPosition, new Vector2(0, DrawHeight)), + Vector2.Distance(startPosition, DrawSize)))); + + int requiredCircles = (int)(maxDistance / DistanceSpacing); + + for (int i = 0; i < requiredCircles; i++) + { + float radius = (i + 1) * DistanceSpacing * 2; + + AddInternal(new CircularProgress + { + Origin = Anchor.Centre, + Position = startPosition, + Current = { Value = 1 }, + Size = new Vector2(radius), + InnerRadius = 4 * 1f / radius, + Colour = GetColourForBeatIndex(i) + }); + } + } + + public override Vector2 GetSnapPosition(Vector2 position) + { + Vector2 direction = position - StartPosition; + float distance = direction.Length; + + float radius = DistanceSpacing; + int radialCount = Math.Max(1, (int)Math.Round(distance / radius)); + + if (radialCount <= 0) + return position; + + Vector2 normalisedDirection = direction * new Vector2(1f / distance); + + return StartPosition + normalisedDirection * radialCount * radius; + } + } + + private class OsuBeatSnapGrid : CircularBeatSnapGrid + { + /// + /// Scoring distance with a speed-adjusted beat length of 1 second. + /// + private const float base_scoring_distance = 100; + + public OsuBeatSnapGrid(OsuHitObject hitObject) + : base(hitObject, hitObject.StackedEndPosition) + { + } + + protected override float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(time); + DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(time); + + double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; + + return (float)(scoringDistance / timingPoint.BeatLength); + } + } + } +} From 4d32a8aa6b5cca76f5b993791ce4e536a4709836 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Oct 2019 17:11:37 +0900 Subject: [PATCH 1730/2815] More tests --- .../TestSceneOsuBeatSnapGrid.cs | 172 +++++++++++++++--- 1 file changed, 145 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs index 9f0d59afab..7baa2f0d72 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs @@ -5,9 +5,11 @@ using System; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Objects; @@ -32,12 +34,13 @@ namespace osu.Game.Rulesets.Osu.Tests [Cached] private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); - private OsuBeatSnapGrid grid; - private Drawable cursor; + private TestOsuBeatSnapGrid grid; public TestSceneOsuBeatSnapGrid() { editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + + createGrid(); } [SetUp] @@ -45,22 +48,14 @@ namespace osu.Game.Rulesets.Osu.Tests { Clear(); + editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1; + editorBeatmap.ControlPointInfo.DifficultyPoints.Clear(); editorBeatmap.ControlPointInfo.TimingPoints.Clear(); editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); beatDivisor.Value = 1; }); - protected override bool OnMouseMove(MouseMoveEvent e) - { - base.OnMouseMove(e); - - if (cursor != null) - cursor.Position = grid?.GetSnapPosition(grid.ToLocalSpace(e.ScreenSpaceMousePosition)) ?? e.ScreenSpaceMousePosition; - - return true; - } - [TestCase(1)] [TestCase(2)] [TestCase(3)] @@ -75,6 +70,80 @@ namespace osu.Game.Rulesets.Osu.Tests createGrid(); } + [TestCase(100, 100)] + [TestCase(200, 100)] + public void TestBeatLength(float beatLength, float expectedSpacing) + { + AddStep($"set beat length = {beatLength}", () => + { + editorBeatmap.ControlPointInfo.TimingPoints.Clear(); + editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beatLength }); + }); + + createGrid(); + AddAssert($"spacing = {expectedSpacing}", () => Precision.AlmostEquals(expectedSpacing, grid.DistanceSpacing)); + } + + [TestCase(0.5f, 50)] + [TestCase(1, 100)] + [TestCase(1.5f, 150)] + public void TestSpeedMultiplier(float multiplier, float expectedSpacing) + { + AddStep($"set speed multiplier = {multiplier}", () => + { + editorBeatmap.ControlPointInfo.DifficultyPoints.Clear(); + editorBeatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = multiplier }); + }); + + createGrid(); + AddAssert($"spacing = {expectedSpacing}", () => Precision.AlmostEquals(expectedSpacing, grid.DistanceSpacing)); + } + + [TestCase(0.5f, 50)] + [TestCase(1, 100)] + [TestCase(1.5f, 150)] + public void TestSliderMultiplier(float multiplier, float expectedSpacing) + { + AddStep($"set speed multiplier = {multiplier}", () => editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = multiplier); + createGrid(); + AddAssert($"spacing = {expectedSpacing}", () => Precision.AlmostEquals(expectedSpacing, grid.DistanceSpacing)); + } + + [Test] + public void TestCursorInCentre() + { + createGrid(); + + AddStep("move mouse to centre", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position))); + assertSnappedDistance((float)beat_length); + } + + [Test] + public void TestCursorBeforeMovementPoint() + { + createGrid(); + + AddStep("move mouse to just before movement point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 1.49f))); + assertSnappedDistance((float)beat_length); + } + + [Test] + public void TestCursorAfterMovementPoint() + { + createGrid(); + + AddStep("move mouse to just after movement point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 1.51f))); + assertSnappedDistance((float)beat_length * 2); + } + + private void assertSnappedDistance(float expectedDistance) => AddAssert($"snap distance = {expectedDistance}", () => + { + Vector2 snappedPosition = grid.GetSnapPosition(grid.ToLocalSpace(InputManager.CurrentState.Mouse.Position)); + float distance = Vector2.Distance(snappedPosition, grid_position); + + return Precision.AlmostEquals(expectedDistance, distance); + }); + private void createGrid() { AddStep("create grid", () => @@ -86,28 +155,77 @@ namespace osu.Game.Rulesets.Osu.Tests RelativeSizeAxes = Axes.Both, Colour = Color4.SlateGray }, - grid = new OsuBeatSnapGrid(new HitCircle { Position = grid_position }), - cursor = new Circle - { - Origin = Anchor.Centre, - Size = new Vector2(50), - Colour = Color4.Red - } + grid = new TestOsuBeatSnapGrid(new HitCircle { Position = grid_position }), + new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnapPosition(grid.ToLocalSpace(v)) } }; }); } + private class SnappingCursorContainer : CompositeDrawable + { + public Func GetSnapPosition; + + private readonly Drawable cursor; + + public SnappingCursorContainer() + { + RelativeSizeAxes = Axes.Both; + + InternalChild = cursor = new Circle + { + Origin = Anchor.Centre, + Size = new Vector2(50), + Colour = Color4.Red + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + updatePosition(GetContainingInputManager().CurrentState.Mouse.Position); + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + base.OnMouseMove(e); + + updatePosition(e.ScreenSpaceMousePosition); + return true; + } + + private void updatePosition(Vector2 screenSpacePosition) + { + cursor.Position = GetSnapPosition.Invoke(screenSpacePosition); + } + } + + private class TestOsuBeatSnapGrid : OsuBeatSnapGrid + { + public new float DistanceSpacing => base.DistanceSpacing; + + public TestOsuBeatSnapGrid(OsuHitObject hitObject) + : base(hitObject) + { + } + } + private abstract class CircularBeatSnapGrid : BeatSnapGrid { - protected override void CreateGrid(Vector2 startPosition) + protected CircularBeatSnapGrid(HitObject hitObject, Vector2 centrePosition) + : base(hitObject, centrePosition) + { + } + + protected override void CreateContent(Vector2 centrePosition) { float maxDistance = Math.Max( - Vector2.Distance(startPosition, Vector2.Zero), + Vector2.Distance(centrePosition, Vector2.Zero), Math.Max( - Vector2.Distance(startPosition, new Vector2(DrawWidth, 0)), + Vector2.Distance(centrePosition, new Vector2(DrawWidth, 0)), Math.Max( - Vector2.Distance(startPosition, new Vector2(0, DrawHeight)), - Vector2.Distance(startPosition, DrawSize)))); + Vector2.Distance(centrePosition, new Vector2(0, DrawHeight)), + Vector2.Distance(centrePosition, DrawSize)))); int requiredCircles = (int)(maxDistance / DistanceSpacing); @@ -118,7 +236,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddInternal(new CircularProgress { Origin = Anchor.Centre, - Position = startPosition, + Position = centrePosition, Current = { Value = 1 }, Size = new Vector2(radius), InnerRadius = 4 * 1f / radius, @@ -129,7 +247,7 @@ namespace osu.Game.Rulesets.Osu.Tests public override Vector2 GetSnapPosition(Vector2 position) { - Vector2 direction = position - StartPosition; + Vector2 direction = position - CentrePosition; float distance = direction.Length; float radius = DistanceSpacing; @@ -140,7 +258,7 @@ namespace osu.Game.Rulesets.Osu.Tests Vector2 normalisedDirection = direction * new Vector2(1f / distance); - return StartPosition + normalisedDirection * radialCount * radius; + return CentrePosition + normalisedDirection * radialCount * radius; } } From 45835f97a177597fb236793116382f5b8c7918ea Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Oct 2019 17:13:28 +0900 Subject: [PATCH 1731/2815] Split out grids into separate files --- .../TestSceneOsuBeatSnapGrid.cs | 80 +------------------ osu.Game.Rulesets.Osu/Edit/OsuBeatSnapGrid.cs | 33 ++++++++ .../Components/CircularBeatSnapGrid.cs | 63 +++++++++++++++ 3 files changed, 97 insertions(+), 79 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Edit/OsuBeatSnapGrid.cs create mode 100644 osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs index 7baa2f0d72..7399f12372 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs @@ -7,16 +7,13 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.MathUtils; -using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Tests.Visual; using osuTK; using osuTK.Graphics; @@ -209,80 +206,5 @@ namespace osu.Game.Rulesets.Osu.Tests { } } - - private abstract class CircularBeatSnapGrid : BeatSnapGrid - { - protected CircularBeatSnapGrid(HitObject hitObject, Vector2 centrePosition) - : base(hitObject, centrePosition) - { - } - - protected override void CreateContent(Vector2 centrePosition) - { - float maxDistance = Math.Max( - Vector2.Distance(centrePosition, Vector2.Zero), - Math.Max( - Vector2.Distance(centrePosition, new Vector2(DrawWidth, 0)), - Math.Max( - Vector2.Distance(centrePosition, new Vector2(0, DrawHeight)), - Vector2.Distance(centrePosition, DrawSize)))); - - int requiredCircles = (int)(maxDistance / DistanceSpacing); - - for (int i = 0; i < requiredCircles; i++) - { - float radius = (i + 1) * DistanceSpacing * 2; - - AddInternal(new CircularProgress - { - Origin = Anchor.Centre, - Position = centrePosition, - Current = { Value = 1 }, - Size = new Vector2(radius), - InnerRadius = 4 * 1f / radius, - Colour = GetColourForBeatIndex(i) - }); - } - } - - public override Vector2 GetSnapPosition(Vector2 position) - { - Vector2 direction = position - CentrePosition; - float distance = direction.Length; - - float radius = DistanceSpacing; - int radialCount = Math.Max(1, (int)Math.Round(distance / radius)); - - if (radialCount <= 0) - return position; - - Vector2 normalisedDirection = direction * new Vector2(1f / distance); - - return CentrePosition + normalisedDirection * radialCount * radius; - } - } - - private class OsuBeatSnapGrid : CircularBeatSnapGrid - { - /// - /// Scoring distance with a speed-adjusted beat length of 1 second. - /// - private const float base_scoring_distance = 100; - - public OsuBeatSnapGrid(OsuHitObject hitObject) - : base(hitObject, hitObject.StackedEndPosition) - { - } - - protected override float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) - { - TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(time); - DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(time); - - double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; - - return (float)(scoringDistance / timingPoint.BeatLength); - } - } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBeatSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuBeatSnapGrid.cs new file mode 100644 index 0000000000..d453e3d062 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/OsuBeatSnapGrid.cs @@ -0,0 +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.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit.Compose.Components; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public class OsuBeatSnapGrid : CircularBeatSnapGrid + { + /// + /// Scoring distance with a speed-adjusted beat length of 1 second. + /// + private const float base_scoring_distance = 100; + + public OsuBeatSnapGrid(OsuHitObject hitObject) + : base(hitObject, hitObject.StackedEndPosition) + { + } + + protected override float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(time); + DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(time); + + double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; + + return (float)(scoringDistance / timingPoint.BeatLength); + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs new file mode 100644 index 0000000000..8492771808 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs @@ -0,0 +1,63 @@ +// 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.UserInterface; +using osu.Game.Rulesets.Objects; +using osuTK; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public abstract class CircularBeatSnapGrid : BeatSnapGrid + { + protected CircularBeatSnapGrid(HitObject hitObject, Vector2 centrePosition) + : base(hitObject, centrePosition) + { + } + + protected override void CreateContent(Vector2 centrePosition) + { + float maxDistance = Math.Max( + Vector2.Distance(centrePosition, Vector2.Zero), + Math.Max( + Vector2.Distance(centrePosition, new Vector2(DrawWidth, 0)), + Math.Max( + Vector2.Distance(centrePosition, new Vector2(0, DrawHeight)), + Vector2.Distance(centrePosition, DrawSize)))); + + int requiredCircles = (int)(maxDistance / DistanceSpacing); + + for (int i = 0; i < requiredCircles; i++) + { + float radius = (i + 1) * DistanceSpacing * 2; + + AddInternal(new CircularProgress + { + Origin = Anchor.Centre, + Position = centrePosition, + Current = { Value = 1 }, + Size = new Vector2(radius), + InnerRadius = 4 * 1f / radius, + Colour = GetColourForBeatIndex(i) + }); + } + } + + public override Vector2 GetSnapPosition(Vector2 position) + { + Vector2 direction = position - CentrePosition; + float distance = direction.Length; + + float radius = DistanceSpacing; + int radialCount = Math.Max(1, (int)Math.Round(distance / radius)); + + if (radialCount <= 0) + return position; + + Vector2 normalisedDirection = direction * new Vector2(1f / distance); + + return CentrePosition + normalisedDirection * radialCount * radius; + } + } +} From de13320a2debb7c48d46691ea5a34a0b09d722a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Oct 2019 18:46:05 +0900 Subject: [PATCH 1732/2815] Add initial table display --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 187 ++++++++++++++++--- 1 file changed, 160 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index b723ffac9a..be7058ae08 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -1,12 +1,18 @@ // 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.Globalization; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -15,6 +21,9 @@ namespace osu.Game.Screens.Edit.Timing { public class TimingScreen : EditorScreenWithTimeline { + [Cached] + private readonly Bindable controlPoint = new Bindable(); + protected override Drawable CreateMainContent() => new GridContainer { RelativeSizeAxes = Axes.Both, @@ -53,37 +62,13 @@ namespace osu.Game.Screens.Edit.Timing new OsuScrollContainer { RelativeSizeAxes = Axes.Both, - Child = new FillFlowContainer + Child = new ControlPointTable { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new ControlPointRow(), - new ControlPointRow(), - new ControlPointRow(), - new ControlPointRow(), - new ControlPointRow(), - new ControlPointRow(), - } - }, + ControlPoints = Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints + } } }; } - - private class ControlPointRow : CompositeDrawable - { - public ControlPointRow() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - InternalChildren = new Drawable[] - { - new OsuSpriteText { Text = "sample row" }, - }; - } - } } public class ControlPointSettings : CompositeDrawable @@ -101,4 +86,152 @@ namespace osu.Game.Screens.Edit.Timing } } } + + public class ControlPointTable : TableContainer + { + private const float horizontal_inset = 20; + private const float row_height = 25; + private const int text_size = 14; + + private readonly FillFlowContainer backgroundFlow; + + [Resolved] + private Bindable controlPoint { get; set; } + + public ControlPointTable() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Padding = new MarginPadding { Horizontal = horizontal_inset }; + RowSize = new Dimension(GridSizeMode.Absolute, row_height); + + AddInternal(backgroundFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Depth = 1f, + Padding = new MarginPadding { Horizontal = -horizontal_inset }, + Margin = new MarginPadding { Top = row_height } + }); + } + + public IReadOnlyList ControlPoints + { + set + { + Content = null; + backgroundFlow.Clear(); + + if (value?.Any() != true) + return; + + for (int i = 0; i < value.Count; i++) + { + var cp = value[i]; + backgroundFlow.Add(new RowBackground { Action = () => controlPoint.Value = cp }); + } + + Columns = createHeaders(); + Content = value.Select((s, i) => createContent(i, s)).ToArray().ToRectangular(); + } + } + + private TableColumn[] createHeaders() + { + var columns = new List + { + new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("offset", Anchor.Centre), + new TableColumn("BPM", Anchor.Centre), + new TableColumn("Meter", Anchor.Centre), + new TableColumn("Sample Set", Anchor.Centre), + new TableColumn("Volume", Anchor.Centre), + }; + + return columns.ToArray(); + } + + private Drawable[] createContent(int index, ControlPoint controlPoint) + { + return new Drawable[] + { + new OsuSpriteText + { + Text = $"#{index + 1}", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) + }, + new OsuSpriteText + { + Text = $"{controlPoint.Time}", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) + }, + new OsuSpriteText + { + Text = $"{(controlPoint as TimingControlPoint)?.BeatLength.ToString(CultureInfo.InvariantCulture) ?? "-"}", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) + }, + new OsuSpriteText + { + Text = $"{(controlPoint as TimingControlPoint)?.TimeSignature.ToString() ?? "-"}", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) + }, + }; + } + + protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty); + + private class HeaderText : OsuSpriteText + { + public HeaderText(string text) + { + Text = text.ToUpper(); + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Black); + } + } + + public class RowBackground : OsuClickableContainer + { + private const int fade_duration = 100; + + private readonly Box hoveredBackground; + + public RowBackground() + { + RelativeSizeAxes = Axes.X; + Height = 25; + + AlwaysPresent = true; + + CornerRadius = 3; + Masking = true; + + Children = new Drawable[] + { + hoveredBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + hoveredBackground.Colour = colours.Blue; + } + + protected override bool OnHover(HoverEvent e) + { + hoveredBackground.FadeIn(fade_duration, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hoveredBackground.FadeOut(fade_duration, Easing.OutQuint); + base.OnHoverLost(e); + } + } + } } From 715fb88316bf4609a18a6cedaa9214a1454acb09 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Oct 2019 19:33:21 +0900 Subject: [PATCH 1733/2815] Fix build scripts not passing arguments to cake --- build.ps1 | 25 ++++++++++++++++++++++++- build.sh | 16 +++++++++++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/build.ps1 b/build.ps1 index 8eb37f2de6..2dbd10a150 100755 --- a/build.ps1 +++ b/build.ps1 @@ -1,4 +1,27 @@ +[CmdletBinding()] +Param( + [string]$Target, + [string]$Configuration, + [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] + [string]$Verbosity, + [switch]$ShowDescription, + [Alias("WhatIf", "Noop")] + [switch]$DryRun, + [Parameter(Position = 0, Mandatory = $false, ValueFromRemainingArguments = $true)] + [string[]]$ScriptArgs +) + +# Build Cake arguments +$cakeArguments = ""; +if ($Target) { $cakeArguments += "-target=$Target" } +if ($Configuration) { $cakeArguments += "-configuration=$Configuration" } +if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" } +if ($ShowDescription) { $cakeArguments += "-showdescription" } +if ($DryRun) { $cakeArguments += "-dryrun" } +if ($Experimental) { $cakeArguments += "-experimental" } +$cakeArguments += $ScriptArgs + dotnet tool install Cake.Tool --global --version 0.35.0 dotnet cake ./build/build.cake --bootstrap -dotnet cake ./build/build.cake +dotnet cake ./build/build.cake $cakeArguments exit $LASTEXITCODE \ No newline at end of file diff --git a/build.sh b/build.sh index d20a9c12fa..ac6bd877a6 100755 --- a/build.sh +++ b/build.sh @@ -1,3 +1,17 @@ +echo "Installing Cake.Tool..." dotnet tool install Cake.Tool --global --version 0.35.0 + +# Parse arguments. +CAKE_ARGUMENTS=() +for i in "$@"; do + case $1 in + -s|--script) SCRIPT="$2"; shift ;; + --) shift; CAKE_ARGUMENTS+=("$@"); break ;; + *) CAKE_ARGUMENTS+=("$1") ;; + esac + shift +done + +echo "Running build script..." dotnet cake ./build/build.cake --bootstrap -dotnet cake ./build/build.cake \ No newline at end of file +dotnet cake ./build/build.cake "${CAKE_ARGUMENTS[@]}" \ No newline at end of file From 13924174c4950a173c575217484006046cfaccf4 Mon Sep 17 00:00:00 2001 From: HoLLy-HaCKeR Date: Sat, 12 Oct 2019 10:04:14 +0200 Subject: [PATCH 1734/2815] Fix PopIn and PopOut resetting cursor scale --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index 371c2983fc..2b499064fb 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -31,6 +31,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private Bindable cursorScale; private Bindable autoCursorScale; + private float calculatedCursorScale; private readonly IBindable beatmap = new Bindable(); public OsuCursorContainer() @@ -69,6 +70,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor scale *= 1f - 0.7f * (1f + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY; } + calculatedCursorScale = scale; ActiveCursor.Scale = cursorTrail.Scale = new Vector2(scale); } @@ -125,13 +127,13 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor protected override void PopIn() { fadeContainer.FadeTo(1, 300, Easing.OutQuint); - ActiveCursor.ScaleTo(1, 400, Easing.OutQuint); + ActiveCursor.ScaleTo(calculatedCursorScale, 400, Easing.OutQuint); } protected override void PopOut() { fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint); - ActiveCursor.ScaleTo(0.8f, 450, Easing.OutQuint); + ActiveCursor.ScaleTo(calculatedCursorScale * 0.8f, 450, Easing.OutQuint); } private class DefaultCursorTrail : CursorTrail From fdc17d2adb07d81c22f7d1b6ad3c44f277917429 Mon Sep 17 00:00:00 2001 From: HoLLy-HaCKeR Date: Sat, 12 Oct 2019 11:51:14 +0200 Subject: [PATCH 1735/2815] Scale OsuResumeCursor with gameplay cursor --- .../UI/Cursor/OsuCursorContainer.cs | 13 ++++++++----- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 14 +++++++++++--- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index 2b499064fb..8ea11d0a4b 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -29,9 +29,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private readonly Drawable cursorTrail; + public IBindable CalculatedCursorScale => calculatedCursorScale; + private Bindable calculatedCursorScale; private Bindable cursorScale; private Bindable autoCursorScale; - private float calculatedCursorScale; private readonly IBindable beatmap = new Bindable(); public OsuCursorContainer() @@ -57,6 +58,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize); autoCursorScale.ValueChanged += _ => calculateScale(); + calculatedCursorScale = new Bindable(); + calculatedCursorScale.ValueChanged += e => ActiveCursor.Scale = cursorTrail.Scale = new Vector2(e.NewValue); + calculateScale(); } @@ -70,8 +74,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor scale *= 1f - 0.7f * (1f + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY; } - calculatedCursorScale = scale; - ActiveCursor.Scale = cursorTrail.Scale = new Vector2(scale); + calculatedCursorScale.Value = scale; } protected override void LoadComplete() @@ -127,13 +130,13 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor protected override void PopIn() { fadeContainer.FadeTo(1, 300, Easing.OutQuint); - ActiveCursor.ScaleTo(calculatedCursorScale, 400, Easing.OutQuint); + ActiveCursor.ScaleTo(calculatedCursorScale.Value, 400, Easing.OutQuint); } protected override void PopOut() { fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint); - ActiveCursor.ScaleTo(calculatedCursorScale * 0.8f, 450, Easing.OutQuint); + ActiveCursor.ScaleTo(calculatedCursorScale.Value * 0.8f, 450, Easing.OutQuint); } private class DefaultCursorTrail : CursorTrail diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 9e5df0d6b1..9033817115 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -38,7 +38,13 @@ namespace osu.Game.Rulesets.Osu.UI clickToResumeCursor.ShowAt(GameplayCursor.ActiveCursor.Position); if (localCursorContainer == null) - Add(localCursorContainer = new OsuCursorContainer()); + { + var newContainer = new OsuCursorContainer(); + Add(localCursorContainer = newContainer); + + clickToResumeCursor.CursorScale = newContainer.CalculatedCursorScale.Value; + newContainer.CalculatedCursorScale.ValueChanged += e => clickToResumeCursor.CursorScale = e.NewValue; + } } public override void Hide() @@ -57,6 +63,8 @@ namespace osu.Game.Rulesets.Osu.UI public Action ResumeRequested; + public float CursorScale; + public OsuClickToResumeCursor() { RelativePositionAxes = Axes.Both; @@ -82,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.UI case OsuAction.RightButton: if (!IsHovered) return false; - this.ScaleTo(new Vector2(2), TRANSITION_TIME, Easing.OutQuint); + this.ScaleTo(2 * CursorScale, TRANSITION_TIME, Easing.OutQuint); ResumeRequested?.Invoke(); return true; @@ -97,7 +105,7 @@ namespace osu.Game.Rulesets.Osu.UI { updateColour(); this.MoveTo(activeCursorPosition); - this.ScaleTo(new Vector2(4)).Then().ScaleTo(Vector2.One, 1000, Easing.OutQuint); + this.ScaleTo(4 * CursorScale).Then().ScaleTo(CursorScale, 1000, Easing.OutQuint); }); private void updateColour() From 7931510d7bda55dc3b54da6d411cd8c4711a7e4e Mon Sep 17 00:00:00 2001 From: HoLLy-HaCKeR Date: Sat, 12 Oct 2019 11:59:22 +0200 Subject: [PATCH 1736/2815] Ensure OsuResumeCursor can change scale when it is being shown --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 9033817115..7221e09c35 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -43,7 +43,13 @@ namespace osu.Game.Rulesets.Osu.UI Add(localCursorContainer = newContainer); clickToResumeCursor.CursorScale = newContainer.CalculatedCursorScale.Value; - newContainer.CalculatedCursorScale.ValueChanged += e => clickToResumeCursor.CursorScale = e.NewValue; + clickToResumeCursor.Scale = new Vector2(newContainer.CalculatedCursorScale.Value); + + newContainer.CalculatedCursorScale.ValueChanged += e => + { + clickToResumeCursor.CursorScale = e.NewValue; + clickToResumeCursor.Scale = new Vector2(e.NewValue); + }; } } From f6b138fe6ed3aa230c1753b4ce0bb7bf576dcfef Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 12 Oct 2019 15:03:24 +0300 Subject: [PATCH 1737/2815] Remove useless ItemGroup --- osu.Game/osu.Game.csproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 4e6511385e..ab7c40116b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -32,7 +32,4 @@ - - - From 4d971e49ff050685a4498cc31b941fd426f41ef1 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 13 Oct 2019 11:50:27 +0300 Subject: [PATCH 1738/2815] Colours update --- osu.Game/Overlays/Comments/CommentsContainer.cs | 2 +- osu.Game/Overlays/Comments/DrawableComment.cs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 265793226e..b66374cb69 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -113,7 +113,7 @@ namespace osu.Game.Overlays.Comments [BackgroundDependencyLoader] private void load() { - background.Colour = colours.Gray3; + background.Colour = colours.Gray2; } } } diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index fd7f874304..4617f6f86e 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -13,7 +13,6 @@ using osu.Game.Utils; using osu.Framework.Graphics.Cursor; using osu.Framework.Bindables; using osu.Framework.Graphics.Shapes; -using osuTK.Graphics; using System.Linq; namespace osu.Game.Overlays.Comments @@ -134,6 +133,7 @@ namespace osu.Game.Overlays.Comments AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(10, 0), + Colour = OsuColour.Gray(0.7f), Children = new Drawable[] { new SpriteText @@ -262,6 +262,7 @@ namespace osu.Game.Overlays.Comments Child = icon = new SpriteIcon { Size = new Vector2(12), + Colour = OsuColour.Gray(0.7f) }; } @@ -340,7 +341,7 @@ namespace osu.Game.Overlays.Comments new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4.Black + Colour = OsuColour.Gray(0.05f) }, new SpriteText { From 795ce8146895f435744a41e929a4621d0a212f16 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 13 Oct 2019 12:10:01 +0300 Subject: [PATCH 1739/2815] Use async loading for comment pages --- .../Overlays/Comments/CommentsContainer.cs | 42 +++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index b66374cb69..3b997540c4 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Online.API.Requests.Responses; +using System.Threading; namespace osu.Game.Overlays.Comments { @@ -28,6 +29,7 @@ namespace osu.Game.Overlays.Comments private OsuColour colours { get; set; } private GetCommentsRequest request; + private CancellationTokenSource loadCancellation; private readonly Box background; private readonly FillFlowContainer content; @@ -79,7 +81,7 @@ namespace osu.Game.Overlays.Comments private void getComments() { request?.Cancel(); - content.Clear(); + loadCancellation?.Cancel(); request = new GetCommentsRequest(type, id, Sort.Value); request.Success += onSuccess; api.Queue(request); @@ -87,27 +89,43 @@ namespace osu.Game.Overlays.Comments private void onSuccess(APICommentsController response) { + loadCancellation = new CancellationTokenSource(); + + FillFlowContainer page = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + }; + foreach (var c in response.Comments) { if (c.IsTopLevel) - content.Add(new DrawableComment(c) + page.Add(new DrawableComment(c) { ShowDeleted = { BindTarget = ShowDeleted } }); } - int deletedComments = 0; - - response.Comments.ForEach(comment => + LoadComponentAsync(page, loaded => { - if (comment.IsDeleted && comment.IsTopLevel) - deletedComments++; - }); + content.Clear(); - content.Add(new DeletedChildsPlaceholder(deletedComments) - { - ShowDeleted = { BindTarget = ShowDeleted } - }); + content.Add(loaded); + + int deletedComments = 0; + + response.Comments.ForEach(comment => + { + if (comment.IsDeleted && comment.IsTopLevel) + deletedComments++; + }); + + content.Add(new DeletedChildsPlaceholder(deletedComments) + { + ShowDeleted = { BindTarget = ShowDeleted } + }); + }, loadCancellation.Token); } [BackgroundDependencyLoader] From 60954f969d77f6350b0f63903cf2f5696046c248 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 13 Oct 2019 12:38:50 +0300 Subject: [PATCH 1740/2815] DeletedChildsPlaceholder refactor --- .../Overlays/Comments/CommentsContainer.cs | 33 ++++++++++++++++--- .../Comments/DeletedChildsPlaceholder.cs | 30 ++++++++++++----- osu.Game/Overlays/Comments/DrawableComment.cs | 5 ++- 3 files changed, 55 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 3b997540c4..1fca9ca5e5 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -33,6 +33,8 @@ namespace osu.Game.Overlays.Comments private readonly Box background; private readonly FillFlowContainer content; + private readonly FillFlowContainer footer; + private readonly DeletedChildsPlaceholder deletedChildsPlaceholder; public CommentsContainer(CommentableType type, long id) { @@ -64,6 +66,32 @@ namespace osu.Game.Overlays.Comments RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.2f) + }, + footer = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + deletedChildsPlaceholder = new DeletedChildsPlaceholder + { + ShowDeleted = { BindTarget = ShowDeleted } + } + } + } + } } } } @@ -121,10 +149,7 @@ namespace osu.Game.Overlays.Comments deletedComments++; }); - content.Add(new DeletedChildsPlaceholder(deletedComments) - { - ShowDeleted = { BindTarget = ShowDeleted } - }); + deletedChildsPlaceholder.DeletedCount.Value = deletedComments; }, loadCancellation.Token); } diff --git a/osu.Game/Overlays/Comments/DeletedChildsPlaceholder.cs b/osu.Game/Overlays/Comments/DeletedChildsPlaceholder.cs index 7aae42908e..b5dcf433f1 100644 --- a/osu.Game/Overlays/Comments/DeletedChildsPlaceholder.cs +++ b/osu.Game/Overlays/Comments/DeletedChildsPlaceholder.cs @@ -17,18 +17,18 @@ namespace osu.Game.Overlays.Comments private const int margin = 10; public readonly BindableBool ShowDeleted = new BindableBool(); + public readonly BindableInt DeletedCount = new BindableInt(); - private readonly bool canBeVisible; + private bool canBeShown; - public DeletedChildsPlaceholder(int count) + private readonly SpriteText countText; + + public DeletedChildsPlaceholder() { - canBeVisible = count != 0; - AutoSizeAxes = Axes.Both; Direction = FillDirection.Horizontal; Spacing = new Vector2(3, 0); Margin = new MarginPadding { Vertical = margin, Left = deleted_placeholder_margin }; - Alpha = 0; Children = new Drawable[] { new SpriteIcon @@ -36,24 +36,38 @@ namespace osu.Game.Overlays.Comments Icon = FontAwesome.Solid.Trash, Size = new Vector2(14), }, - new SpriteText + countText = new SpriteText { Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true), - Text = $@"{count} deleted comment{(count.ToString().ToCharArray().Last() == '1' ? "" : "s")}" } }; } protected override void LoadComplete() { + DeletedCount.BindValueChanged(onCountChanged, true); ShowDeleted.BindValueChanged(onShowDeletedChanged, true); base.LoadComplete(); } private void onShowDeletedChanged(ValueChangedEvent showDeleted) { - if (canBeVisible) + if (canBeShown) this.FadeTo(showDeleted.NewValue ? 0 : 1); } + + private void onCountChanged(ValueChangedEvent count) + { + canBeShown = count.NewValue != 0; + + if (!canBeShown) + { + Hide(); + return; + } + + countText.Text = $@"{count.NewValue} deleted comment{(count.NewValue.ToString().ToCharArray().Last() == '1' ? "" : "s")}"; + Show(); + } } } diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 4617f6f86e..8c356a6156 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -32,6 +32,7 @@ namespace osu.Game.Overlays.Comments private readonly FillFlowContainer childCommentsVisibilityContainer; private readonly Comment comment; + private readonly DeletedChildsPlaceholder deletedChildsPlaceholder; public DrawableComment(Comment comment) { @@ -168,7 +169,7 @@ namespace osu.Game.Overlays.Comments AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical }, - new DeletedChildsPlaceholder(comment.GetDeletedChildsCount()) + deletedChildsPlaceholder = new DeletedChildsPlaceholder { ShowDeleted = { BindTarget = ShowDeleted } } @@ -177,6 +178,8 @@ namespace osu.Game.Overlays.Comments } }; + deletedChildsPlaceholder.DeletedCount.Value = comment.GetDeletedChildsCount(); + if (comment.UserId == null) username.AddText(comment.LegacyName); else From a44cc2e70baf13f3f90c6635d23bb2645b06841f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 13 Oct 2019 14:43:30 +0300 Subject: [PATCH 1741/2815] Implement CommentsShowMoreButton --- .../Online/TestSceneCommentsContainer.cs | 6 ++ .../Visual/Online/TestSceneShowMoreButton.cs | 5 +- .../UserInterface}/ShowMoreButton.cs | 67 ++++++++++--------- .../Overlays/Comments/CommentsContainer.cs | 54 ++++++++++++--- .../Comments/CommentsShowMoreButton.cs | 32 +++++++++ .../Comments/DeletedChildsPlaceholder.cs | 8 ++- .../Profile/Sections/PaginatedContainer.cs | 4 +- .../Profile/Sections/ProfileShowMoreButton.cs | 20 ++++++ 8 files changed, 151 insertions(+), 45 deletions(-) rename osu.Game/{Overlays/Profile/Sections => Graphics/UserInterface}/ShowMoreButton.cs (77%) create mode 100644 osu.Game/Overlays/Comments/CommentsShowMoreButton.cs create mode 100644 osu.Game/Overlays/Profile/Sections/ProfileShowMoreButton.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs index 342ba487f0..a283663a4a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -47,6 +47,12 @@ namespace osu.Game.Tests.Visual.Online scrollFlow.Clear(); scrollFlow.Add(new CommentsContainer(CommentableType.Beatmapset, 24313)); }); + + AddStep("lazer build comments", () => + { + scrollFlow.Clear(); + scrollFlow.Add(new CommentsContainer(CommentableType.Build, 4772)); + }); } } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneShowMoreButton.cs b/osu.Game.Tests/Visual/Online/TestSceneShowMoreButton.cs index bccb263600..8d4955abf0 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneShowMoreButton.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneShowMoreButton.cs @@ -5,6 +5,7 @@ using osu.Game.Overlays.Profile.Sections; using System; using System.Collections.Generic; using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; namespace osu.Game.Tests.Visual.Online { @@ -17,11 +18,11 @@ namespace osu.Game.Tests.Visual.Online public TestSceneShowMoreButton() { - ShowMoreButton button = null; + ProfileShowMoreButton button = null; int fireCount = 0; - Add(button = new ShowMoreButton + Add(button = new ProfileShowMoreButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Overlays/Profile/Sections/ShowMoreButton.cs b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs similarity index 77% rename from osu.Game/Overlays/Profile/Sections/ShowMoreButton.cs rename to osu.Game/Graphics/UserInterface/ShowMoreButton.cs index cf4e1c0dde..627ad995e8 100644 --- a/osu.Game/Overlays/Profile/Sections/ShowMoreButton.cs +++ b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs @@ -1,30 +1,36 @@ -// 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 osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; -using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osuTK; +using osuTK.Graphics; using System.Collections.Generic; -namespace osu.Game.Overlays.Profile.Sections +namespace osu.Game.Graphics.UserInterface { public class ShowMoreButton : OsuHoverContainer { private const float fade_duration = 200; - private readonly Box background; - private readonly LoadingAnimation loading; - private readonly FillFlowContainer content; + private Color4 chevronIconColour; - protected override IEnumerable EffectTargets => new[] { background }; + public Color4 ChevronIconColour + { + get => chevronIconColour; + set { chevronIconColour = leftChevron.AccentColour = rightChevron.AccentColour = value; } + } + + public string Text + { + get => text.Text; + set { text.Text = value; } + } private bool isLoading; @@ -33,26 +39,32 @@ namespace osu.Game.Overlays.Profile.Sections get => isLoading; set { - if (isLoading == value) - return; - isLoading = value; Enabled.Value = !isLoading; if (value) { - loading.FadeIn(fade_duration, Easing.OutQuint); + loading.Show(); content.FadeOut(fade_duration, Easing.OutQuint); } else { - loading.FadeOut(fade_duration, Easing.OutQuint); + loading.Hide(); content.FadeIn(fade_duration, Easing.OutQuint); } } } + private readonly Box background; + private readonly LoadingAnimation loading; + private readonly FillFlowContainer content; + private readonly ChevronIcon leftChevron; + private readonly ChevronIcon rightChevron; + private readonly SpriteText text; + + protected override IEnumerable EffectTargets => new[] { background }; + public ShowMoreButton() { AutoSizeAxes = Axes.Both; @@ -77,15 +89,15 @@ namespace osu.Game.Overlays.Profile.Sections Spacing = new Vector2(7), Children = new Drawable[] { - new ChevronIcon(), - new OsuSpriteText + leftChevron = new ChevronIcon(), + text = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), Text = "show more".ToUpper(), }, - new ChevronIcon(), + rightChevron = new ChevronIcon(), } }, loading = new LoadingAnimation @@ -99,13 +111,6 @@ namespace osu.Game.Overlays.Profile.Sections }; } - [BackgroundDependencyLoader] - private void load(OsuColour colors) - { - IdleColour = colors.GreySeafoamDark; - HoverColour = colors.GreySeafoam; - } - protected override bool OnClick(ClickEvent e) { if (!Enabled.Value) @@ -126,6 +131,14 @@ namespace osu.Game.Overlays.Profile.Sections { private const int icon_size = 8; + private Color4 accentColour; + + public Color4 AccentColour + { + get => accentColour; + set { accentColour = Colour = value; } + } + public ChevronIcon() { Anchor = Anchor.Centre; @@ -133,12 +146,6 @@ namespace osu.Game.Overlays.Profile.Sections Size = new Vector2(icon_size); Icon = FontAwesome.Solid.ChevronDown; } - - [BackgroundDependencyLoader] - private void load(OsuColour colors) - { - Colour = colors.Yellow; - } } } } diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 1fca9ca5e5..1111313d7f 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -16,6 +16,8 @@ namespace osu.Game.Overlays.Comments { public class CommentsContainer : CompositeDrawable { + private const int more_button_margin = 5; + private readonly CommentableType type; private readonly long id; @@ -30,11 +32,13 @@ namespace osu.Game.Overlays.Comments private GetCommentsRequest request; private CancellationTokenSource loadCancellation; + private int currentPage; + private int loadedTopLevelComments; private readonly Box background; private readonly FillFlowContainer content; - private readonly FillFlowContainer footer; private readonly DeletedChildsPlaceholder deletedChildsPlaceholder; + private readonly CommentsShowMoreButton moreButton; public CommentsContainer(CommentableType type, long id) { @@ -78,7 +82,7 @@ namespace osu.Game.Overlays.Comments RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(0.2f) }, - footer = new FillFlowContainer + new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -88,6 +92,18 @@ namespace osu.Game.Overlays.Comments deletedChildsPlaceholder = new DeletedChildsPlaceholder { ShowDeleted = { BindTarget = ShowDeleted } + }, + new Container + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Child = moreButton = new CommentsShowMoreButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding(more_button_margin), + Action = () => getComments(false), + } } } } @@ -106,16 +122,25 @@ namespace osu.Game.Overlays.Comments private void onSortChanged(ValueChangedEvent sort) => getComments(); - private void getComments() + private void getComments(bool initial = true) { + if (initial) + { + currentPage = 1; + loadedTopLevelComments = 0; + deletedChildsPlaceholder.DeletedCount.Value = 0; + moreButton.IsLoading = true; + content.Clear(); + } + request?.Cancel(); loadCancellation?.Cancel(); - request = new GetCommentsRequest(type, id, Sort.Value); - request.Success += onSuccess; + request = new GetCommentsRequest(type, id, Sort.Value, currentPage++); + request.Success += response => onSuccess(response, initial); api.Queue(request); } - private void onSuccess(APICommentsController response) + private void onSuccess(APICommentsController response, bool initial) { loadCancellation = new CancellationTokenSource(); @@ -137,8 +162,6 @@ namespace osu.Game.Overlays.Comments LoadComponentAsync(page, loaded => { - content.Clear(); - content.Add(loaded); int deletedComments = 0; @@ -149,7 +172,20 @@ namespace osu.Game.Overlays.Comments deletedComments++; }); - deletedChildsPlaceholder.DeletedCount.Value = deletedComments; + deletedChildsPlaceholder.DeletedCount.Value = initial ? deletedComments : deletedChildsPlaceholder.DeletedCount.Value + deletedComments; + + if (response.HasMore) + { + response.Comments.ForEach(comment => + { + if (comment.IsTopLevel) + loadedTopLevelComments++; + }); + moreButton.Current.Value = response.TopLevelCount - loadedTopLevelComments; + moreButton.IsLoading = false; + } + moreButton.FadeTo(response.HasMore ? 1 : 0); + }, loadCancellation.Token); } diff --git a/osu.Game/Overlays/Comments/CommentsShowMoreButton.cs b/osu.Game/Overlays/Comments/CommentsShowMoreButton.cs new file mode 100644 index 0000000000..b0174e7b1a --- /dev/null +++ b/osu.Game/Overlays/Comments/CommentsShowMoreButton.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 osu.Framework.Bindables; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Comments +{ + public class CommentsShowMoreButton : ShowMoreButton + { + public readonly BindableInt Current = new BindableInt(); + + public CommentsShowMoreButton() + { + IdleColour = OsuColour.Gray(0.3f); + HoverColour = OsuColour.Gray(0.4f); + ChevronIconColour = OsuColour.Gray(0.5f); + } + + protected override void LoadComplete() + { + Current.BindValueChanged(onCurrentChanged, true); + base.LoadComplete(); + } + + private void onCurrentChanged(ValueChangedEvent count) + { + Text = $@"Show More ({count.NewValue})".ToUpper(); + } + } +} diff --git a/osu.Game/Overlays/Comments/DeletedChildsPlaceholder.cs b/osu.Game/Overlays/Comments/DeletedChildsPlaceholder.cs index b5dcf433f1..d626c13afd 100644 --- a/osu.Game/Overlays/Comments/DeletedChildsPlaceholder.cs +++ b/osu.Game/Overlays/Comments/DeletedChildsPlaceholder.cs @@ -7,7 +7,6 @@ using osu.Game.Graphics; using osu.Framework.Graphics.Sprites; using osuTK; using osu.Framework.Bindables; -using System.Linq; namespace osu.Game.Overlays.Comments { @@ -66,7 +65,12 @@ namespace osu.Game.Overlays.Comments return; } - countText.Text = $@"{count.NewValue} deleted comment{(count.NewValue.ToString().ToCharArray().Last() == '1' ? "" : "s")}"; + string str = $@"{count.NewValue} deleted comment"; + + if (!(count.NewValue.ToString().EndsWith("1") && !count.NewValue.ToString().EndsWith("11"))) + str += "s"; + + countText.Text = str; Show(); } } diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index bb221bd43a..dc1a847b14 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Profile.Sections { public abstract class PaginatedContainer : FillFlowContainer { - private readonly ShowMoreButton moreButton; + private readonly ProfileShowMoreButton moreButton; private readonly OsuSpriteText missingText; private APIRequest> retrievalRequest; private CancellationTokenSource loadCancellation; @@ -56,7 +56,7 @@ namespace osu.Game.Overlays.Profile.Sections RelativeSizeAxes = Axes.X, Spacing = new Vector2(0, 2), }, - moreButton = new ShowMoreButton + moreButton = new ProfileShowMoreButton { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game/Overlays/Profile/Sections/ProfileShowMoreButton.cs b/osu.Game/Overlays/Profile/Sections/ProfileShowMoreButton.cs new file mode 100644 index 0000000000..28486cc743 --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/ProfileShowMoreButton.cs @@ -0,0 +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 osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Profile.Sections +{ + public class ProfileShowMoreButton : ShowMoreButton + { + [BackgroundDependencyLoader] + private void load(OsuColour colors) + { + IdleColour = colors.GreySeafoamDark; + HoverColour = colors.GreySeafoam; + ChevronIconColour = colors.Yellow; + } + } +} From 328b4d6863a3002b396b65bbcdde32d0a95568ff Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 13 Oct 2019 16:22:10 +0300 Subject: [PATCH 1742/2815] Cancel request on dispose --- osu.Game/Overlays/Comments/CommentsContainer.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 1111313d7f..4c27c498c3 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -194,5 +194,12 @@ namespace osu.Game.Overlays.Comments { background.Colour = colours.Gray2; } + + protected override void Dispose(bool isDisposing) + { + request?.Cancel(); + loadCancellation?.Cancel(); + base.Dispose(isDisposing); + } } } From ae2fe62fd9ed27d1ef5bdd60927795b9d509ca97 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Oct 2019 17:13:36 +0900 Subject: [PATCH 1743/2815] Use BindValueChanged --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 7221e09c35..1ee2a04a3b 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Rulesets.Osu.UI.Cursor; -using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osuTK; using osuTK.Graphics; @@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.UI { private OsuClickToResumeCursor clickToResumeCursor; - private GameplayCursorContainer localCursorContainer; + private OsuCursorContainer localCursorContainer; public override CursorContainer LocalCursor => State.Value == Visibility.Visible ? localCursorContainer : null; @@ -39,17 +38,13 @@ namespace osu.Game.Rulesets.Osu.UI if (localCursorContainer == null) { - var newContainer = new OsuCursorContainer(); - Add(localCursorContainer = newContainer); + Add(localCursorContainer = new OsuCursorContainer()); - clickToResumeCursor.CursorScale = newContainer.CalculatedCursorScale.Value; - clickToResumeCursor.Scale = new Vector2(newContainer.CalculatedCursorScale.Value); - - newContainer.CalculatedCursorScale.ValueChanged += e => + localCursorContainer.CalculatedCursorScale.BindValueChanged(scale => { - clickToResumeCursor.CursorScale = e.NewValue; - clickToResumeCursor.Scale = new Vector2(e.NewValue); - }; + clickToResumeCursor.CursorScale = scale.NewValue; + clickToResumeCursor.Scale = new Vector2(scale.NewValue); + }, true); } } From 9372526d3af9f42da19442ed21baca4c692a358d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Oct 2019 20:08:05 +0900 Subject: [PATCH 1744/2815] Don't automatically return to gameplay from map pool if no picks are made Closes #6491. --- osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index d32c0d6156..ec55bb5b54 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -196,7 +196,7 @@ namespace osu.Game.Tournament.Screens.MapPool setNextMode(); - if (pickType == ChoiceType.Pick) + if (pickType == ChoiceType.Pick && currentMatch.Value.PicksBans.Any(i => i.Type == ChoiceType.Pick)) { scheduledChange?.Cancel(); scheduledChange = Scheduler.AddDelayed(() => { sceneManager?.SetScreen(typeof(GameplayScreen)); }, 10000); From e191c2c50e5652d32f04b7fccb7a7a57ed5c2e9d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Oct 2019 21:32:16 +0900 Subject: [PATCH 1745/2815] Tidy up constants and method naming --- osu.Game/Overlays/Comments/CommentsHeader.cs | 16 +++++++--------- osu.Game/Overlays/Comments/HeaderButton.cs | 19 ++++++++----------- osu.Game/Overlays/Comments/SortSelector.cs | 4 ++-- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/osu.Game/Overlays/Comments/CommentsHeader.cs b/osu.Game/Overlays/Comments/CommentsHeader.cs index 2bd2bf21a6..1df347eb82 100644 --- a/osu.Game/Overlays/Comments/CommentsHeader.cs +++ b/osu.Game/Overlays/Comments/CommentsHeader.cs @@ -15,10 +15,7 @@ namespace osu.Game.Overlays.Comments { public class CommentsHeader : CompositeDrawable { - private const int height = 40; - private const int spacing = 10; - private const int padding = 50; - private const int text_size = 14; + private const int font_size = 14; public readonly Bindable Sort = new Bindable(); public readonly BindableBool ShowDeleted = new BindableBool(); @@ -28,7 +25,8 @@ namespace osu.Game.Overlays.Comments public CommentsHeader() { RelativeSizeAxes = Axes.X; - Height = height; + Height = 40; + AddRangeInternal(new Drawable[] { background = new Box @@ -38,14 +36,14 @@ namespace osu.Game.Overlays.Comments new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = padding }, + Padding = new MarginPadding { Horizontal = 50 }, Children = new Drawable[] { new FillFlowContainer { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Spacing = new Vector2(spacing, 0), + Spacing = new Vector2(10, 0), Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Children = new Drawable[] @@ -54,7 +52,7 @@ namespace osu.Game.Overlays.Comments { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: text_size), + Font = OsuFont.GetFont(size: font_size), Text = @"Sort by" }, new SortSelector @@ -107,7 +105,7 @@ namespace osu.Game.Overlays.Comments { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: text_size), + Font = OsuFont.GetFont(size: font_size), Text = @"Show deleted" } }, diff --git a/osu.Game/Overlays/Comments/HeaderButton.cs b/osu.Game/Overlays/Comments/HeaderButton.cs index 231a364759..8789cf5830 100644 --- a/osu.Game/Overlays/Comments/HeaderButton.cs +++ b/osu.Game/Overlays/Comments/HeaderButton.cs @@ -13,10 +13,7 @@ namespace osu.Game.Overlays.Comments { public class HeaderButton : Container { - private const int height = 20; - private const int corner_radius = 3; - private const int margin = 10; - private const int duration = 200; + private const int transition_duration = 200; protected override Container Content => content; @@ -26,9 +23,9 @@ namespace osu.Game.Overlays.Comments public HeaderButton() { AutoSizeAxes = Axes.X; - Height = height; + Height = 20; Masking = true; - CornerRadius = corner_radius; + CornerRadius = 3; AddRangeInternal(new Drawable[] { background = new Box @@ -41,7 +38,7 @@ namespace osu.Game.Overlays.Comments AutoSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Margin = new MarginPadding { Horizontal = margin } + Margin = new MarginPadding { Horizontal = 10 } }, new HoverClickSounds(), }); @@ -55,18 +52,18 @@ namespace osu.Game.Overlays.Comments protected override bool OnHover(HoverEvent e) { - FadeInBackground(); + ShowBackground(); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { base.OnHoverLost(e); - FadeOutBackground(); + HideBackground(); } - protected void FadeInBackground() => background.FadeIn(duration, Easing.OutQuint); + protected void ShowBackground() => background.FadeIn(transition_duration, Easing.OutQuint); - protected void FadeOutBackground() => background.FadeOut(duration, Easing.OutQuint); + protected void HideBackground() => background.FadeOut(transition_duration, Easing.OutQuint); } } diff --git a/osu.Game/Overlays/Comments/SortSelector.cs b/osu.Game/Overlays/Comments/SortSelector.cs index 100ae83291..90e7defb9a 100644 --- a/osu.Game/Overlays/Comments/SortSelector.cs +++ b/osu.Game/Overlays/Comments/SortSelector.cs @@ -75,7 +75,7 @@ namespace osu.Game.Overlays.Comments public void Activate() { - FadeInBackground(); + ShowBackground(); text.Font = text.Font.With(weight: FontWeight.Bold); text.Colour = colours.BlueLighter; } @@ -83,7 +83,7 @@ namespace osu.Game.Overlays.Comments public void Deactivate() { if (!IsHovered) - FadeOutBackground(); + HideBackground(); text.Font = text.Font.With(weight: FontWeight.Medium); text.Colour = Color4.White; From 89f270a19a32df0696cbd244eb9e39d826220f4b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Oct 2019 21:32:41 +0900 Subject: [PATCH 1746/2815] SortSelector -> SortTabControl --- osu.Game.Tests/Visual/Online/TestSceneCommentsHeader.cs | 2 +- osu.Game/Overlays/Comments/CommentsHeader.cs | 2 +- .../Overlays/Comments/{SortSelector.cs => SortTabControl.cs} | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Overlays/Comments/{SortSelector.cs => SortTabControl.cs} (96%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsHeader.cs index 949dbbe5c4..bc3e0eff1a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsHeader.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.Online { typeof(CommentsHeader), typeof(HeaderButton), - typeof(SortSelector), + typeof(SortTabControl), }; private readonly Bindable sort = new Bindable(); diff --git a/osu.Game/Overlays/Comments/CommentsHeader.cs b/osu.Game/Overlays/Comments/CommentsHeader.cs index 1df347eb82..81be16967f 100644 --- a/osu.Game/Overlays/Comments/CommentsHeader.cs +++ b/osu.Game/Overlays/Comments/CommentsHeader.cs @@ -55,7 +55,7 @@ namespace osu.Game.Overlays.Comments Font = OsuFont.GetFont(size: font_size), Text = @"Sort by" }, - new SortSelector + new SortTabControl { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game/Overlays/Comments/SortSelector.cs b/osu.Game/Overlays/Comments/SortTabControl.cs similarity index 96% rename from osu.Game/Overlays/Comments/SortSelector.cs rename to osu.Game/Overlays/Comments/SortTabControl.cs index 90e7defb9a..8dc1f14c3d 100644 --- a/osu.Game/Overlays/Comments/SortSelector.cs +++ b/osu.Game/Overlays/Comments/SortTabControl.cs @@ -15,7 +15,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Comments { - public class SortSelector : OsuTabControl + public class SortTabControl : OsuTabControl { private const int spacing = 5; @@ -30,7 +30,7 @@ namespace osu.Game.Overlays.Comments Spacing = new Vector2(spacing, 0), }; - public SortSelector() + public SortTabControl() { AutoSizeAxes = Axes.Both; } From 4822496c130880a5f3c20e847942cbab870849a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Oct 2019 21:34:16 +0900 Subject: [PATCH 1747/2815] Fix more naming --- osu.Game/Overlays/Comments/SortTabControl.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Comments/SortTabControl.cs b/osu.Game/Overlays/Comments/SortTabControl.cs index 8dc1f14c3d..3f1b5c54bd 100644 --- a/osu.Game/Overlays/Comments/SortTabControl.cs +++ b/osu.Game/Overlays/Comments/SortTabControl.cs @@ -37,23 +37,23 @@ namespace osu.Game.Overlays.Comments private class SortTabItem : TabItem { - private readonly TabContent content; + private readonly TabButton button; public SortTabItem(CommentsSortCriteria value) : base(value) { AutoSizeAxes = Axes.Both; - Child = content = new TabContent(value) + Child = button = new TabButton(value) { Active = { BindTarget = Active } }; } - protected override void OnActivated() => content.Activate(); + protected override void OnActivated() => button.Activate(); - protected override void OnDeactivated() => content.Deactivate(); + protected override void OnDeactivated() => button.Deactivate(); - private class TabContent : HeaderButton + private class TabButton : HeaderButton { private const int text_size = 14; @@ -64,7 +64,7 @@ namespace osu.Game.Overlays.Comments private readonly SpriteText text; - public TabContent(CommentsSortCriteria value) + public TabButton(CommentsSortCriteria value) { Add(text = new SpriteText { From 779445755034234c0c986dd8f874b0b2162ea35e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Oct 2019 21:34:43 +0900 Subject: [PATCH 1748/2815] Remove more constants --- osu.Game/Overlays/Comments/SortTabControl.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Comments/SortTabControl.cs b/osu.Game/Overlays/Comments/SortTabControl.cs index 3f1b5c54bd..596822a86c 100644 --- a/osu.Game/Overlays/Comments/SortTabControl.cs +++ b/osu.Game/Overlays/Comments/SortTabControl.cs @@ -17,8 +17,6 @@ namespace osu.Game.Overlays.Comments { public class SortTabControl : OsuTabControl { - private const int spacing = 5; - protected override Dropdown CreateDropdown() => null; protected override TabItem CreateTabItem(CommentsSortCriteria value) => new SortTabItem(value); @@ -27,7 +25,7 @@ namespace osu.Game.Overlays.Comments { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Spacing = new Vector2(spacing, 0), + Spacing = new Vector2(5, 0), }; public SortTabControl() @@ -55,8 +53,6 @@ namespace osu.Game.Overlays.Comments private class TabButton : HeaderButton { - private const int text_size = 14; - public readonly BindableBool Active = new BindableBool(); [Resolved] @@ -68,7 +64,7 @@ namespace osu.Game.Overlays.Comments { Add(text = new SpriteText { - Font = OsuFont.GetFont(size: text_size), + Font = OsuFont.GetFont(size: 14), Text = value.ToString() }); } From 4e6ab1dad397f0151861f3fc39bed5c3c9f0617f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Oct 2019 21:42:07 +0900 Subject: [PATCH 1749/2815] Tidy up state management via bindable usage --- osu.Game/Overlays/Comments/SortTabControl.cs | 51 +++++++++++--------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/osu.Game/Overlays/Comments/SortTabControl.cs b/osu.Game/Overlays/Comments/SortTabControl.cs index 596822a86c..f5423e692f 100644 --- a/osu.Game/Overlays/Comments/SortTabControl.cs +++ b/osu.Game/Overlays/Comments/SortTabControl.cs @@ -35,21 +35,20 @@ namespace osu.Game.Overlays.Comments private class SortTabItem : TabItem { - private readonly TabButton button; - public SortTabItem(CommentsSortCriteria value) : base(value) { AutoSizeAxes = Axes.Both; - Child = button = new TabButton(value) - { - Active = { BindTarget = Active } - }; + Child = new TabButton(value) { Active = { BindTarget = Active } }; } - protected override void OnActivated() => button.Activate(); + protected override void OnActivated() + { + } - protected override void OnDeactivated() => button.Deactivate(); + protected override void OnDeactivated() + { + } private class TabButton : HeaderButton { @@ -69,25 +68,33 @@ namespace osu.Game.Overlays.Comments }); } - public void Activate() + protected override void LoadComplete() { - ShowBackground(); - text.Font = text.Font.With(weight: FontWeight.Bold); - text.Colour = colours.BlueLighter; + base.LoadComplete(); + + Active.BindValueChanged(active => + { + updateBackgroundState(); + + text.Font = text.Font.With(weight: active.NewValue ? FontWeight.Bold : FontWeight.Medium); + text.Colour = active.NewValue ? colours.BlueLighter : Color4.White; + }, true); } - public void Deactivate() + protected override bool OnHover(HoverEvent e) { - if (!IsHovered) + updateBackgroundState(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) => updateBackgroundState(); + + private void updateBackgroundState() + { + if (Active.Value || IsHovered) + ShowBackground(); + else HideBackground(); - - text.Font = text.Font.With(weight: FontWeight.Medium); - text.Colour = Color4.White; - } - - protected override void OnHoverLost(HoverLostEvent e) - { - if (!Active.Value) base.OnHoverLost(e); } } } From b7ddf160b46d874e20c928aae44595daccfe692a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Oct 2019 21:48:12 +0900 Subject: [PATCH 1750/2815] OnClick should actually handle the event --- osu.Game/Overlays/Comments/CommentsHeader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/CommentsHeader.cs b/osu.Game/Overlays/Comments/CommentsHeader.cs index 81be16967f..1797a36b71 100644 --- a/osu.Game/Overlays/Comments/CommentsHeader.cs +++ b/osu.Game/Overlays/Comments/CommentsHeader.cs @@ -126,7 +126,7 @@ namespace osu.Game.Overlays.Comments protected override bool OnClick(ClickEvent e) { Checked.Value = !Checked.Value; - return base.OnClick(e); + return true; } } } From f0e970034950ff034e380be1ecf34947abf806fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Oct 2019 21:49:02 +0900 Subject: [PATCH 1751/2815] Inline delegate event --- osu.Game/Overlays/Comments/CommentsHeader.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Comments/CommentsHeader.cs b/osu.Game/Overlays/Comments/CommentsHeader.cs index 1797a36b71..66fe7ff3fa 100644 --- a/osu.Game/Overlays/Comments/CommentsHeader.cs +++ b/osu.Game/Overlays/Comments/CommentsHeader.cs @@ -114,15 +114,10 @@ namespace osu.Game.Overlays.Comments protected override void LoadComplete() { - Checked.BindValueChanged(onCheckedChanged, true); + Checked.BindValueChanged(isChecked => checkboxIcon.Icon = isChecked.NewValue ? FontAwesome.Solid.CheckSquare : FontAwesome.Regular.Square, true); base.LoadComplete(); } - private void onCheckedChanged(ValueChangedEvent isChecked) - { - checkboxIcon.Icon = isChecked.NewValue ? FontAwesome.Solid.CheckSquare : FontAwesome.Regular.Square; - } - protected override bool OnClick(ClickEvent e) { Checked.Value = !Checked.Value; From 7cd3f5656d4c7b482fb995ce9ce4489db0a20d3d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 14 Oct 2019 16:43:43 +0300 Subject: [PATCH 1752/2815] Cleanups --- .../Online/TestSceneCommentsContainer.cs | 2 +- .../Online/API/Requests/Responses/Comment.cs | 6 ++++- .../Overlays/Comments/CommentsContainer.cs | 4 +-- .../Comments/DeletedChildsPlaceholder.cs | 5 +--- osu.Game/Overlays/Comments/DrawableComment.cs | 27 +++++++------------ .../Overlays/Comments/ShowChildsButton.cs | 2 +- 6 files changed, 18 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs index a283663a4a..5e4aa27fae 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Online typeof(CommentsHeader), typeof(DrawableComment), typeof(HeaderButton), - typeof(SortSelector), + typeof(SortTabControl), typeof(ShowChildsButton), typeof(DeletedChildsPlaceholder) }; diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs index 2334c86519..d0f7e4fac5 100644 --- a/osu.Game/Online/API/Requests/Responses/Comment.cs +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -6,6 +6,7 @@ using osu.Game.Users; using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; namespace osu.Game.Online.API.Requests.Responses { @@ -90,7 +91,10 @@ namespace osu.Game.Online.API.Requests.Responses public string GetMessage() { - return IsDeleted ? @"deleted" : MessageHTML.Replace("
", "").Replace("

", "").Replace("
", "").Replace("

", "").Replace("
", "").Replace(""", "\""); + if (IsDeleted) + return @"deleted"; + + return Regex.Replace(MessageHTML, @"\<.*?\>", ""); } public int GetDeletedChildsCount() diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 4c27c498c3..8680234d4b 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -16,8 +16,6 @@ namespace osu.Game.Overlays.Comments { public class CommentsContainer : CompositeDrawable { - private const int more_button_margin = 5; - private readonly CommentableType type; private readonly long id; @@ -101,7 +99,7 @@ namespace osu.Game.Overlays.Comments { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Margin = new MarginPadding(more_button_margin), + Margin = new MarginPadding(5), Action = () => getComments(false), } } diff --git a/osu.Game/Overlays/Comments/DeletedChildsPlaceholder.cs b/osu.Game/Overlays/Comments/DeletedChildsPlaceholder.cs index d626c13afd..058f8cc750 100644 --- a/osu.Game/Overlays/Comments/DeletedChildsPlaceholder.cs +++ b/osu.Game/Overlays/Comments/DeletedChildsPlaceholder.cs @@ -12,9 +12,6 @@ namespace osu.Game.Overlays.Comments { public class DeletedChildsPlaceholder : FillFlowContainer { - private const int deleted_placeholder_margin = 80; - private const int margin = 10; - public readonly BindableBool ShowDeleted = new BindableBool(); public readonly BindableInt DeletedCount = new BindableInt(); @@ -27,7 +24,7 @@ namespace osu.Game.Overlays.Comments AutoSizeAxes = Axes.Both; Direction = FillDirection.Horizontal; Spacing = new Vector2(3, 0); - Margin = new MarginPadding { Vertical = margin, Left = deleted_placeholder_margin }; + Margin = new MarginPadding { Vertical = 10, Left = 80 }; Children = new Drawable[] { new SpriteIcon diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 8c356a6156..756eb8caf9 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -21,18 +21,14 @@ namespace osu.Game.Overlays.Comments { private const int avatar_size = 40; private const int margin = 10; - private const int child_margin = 20; - private const int chevron_margin = 30; - private const int message_padding = 40; - private const float separator_height = 1.5f; public readonly BindableBool ShowDeleted = new BindableBool(); private readonly BindableBool childExpanded = new BindableBool(true); private readonly FillFlowContainer childCommentsVisibilityContainer; - private readonly Comment comment; private readonly DeletedChildsPlaceholder deletedChildsPlaceholder; + private readonly Comment comment; public DrawableComment(Comment comment) { @@ -127,7 +123,7 @@ namespace osu.Game.Overlays.Comments { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Right = message_padding } + Padding = new MarginPadding { Right = 40 } }, info = new FillFlowContainer { @@ -164,7 +160,7 @@ namespace osu.Game.Overlays.Comments { childCommentsContainer = new FillFlowContainer { - Margin = new MarginPadding { Left = child_margin }, + Margin = new MarginPadding { Left = 20 }, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical @@ -180,10 +176,10 @@ namespace osu.Game.Overlays.Comments deletedChildsPlaceholder.DeletedCount.Value = comment.GetDeletedChildsCount(); - if (comment.UserId == null) - username.AddText(comment.LegacyName); - else + if (comment.UserId.HasValue) username.AddUserLink(comment.User); + else + username.AddText(comment.LegacyName); if (comment.EditedAt.HasValue) { @@ -209,7 +205,7 @@ namespace osu.Game.Overlays.Comments AddInternal(new Container { RelativeSizeAxes = Axes.X, - Height = separator_height, + Height = 1.5f, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Child = new Box @@ -225,7 +221,7 @@ namespace osu.Game.Overlays.Comments { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Margin = new MarginPadding { Right = chevron_margin, Top = margin }, + Margin = new MarginPadding { Right = 30, Top = margin }, Expanded = { BindTarget = childExpanded } }); } @@ -240,15 +236,10 @@ namespace osu.Game.Overlays.Comments protected override void LoadComplete() { ShowDeleted.BindValueChanged(onShowDeletedChanged, true); - childExpanded.BindValueChanged(onChildExpandedChanged, true); + childExpanded.BindValueChanged(expanded => childCommentsVisibilityContainer.FadeTo(expanded.NewValue ? 1 : 0), true); base.LoadComplete(); } - private void onChildExpandedChanged(ValueChangedEvent expanded) - { - childCommentsVisibilityContainer.FadeTo(expanded.NewValue ? 1 : 0); - } - private void onShowDeletedChanged(ValueChangedEvent show) { if (comment.IsDeleted) diff --git a/osu.Game/Overlays/Comments/ShowChildsButton.cs b/osu.Game/Overlays/Comments/ShowChildsButton.cs index 81280a71b5..b29e316e80 100644 --- a/osu.Game/Overlays/Comments/ShowChildsButton.cs +++ b/osu.Game/Overlays/Comments/ShowChildsButton.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Comments protected override bool OnClick(ClickEvent e) { Expanded.Value = !Expanded.Value; - return base.OnClick(e); + return true; } } } From a81d5cd81968fc113f924552c87f728da17d0ef6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 14 Oct 2019 16:56:07 +0300 Subject: [PATCH 1753/2815] Handle links in message --- osu.Game/Overlays/Comments/DrawableComment.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 756eb8caf9..c492e48acf 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Bindables; using osu.Framework.Graphics.Shapes; using System.Linq; +using osu.Game.Online.Chat; namespace osu.Game.Overlays.Comments { @@ -35,7 +36,7 @@ namespace osu.Game.Overlays.Comments LinkFlowContainer username; FillFlowContainer childCommentsContainer; FillFlowContainer info; - TextFlowContainer message; + LinkFlowContainer message; GridContainer content; VotePill votePill; @@ -119,7 +120,7 @@ namespace osu.Game.Overlays.Comments } } }, - message = new TextFlowContainer(s => s.Font = OsuFont.GetFont(size: 14)) + message = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14)) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -193,7 +194,10 @@ namespace osu.Game.Overlays.Comments } if (!comment.IsDeleted) - message.Text = comment.GetMessage(); + { + var formattedSource = MessageFormatter.FormatText(comment.GetMessage()); + message.AddLinks(formattedSource.Text, formattedSource.Links); + } else { content.FadeColour(OsuColour.Gray(0.5f)); From a4ffd4798dfc76b516f4da2cb8b2f8f58a5f858f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 14 Oct 2019 17:02:48 +0300 Subject: [PATCH 1754/2815] Fix escaped html strings not being unescaped --- osu.Game/Online/API/Requests/Responses/Comment.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs index d0f7e4fac5..5e67bff859 100644 --- a/osu.Game/Online/API/Requests/Responses/Comment.cs +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -6,6 +6,7 @@ using osu.Game.Users; using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Text.RegularExpressions; namespace osu.Game.Online.API.Requests.Responses @@ -94,7 +95,7 @@ namespace osu.Game.Online.API.Requests.Responses if (IsDeleted) return @"deleted"; - return Regex.Replace(MessageHTML, @"\<.*?\>", ""); + return WebUtility.HtmlDecode(Regex.Replace(MessageHTML, @"<(.|\n)*?>", string.Empty)); } public int GetDeletedChildsCount() From b53fb0d228ba112044285ca31272432a53938a43 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 14 Oct 2019 17:07:50 +0300 Subject: [PATCH 1755/2815] Remove empty line --- osu.Game/Overlays/Comments/CommentsContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 8680234d4b..a5e921e2c0 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -183,7 +183,6 @@ namespace osu.Game.Overlays.Comments moreButton.IsLoading = false; } moreButton.FadeTo(response.HasMore ? 1 : 0); - }, loadCancellation.Token); } From 139170cdc859f2689e99b0a940f8334b2b46fa19 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 14 Oct 2019 17:26:12 +0300 Subject: [PATCH 1756/2815] Fix incorrect padding for nested comments --- osu.Game/Overlays/Comments/DrawableComment.cs | 169 +++++++++--------- 1 file changed, 87 insertions(+), 82 deletions(-) diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index c492e48acf..e5258ef3cc 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -51,100 +51,105 @@ namespace osu.Game.Overlays.Comments Direction = FillDirection.Vertical, Children = new Drawable[] { - content = new GridContainer + new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Margin = new MarginPadding(margin), - ColumnDimensions = new[] + Padding = new MarginPadding(margin), + Child = content = new GridContainer { - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new Drawable[] + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ColumnDimensions = new[] { - new FillFlowContainer + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] { - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Horizontal = margin }, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5, 0), - Children = new Drawable[] + new FillFlowContainer { - votePill = new VotePill(comment.VotesCount) + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Horizontal = margin }, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AlwaysPresent = true, - }, - new UpdateableAvatar(comment.User) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(avatar_size), - Masking = true, - CornerRadius = avatar_size / 2, - }, - } - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0, 3), - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(7, 0), - Children = new Drawable[] + votePill = new VotePill(comment.VotesCount) { - username = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true)) + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AlwaysPresent = true, + }, + new UpdateableAvatar(comment.User) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(avatar_size), + Masking = true, + CornerRadius = avatar_size / 2, + }, + } + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0, 3), + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(7, 0), + Children = new Drawable[] { - AutoSizeAxes = Axes.Both, - }, - new ParentUsername(comment), - new SpriteText - { - Alpha = comment.IsDeleted? 1 : 0, - Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true), - Text = @"deleted", + username = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true)) + { + AutoSizeAxes = Axes.Both, + }, + new ParentUsername(comment), + new SpriteText + { + Alpha = comment.IsDeleted? 1 : 0, + Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true), + Text = @"deleted", + } } - } - }, - message = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14)) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Right = 40 } - }, - info = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), - Colour = OsuColour.Gray(0.7f), - Children = new Drawable[] + }, + message = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14)) { - new SpriteText + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Right = 40 } + }, + info = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Colour = OsuColour.Gray(0.7f), + Children = new Drawable[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: 12), - Text = HumanizerUtils.Humanize(comment.CreatedAt) - }, - new RepliesButton(comment.RepliesCount) - { - Expanded = { BindTarget = childExpanded } - }, + new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: 12), + Text = HumanizerUtils.Humanize(comment.CreatedAt) + }, + new RepliesButton(comment.RepliesCount) + { + Expanded = { BindTarget = childExpanded } + }, + } } } } @@ -161,7 +166,7 @@ namespace osu.Game.Overlays.Comments { childCommentsContainer = new FillFlowContainer { - Margin = new MarginPadding { Left = 20 }, + Padding = new MarginPadding { Left = 20 }, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical From d4843285dbf091b6756b71e86599a94ffb8bbde0 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 14 Oct 2019 17:33:14 +0300 Subject: [PATCH 1757/2815] CI fixes --- osu.Game/Graphics/UserInterface/ShowMoreButton.cs | 6 +++--- osu.Game/Online/API/Requests/Responses/Comment.cs | 4 ++-- osu.Game/Overlays/Comments/CommentsContainer.cs | 1 + osu.Game/Overlays/Comments/DrawableComment.cs | 15 +++++---------- osu.Game/Overlays/Comments/ShowChildsButton.cs | 2 +- 5 files changed, 12 insertions(+), 16 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs index 627ad995e8..8b2eb31d96 100644 --- a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs +++ b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs @@ -23,13 +23,13 @@ namespace osu.Game.Graphics.UserInterface public Color4 ChevronIconColour { get => chevronIconColour; - set { chevronIconColour = leftChevron.AccentColour = rightChevron.AccentColour = value; } + set => chevronIconColour = leftChevron.AccentColour = rightChevron.AccentColour = value; } public string Text { get => text.Text; - set { text.Text = value; } + set => text.Text = value; } private bool isLoading; @@ -136,7 +136,7 @@ namespace osu.Game.Graphics.UserInterface public Color4 AccentColour { get => accentColour; - set { accentColour = Colour = value; } + set => accentColour = Colour = value; } public ChevronIcon() diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs index 5e67bff859..6243ea4fb6 100644 --- a/osu.Game/Online/API/Requests/Responses/Comment.cs +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -42,7 +42,7 @@ namespace osu.Game.Online.API.Requests.Responses public string Message { get; set; } [JsonProperty(@"message_html")] - public string MessageHTML { get; set; } + public string MessageHtml { get; set; } [JsonProperty(@"replies_count")] public int RepliesCount { get; set; } @@ -95,7 +95,7 @@ namespace osu.Game.Online.API.Requests.Responses if (IsDeleted) return @"deleted"; - return WebUtility.HtmlDecode(Regex.Replace(MessageHTML, @"<(.|\n)*?>", string.Empty)); + return WebUtility.HtmlDecode(Regex.Replace(MessageHtml, @"<(.|\n)*?>", string.Empty)); } public int GetDeletedChildsCount() diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index a5e921e2c0..6c674678df 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -182,6 +182,7 @@ namespace osu.Game.Overlays.Comments moreButton.Current.Value = response.TopLevelCount - loadedTopLevelComments; moreButton.IsLoading = false; } + moreButton.FadeTo(response.HasMore ? 1 : 0); }, loadCancellation.Token); } diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index e5258ef3cc..38e45949e1 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -28,13 +28,13 @@ namespace osu.Game.Overlays.Comments private readonly BindableBool childExpanded = new BindableBool(true); private readonly FillFlowContainer childCommentsVisibilityContainer; - private readonly DeletedChildsPlaceholder deletedChildsPlaceholder; private readonly Comment comment; public DrawableComment(Comment comment) { LinkFlowContainer username; FillFlowContainer childCommentsContainer; + DeletedChildsPlaceholder deletedChildsPlaceholder; FillFlowContainer info; LinkFlowContainer message; GridContainer content; @@ -93,7 +93,7 @@ namespace osu.Game.Overlays.Comments Origin = Anchor.Centre, Size = new Vector2(avatar_size), Masking = true, - CornerRadius = avatar_size / 2, + CornerRadius = avatar_size / 2f, }, } }, @@ -118,7 +118,7 @@ namespace osu.Game.Overlays.Comments new ParentUsername(comment), new SpriteText { - Alpha = comment.IsDeleted? 1 : 0, + Alpha = comment.IsDeleted ? 1 : 0, Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true), Text = @"deleted", } @@ -299,8 +299,6 @@ namespace osu.Game.Overlays.Comments private class ParentUsername : FillFlowContainer, IHasTooltip { - private const int spacing = 3; - public string TooltipText => comment.ParentComment?.GetMessage() ?? ""; private readonly Comment comment; @@ -311,7 +309,7 @@ namespace osu.Game.Overlays.Comments AutoSizeAxes = Axes.Both; Direction = FillDirection.Horizontal; - Spacing = new Vector2(spacing, 0); + Spacing = new Vector2(3, 0); Alpha = comment.ParentId == null ? 0 : 1; Children = new Drawable[] { @@ -331,13 +329,10 @@ namespace osu.Game.Overlays.Comments private class VotePill : CircularContainer { - private const int height = 20; - private const int margin = 10; - public VotePill(int count) { AutoSizeAxes = Axes.X; - Height = height; + Height = 20; Masking = true; Children = new Drawable[] { diff --git a/osu.Game/Overlays/Comments/ShowChildsButton.cs b/osu.Game/Overlays/Comments/ShowChildsButton.cs index b29e316e80..464c0a1503 100644 --- a/osu.Game/Overlays/Comments/ShowChildsButton.cs +++ b/osu.Game/Overlays/Comments/ShowChildsButton.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Comments { public readonly BindableBool Expanded = new BindableBool(true); - public ShowChildsButton() + protected ShowChildsButton() { AutoSizeAxes = Axes.Both; } From 6da1b4d0120317643473c61c4e9e9ce6f34f6784 Mon Sep 17 00:00:00 2001 From: miterosan Date: Mon, 14 Oct 2019 21:46:01 +0200 Subject: [PATCH 1758/2815] Fix incorrect current directory that accours on some devices on android. --- osu.Android/OsuGameActivity.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index 762a9c418d..41531617af 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -16,6 +16,11 @@ namespace osu.Android protected override void OnCreate(Bundle savedInstanceState) { + // The default current directory on android is '/'. + // On some devices '/' maps to the app data directory. On others it maps to the root of the internal storage. + // In order to have a consitend current directory on all devices the full path of the app data directory is set as the current directory. + System.Environment.CurrentDirectory = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal); + base.OnCreate(savedInstanceState); Window.AddFlags(WindowManagerFlags.Fullscreen); From b1f7a673e719ede09fd76bd4e2da36658aaa53bc Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 15 Oct 2019 00:10:02 +0300 Subject: [PATCH 1759/2815] Simplify chevron icon coloring --- osu.Game/Graphics/UserInterface/ShowMoreButton.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs index 8b2eb31d96..854b7abce1 100644 --- a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs +++ b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs @@ -23,7 +23,7 @@ namespace osu.Game.Graphics.UserInterface public Color4 ChevronIconColour { get => chevronIconColour; - set => chevronIconColour = leftChevron.AccentColour = rightChevron.AccentColour = value; + set => chevronIconColour = leftChevron.Colour = rightChevron.Colour = value; } public string Text @@ -131,14 +131,6 @@ namespace osu.Game.Graphics.UserInterface { private const int icon_size = 8; - private Color4 accentColour; - - public Color4 AccentColour - { - get => accentColour; - set => accentColour = Colour = value; - } - public ChevronIcon() { Anchor = Anchor.Centre; From 0676c880b53a612093ef2f0fb8f46c03246f355d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 15 Oct 2019 00:26:31 +0300 Subject: [PATCH 1760/2815] Simplify IsTopLevel and IsDeleted properties --- .../Online/API/Requests/Responses/Comment.cs | 30 ++++--------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs index 6243ea4fb6..5accd7fd5b 100644 --- a/osu.Game/Online/API/Requests/Responses/Comment.cs +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -16,20 +16,10 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"id")] public long Id { get; set; } - private long? parentId; - [JsonProperty(@"parent_id")] - public long? ParentId - { - get => parentId; - set - { - parentId = value; - IsTopLevel = value == null; - } - } + public long? ParentId { get; set; } - public List ChildComments = new List(); + public readonly List ChildComments = new List(); public Comment ParentComment { get; set; } @@ -65,18 +55,8 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"updated_at")] public DateTimeOffset? UpdatedAt { get; set; } - private DateTimeOffset? deletedAt; - [JsonProperty(@"deleted_at")] - public DateTimeOffset? DeletedAt - { - get => deletedAt; - set - { - deletedAt = value; - IsDeleted = value != null; - } - } + public DateTimeOffset? DeletedAt { get; set; } [JsonProperty(@"edited_at")] public DateTimeOffset? EditedAt { get; set; } @@ -86,9 +66,9 @@ namespace osu.Game.Online.API.Requests.Responses public User EditedUser { get; set; } - public bool IsTopLevel { get; set; } + public bool IsTopLevel => !ParentId.HasValue; - public bool IsDeleted { get; set; } + public bool IsDeleted => DeletedAt.HasValue; public string GetMessage() { From 09621f066e8fc1ac9b93e755c1fc94b416b8148e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 15 Oct 2019 00:32:21 +0300 Subject: [PATCH 1761/2815] Childs -> Children --- .../Online/TestSceneCommentsContainer.cs | 4 ++-- .../Online/API/Requests/Responses/Comment.cs | 2 +- .../Overlays/Comments/CommentsContainer.cs | 8 ++++---- ...holder.cs => DeletedChildrenPlaceholder.cs} | 4 ++-- osu.Game/Overlays/Comments/DrawableComment.cs | 18 +++++++++--------- ...owChildsButton.cs => ShowChildrenButton.cs} | 4 ++-- 6 files changed, 20 insertions(+), 20 deletions(-) rename osu.Game/Overlays/Comments/{DeletedChildsPlaceholder.cs => DeletedChildrenPlaceholder.cs} (95%) rename osu.Game/Overlays/Comments/{ShowChildsButton.cs => ShowChildrenButton.cs} (89%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs index 5e4aa27fae..436e80d6f5 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -21,8 +21,8 @@ namespace osu.Game.Tests.Visual.Online typeof(DrawableComment), typeof(HeaderButton), typeof(SortTabControl), - typeof(ShowChildsButton), - typeof(DeletedChildsPlaceholder) + typeof(ShowChildrenButton), + typeof(DeletedChildrenPlaceholder) }; protected override bool UseOnlineAPI => true; diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs index 5accd7fd5b..9e8f0cada2 100644 --- a/osu.Game/Online/API/Requests/Responses/Comment.cs +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -78,7 +78,7 @@ namespace osu.Game.Online.API.Requests.Responses return WebUtility.HtmlDecode(Regex.Replace(MessageHtml, @"<(.|\n)*?>", string.Empty)); } - public int GetDeletedChildsCount() + public int GetDeletedChildrenCount() { int count = 0; diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 6c674678df..48b3952093 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -35,7 +35,7 @@ namespace osu.Game.Overlays.Comments private readonly Box background; private readonly FillFlowContainer content; - private readonly DeletedChildsPlaceholder deletedChildsPlaceholder; + private readonly DeletedChildrenPlaceholder deletedChildrenPlaceholder; private readonly CommentsShowMoreButton moreButton; public CommentsContainer(CommentableType type, long id) @@ -87,7 +87,7 @@ namespace osu.Game.Overlays.Comments Direction = FillDirection.Vertical, Children = new Drawable[] { - deletedChildsPlaceholder = new DeletedChildsPlaceholder + deletedChildrenPlaceholder = new DeletedChildrenPlaceholder { ShowDeleted = { BindTarget = ShowDeleted } }, @@ -126,7 +126,7 @@ namespace osu.Game.Overlays.Comments { currentPage = 1; loadedTopLevelComments = 0; - deletedChildsPlaceholder.DeletedCount.Value = 0; + deletedChildrenPlaceholder.DeletedCount.Value = 0; moreButton.IsLoading = true; content.Clear(); } @@ -170,7 +170,7 @@ namespace osu.Game.Overlays.Comments deletedComments++; }); - deletedChildsPlaceholder.DeletedCount.Value = initial ? deletedComments : deletedChildsPlaceholder.DeletedCount.Value + deletedComments; + deletedChildrenPlaceholder.DeletedCount.Value = initial ? deletedComments : deletedChildrenPlaceholder.DeletedCount.Value + deletedComments; if (response.HasMore) { diff --git a/osu.Game/Overlays/Comments/DeletedChildsPlaceholder.cs b/osu.Game/Overlays/Comments/DeletedChildrenPlaceholder.cs similarity index 95% rename from osu.Game/Overlays/Comments/DeletedChildsPlaceholder.cs rename to osu.Game/Overlays/Comments/DeletedChildrenPlaceholder.cs index 058f8cc750..f537141d7f 100644 --- a/osu.Game/Overlays/Comments/DeletedChildsPlaceholder.cs +++ b/osu.Game/Overlays/Comments/DeletedChildrenPlaceholder.cs @@ -10,7 +10,7 @@ using osu.Framework.Bindables; namespace osu.Game.Overlays.Comments { - public class DeletedChildsPlaceholder : FillFlowContainer + public class DeletedChildrenPlaceholder : FillFlowContainer { public readonly BindableBool ShowDeleted = new BindableBool(); public readonly BindableInt DeletedCount = new BindableInt(); @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Comments private readonly SpriteText countText; - public DeletedChildsPlaceholder() + public DeletedChildrenPlaceholder() { AutoSizeAxes = Axes.Both; Direction = FillDirection.Horizontal; diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 38e45949e1..81a6c6a743 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Comments public readonly BindableBool ShowDeleted = new BindableBool(); - private readonly BindableBool childExpanded = new BindableBool(true); + private readonly BindableBool childrenExpanded = new BindableBool(true); private readonly FillFlowContainer childCommentsVisibilityContainer; private readonly Comment comment; @@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Comments { LinkFlowContainer username; FillFlowContainer childCommentsContainer; - DeletedChildsPlaceholder deletedChildsPlaceholder; + DeletedChildrenPlaceholder deletedChildrenPlaceholder; FillFlowContainer info; LinkFlowContainer message; GridContainer content; @@ -147,7 +147,7 @@ namespace osu.Game.Overlays.Comments }, new RepliesButton(comment.RepliesCount) { - Expanded = { BindTarget = childExpanded } + Expanded = { BindTarget = childrenExpanded } }, } } @@ -171,7 +171,7 @@ namespace osu.Game.Overlays.Comments AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical }, - deletedChildsPlaceholder = new DeletedChildsPlaceholder + deletedChildrenPlaceholder = new DeletedChildrenPlaceholder { ShowDeleted = { BindTarget = ShowDeleted } } @@ -180,7 +180,7 @@ namespace osu.Game.Overlays.Comments } }; - deletedChildsPlaceholder.DeletedCount.Value = comment.GetDeletedChildsCount(); + deletedChildrenPlaceholder.DeletedCount.Value = comment.GetDeletedChildrenCount(); if (comment.UserId.HasValue) username.AddUserLink(comment.User); @@ -231,7 +231,7 @@ namespace osu.Game.Overlays.Comments Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Margin = new MarginPadding { Right = 30, Top = margin }, - Expanded = { BindTarget = childExpanded } + Expanded = { BindTarget = childrenExpanded } }); } } @@ -245,7 +245,7 @@ namespace osu.Game.Overlays.Comments protected override void LoadComplete() { ShowDeleted.BindValueChanged(onShowDeletedChanged, true); - childExpanded.BindValueChanged(expanded => childCommentsVisibilityContainer.FadeTo(expanded.NewValue ? 1 : 0), true); + childrenExpanded.BindValueChanged(expanded => childCommentsVisibilityContainer.FadeTo(expanded.NewValue ? 1 : 0), true); base.LoadComplete(); } @@ -255,7 +255,7 @@ namespace osu.Game.Overlays.Comments this.FadeTo(show.NewValue ? 1 : 0); } - private class ChevronButton : ShowChildsButton + private class ChevronButton : ShowChildrenButton { private readonly SpriteIcon icon; @@ -275,7 +275,7 @@ namespace osu.Game.Overlays.Comments } } - private class RepliesButton : ShowChildsButton + private class RepliesButton : ShowChildrenButton { private readonly SpriteText text; private readonly int count; diff --git a/osu.Game/Overlays/Comments/ShowChildsButton.cs b/osu.Game/Overlays/Comments/ShowChildrenButton.cs similarity index 89% rename from osu.Game/Overlays/Comments/ShowChildsButton.cs rename to osu.Game/Overlays/Comments/ShowChildrenButton.cs index 464c0a1503..be04b6e5de 100644 --- a/osu.Game/Overlays/Comments/ShowChildsButton.cs +++ b/osu.Game/Overlays/Comments/ShowChildrenButton.cs @@ -8,11 +8,11 @@ using osu.Framework.Bindables; namespace osu.Game.Overlays.Comments { - public abstract class ShowChildsButton : OsuHoverContainer + public abstract class ShowChildrenButton : OsuHoverContainer { public readonly BindableBool Expanded = new BindableBool(true); - protected ShowChildsButton() + protected ShowChildrenButton() { AutoSizeAxes = Axes.Both; } From b84c9dfd84f206d3766b950803116fd687c61c7d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 15 Oct 2019 00:35:44 +0300 Subject: [PATCH 1762/2815] Use Humanizer.ToQuantity instead of manual parsing --- osu.Game/Overlays/Comments/DeletedChildrenPlaceholder.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Comments/DeletedChildrenPlaceholder.cs b/osu.Game/Overlays/Comments/DeletedChildrenPlaceholder.cs index f537141d7f..21cf01f993 100644 --- a/osu.Game/Overlays/Comments/DeletedChildrenPlaceholder.cs +++ b/osu.Game/Overlays/Comments/DeletedChildrenPlaceholder.cs @@ -7,6 +7,7 @@ using osu.Game.Graphics; using osu.Framework.Graphics.Sprites; using osuTK; using osu.Framework.Bindables; +using Humanizer; namespace osu.Game.Overlays.Comments { @@ -62,12 +63,7 @@ namespace osu.Game.Overlays.Comments return; } - string str = $@"{count.NewValue} deleted comment"; - - if (!(count.NewValue.ToString().EndsWith("1") && !count.NewValue.ToString().EndsWith("11"))) - str += "s"; - - countText.Text = str; + countText.Text = @"deleted comment".ToQuantity(count.NewValue); Show(); } } From 0fd6b0c8524912ec9cc4b98cae03434a9db1154c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 15 Oct 2019 00:55:33 +0300 Subject: [PATCH 1763/2815] Use linq expression to count deleted comments --- osu.Game/Online/API/Requests/Responses/Comment.cs | 14 +------------- osu.Game/Overlays/Comments/CommentsContainer.cs | 9 ++------- osu.Game/Overlays/Comments/DrawableComment.cs | 2 +- 3 files changed, 4 insertions(+), 21 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs index 9e8f0cada2..046de194db 100644 --- a/osu.Game/Online/API/Requests/Responses/Comment.cs +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -78,18 +78,6 @@ namespace osu.Game.Online.API.Requests.Responses return WebUtility.HtmlDecode(Regex.Replace(MessageHtml, @"<(.|\n)*?>", string.Empty)); } - public int GetDeletedChildrenCount() - { - int count = 0; - - if (ChildComments.Any()) - ChildComments.ForEach(child => - { - if (child.IsDeleted) - count++; - }); - - return count; - } + public int GetDeletedChildrenCount => ChildComments.Select(c => c.IsDeleted).Where(c => c).Count(); } } diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 48b3952093..318422bedb 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Online.API.Requests.Responses; using System.Threading; +using System.Linq; namespace osu.Game.Overlays.Comments { @@ -162,13 +163,7 @@ namespace osu.Game.Overlays.Comments { content.Add(loaded); - int deletedComments = 0; - - response.Comments.ForEach(comment => - { - if (comment.IsDeleted && comment.IsTopLevel) - deletedComments++; - }); + int deletedComments = response.Comments.Select(c => c.IsDeleted && c.IsTopLevel).Where(c => c).Count(); deletedChildrenPlaceholder.DeletedCount.Value = initial ? deletedComments : deletedChildrenPlaceholder.DeletedCount.Value + deletedComments; diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 81a6c6a743..13c67b9f5b 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -180,7 +180,7 @@ namespace osu.Game.Overlays.Comments } }; - deletedChildrenPlaceholder.DeletedCount.Value = comment.GetDeletedChildrenCount(); + deletedChildrenPlaceholder.DeletedCount.Value = comment.GetDeletedChildrenCount; if (comment.UserId.HasValue) username.AddUserLink(comment.User); From 42a06a54ffb3779c7079e0b8cbd934e7d204fe77 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 15 Oct 2019 01:08:23 +0300 Subject: [PATCH 1764/2815] Don't use ProfileShowMoreButton in the test scene to avoid confusion --- .../Visual/Online/TestSceneShowMoreButton.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneShowMoreButton.cs b/osu.Game.Tests/Visual/Online/TestSceneShowMoreButton.cs index 8d4955abf0..b9fbbfef6b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneShowMoreButton.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneShowMoreButton.cs @@ -1,11 +1,12 @@ // 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.Overlays.Profile.Sections; using System; using System.Collections.Generic; using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; +using osu.Framework.Allocation; +using osu.Game.Graphics; namespace osu.Game.Tests.Visual.Online { @@ -18,11 +19,11 @@ namespace osu.Game.Tests.Visual.Online public TestSceneShowMoreButton() { - ProfileShowMoreButton button = null; + TestButton button = null; int fireCount = 0; - Add(button = new ProfileShowMoreButton + Add(button = new TestButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -52,5 +53,16 @@ namespace osu.Game.Tests.Visual.Online AddAssert("action fired twice", () => fireCount == 2); AddAssert("is in loading state", () => button.IsLoading); } + + private class TestButton : ShowMoreButton + { + [BackgroundDependencyLoader] + private void load(OsuColour colors) + { + IdleColour = colors.YellowDark; + HoverColour = colors.Yellow; + ChevronIconColour = colors.Red; + } + } } } From ad32d663652c2432a4f31bb73dc3b555a1e743c4 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 15 Oct 2019 01:10:23 +0300 Subject: [PATCH 1765/2815] CI fix --- osu.Game/Online/API/Requests/Responses/Comment.cs | 2 +- osu.Game/Overlays/Comments/CommentsContainer.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs index 046de194db..9d011c49c1 100644 --- a/osu.Game/Online/API/Requests/Responses/Comment.cs +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -78,6 +78,6 @@ namespace osu.Game.Online.API.Requests.Responses return WebUtility.HtmlDecode(Regex.Replace(MessageHtml, @"<(.|\n)*?>", string.Empty)); } - public int GetDeletedChildrenCount => ChildComments.Select(c => c.IsDeleted).Where(c => c).Count(); + public int GetDeletedChildrenCount => ChildComments.Select(c => c.IsDeleted).Count(c => c); } } diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 318422bedb..49c479f6e5 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -163,7 +163,7 @@ namespace osu.Game.Overlays.Comments { content.Add(loaded); - int deletedComments = response.Comments.Select(c => c.IsDeleted && c.IsTopLevel).Where(c => c).Count(); + int deletedComments = response.Comments.Select(c => c.IsDeleted && c.IsTopLevel).Count(c => c); deletedChildrenPlaceholder.DeletedCount.Value = initial ? deletedComments : deletedChildrenPlaceholder.DeletedCount.Value + deletedComments; From ccc753a3151b1f8066e5015527d0bba6f448687a Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 14 Oct 2019 16:27:59 -0700 Subject: [PATCH 1766/2815] Make OverlayContainers with no blocking input VisibilityContainers --- osu.Game/OsuGame.cs | 2 +- osu.Game/Overlays/Toolbar/Toolbar.cs | 4 +--- osu.Game/Overlays/VolumeOverlay.cs | 4 +--- osu.Game/Screens/Play/ResumeOverlay.cs | 4 +--- osu.Game/Screens/Play/SkipOverlay.cs | 3 +-- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 4 +--- 6 files changed, 6 insertions(+), 15 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 5742d423bb..4dcc181bea 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -102,7 +102,7 @@ namespace osu.Game private readonly List overlays = new List(); - private readonly List toolbarElements = new List(); + private readonly List toolbarElements = new List(); private readonly List visibleBlockingOverlays = new List(); diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 19038c3981..b044bc4de0 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -16,7 +16,7 @@ using osu.Game.Rulesets; namespace osu.Game.Overlays.Toolbar { - public class Toolbar : OverlayContainer + public class Toolbar : VisibilityContainer { public const float HEIGHT = 40; public const float TOOLTIP_HEIGHT = 30; @@ -26,8 +26,6 @@ namespace osu.Game.Overlays.Toolbar private ToolbarUserButton userButton; private ToolbarRulesetSelector rulesetSelector; - protected override bool BlockPositionalInput => false; - private const double transition_time = 500; private const float alpha_hovering = 0.8f; diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index 27e2eef200..ca7665eba5 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -19,7 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays { - public class VolumeOverlay : OverlayContainer + public class VolumeOverlay : VisibilityContainer { private const float offset = 10; @@ -28,8 +28,6 @@ namespace osu.Game.Overlays private VolumeMeter volumeMeterMusic; private MuteButton muteButton; - protected override bool BlockPositionalInput => false; - private readonly BindableDouble muteAdjustment = new BindableDouble(); private readonly Bindable isMuted = new Bindable(); diff --git a/osu.Game/Screens/Play/ResumeOverlay.cs b/osu.Game/Screens/Play/ResumeOverlay.cs index 2ef76069c2..641d5358ba 100644 --- a/osu.Game/Screens/Play/ResumeOverlay.cs +++ b/osu.Game/Screens/Play/ResumeOverlay.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.Play /// /// An overlay which can be used to require further user actions before gameplay is resumed. /// - public abstract class ResumeOverlay : OverlayContainer + public abstract class ResumeOverlay : VisibilityContainer { public CursorContainer GameplayCursor { get; set; } @@ -29,8 +29,6 @@ namespace osu.Game.Screens.Play protected const float TRANSITION_TIME = 500; - protected override bool BlockPositionalInput => false; - protected abstract string Message { get; } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index d6c2b59d98..31cdff5fb9 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -23,7 +23,7 @@ using osu.Game.Input.Bindings; namespace osu.Game.Screens.Play { - public class SkipOverlay : OverlayContainer, IKeyBindingHandler + public class SkipOverlay : VisibilityContainer, IKeyBindingHandler { private readonly double startTime; @@ -36,7 +36,6 @@ namespace osu.Game.Screens.Play private double displayTime; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; - protected override bool BlockPositionalInput => false; /// /// Displays a skip overlay, giving the user the ability to skip forward. diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 8b360d4a86..d54c13c7db 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -29,7 +29,7 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Select { - public class BeatmapInfoWedge : OverlayContainer + public class BeatmapInfoWedge : VisibilityContainer { private const float shear_width = 36.75f; @@ -62,8 +62,6 @@ namespace osu.Game.Screens.Select ruleset.ValueChanged += _ => updateDisplay(); } - protected override bool BlockPositionalInput => false; - protected override void PopIn() { this.MoveToX(0, 800, Easing.OutQuint); From f4924dc3cf876999b027c0bc0e9a89e203999e1b Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 14 Oct 2019 16:37:54 -0700 Subject: [PATCH 1767/2815] Fix volume scrolling when hovering VersionManager --- osu.Desktop/Overlays/VersionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs index 6eed46867a..8c759f8487 100644 --- a/osu.Desktop/Overlays/VersionManager.cs +++ b/osu.Desktop/Overlays/VersionManager.cs @@ -15,7 +15,7 @@ using osuTK.Graphics; namespace osu.Desktop.Overlays { - public class VersionManager : OverlayContainer + public class VersionManager : VisibilityContainer { [BackgroundDependencyLoader] private void load(OsuColour colours, TextureStore textures, OsuGameBase game) From efc201ec852561e3ab31491c4e171d9767a93665 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 14 Oct 2019 16:40:53 -0700 Subject: [PATCH 1768/2815] Make PlaylistOverlay a VisibilityContainer --- osu.Game/Overlays/Music/PlaylistOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index bb88960280..b89a577282 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -16,7 +16,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Music { - public class PlaylistOverlay : OverlayContainer + public class PlaylistOverlay : VisibilityContainer { private const float transition_duration = 600; private const float playlist_height = 510; From 12cd57744b87d559679e179f5f366ab1e64f6238 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 15 Oct 2019 16:14:06 +0900 Subject: [PATCH 1769/2815] Make RulestStore initialise at construction time --- .../Background/TestSceneUserDimContainer.cs | 6 ++ .../SongSelect/TestScenePlaySongSelect.cs | 6 ++ osu.Game/OsuGameBase.cs | 6 ++ osu.Game/Rulesets/RulesetStore.cs | 57 +++++++++++-------- 4 files changed, 52 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index 3061a3a542..f858174ff2 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -285,6 +285,12 @@ namespace osu.Game.Tests.Visual.Background }); } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + rulesets?.Dispose(); + } + private class DummySongSelect : PlaySongSelect { protected override BackgroundScreen CreateBackground() diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index a7020b6534..efe7fee5e4 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -349,5 +349,11 @@ namespace osu.Game.Tests.Visual.SongSelect DateAdded = DateTimeOffset.UtcNow, }; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + rulesets?.Dispose(); + } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 8578517a17..194a439b06 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -298,6 +298,12 @@ namespace osu.Game public string[] HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions).ToArray(); + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + RulesetStore?.Dispose(); + } + private class OsuUserInputManager : UserInputManager { protected override MouseButtonEventManager CreateButtonManagerFor(MouseButton button) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 47aad43966..1df8568ee1 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -14,25 +14,22 @@ namespace osu.Game.Rulesets /// /// Todo: All of this needs to be moved to a RulesetStore. /// - public class RulesetStore : DatabaseBackedStore + public class RulesetStore : DatabaseBackedStore, IDisposable { - private static readonly Dictionary loaded_assemblies = new Dictionary(); + private const string ruleset_library_prefix = "osu.Game.Rulesets"; - static RulesetStore() - { - AppDomain.CurrentDomain.AssemblyResolve += currentDomain_AssemblyResolve; - - // On android in release configuration assemblies are loaded from the apk directly into memory. - // We cannot read assemblies from cwd, so should check loaded assemblies instead. - loadFromAppDomain(); - - loadFromDisk(); - } + private readonly Dictionary loadedAssemblies = new Dictionary(); public RulesetStore(IDatabaseContextFactory factory) : base(factory) { + // On android in release configuration assemblies are loaded from the apk directly into memory. + // We cannot read assemblies from cwd, so should check loaded assemblies instead. + loadFromAppDomain(); + loadFromDisk(); addMissingRulesets(); + + AppDomain.CurrentDomain.AssemblyResolve += resolveRulesetAssembly; } /// @@ -54,9 +51,7 @@ namespace osu.Game.Rulesets /// public IEnumerable AvailableRulesets { get; private set; } - private static Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args) => loaded_assemblies.Keys.FirstOrDefault(a => a.FullName == args.Name); - - private const string ruleset_library_prefix = "osu.Game.Rulesets"; + private Assembly resolveRulesetAssembly(object sender, ResolveEventArgs args) => loadedAssemblies.Keys.FirstOrDefault(a => a.FullName == args.Name); private void addMissingRulesets() { @@ -64,7 +59,7 @@ namespace osu.Game.Rulesets { var context = usage.Context; - var instances = loaded_assemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r, (RulesetInfo)null)).ToList(); + var instances = loadedAssemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r, (RulesetInfo)null)).ToList(); //add all legacy modes in correct order foreach (var r in instances.Where(r => r.LegacyID != null).OrderBy(r => r.LegacyID)) @@ -113,7 +108,7 @@ namespace osu.Game.Rulesets } } - private static void loadFromAppDomain() + private void loadFromAppDomain() { foreach (var ruleset in AppDomain.CurrentDomain.GetAssemblies()) { @@ -126,7 +121,7 @@ namespace osu.Game.Rulesets } } - private static void loadFromDisk() + private void loadFromDisk() { try { @@ -141,11 +136,11 @@ namespace osu.Game.Rulesets } } - private static void loadRulesetFromFile(string file) + private void loadRulesetFromFile(string file) { var filename = Path.GetFileNameWithoutExtension(file); - if (loaded_assemblies.Values.Any(t => t.Namespace == filename)) + if (loadedAssemblies.Values.Any(t => t.Namespace == filename)) return; try @@ -158,19 +153,35 @@ namespace osu.Game.Rulesets } } - private static void addRuleset(Assembly assembly) + private void addRuleset(Assembly assembly) { - if (loaded_assemblies.ContainsKey(assembly)) + if (loadedAssemblies.ContainsKey(assembly)) return; try { - loaded_assemblies[assembly] = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset))); + loadedAssemblies[assembly] = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset))); } catch (Exception e) { Logger.Error(e, $"Failed to add ruleset {assembly}"); } } + + ~RulesetStore() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + AppDomain.CurrentDomain.AssemblyResolve -= resolveRulesetAssembly; + } } } From 96c6aeefe92a864ecd783f253506b899a6a18741 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 15 Oct 2019 16:16:33 +0900 Subject: [PATCH 1770/2815] Remove out-of-date todo --- osu.Game/Rulesets/RulesetStore.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 1df8568ee1..0e6e0b8676 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -11,9 +11,6 @@ using osu.Game.Database; namespace osu.Game.Rulesets { - /// - /// Todo: All of this needs to be moved to a RulesetStore. - /// public class RulesetStore : DatabaseBackedStore, IDisposable { private const string ruleset_library_prefix = "osu.Game.Rulesets"; From 3c714dc01307216b23250329c5bc3bf0560138fe Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 15 Oct 2019 11:20:06 +0300 Subject: [PATCH 1771/2815] APICommentsController -> CommentBundle --- osu.Game/Online/API/Requests/GetCommentsRequest.cs | 2 +- .../Responses/{APICommentsController.cs => CommentBundle.cs} | 2 +- osu.Game/Overlays/Comments/CommentsContainer.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename osu.Game/Online/API/Requests/Responses/{APICommentsController.cs => CommentBundle.cs} (98%) diff --git a/osu.Game/Online/API/Requests/GetCommentsRequest.cs b/osu.Game/Online/API/Requests/GetCommentsRequest.cs index 834a5106a0..7763501860 100644 --- a/osu.Game/Online/API/Requests/GetCommentsRequest.cs +++ b/osu.Game/Online/API/Requests/GetCommentsRequest.cs @@ -8,7 +8,7 @@ using osu.Game.Overlays.Comments; namespace osu.Game.Online.API.Requests { - public class GetCommentsRequest : APIRequest + public class GetCommentsRequest : APIRequest { private readonly long id; private readonly int page; diff --git a/osu.Game/Online/API/Requests/Responses/APICommentsController.cs b/osu.Game/Online/API/Requests/Responses/CommentBundle.cs similarity index 98% rename from osu.Game/Online/API/Requests/Responses/APICommentsController.cs rename to osu.Game/Online/API/Requests/Responses/CommentBundle.cs index ca6062e371..7063581605 100644 --- a/osu.Game/Online/API/Requests/Responses/APICommentsController.cs +++ b/osu.Game/Online/API/Requests/Responses/CommentBundle.cs @@ -7,7 +7,7 @@ using System.Collections.Generic; namespace osu.Game.Online.API.Requests.Responses { - public class APICommentsController + public class CommentBundle { private List comments; diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 49c479f6e5..62f0ce947b 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -139,7 +139,7 @@ namespace osu.Game.Overlays.Comments api.Queue(request); } - private void onSuccess(APICommentsController response, bool initial) + private void onSuccess(CommentBundle response, bool initial) { loadCancellation = new CancellationTokenSource(); From eb5dad08aa6a8560d544358776c8e6ab3e18e2ea Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 15 Oct 2019 11:25:58 +0300 Subject: [PATCH 1772/2815] Remove initial filed --- .../Online/API/Requests/Responses/Comment.cs | 2 +- .../Overlays/Comments/CommentsContainer.cs | 36 ++++++++++--------- osu.Game/Overlays/Comments/DrawableComment.cs | 2 +- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs index 9d011c49c1..68a4c28726 100644 --- a/osu.Game/Online/API/Requests/Responses/Comment.cs +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -78,6 +78,6 @@ namespace osu.Game.Online.API.Requests.Responses return WebUtility.HtmlDecode(Regex.Replace(MessageHtml, @"<(.|\n)*?>", string.Empty)); } - public int GetDeletedChildrenCount => ChildComments.Select(c => c.IsDeleted).Count(c => c); + public int DeletedChildrenCount => ChildComments.Count(c => c.IsDeleted); } } diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 62f0ce947b..824d9822be 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -101,7 +101,7 @@ namespace osu.Game.Overlays.Comments Anchor = Anchor.Centre, Origin = Anchor.Centre, Margin = new MarginPadding(5), - Action = () => getComments(false), + Action = getComments } } } @@ -119,27 +119,31 @@ namespace osu.Game.Overlays.Comments base.LoadComplete(); } - private void onSortChanged(ValueChangedEvent sort) => getComments(); - - private void getComments(bool initial = true) + private void onSortChanged(ValueChangedEvent sort) { - if (initial) - { - currentPage = 1; - loadedTopLevelComments = 0; - deletedChildrenPlaceholder.DeletedCount.Value = 0; - moreButton.IsLoading = true; - content.Clear(); - } + clearComments(); + getComments(); + } + private void getComments() + { request?.Cancel(); loadCancellation?.Cancel(); request = new GetCommentsRequest(type, id, Sort.Value, currentPage++); - request.Success += response => onSuccess(response, initial); + request.Success += response => onSuccess(response); api.Queue(request); } - private void onSuccess(CommentBundle response, bool initial) + private void clearComments() + { + currentPage = 1; + loadedTopLevelComments = 0; + deletedChildrenPlaceholder.DeletedCount.Value = 0; + moreButton.IsLoading = true; + content.Clear(); + } + + private void onSuccess(CommentBundle response) { loadCancellation = new CancellationTokenSource(); @@ -163,9 +167,7 @@ namespace osu.Game.Overlays.Comments { content.Add(loaded); - int deletedComments = response.Comments.Select(c => c.IsDeleted && c.IsTopLevel).Count(c => c); - - deletedChildrenPlaceholder.DeletedCount.Value = initial ? deletedComments : deletedChildrenPlaceholder.DeletedCount.Value + deletedComments; + deletedChildrenPlaceholder.DeletedCount.Value += response.Comments.Count(c => c.IsDeleted && c.IsTopLevel); if (response.HasMore) { diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 13c67b9f5b..b22bd6d426 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -180,7 +180,7 @@ namespace osu.Game.Overlays.Comments } }; - deletedChildrenPlaceholder.DeletedCount.Value = comment.GetDeletedChildrenCount; + deletedChildrenPlaceholder.DeletedCount.Value = comment.DeletedChildrenCount; if (comment.UserId.HasValue) username.AddUserLink(comment.User); From b2885e7b139b8def41fc532181d2d19f4a871fc3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 15 Oct 2019 11:26:58 +0300 Subject: [PATCH 1773/2815] Move load() under the ctor --- osu.Game/Overlays/Comments/CommentsContainer.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 824d9822be..bd1a8562d3 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -113,6 +113,12 @@ namespace osu.Game.Overlays.Comments }); } + [BackgroundDependencyLoader] + private void load() + { + background.Colour = colours.Gray2; + } + protected override void LoadComplete() { Sort.BindValueChanged(onSortChanged, true); @@ -184,12 +190,6 @@ namespace osu.Game.Overlays.Comments }, loadCancellation.Token); } - [BackgroundDependencyLoader] - private void load() - { - background.Colour = colours.Gray2; - } - protected override void Dispose(bool isDisposing) { request?.Cancel(); From 213f00556d30b3f4bededf2c57832bb6e5182802 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 15 Oct 2019 11:30:50 +0300 Subject: [PATCH 1774/2815] Remove onShowDeletedChanged function --- osu.Game/Overlays/Comments/DrawableComment.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index b22bd6d426..59d3d08122 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -244,17 +244,15 @@ namespace osu.Game.Overlays.Comments protected override void LoadComplete() { - ShowDeleted.BindValueChanged(onShowDeletedChanged, true); + ShowDeleted.BindValueChanged(show => + { + if (comment.IsDeleted) + this.FadeTo(show.NewValue ? 1 : 0); + }, true); childrenExpanded.BindValueChanged(expanded => childCommentsVisibilityContainer.FadeTo(expanded.NewValue ? 1 : 0), true); base.LoadComplete(); } - private void onShowDeletedChanged(ValueChangedEvent show) - { - if (comment.IsDeleted) - this.FadeTo(show.NewValue ? 1 : 0); - } - private class ChevronButton : ShowChildrenButton { private readonly SpriteIcon icon; From 96e31b9cca2b62147354b6608c11857280a7cade Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 15 Oct 2019 12:07:01 +0300 Subject: [PATCH 1775/2815] Add support for deleted comments with message --- .../Online/API/Requests/Responses/Comment.cs | 8 ++----- osu.Game/Overlays/Comments/DrawableComment.cs | 23 +++++++++++++------ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs index 68a4c28726..29abaa74e5 100644 --- a/osu.Game/Online/API/Requests/Responses/Comment.cs +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -70,13 +70,9 @@ namespace osu.Game.Online.API.Requests.Responses public bool IsDeleted => DeletedAt.HasValue; - public string GetMessage() - { - if (IsDeleted) - return @"deleted"; + public bool HasMessage => !string.IsNullOrEmpty(MessageHtml); - return WebUtility.HtmlDecode(Regex.Replace(MessageHtml, @"<(.|\n)*?>", string.Empty)); - } + public string GetMessage => HasMessage ? WebUtility.HtmlDecode(Regex.Replace(MessageHtml, @"<(.|\n)*?>", string.Empty)) : string.Empty; public int DeletedChildrenCount => ChildComments.Count(c => c.IsDeleted); } diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 59d3d08122..89abda92cf 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -198,12 +198,13 @@ namespace osu.Game.Overlays.Comments }); } - if (!comment.IsDeleted) + if (comment.HasMessage) { - var formattedSource = MessageFormatter.FormatText(comment.GetMessage()); + var formattedSource = MessageFormatter.FormatText(comment.GetMessage); message.AddLinks(formattedSource.Text, formattedSource.Links); } - else + + if (comment.IsDeleted) { content.FadeColour(OsuColour.Gray(0.5f)); votePill.Hide(); @@ -297,13 +298,13 @@ namespace osu.Game.Overlays.Comments private class ParentUsername : FillFlowContainer, IHasTooltip { - public string TooltipText => comment.ParentComment?.GetMessage() ?? ""; + public string TooltipText => getParentMessage(); - private readonly Comment comment; + private readonly Comment parentComment; public ParentUsername(Comment comment) { - this.comment = comment; + parentComment = comment.ParentComment; AutoSizeAxes = Axes.Both; Direction = FillDirection.Horizontal; @@ -319,10 +320,18 @@ namespace osu.Game.Overlays.Comments new SpriteText { Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true), - Text = comment.ParentComment?.User?.Username ?? comment.ParentComment?.LegacyName + Text = parentComment?.User?.Username ?? parentComment?.LegacyName } }; } + + private string getParentMessage() + { + if (parentComment == null) + return string.Empty; + + return parentComment.HasMessage ? parentComment.GetMessage : parentComment.IsDeleted ? @"deleted" : string.Empty; + } } private class VotePill : CircularContainer From d321794ef5b0fefbb2330a3f9e35ef42457e0299 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 15 Oct 2019 12:26:16 +0300 Subject: [PATCH 1776/2815] Make loadedTopLevelComments a local filed --- osu.Game/Overlays/Comments/CommentsContainer.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index bd1a8562d3..6ac31b258f 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics; using osu.Game.Online.API.Requests.Responses; using System.Threading; using System.Linq; +using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Overlays.Comments { @@ -32,7 +33,6 @@ namespace osu.Game.Overlays.Comments private GetCommentsRequest request; private CancellationTokenSource loadCancellation; private int currentPage; - private int loadedTopLevelComments; private readonly Box background; private readonly FillFlowContainer content; @@ -143,7 +143,6 @@ namespace osu.Game.Overlays.Comments private void clearComments() { currentPage = 1; - loadedTopLevelComments = 0; deletedChildrenPlaceholder.DeletedCount.Value = 0; moreButton.IsLoading = true; content.Clear(); @@ -177,11 +176,9 @@ namespace osu.Game.Overlays.Comments if (response.HasMore) { - response.Comments.ForEach(comment => - { - if (comment.IsTopLevel) - loadedTopLevelComments++; - }); + int loadedTopLevelComments = 0; + content.Children.OfType().ForEach(p => loadedTopLevelComments += p.Children.OfType().Count()); + moreButton.Current.Value = response.TopLevelCount - loadedTopLevelComments; moreButton.IsLoading = false; } From 7ba15df0c07f193016e3e4f0a71c00ccd7b99c15 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 15 Oct 2019 12:27:32 +0300 Subject: [PATCH 1777/2815] Convert to method group --- osu.Game/Overlays/Comments/CommentsContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 6ac31b258f..abc1b7233d 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -136,7 +136,7 @@ namespace osu.Game.Overlays.Comments request?.Cancel(); loadCancellation?.Cancel(); request = new GetCommentsRequest(type, id, Sort.Value, currentPage++); - request.Success += response => onSuccess(response); + request.Success += onSuccess; api.Queue(request); } From 2543de22bd4f2bf5c8fe95f46944419312cb9361 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 15 Oct 2019 12:47:35 +0300 Subject: [PATCH 1778/2815] Simplify DeletedChildrenPlaceholder behavior --- .../Comments/DeletedChildrenPlaceholder.cs | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/Comments/DeletedChildrenPlaceholder.cs b/osu.Game/Overlays/Comments/DeletedChildrenPlaceholder.cs index 21cf01f993..e849691597 100644 --- a/osu.Game/Overlays/Comments/DeletedChildrenPlaceholder.cs +++ b/osu.Game/Overlays/Comments/DeletedChildrenPlaceholder.cs @@ -16,8 +16,6 @@ namespace osu.Game.Overlays.Comments public readonly BindableBool ShowDeleted = new BindableBool(); public readonly BindableInt DeletedCount = new BindableInt(); - private bool canBeShown; - private readonly SpriteText countText; public DeletedChildrenPlaceholder() @@ -42,29 +40,22 @@ namespace osu.Game.Overlays.Comments protected override void LoadComplete() { - DeletedCount.BindValueChanged(onCountChanged, true); - ShowDeleted.BindValueChanged(onShowDeletedChanged, true); + DeletedCount.BindValueChanged(_ => updateDisplay(), true); + ShowDeleted.BindValueChanged(_ => updateDisplay(), true); base.LoadComplete(); } - private void onShowDeletedChanged(ValueChangedEvent showDeleted) + private void updateDisplay() { - if (canBeShown) - this.FadeTo(showDeleted.NewValue ? 0 : 1); - } - - private void onCountChanged(ValueChangedEvent count) - { - canBeShown = count.NewValue != 0; - - if (!canBeShown) + if (DeletedCount.Value != 0) + { + countText.Text = @"deleted comment".ToQuantity(DeletedCount.Value); + this.FadeTo(ShowDeleted.Value ? 0 : 1); + } + else { Hide(); - return; } - - countText.Text = @"deleted comment".ToQuantity(count.NewValue); - Show(); } } } From 8c671d7fde49cd038378105e029be1fa37113c79 Mon Sep 17 00:00:00 2001 From: HoLLy Date: Tue, 15 Oct 2019 20:12:08 +0200 Subject: [PATCH 1779/2815] Rename cursorScale and calculatedCursorScale --- .../UI/Cursor/OsuCursorContainer.cs | 20 +++++++++---------- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index 8ea11d0a4b..52e2e493d5 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -29,9 +29,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private readonly Drawable cursorTrail; - public IBindable CalculatedCursorScale => calculatedCursorScale; - private Bindable calculatedCursorScale; + public IBindable CursorScale => cursorScale; private Bindable cursorScale; + private Bindable userCursorScale; private Bindable autoCursorScale; private readonly IBindable beatmap = new Bindable(); @@ -52,21 +52,21 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor this.beatmap.BindTo(beatmap); this.beatmap.ValueChanged += _ => calculateScale(); - cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize); - cursorScale.ValueChanged += _ => calculateScale(); + userCursorScale = config.GetBindable(OsuSetting.GameplayCursorSize); + userCursorScale.ValueChanged += _ => calculateScale(); autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize); autoCursorScale.ValueChanged += _ => calculateScale(); - calculatedCursorScale = new Bindable(); - calculatedCursorScale.ValueChanged += e => ActiveCursor.Scale = cursorTrail.Scale = new Vector2(e.NewValue); + cursorScale = new Bindable(); + cursorScale.ValueChanged += e => ActiveCursor.Scale = cursorTrail.Scale = new Vector2(e.NewValue); calculateScale(); } private void calculateScale() { - float scale = cursorScale.Value; + float scale = userCursorScale.Value; if (autoCursorScale.Value && beatmap.Value != null) { @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor scale *= 1f - 0.7f * (1f + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY; } - calculatedCursorScale.Value = scale; + cursorScale.Value = scale; } protected override void LoadComplete() @@ -130,13 +130,13 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor protected override void PopIn() { fadeContainer.FadeTo(1, 300, Easing.OutQuint); - ActiveCursor.ScaleTo(calculatedCursorScale.Value, 400, Easing.OutQuint); + ActiveCursor.ScaleTo(cursorScale.Value, 400, Easing.OutQuint); } protected override void PopOut() { fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint); - ActiveCursor.ScaleTo(calculatedCursorScale.Value * 0.8f, 450, Easing.OutQuint); + ActiveCursor.ScaleTo(cursorScale.Value * 0.8f, 450, Easing.OutQuint); } private class DefaultCursorTrail : CursorTrail diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 1ee2a04a3b..64821ac24f 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.UI { Add(localCursorContainer = new OsuCursorContainer()); - localCursorContainer.CalculatedCursorScale.BindValueChanged(scale => + localCursorContainer.CursorScale.BindValueChanged(scale => { clickToResumeCursor.CursorScale = scale.NewValue; clickToResumeCursor.Scale = new Vector2(scale.NewValue); From 60133ba0c3fa62ef7f7cc6f8f14ea47f886b2015 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 15 Oct 2019 23:33:50 +0300 Subject: [PATCH 1780/2815] Propagate BeatmapSetInfo to tab items with bindable --- .../BeatmapSet/BeatmapRulesetSelector.cs | 22 +++++++--------- .../BeatmapSet/BeatmapRulesetTabItem.cs | 26 +++++++++---------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs index bfb188a83b..a0bedc848e 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.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.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; @@ -13,23 +14,17 @@ namespace osu.Game.Overlays.BeatmapSet { public class BeatmapRulesetSelector : RulesetSelector { - private BeatmapSetInfo beatmapSet; + private readonly Bindable beatmapSet = new Bindable(); public BeatmapSetInfo BeatmapSet { - get => beatmapSet; + get => beatmapSet.Value; set { - if (value == beatmapSet) - return; + // propagate value to tab items first to enable only available rulesets. + beatmapSet.Value = value; - beatmapSet = value; - - foreach (var tab in TabContainer.TabItems.OfType()) - tab.SetBeatmaps(beatmapSet?.Beatmaps.FindAll(b => b.Ruleset.Equals(tab.Value))); - - var firstRuleset = beatmapSet?.Beatmaps.OrderBy(b => b.Ruleset.ID).FirstOrDefault()?.Ruleset; - SelectTab(TabContainer.TabItems.FirstOrDefault(t => t.Value.Equals(firstRuleset))); + SelectTab(TabContainer.TabItems.FirstOrDefault(t => t.Enabled.Value)); } } @@ -38,7 +33,10 @@ namespace osu.Game.Overlays.BeatmapSet AutoSizeAxes = Axes.Both; } - protected override TabItem CreateTabItem(RulesetInfo value) => new BeatmapRulesetTabItem(value); + protected override TabItem CreateTabItem(RulesetInfo value) => new BeatmapRulesetTabItem(value) + { + BeatmapSet = { BindTarget = beatmapSet } + }; protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer { diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs index 19c9af13d5..5227bec92a 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs @@ -2,6 +2,7 @@ // 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; @@ -15,8 +16,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osuTK; using osuTK.Graphics; -using System.Collections.Generic; -using System.Diagnostics; +using System.Linq; namespace osu.Game.Overlays.BeatmapSet { @@ -25,6 +25,8 @@ namespace osu.Game.Overlays.BeatmapSet private readonly OsuSpriteText name, count; private readonly Box bar; + public readonly Bindable BeatmapSet = new Bindable(); + public override bool PropagatePositionalInputSubTree => Enabled.Value && !Active.Value && base.PropagatePositionalInputSubTree; public BeatmapRulesetTabItem(RulesetInfo value) @@ -90,6 +92,15 @@ namespace osu.Game.Overlays.BeatmapSet new HoverClickSounds(), }; + BeatmapSet.BindValueChanged(setInfo => + { + var beatmapsCount = setInfo.NewValue?.Beatmaps.Count(b => b.Ruleset.Equals(Value)) ?? 0; + count.Text = beatmapsCount.ToString(); + + count.Alpha = beatmapsCount > 0 ? 1f : 0f; + Enabled.Value = beatmapsCount > 0; + }, true); + Enabled.BindValueChanged(v => nameContainer.Alpha = v.NewValue ? 1f : 0.5f, true); } @@ -106,17 +117,6 @@ namespace osu.Game.Overlays.BeatmapSet updateState(); } - public void SetBeatmaps(List beatmaps) - { - Trace.Assert(beatmaps?.TrueForAll(b => b.Ruleset.Equals(Value)) ?? true, "A beatmap has a ruleset not of this tab value"); - - count.Text = beatmaps?.Count.ToString(); - - var hasBeatmaps = (beatmaps?.Count ?? 0) > 0; - count.Alpha = hasBeatmaps ? 1f : 0f; - Enabled.Value = hasBeatmaps; - } - private void updateState() { var isHoveredOrActive = IsHovered || Active.Value; From 14c72f85fa56387b3932908c616c0de68e3aee36 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 15 Oct 2019 23:40:48 +0300 Subject: [PATCH 1781/2815] Fix incorrect beatmap set info equality check on non-online set info --- osu.Game/Beatmaps/BeatmapSetInfo.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 03bc7c7312..90346a8c8b 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -63,6 +63,12 @@ namespace osu.Game.Beatmaps public bool Protected { get; set; } - public bool Equals(BeatmapSetInfo other) => OnlineBeatmapSetID == other?.OnlineBeatmapSetID; + public bool Equals(BeatmapSetInfo other) + { + if (!OnlineBeatmapSetID.HasValue || !(other?.OnlineBeatmapSetID.HasValue ?? false)) + return ReferenceEquals(this, other); + + return OnlineBeatmapSetID == other.OnlineBeatmapSetID; + } } } From 13e11992296cbacadebe87ae239f84f201739f41 Mon Sep 17 00:00:00 2001 From: HoLLy Date: Tue, 15 Oct 2019 22:44:04 +0200 Subject: [PATCH 1782/2815] Move click to resume cursor scaling responsibility to container --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 25 ++++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 64821ac24f..908ad1ce49 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -17,6 +17,7 @@ namespace osu.Game.Rulesets.Osu.UI { public class OsuResumeOverlay : ResumeOverlay { + private Container cursorScaleContainer; private OsuClickToResumeCursor clickToResumeCursor; private OsuCursorContainer localCursorContainer; @@ -28,23 +29,24 @@ namespace osu.Game.Rulesets.Osu.UI [BackgroundDependencyLoader] private void load() { - Add(clickToResumeCursor = new OsuClickToResumeCursor { ResumeRequested = Resume }); + Add(cursorScaleContainer = new Container + { + RelativePositionAxes = Axes.Both, + Child = clickToResumeCursor = new OsuClickToResumeCursor { ResumeRequested = Resume } + }); } public override void Show() { base.Show(); - clickToResumeCursor.ShowAt(GameplayCursor.ActiveCursor.Position); + cursorScaleContainer.MoveTo(GameplayCursor.ActiveCursor.Position); + clickToResumeCursor.Appear(); if (localCursorContainer == null) { Add(localCursorContainer = new OsuCursorContainer()); - localCursorContainer.CursorScale.BindValueChanged(scale => - { - clickToResumeCursor.CursorScale = scale.NewValue; - clickToResumeCursor.Scale = new Vector2(scale.NewValue); - }, true); + localCursorContainer.CursorScale.BindValueChanged(scale => cursorScaleContainer.Scale = new Vector2(scale.NewValue), true); } } @@ -64,8 +66,6 @@ namespace osu.Game.Rulesets.Osu.UI public Action ResumeRequested; - public float CursorScale; - public OsuClickToResumeCursor() { RelativePositionAxes = Axes.Both; @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.UI case OsuAction.RightButton: if (!IsHovered) return false; - this.ScaleTo(2 * CursorScale, TRANSITION_TIME, Easing.OutQuint); + this.ScaleTo(2, TRANSITION_TIME, Easing.OutQuint); ResumeRequested?.Invoke(); return true; @@ -102,11 +102,10 @@ namespace osu.Game.Rulesets.Osu.UI public bool OnReleased(OsuAction action) => false; - public void ShowAt(Vector2 activeCursorPosition) => Schedule(() => + public void Appear() => Schedule(() => { updateColour(); - this.MoveTo(activeCursorPosition); - this.ScaleTo(4 * CursorScale).Then().ScaleTo(CursorScale, 1000, Easing.OutQuint); + this.ScaleTo(4).Then().ScaleTo(1, 1000, Easing.OutQuint); }); private void updateColour() From 649951198e800e23b46292f389f1271d899086f6 Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 15 Oct 2019 14:47:48 -0700 Subject: [PATCH 1783/2815] Make most textbox carets movable --- osu.Game/Graphics/UserInterface/SearchTextBox.cs | 2 -- osu.Game/Screens/Select/FilterControl.cs | 9 +++++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/SearchTextBox.cs b/osu.Game/Graphics/UserInterface/SearchTextBox.cs index c3efe2ed45..e2b0e1b425 100644 --- a/osu.Game/Graphics/UserInterface/SearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/SearchTextBox.cs @@ -14,8 +14,6 @@ namespace osu.Game.Graphics.UserInterface { protected virtual bool AllowCommit => false; - public override bool HandleLeftRightArrows => false; - public SearchTextBox() { Height = 35; diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 8755c3fda6..2c878f8d90 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -49,7 +49,7 @@ namespace osu.Game.Screens.Select return criteria; } - private readonly SearchTextBox searchTextBox; + private readonly SongSelectTextBox searchTextBox; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || groupTabs.ReceivePositionalInputAt(screenSpacePos) || sortTabs.ReceivePositionalInputAt(screenSpacePos); @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Select Origin = Anchor.TopRight, Children = new Drawable[] { - searchTextBox = new SearchTextBox { RelativeSizeAxes = Axes.X }, + searchTextBox = new SongSelectTextBox { RelativeSizeAxes = Axes.X }, new Box { RelativeSizeAxes = Axes.X, @@ -170,5 +170,10 @@ namespace osu.Game.Screens.Select } private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria()); + + private class SongSelectTextBox : SearchTextBox + { + public override bool HandleLeftRightArrows => false; + } } } From 350d139cbf22fef749454d867b3b716f8e947bf4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Oct 2019 15:54:00 +0900 Subject: [PATCH 1784/2815] Make chevron icon colour protected --- osu.Game/Graphics/UserInterface/ShowMoreButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs index 854b7abce1..5296b9dd7f 100644 --- a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs +++ b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs @@ -20,7 +20,7 @@ namespace osu.Game.Graphics.UserInterface private Color4 chevronIconColour; - public Color4 ChevronIconColour + protected Color4 ChevronIconColour { get => chevronIconColour; set => chevronIconColour = leftChevron.Colour = rightChevron.Colour = value; From 5ac5e34f8585fc0910ef4d6cf81307ca90e51b47 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Oct 2019 19:32:45 +0900 Subject: [PATCH 1785/2815] Use a cleaner distance function --- .../Edit/Compose/Components/CircularBeatSnapGrid.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs index 8492771808..bf363aeb37 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs @@ -18,13 +18,9 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override void CreateContent(Vector2 centrePosition) { - float maxDistance = Math.Max( - Vector2.Distance(centrePosition, Vector2.Zero), - Math.Max( - Vector2.Distance(centrePosition, new Vector2(DrawWidth, 0)), - Math.Max( - Vector2.Distance(centrePosition, new Vector2(0, DrawHeight)), - Vector2.Distance(centrePosition, DrawSize)))); + float dx = Math.Max(centrePosition.X, DrawWidth - centrePosition.X); + float dy = Math.Max(centrePosition.Y, DrawHeight - centrePosition.Y); + float maxDistance = new Vector2(dx, dy).Length; int requiredCircles = (int)(maxDistance / DistanceSpacing); From 2d4b7dc361ca061fd74cef1654ab86875c58d518 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Oct 2019 19:33:18 +0900 Subject: [PATCH 1786/2815] Remove redundant code --- .../Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs index bf363aeb37..09679f0553 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs @@ -48,11 +48,7 @@ namespace osu.Game.Screens.Edit.Compose.Components float radius = DistanceSpacing; int radialCount = Math.Max(1, (int)Math.Round(distance / radius)); - if (radialCount <= 0) - return position; - Vector2 normalisedDirection = direction * new Vector2(1f / distance); - return CentrePosition + normalisedDirection * radialCount * radius; } } From b6b8098b989383b0402e2efe48476048f9b05f3f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Oct 2019 19:44:53 +0900 Subject: [PATCH 1787/2815] Add an arbitrary offset to prevent div-by-0 --- .../Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs index 09679f0553..5e378f8393 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs @@ -43,6 +43,10 @@ namespace osu.Game.Screens.Edit.Compose.Components public override Vector2 GetSnapPosition(Vector2 position) { Vector2 direction = position - CentrePosition; + + if (direction == Vector2.Zero) + direction = new Vector2(0.001f, 0.001f); + float distance = direction.Length; float radius = DistanceSpacing; From 2dac3a6efe89934faa8cc354e5581a83bd182bd3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 16 Oct 2019 13:58:29 +0300 Subject: [PATCH 1788/2815] Handle hitting the maximum allowed number of favourited beatmaps --- .../BeatmapSet/Buttons/FavouriteButton.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index fcea20ef11..e2bc1ee008 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs @@ -11,6 +11,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Notifications; using osuTK; namespace osu.Game.Overlays.BeatmapSet.Buttons @@ -26,8 +27,8 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons public string TooltipText => (favourited.Value ? "Unfavourite" : "Favourite") + " this beatmapset"; - [BackgroundDependencyLoader] - private void load(IAPIProvider api) + [BackgroundDependencyLoader(true)] + private void load(IAPIProvider api, NotificationOverlay notifications) { SpriteIcon icon; AddRange(new Drawable[] @@ -68,6 +69,18 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons request?.Cancel(); request = new PostBeatmapFavouriteRequest(BeatmapSet.Value?.OnlineBeatmapSetID ?? 0, favourited.Value ? BeatmapFavouriteAction.UnFavourite : BeatmapFavouriteAction.Favourite); request.Success += () => favourited.Value = !favourited.Value; + request.Failure += exception => + { + if (exception.Message == "UnprocessableEntity") + { + notifications.Post(new SimpleNotification + { + Text = @"You have too many favourited beatmaps! Please unfavourite some before trying again.", + Icon = FontAwesome.Solid.Times, + }); + loading.Hide(); + } + }; api.Queue(request); }; } From 79b2c7b480f34c2d122f69aed9a69ae413bf7108 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Oct 2019 20:04:15 +0900 Subject: [PATCH 1789/2815] Make BeginPlacement() set the hitobject start time --- .../Edit/Blueprints/ManiaPlacementBlueprint.cs | 4 +--- .../Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs | 1 - .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 -- .../Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs | 2 -- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 4 +++- 5 files changed, 4 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index 3142f22fcd..b28d8bb0e6 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -49,10 +49,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints if (Column == null) return base.OnMouseDown(e); - HitObject.StartTime = TimeAt(e.ScreenSpaceMousePosition); HitObject.Column = Column.Index; - - BeginPlacement(); + BeginPlacement(TimeAt(e.ScreenSpaceMousePosition)); return true; } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs index 6c08990ad6..bb47c7e464 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs @@ -30,7 +30,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles protected override bool OnClick(ClickEvent e) { - HitObject.StartTime = EditorClock.CurrentTime; EndPlacement(); return true; } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index fc074ef8af..2fb18bf8ba 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -104,8 +104,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void beginCurve() { BeginPlacement(); - - HitObject.StartTime = EditorClock.CurrentTime; setState(PlacementState.Body); } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs index 8319f49cbc..5525b8936e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs @@ -41,8 +41,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners } else { - HitObject.StartTime = EditorClock.CurrentTime; - isPlacingEnd = true; piece.FadeTo(1f, 150, Easing.OutQuint); diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 290fd8d27d..07283d2245 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -91,8 +91,10 @@ namespace osu.Game.Rulesets.Edit /// /// Signals that the placement of has started. /// - protected void BeginPlacement() + /// The start time of at the placement point. If null, the current clock time is used. + protected void BeginPlacement(double? startTime = null) { + HitObject.StartTime = startTime ?? EditorClock.CurrentTime; placementHandler.BeginPlacement(HitObject); PlacementBegun = true; } From 4ac2e1c58ee1aab9bbac8cb62dc717a8e03f5d3a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Oct 2019 21:41:18 +0900 Subject: [PATCH 1790/2815] Move load() to below ctor() --- .../Objects/Drawables/DrawableSlider.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 9e8ad9851c..09157d4fdc 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -94,13 +94,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - protected override void UpdateInitialTransforms() - { - base.UpdateInitialTransforms(); - - Body.FadeInFromZero(HitObject.TimeFadeIn); - } - [BackgroundDependencyLoader] private void load() { @@ -129,6 +122,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }, true); } + protected override void UpdateInitialTransforms() + { + base.UpdateInitialTransforms(); + + Body.FadeInFromZero(HitObject.TimeFadeIn); + } + public readonly Bindable Tracking = new Bindable(); protected override void Update() From 8d7453c251b68243ed6a082f815a1d2133e71aa4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Oct 2019 22:10:50 +0900 Subject: [PATCH 1791/2815] Rework construction of nested hitobjects --- .../Objects/Drawables/DrawableSlider.cs | 124 +++++++++++------- .../Objects/Drawables/DrawableHitObject.cs | 44 +++++-- 2 files changed, 111 insertions(+), 57 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 09157d4fdc..21411259ae 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -5,7 +5,6 @@ using osuTK; using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; -using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -21,15 +20,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSlider : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach { - private readonly Slider slider; - private readonly List components = new List(); - - public readonly DrawableHitCircle HeadCircle; - public readonly DrawableSliderTail TailCircle; + public DrawableSliderHead HeadCircle { get; private set; } + public DrawableSliderTail TailCircle { get; private set; } public readonly SnakingSliderBody Body; public readonly SliderBall Ball; + private readonly Container headContainer; + private readonly Container tailContainer; + private readonly Container tickContainer; + private readonly Container repeatContainer; + + private readonly Slider slider; + private readonly IBindable positionBindable = new Bindable(); private readonly IBindable scaleBindable = new Bindable(); private readonly IBindable pathBindable = new Bindable(); @@ -44,14 +47,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Position = s.StackedPosition; - Container ticks; - Container repeatPoints; - InternalChildren = new Drawable[] { Body = new SnakingSliderBody(s), - ticks = new Container { RelativeSizeAxes = Axes.Both }, - repeatPoints = new Container { RelativeSizeAxes = Axes.Both }, + tickContainer = new Container { RelativeSizeAxes = Axes.Both }, + repeatContainer = new Container { RelativeSizeAxes = Axes.Both }, Ball = new SliderBall(s, this) { GetInitialHitAction = () => HeadCircle.HitAction, @@ -60,38 +60,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables AlwaysPresent = true, Alpha = 0 }, - HeadCircle = new DrawableSliderHead(s, s.HeadCircle) - { - OnShake = Shake - }, - TailCircle = new DrawableSliderTail(s, s.TailCircle) + headContainer = new Container { RelativeSizeAxes = Axes.Both }, + tailContainer = new Container { RelativeSizeAxes = Axes.Both }, }; - - components.Add(Body); - components.Add(Ball); - - AddNested(HeadCircle); - - AddNested(TailCircle); - components.Add(TailCircle); - - foreach (var tick in s.NestedHitObjects.OfType()) - { - var drawableTick = new DrawableSliderTick(tick) { Position = tick.Position - s.Position }; - - ticks.Add(drawableTick); - components.Add(drawableTick); - AddNested(drawableTick); - } - - foreach (var repeatPoint in s.NestedHitObjects.OfType()) - { - var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this) { Position = repeatPoint.Position - s.Position }; - - repeatPoints.Add(drawableRepeatPoint); - components.Add(drawableRepeatPoint); - AddNested(drawableRepeatPoint); - } } [BackgroundDependencyLoader] @@ -122,6 +93,60 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }, true); } + protected override void AddNested(DrawableHitObject h) + { + base.AddNested(h); + + switch (h) + { + case DrawableSliderHead head: + headContainer.Child = HeadCircle = head; + break; + + case DrawableSliderTail tail: + tailContainer.Child = TailCircle = tail; + break; + + case DrawableSliderTick tick: + tickContainer.Add(tick); + break; + + case DrawableRepeatPoint repeat: + repeatContainer.Add(repeat); + break; + } + } + + protected override void ClearNested() + { + base.ClearNested(); + + headContainer.Clear(); + tailContainer.Clear(); + repeatContainer.Clear(); + tickContainer.Clear(); + } + + protected override DrawableHitObject CreateNested(HitObject hitObject) + { + switch (hitObject) + { + case SliderTailCircle tail: + return new DrawableSliderTail(slider, tail); + + case HitCircle head: + return new DrawableSliderHead(slider, head) { OnShake = Shake }; + + case SliderTick tick: + return new DrawableSliderTick(tick) { Position = tick.Position - slider.Position }; + + case RepeatPoint repeat: + return new DrawableRepeatPoint(repeat, this) { Position = repeat.Position - slider.Position }; + } + + return base.CreateNested(hitObject); + } + protected override void UpdateInitialTransforms() { base.UpdateInitialTransforms(); @@ -139,9 +164,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); - foreach (var c in components.OfType()) c.UpdateProgress(completionProgress); - foreach (var c in components.OfType()) c.UpdateSnakingPosition(slider.Path.PositionAt(Body.SnakedStart ?? 0), slider.Path.PositionAt(Body.SnakedEnd ?? 0)); - foreach (var t in components.OfType()) t.Tracking = Ball.Tracking; + Ball.UpdateProgress(completionProgress); + Body.UpdateProgress(completionProgress); + + foreach (DrawableHitObject hitObject in NestedHitObjects) + { + if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(slider.Path.PositionAt(Body.SnakedStart ?? 0), slider.Path.PositionAt(Body.SnakedEnd ?? 0)); + if (hitObject is IRequireTracking t) t.Tracking = Ball.Tracking; + } Size = Body.Size; OriginPosition = Body.PathOffset; @@ -187,7 +217,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ApplyResult(r => { - var judgementsCount = NestedHitObjects.Count(); + var judgementsCount = NestedHitObjects.Count; var judgementsHit = NestedHitObjects.Count(h => h.IsHit); var hitFraction = (double)judgementsHit / judgementsCount; @@ -228,7 +258,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - public Drawable ProxiedLayer => HeadCircle.ApproachCircle; + public Drawable ProxiedLayer => new Container(); // Todo: public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Body.ReceivePositionalInputAt(screenSpacePos); } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 7f3bfd3b5c..6bc2ccb889 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Objects.Drawables protected virtual IEnumerable GetSamples() => HitObject.Samples; private readonly Lazy> nestedHitObjects = new Lazy>(); - public IEnumerable NestedHitObjects => nestedHitObjects.IsValueCreated ? nestedHitObjects.Value : Enumerable.Empty(); + public IReadOnlyList NestedHitObjects => nestedHitObjects.IsValueCreated ? nestedHitObjects.Value : (IReadOnlyList)Array.Empty(); /// /// Invoked when a has been applied by this or a nested . @@ -125,6 +125,8 @@ namespace osu.Game.Rulesets.Objects.Drawables { base.LoadComplete(); + Apply(HitObject); + if (HitObject is IHasComboInformation combo) { comboIndexBindable = combo.ComboIndexBindable.GetBoundCopy(); @@ -134,6 +136,37 @@ namespace osu.Game.Rulesets.Objects.Drawables updateState(ArmedState.Idle, true); } + protected void Apply(HitObject hitObject) + { + if (nestedHitObjects.IsValueCreated) + { + nestedHitObjects.Value.Clear(); + ClearNested(); + } + + foreach (var h in hitObject.NestedHitObjects) + { + var drawableNested = CreateNested(h) ?? throw new InvalidOperationException($"{nameof(CreateNested)} returned null for {h.GetType().ReadableName()}."); + + drawableNested.OnNewResult += (d, r) => OnNewResult?.Invoke(d, r); + drawableNested.OnRevertResult += (d, r) => OnRevertResult?.Invoke(d, r); + drawableNested.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j); + + nestedHitObjects.Value.Add(drawableNested); + AddNested(drawableNested); + } + } + + protected virtual void AddNested(DrawableHitObject h) + { + } + + protected virtual void ClearNested() + { + } + + protected virtual DrawableHitObject CreateNested(HitObject hitObject) => null; + #region State / Transform Management /// @@ -356,15 +389,6 @@ namespace osu.Game.Rulesets.Objects.Drawables UpdateResult(false); } - protected virtual void AddNested(DrawableHitObject h) - { - h.OnNewResult += (d, r) => OnNewResult?.Invoke(d, r); - h.OnRevertResult += (d, r) => OnRevertResult?.Invoke(d, r); - h.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j); - - nestedHitObjects.Value.Add(h); - } - /// /// Applies the of this , notifying responders such as /// the of the . From 405ab07800b204b9cf3b882dfc978378eee1f72c Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 17 Oct 2019 01:18:29 +0300 Subject: [PATCH 1792/2815] Check equality by ID -> OnlineBeatmapSetID -> Hash -> ReferenceEquals --- osu.Game/Beatmaps/BeatmapSetInfo.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 90346a8c8b..a8b83dca38 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -65,10 +65,19 @@ namespace osu.Game.Beatmaps public bool Equals(BeatmapSetInfo other) { - if (!OnlineBeatmapSetID.HasValue || !(other?.OnlineBeatmapSetID.HasValue ?? false)) - return ReferenceEquals(this, other); + if (other == null) + return false; - return OnlineBeatmapSetID == other.OnlineBeatmapSetID; + if (ID != 0 && other.ID != 0) + return ID == other.ID; + + if (OnlineBeatmapSetID.HasValue && other.OnlineBeatmapSetID.HasValue) + return OnlineBeatmapSetID == other.OnlineBeatmapSetID; + + if (!string.IsNullOrEmpty(Hash) && !string.IsNullOrEmpty(other.Hash)) + return Hash == other.Hash; + + return ReferenceEquals(this, other); } } } From 40fc655b50244d0b0d588abb1b97f3ae9cbde831 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 17 Oct 2019 01:19:50 +0300 Subject: [PATCH 1793/2815] Add equality check test to ensure correct values --- .../NonVisual/BeatmapSetInfoEqualityTest.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs diff --git a/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs new file mode 100644 index 0000000000..42a3b4cf43 --- /dev/null +++ b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs @@ -0,0 +1,48 @@ +// 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.Beatmaps; + +namespace osu.Game.Tests.NonVisual +{ + [TestFixture] + public class BeatmapSetInfoEqualityTest + { + [Test] + public void TestOnlineWithOnline() + { + var ourInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 123 }; + var otherInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 123 }; + + Assert.AreEqual(ourInfo, otherInfo); + } + + [Test] + public void TestDatabasedWithDatabased() + { + var ourInfo = new BeatmapSetInfo { ID = 123 }; + var otherInfo = new BeatmapSetInfo { ID = 123 }; + + Assert.AreEqual(ourInfo, otherInfo); + } + + [Test] + public void TestDatabasedWithOnline() + { + var ourInfo = new BeatmapSetInfo { ID = 123, OnlineBeatmapSetID = 12 }; + var otherInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 12 }; + + Assert.AreEqual(ourInfo, otherInfo); + } + + [Test] + public void TestCheckNullID() + { + var ourInfo = new BeatmapSetInfo { Status = BeatmapSetOnlineStatus.Loved }; + var otherInfo = new BeatmapSetInfo { Status = BeatmapSetOnlineStatus.Approved }; + + Assert.AreNotEqual(ourInfo, otherInfo); + } + } +} From 9b9138253c13ba60fbb16f1735b1749722e7324a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 17 Oct 2019 11:27:23 +0900 Subject: [PATCH 1794/2815] Remove finalizer --- osu.Game/Rulesets/RulesetStore.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 0e6e0b8676..23988ff0ff 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -165,11 +165,6 @@ namespace osu.Game.Rulesets } } - ~RulesetStore() - { - Dispose(false); - } - public void Dispose() { Dispose(true); From d49ef6a36bac0a60364f42a3ff7daab2c72e0fc0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 17 Oct 2019 11:57:00 +0900 Subject: [PATCH 1795/2815] Make taiko use the new nested hitobject structure --- .../Objects/Drawables/DrawableDrumRoll.cs | 69 ++++++++++++++----- .../Objects/Drawables/DrawableSwell.cs | 56 +++++++++++---- .../Drawables/DrawableTaikoHitObject.cs | 39 ++++++++--- 3 files changed, 124 insertions(+), 40 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 8e16a21199..d98043b1b7 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Taiko.Objects.Drawables @@ -28,31 +29,18 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables /// private int rollingHits; + private readonly Container tickContainer; + + private Color4 colourIdle; + private Color4 colourEngaged; + public DrawableDrumRoll(DrumRoll drumRoll) : base(drumRoll) { RelativeSizeAxes = Axes.Y; - - Container tickContainer; MainPiece.Add(tickContainer = new Container { RelativeSizeAxes = Axes.Both }); - - foreach (var tick in drumRoll.NestedHitObjects.OfType()) - { - var newTick = new DrawableDrumRollTick(tick); - newTick.OnNewResult += onNewTickResult; - - AddNested(newTick); - tickContainer.Add(newTick); - } } - protected override TaikoPiece CreateMainPiece() => new ElongatedCirclePiece(); - - public override bool OnPressed(TaikoAction action) => false; - - private Color4 colourIdle; - private Color4 colourEngaged; - [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -60,8 +48,51 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables colourEngaged = colours.YellowDarker; } - private void onNewTickResult(DrawableHitObject obj, JudgementResult result) + protected override void LoadComplete() { + base.LoadComplete(); + + OnNewResult += onNewResult; + } + + protected override void AddNested(DrawableHitObject h) + { + base.AddNested(h); + + switch (h) + { + case DrawableDrumRollTick tick: + tickContainer.Add(tick); + break; + } + } + + protected override void ClearNested() + { + base.ClearNested(); + tickContainer.Clear(); + } + + protected override DrawableHitObject CreateNested(HitObject hitObject) + { + switch (hitObject) + { + case DrumRollTick tick: + return new DrawableDrumRollTick(tick); + } + + return base.CreateNested(hitObject); + } + + protected override TaikoPiece CreateMainPiece() => new ElongatedCirclePiece(); + + public override bool OnPressed(TaikoAction action) => false; + + private void onNewResult(DrawableHitObject obj, JudgementResult result) + { + if (!(obj is DrawableDrumRollTick)) + return; + if (result.Type > HitResult.Miss) rollingHits++; else diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 07af7fe7e0..164944f00a 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -2,7 +2,6 @@ // 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.Extensions.Color4Extensions; @@ -14,6 +13,7 @@ using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Taiko.Objects.Drawables @@ -30,8 +30,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables /// private const double ring_appear_offset = 100; - private readonly List ticks = new List(); - + private readonly Container ticks; private readonly Container bodyContainer; private readonly CircularContainer targetRing; private readonly CircularContainer expandingRing; @@ -108,16 +107,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } }); + AddInternal(ticks = new Container { RelativeSizeAxes = Axes.Both }); + MainPiece.Add(symbol = new SwellSymbolPiece()); - - foreach (var tick in HitObject.NestedHitObjects.OfType()) - { - var vis = new DrawableSwellTick(tick); - - ticks.Add(vis); - AddInternal(vis); - AddNested(vis); - } } [BackgroundDependencyLoader] @@ -136,11 +128,49 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Width *= Parent.RelativeChildSize.X; } + protected override void AddNested(DrawableHitObject h) + { + base.AddNested(h); + + switch (h) + { + case DrawableSwellTick tick: + ticks.Add(tick); + break; + } + } + + protected override void ClearNested() + { + base.ClearNested(); + ticks.Clear(); + } + + protected override DrawableHitObject CreateNested(HitObject hitObject) + { + switch (hitObject) + { + case SwellTick tick: + return new DrawableSwellTick(tick); + } + + return base.CreateNested(hitObject); + } + protected override void CheckForResult(bool userTriggered, double timeOffset) { if (userTriggered) { - var nextTick = ticks.Find(j => !j.IsHit); + DrawableSwellTick nextTick = null; + + foreach (var t in ticks) + { + if (!t.IsHit) + { + nextTick = t; + break; + } + } nextTick?.TriggerResult(HitResult.Great); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 423f65b2d3..b89cd7c09f 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -11,6 +11,7 @@ using osu.Game.Audio; using System.Collections.Generic; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; +using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { @@ -109,11 +110,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { public override Vector2 OriginPosition => new Vector2(DrawHeight / 2); - protected readonly Vector2 BaseSize; + public new TaikoHitType HitObject; + protected readonly Vector2 BaseSize; protected readonly TaikoPiece MainPiece; - public new TaikoHitType HitObject; + private readonly Container strongHitContainer; protected DrawableTaikoHitObject(TaikoHitType hitObject) : base(hitObject) @@ -129,17 +131,38 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Content.Add(MainPiece = CreateMainPiece()); MainPiece.KiaiMode = HitObject.Kiai; - var strongObject = HitObject.NestedHitObjects.OfType().FirstOrDefault(); + AddInternal(strongHitContainer = new Container()); + } - if (strongObject != null) + protected override void AddNested(DrawableHitObject h) + { + base.AddNested(h); + + switch (h) { - var strongHit = CreateStrongHit(strongObject); - - AddNested(strongHit); - AddInternal(strongHit); + case DrawableStrongNestedHit strong: + strongHitContainer.Add(strong); + break; } } + protected override void ClearNested() + { + base.ClearNested(); + strongHitContainer.Clear(); + } + + protected override DrawableHitObject CreateNested(HitObject hitObject) + { + switch (hitObject) + { + case StrongHitObject strong: + return CreateStrongHit(strong); + } + + return base.CreateNested(hitObject); + } + // Normal and clap samples are handled by the drum protected override IEnumerable GetSamples() => HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP); From 1a0dfcdd4601ec2eff43eebf4ebee20e14570405 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 17 Oct 2019 12:37:09 +0900 Subject: [PATCH 1796/2815] Make catch use the new nested hitobject structure --- .../Objects/Drawable/DrawableBananaShower.cs | 27 +++++++++++++---- .../Objects/Drawable/DrawableJuiceStream.cs | 30 +++++++++++++------ 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs index 42646851d7..57ffd23e85 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs @@ -2,35 +2,50 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Catch.Objects.Drawable { public class DrawableBananaShower : DrawableCatchHitObject { + private readonly Func> createDrawableRepresentation; private readonly Container bananaContainer; public DrawableBananaShower(BananaShower s, Func> createDrawableRepresentation = null) : base(s) { + this.createDrawableRepresentation = createDrawableRepresentation; RelativeSizeAxes = Axes.X; Origin = Anchor.BottomLeft; X = 0; AddInternal(bananaContainer = new Container { RelativeSizeAxes = Axes.Both }); - - foreach (var b in s.NestedHitObjects.Cast()) - AddNested(createDrawableRepresentation?.Invoke(b)); } protected override void AddNested(DrawableHitObject h) { - ((DrawableCatchHitObject)h).CheckPosition = o => CheckPosition?.Invoke(o) ?? false; - bananaContainer.Add(h); base.AddNested(h); + bananaContainer.Add(h); + } + + protected override void ClearNested() + { + base.ClearNested(); + bananaContainer.Clear(); + } + + protected override DrawableHitObject CreateNested(HitObject hitObject) + { + switch (hitObject) + { + case Banana banana: + return createDrawableRepresentation?.Invoke(banana)?.With(o => ((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false); + } + + return base.CreateNested(hitObject); } } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs index 9e5e9f6a04..1bbb7b08a5 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs @@ -2,38 +2,50 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Catch.Objects.Drawable { public class DrawableJuiceStream : DrawableCatchHitObject { + private readonly Func> createDrawableRepresentation; private readonly Container dropletContainer; public DrawableJuiceStream(JuiceStream s, Func> createDrawableRepresentation = null) : base(s) { + this.createDrawableRepresentation = createDrawableRepresentation; RelativeSizeAxes = Axes.Both; Origin = Anchor.BottomLeft; X = 0; AddInternal(dropletContainer = new Container { RelativeSizeAxes = Axes.Both, }); - - foreach (var o in s.NestedHitObjects.Cast()) - AddNested(createDrawableRepresentation?.Invoke(o)); } protected override void AddNested(DrawableHitObject h) { - var catchObject = (DrawableCatchHitObject)h; - - catchObject.CheckPosition = o => CheckPosition?.Invoke(o) ?? false; - - dropletContainer.Add(h); base.AddNested(h); + dropletContainer.Add(h); + } + + protected override void ClearNested() + { + base.ClearNested(); + dropletContainer.Clear(); + } + + protected override DrawableHitObject CreateNested(HitObject hitObject) + { + switch (hitObject) + { + case CatchHitObject catchObject: + return createDrawableRepresentation?.Invoke(catchObject)?.With(o => ((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false); + } + + return base.CreateNested(hitObject); } } } From 8a284bacba34ed8a4a5759e89a237df19fea4087 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 17 Oct 2019 12:37:20 +0900 Subject: [PATCH 1797/2815] Make mania use the new nested hitobject structure --- .../Objects/Drawables/DrawableHoldNote.cs | 106 ++++++++++++------ 1 file changed, 70 insertions(+), 36 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index c5c157608f..78969b7361 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -2,13 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System.Diagnostics; -using System.Linq; using osu.Framework.Bindables; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI.Scrolling; @@ -22,8 +21,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { public override bool DisplayResult => false; - public readonly DrawableNote Head; - public readonly DrawableNote Tail; + private readonly Container headContainer; + private readonly Container tailContainer; + private readonly Container tickContainer; + + public DrawableNote Head { get; private set; } + public DrawableNote Tail { get; private set; } private readonly BodyPiece bodyPiece; @@ -40,50 +43,81 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public DrawableHoldNote(HoldNote hitObject) : base(hitObject) { - Container tickContainer; RelativeSizeAxes = Axes.X; AddRangeInternal(new Drawable[] { - bodyPiece = new BodyPiece - { - RelativeSizeAxes = Axes.X, - }, - tickContainer = new Container - { - RelativeSizeAxes = Axes.Both, - ChildrenEnumerable = HitObject.NestedHitObjects.OfType().Select(tick => new DrawableHoldNoteTick(tick) - { - HoldStartTime = () => holdStartTime - }) - }, - Head = new DrawableHeadNote(this) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre - }, - Tail = new DrawableTailNote(this) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre - } + bodyPiece = new BodyPiece { RelativeSizeAxes = Axes.X }, + tickContainer = new Container { RelativeSizeAxes = Axes.Both }, + headContainer = new Container { RelativeSizeAxes = Axes.Both }, + tailContainer = new Container { RelativeSizeAxes = Axes.Both }, }); - foreach (var tick in tickContainer) - AddNested(tick); - - AddNested(Head); - AddNested(Tail); - AccentColour.BindValueChanged(colour => { bodyPiece.AccentColour = colour.NewValue; - Head.AccentColour.Value = colour.NewValue; - Tail.AccentColour.Value = colour.NewValue; - tickContainer.ForEach(t => t.AccentColour.Value = colour.NewValue); }, true); } + protected override void AddNested(DrawableHitObject h) + { + base.AddNested(h); + + switch (h) + { + case DrawableHeadNote head: + headContainer.Child = head; + break; + + case DrawableTailNote tail: + tailContainer.Child = tail; + break; + + case DrawableHoldNoteTick tick: + tickContainer.Add(tick); + break; + } + } + + protected override void ClearNested() + { + base.ClearNested(); + headContainer.Clear(); + tailContainer.Clear(); + tickContainer.Clear(); + } + + protected override DrawableHitObject CreateNested(HitObject hitObject) + { + switch (hitObject) + { + case TailNote _: + return Tail = new DrawableTailNote(this) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AccentColour = { BindTarget = AccentColour } + }; + + case Note _: + return Head = new DrawableHeadNote(this) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AccentColour = { BindTarget = AccentColour } + }; + + case HoldNoteTick tick: + return new DrawableHoldNoteTick(tick) + { + HoldStartTime = () => holdStartTime, + AccentColour = { BindTarget = AccentColour } + }; + } + + return base.CreateNested(hitObject); + } + protected override void OnDirectionChanged(ValueChangedEvent e) { base.OnDirectionChanged(e); From 3a1acf7b0aab924aa9b48ef341ea2518d9ec3b92 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 17 Oct 2019 12:50:22 +0900 Subject: [PATCH 1798/2815] Fix slider approach circle proxies --- .../Objects/Drawables/DrawableSlider.cs | 2 +- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 14 ++++---------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 21411259ae..6ab14cb036 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -258,7 +258,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - public Drawable ProxiedLayer => new Container(); // Todo: + public Drawable ProxiedLayer => HeadCircle.ProxiedLayer; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Body.ReceivePositionalInputAt(screenSpacePos); } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index d1757de445..69e53d6eea 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -57,21 +57,15 @@ namespace osu.Game.Rulesets.Osu.UI public override void Add(DrawableHitObject h) { h.OnNewResult += onNewResult; - - if (h is IDrawableHitObjectWithProxiedApproach c) + h.OnLoadComplete += d => { - var original = c.ProxiedLayer; - - // Hitobjects only have lifetimes set on LoadComplete. For nested hitobjects (e.g. SliderHeads), this only happens when the parenting slider becomes visible. - // This delegation is required to make sure that the approach circles for those not-yet-loaded objects aren't added prematurely. - original.OnLoadComplete += addApproachCircleProxy; - } + if (d is IDrawableHitObjectWithProxiedApproach c) + approachCircles.Add(c.ProxiedLayer.CreateProxy()); + }; base.Add(h); } - private void addApproachCircleProxy(Drawable d) => approachCircles.Add(d.CreateProxy()); - public override void PostProcess() { connectionLayer.HitObjects = HitObjectContainer.Objects.Select(d => d.HitObject).OfType(); From d8f3678c3cadbdc02adf3db8d79796b39c646b4c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 17 Oct 2019 12:53:54 +0900 Subject: [PATCH 1799/2815] Rename parameter --- .../Objects/Drawable/DrawableBananaShower.cs | 6 +++--- .../Objects/Drawable/DrawableJuiceStream.cs | 6 +++--- .../Objects/Drawables/DrawableHoldNote.cs | 6 +++--- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 6 +++--- .../Objects/Drawables/DrawableDrumRoll.cs | 6 +++--- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs | 6 +++--- .../Objects/Drawables/DrawableTaikoHitObject.cs | 6 +++--- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- 8 files changed, 22 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs index 57ffd23e85..f46abea68f 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs @@ -25,10 +25,10 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable AddInternal(bananaContainer = new Container { RelativeSizeAxes = Axes.Both }); } - protected override void AddNested(DrawableHitObject h) + protected override void AddNested(DrawableHitObject hitObject) { - base.AddNested(h); - bananaContainer.Add(h); + base.AddNested(hitObject); + bananaContainer.Add(hitObject); } protected override void ClearNested() diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs index 1bbb7b08a5..7af3f49267 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs @@ -25,10 +25,10 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable AddInternal(dropletContainer = new Container { RelativeSizeAxes = Axes.Both, }); } - protected override void AddNested(DrawableHitObject h) + protected override void AddNested(DrawableHitObject hitObject) { - base.AddNested(h); - dropletContainer.Add(h); + base.AddNested(hitObject); + dropletContainer.Add(hitObject); } protected override void ClearNested() diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 78969b7361..2d4f90876e 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -59,11 +59,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables }, true); } - protected override void AddNested(DrawableHitObject h) + protected override void AddNested(DrawableHitObject hitObject) { - base.AddNested(h); + base.AddNested(hitObject); - switch (h) + switch (hitObject) { case DrawableHeadNote head: headContainer.Child = head; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 6ab14cb036..b937fd346f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -93,11 +93,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }, true); } - protected override void AddNested(DrawableHitObject h) + protected override void AddNested(DrawableHitObject hitObject) { - base.AddNested(h); + base.AddNested(hitObject); - switch (h) + switch (hitObject) { case DrawableSliderHead head: headContainer.Child = HeadCircle = head; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index d98043b1b7..b212c81020 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -55,11 +55,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables OnNewResult += onNewResult; } - protected override void AddNested(DrawableHitObject h) + protected override void AddNested(DrawableHitObject hitObject) { - base.AddNested(h); + base.AddNested(hitObject); - switch (h) + switch (hitObject) { case DrawableDrumRollTick tick: tickContainer.Add(tick); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 164944f00a..162c8f4810 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -128,11 +128,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Width *= Parent.RelativeChildSize.X; } - protected override void AddNested(DrawableHitObject h) + protected override void AddNested(DrawableHitObject hitObject) { - base.AddNested(h); + base.AddNested(hitObject); - switch (h) + switch (hitObject) { case DrawableSwellTick tick: ticks.Add(tick); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index b89cd7c09f..ddc29f1de6 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -134,11 +134,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables AddInternal(strongHitContainer = new Container()); } - protected override void AddNested(DrawableHitObject h) + protected override void AddNested(DrawableHitObject hitObject) { - base.AddNested(h); + base.AddNested(hitObject); - switch (h) + switch (hitObject) { case DrawableStrongNestedHit strong: strongHitContainer.Add(strong); diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 6bc2ccb889..424776f61b 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -157,7 +157,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } } - protected virtual void AddNested(DrawableHitObject h) + protected virtual void AddNested(DrawableHitObject hitObject) { } From f429a8f7c272b4849898cec0716257ec50c70d2c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 17 Oct 2019 13:52:21 +0900 Subject: [PATCH 1800/2815] Add back/obsolete old AddNested() method --- .../Objects/Drawable/DrawableBananaShower.cs | 12 ++--- .../Objects/Drawable/DrawableJuiceStream.cs | 12 ++--- .../Objects/Drawables/DrawableHoldNote.cs | 12 ++--- .../Objects/Drawables/DrawableSlider.cs | 12 ++--- .../Objects/Drawables/DrawableDrumRoll.cs | 12 ++--- .../Objects/Drawables/DrawableSwell.cs | 12 ++--- .../Drawables/DrawableTaikoHitObject.cs | 12 ++--- .../Objects/Drawables/DrawableHitObject.cs | 44 ++++++++++++++++--- 8 files changed, 80 insertions(+), 48 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs index f46abea68f..ea415e18fa 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs @@ -25,19 +25,19 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable AddInternal(bananaContainer = new Container { RelativeSizeAxes = Axes.Both }); } - protected override void AddNested(DrawableHitObject hitObject) + protected override void AddNestedHitObject(DrawableHitObject hitObject) { - base.AddNested(hitObject); + base.AddNestedHitObject(hitObject); bananaContainer.Add(hitObject); } - protected override void ClearNested() + protected override void ClearNestedHitObjects() { - base.ClearNested(); + base.ClearNestedHitObjects(); bananaContainer.Clear(); } - protected override DrawableHitObject CreateNested(HitObject hitObject) + protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject) { switch (hitObject) { @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable return createDrawableRepresentation?.Invoke(banana)?.With(o => ((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false); } - return base.CreateNested(hitObject); + return base.CreateNestedHitObject(hitObject); } } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs index 7af3f49267..a24821b3ce 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs @@ -25,19 +25,19 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable AddInternal(dropletContainer = new Container { RelativeSizeAxes = Axes.Both, }); } - protected override void AddNested(DrawableHitObject hitObject) + protected override void AddNestedHitObject(DrawableHitObject hitObject) { - base.AddNested(hitObject); + base.AddNestedHitObject(hitObject); dropletContainer.Add(hitObject); } - protected override void ClearNested() + protected override void ClearNestedHitObjects() { - base.ClearNested(); + base.ClearNestedHitObjects(); dropletContainer.Clear(); } - protected override DrawableHitObject CreateNested(HitObject hitObject) + protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject) { switch (hitObject) { @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable return createDrawableRepresentation?.Invoke(catchObject)?.With(o => ((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false); } - return base.CreateNested(hitObject); + return base.CreateNestedHitObject(hitObject); } } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 2d4f90876e..78d49c217e 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -59,9 +59,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables }, true); } - protected override void AddNested(DrawableHitObject hitObject) + protected override void AddNestedHitObject(DrawableHitObject hitObject) { - base.AddNested(hitObject); + base.AddNestedHitObject(hitObject); switch (hitObject) { @@ -79,15 +79,15 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables } } - protected override void ClearNested() + protected override void ClearNestedHitObjects() { - base.ClearNested(); + base.ClearNestedHitObjects(); headContainer.Clear(); tailContainer.Clear(); tickContainer.Clear(); } - protected override DrawableHitObject CreateNested(HitObject hitObject) + protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject) { switch (hitObject) { @@ -115,7 +115,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables }; } - return base.CreateNested(hitObject); + return base.CreateNestedHitObject(hitObject); } protected override void OnDirectionChanged(ValueChangedEvent e) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index b937fd346f..f057c67efe 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -93,9 +93,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }, true); } - protected override void AddNested(DrawableHitObject hitObject) + protected override void AddNestedHitObject(DrawableHitObject hitObject) { - base.AddNested(hitObject); + base.AddNestedHitObject(hitObject); switch (hitObject) { @@ -117,9 +117,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - protected override void ClearNested() + protected override void ClearNestedHitObjects() { - base.ClearNested(); + base.ClearNestedHitObjects(); headContainer.Clear(); tailContainer.Clear(); @@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables tickContainer.Clear(); } - protected override DrawableHitObject CreateNested(HitObject hitObject) + protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject) { switch (hitObject) { @@ -144,7 +144,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return new DrawableRepeatPoint(repeat, this) { Position = repeat.Position - slider.Position }; } - return base.CreateNested(hitObject); + return base.CreateNestedHitObject(hitObject); } protected override void UpdateInitialTransforms() diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index b212c81020..cc0d6829ba 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -55,9 +55,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables OnNewResult += onNewResult; } - protected override void AddNested(DrawableHitObject hitObject) + protected override void AddNestedHitObject(DrawableHitObject hitObject) { - base.AddNested(hitObject); + base.AddNestedHitObject(hitObject); switch (hitObject) { @@ -67,13 +67,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } } - protected override void ClearNested() + protected override void ClearNestedHitObjects() { - base.ClearNested(); + base.ClearNestedHitObjects(); tickContainer.Clear(); } - protected override DrawableHitObject CreateNested(HitObject hitObject) + protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject) { switch (hitObject) { @@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables return new DrawableDrumRollTick(tick); } - return base.CreateNested(hitObject); + return base.CreateNestedHitObject(hitObject); } protected override TaikoPiece CreateMainPiece() => new ElongatedCirclePiece(); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 162c8f4810..9c9dfc5f9e 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -128,9 +128,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Width *= Parent.RelativeChildSize.X; } - protected override void AddNested(DrawableHitObject hitObject) + protected override void AddNestedHitObject(DrawableHitObject hitObject) { - base.AddNested(hitObject); + base.AddNestedHitObject(hitObject); switch (hitObject) { @@ -140,13 +140,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } } - protected override void ClearNested() + protected override void ClearNestedHitObjects() { - base.ClearNested(); + base.ClearNestedHitObjects(); ticks.Clear(); } - protected override DrawableHitObject CreateNested(HitObject hitObject) + protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject) { switch (hitObject) { @@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables return new DrawableSwellTick(tick); } - return base.CreateNested(hitObject); + return base.CreateNestedHitObject(hitObject); } protected override void CheckForResult(bool userTriggered, double timeOffset) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index ddc29f1de6..0db6498c12 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -134,9 +134,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables AddInternal(strongHitContainer = new Container()); } - protected override void AddNested(DrawableHitObject hitObject) + protected override void AddNestedHitObject(DrawableHitObject hitObject) { - base.AddNested(hitObject); + base.AddNestedHitObject(hitObject); switch (hitObject) { @@ -146,13 +146,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } } - protected override void ClearNested() + protected override void ClearNestedHitObjects() { - base.ClearNested(); + base.ClearNestedHitObjects(); strongHitContainer.Clear(); } - protected override DrawableHitObject CreateNested(HitObject hitObject) + protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject) { switch (hitObject) { @@ -160,7 +160,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables return CreateStrongHit(strong); } - return base.CreateNested(hitObject); + return base.CreateNestedHitObject(hitObject); } // Normal and clap samples are handled by the drum diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 424776f61b..99b0c07570 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; @@ -138,34 +139,65 @@ namespace osu.Game.Rulesets.Objects.Drawables protected void Apply(HitObject hitObject) { +#pragma warning disable 618 // can be removed 20200417 + if (GetType().GetMethod(nameof(AddNested), BindingFlags.NonPublic | BindingFlags.Instance)?.DeclaringType != typeof(DrawableHitObject)) + return; +#pragma warning restore 618 + if (nestedHitObjects.IsValueCreated) { nestedHitObjects.Value.Clear(); - ClearNested(); + ClearNestedHitObjects(); } foreach (var h in hitObject.NestedHitObjects) { - var drawableNested = CreateNested(h) ?? throw new InvalidOperationException($"{nameof(CreateNested)} returned null for {h.GetType().ReadableName()}."); + var drawableNested = CreateNestedHitObject(h) ?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}."); drawableNested.OnNewResult += (d, r) => OnNewResult?.Invoke(d, r); drawableNested.OnRevertResult += (d, r) => OnRevertResult?.Invoke(d, r); drawableNested.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j); nestedHitObjects.Value.Add(drawableNested); - AddNested(drawableNested); + AddNestedHitObject(drawableNested); } } - protected virtual void AddNested(DrawableHitObject hitObject) + /// + /// Invoked by the base to add nested s to the hierarchy. + /// + /// The to be added. + protected virtual void AddNestedHitObject(DrawableHitObject hitObject) { } - protected virtual void ClearNested() + /// + /// Adds a nested . This should not be used except for legacy nested usages. + /// + /// + [Obsolete("Use AddNestedHitObject() / ClearNestedHitObjects() / CreateNestedHitObject() instead.")] // can be removed 20200417 + protected virtual void AddNested(DrawableHitObject h) + { + h.OnNewResult += (d, r) => OnNewResult?.Invoke(d, r); + h.OnRevertResult += (d, r) => OnRevertResult?.Invoke(d, r); + h.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j); + + nestedHitObjects.Value.Add(h); + } + + /// + /// Invoked by the base to remove all previously-added nested s. + /// + protected virtual void ClearNestedHitObjects() { } - protected virtual DrawableHitObject CreateNested(HitObject hitObject) => null; + /// + /// Creates the drawable representation for a nested . + /// + /// The . + /// The drawable representation for . + protected virtual DrawableHitObject CreateNestedHitObject(HitObject hitObject) => null; #region State / Transform Management From bc41eb176ee6ad63a153d7ba82e1a8a17d13c0c0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 17 Oct 2019 14:02:23 +0900 Subject: [PATCH 1801/2815] Clean up head/tail setting in various DHOs --- .../Objects/Drawables/DrawableHoldNote.cs | 10 +++++----- .../Objects/Drawables/DrawableSlider.cs | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 78d49c217e..87b9633c80 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -21,13 +21,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { public override bool DisplayResult => false; + public DrawableNote Head => headContainer.Child; + public DrawableNote Tail => tailContainer.Child; + private readonly Container headContainer; private readonly Container tailContainer; private readonly Container tickContainer; - public DrawableNote Head { get; private set; } - public DrawableNote Tail { get; private set; } - private readonly BodyPiece bodyPiece; /// @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables switch (hitObject) { case TailNote _: - return Tail = new DrawableTailNote(this) + return new DrawableTailNote(this) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables }; case Note _: - return Head = new DrawableHeadNote(this) + return new DrawableHeadNote(this) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index f057c67efe..6d45bb9ac4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -20,8 +20,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSlider : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach { - public DrawableSliderHead HeadCircle { get; private set; } - public DrawableSliderTail TailCircle { get; private set; } + public DrawableSliderHead HeadCircle => headContainer.Child; + public DrawableSliderTail TailCircle => tailContainer.Child; public readonly SnakingSliderBody Body; public readonly SliderBall Ball; @@ -100,11 +100,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables switch (hitObject) { case DrawableSliderHead head: - headContainer.Child = HeadCircle = head; + headContainer.Child = head; break; case DrawableSliderTail tail: - tailContainer.Child = TailCircle = tail; + tailContainer.Child = tail; break; case DrawableSliderTick tick: From f92331531c6e183a4644a423d20b6e9df17df2cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Oct 2019 15:32:02 +0900 Subject: [PATCH 1802/2815] Rename grid to DistanceSnap to be more in line with its purpose --- ...SnapGrid.cs => TestSceneOsuDistanceSnapGrid.cs} | 12 ++++++------ .../{OsuBeatSnapGrid.cs => OsuDistanceSnapGrid.cs} | 4 ++-- ...eatSnapGrid.cs => TestSceneDistanceSnapGrid.cs} | 14 +++++++------- ...BeatSnapGrid.cs => CircularDistanceSnapGrid.cs} | 4 ++-- .../{BeatSnapGrid.cs => DistanceSnapGrid.cs} | 13 ++++++++----- 5 files changed, 25 insertions(+), 22 deletions(-) rename osu.Game.Rulesets.Osu.Tests/{TestSceneOsuBeatSnapGrid.cs => TestSceneOsuDistanceSnapGrid.cs} (94%) rename osu.Game.Rulesets.Osu/Edit/{OsuBeatSnapGrid.cs => OsuDistanceSnapGrid.cs} (90%) rename osu.Game.Tests/Visual/Editor/{TestSceneBeatSnapGrid.cs => TestSceneDistanceSnapGrid.cs} (93%) rename osu.Game/Screens/Edit/Compose/Components/{CircularBeatSnapGrid.cs => CircularDistanceSnapGrid.cs} (92%) rename osu.Game/Screens/Edit/Compose/Components/{BeatSnapGrid.cs => DistanceSnapGrid.cs} (92%) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs similarity index 94% rename from osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs index 7399f12372..da7708081b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs @@ -20,7 +20,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Tests { - public class TestSceneOsuBeatSnapGrid : ManualInputManagerTestScene + public class TestSceneOsuDistanceSnapGrid : ManualInputManagerTestScene { private const double beat_length = 100; private static readonly Vector2 grid_position = new Vector2(512, 384); @@ -31,9 +31,9 @@ namespace osu.Game.Rulesets.Osu.Tests [Cached] private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); - private TestOsuBeatSnapGrid grid; + private TestOsuDistanceSnapGrid grid; - public TestSceneOsuBeatSnapGrid() + public TestSceneOsuDistanceSnapGrid() { editorBeatmap = new EditorBeatmap(new OsuBeatmap()); @@ -152,7 +152,7 @@ namespace osu.Game.Rulesets.Osu.Tests RelativeSizeAxes = Axes.Both, Colour = Color4.SlateGray }, - grid = new TestOsuBeatSnapGrid(new HitCircle { Position = grid_position }), + grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }), new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnapPosition(grid.ToLocalSpace(v)) } }; }); @@ -197,11 +197,11 @@ namespace osu.Game.Rulesets.Osu.Tests } } - private class TestOsuBeatSnapGrid : OsuBeatSnapGrid + private class TestOsuDistanceSnapGrid : OsuDistanceSnapGrid { public new float DistanceSpacing => base.DistanceSpacing; - public TestOsuBeatSnapGrid(OsuHitObject hitObject) + public TestOsuDistanceSnapGrid(OsuHitObject hitObject) : base(hitObject) { } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBeatSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs similarity index 90% rename from osu.Game.Rulesets.Osu/Edit/OsuBeatSnapGrid.cs rename to osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs index d453e3d062..558993f8b2 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs @@ -8,14 +8,14 @@ using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Osu.Edit { - public class OsuBeatSnapGrid : CircularBeatSnapGrid + public class OsuDistanceSnapGrid : CircularDistanceSnapGrid { /// /// Scoring distance with a speed-adjusted beat length of 1 second. /// private const float base_scoring_distance = 100; - public OsuBeatSnapGrid(OsuHitObject hitObject) + public OsuDistanceSnapGrid(OsuHitObject hitObject) : base(hitObject, hitObject.StackedEndPosition) { } diff --git a/osu.Game.Tests/Visual/Editor/TestSceneBeatSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs similarity index 93% rename from osu.Game.Tests/Visual/Editor/TestSceneBeatSnapGrid.cs rename to osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs index 073cec7315..a9e5930478 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneBeatSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs @@ -19,7 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Editor { - public class TestSceneBeatSnapGrid : EditorClockTestScene + public class TestSceneDistanceSnapGrid : EditorClockTestScene { private const double beat_length = 100; private static readonly Vector2 grid_position = new Vector2(512, 384); @@ -27,9 +27,9 @@ namespace osu.Game.Tests.Visual.Editor [Cached(typeof(IEditorBeatmap))] private readonly EditorBeatmap editorBeatmap; - private TestBeatSnapGrid grid; + private TestDistanceSnapGrid grid; - public TestSceneBeatSnapGrid() + public TestSceneDistanceSnapGrid() { editorBeatmap = new EditorBeatmap(new OsuBeatmap()); editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); @@ -112,7 +112,7 @@ namespace osu.Game.Tests.Visual.Editor AddAssert("snap time is now 0.5 beats away", () => Precision.AlmostEquals(beat_length / 2, grid.GetSnapTime(snapPosition), 0.01)); } - private void createGrid(Action func = null, string description = null) + private void createGrid(Action func = null, string description = null) { AddStep($"create grid {description ?? string.Empty}", () => { @@ -123,20 +123,20 @@ namespace osu.Game.Tests.Visual.Editor RelativeSizeAxes = Axes.Both, Colour = Color4.SlateGray }, - grid = new TestBeatSnapGrid(new HitObject(), grid_position) + grid = new TestDistanceSnapGrid(new HitObject(), grid_position) }; func?.Invoke(grid); }); } - private class TestBeatSnapGrid : BeatSnapGrid + private class TestDistanceSnapGrid : DistanceSnapGrid { public new float Velocity = 1; public new float DistanceSpacing => base.DistanceSpacing; - public TestBeatSnapGrid(HitObject hitObject, Vector2 centrePosition) + public TestDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) : base(hitObject, centrePosition) { } diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs similarity index 92% rename from osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs rename to osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index 5e378f8393..3cbf926d4f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -9,9 +9,9 @@ using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { - public abstract class CircularBeatSnapGrid : BeatSnapGrid + public abstract class CircularDistanceSnapGrid : DistanceSnapGrid { - protected CircularBeatSnapGrid(HitObject hitObject, Vector2 centrePosition) + protected CircularDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) : base(hitObject, centrePosition) { } diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs similarity index 92% rename from osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs rename to osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 9040843144..299e78b7c0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -15,7 +15,10 @@ using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { - public abstract class BeatSnapGrid : CompositeDrawable + /// + /// A grid which takes user input and returns a quantized ("snapped") position and time. + /// + public abstract class DistanceSnapGrid : CompositeDrawable { /// /// The velocity of the beatmap at the point of placement in pixels per millisecond. @@ -48,7 +51,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private double startTime; private double beatLength; - protected BeatSnapGrid(HitObject hitObject, Vector2 centrePosition) + protected DistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) { this.hitObject = hitObject; this.CentrePosition = centrePosition; @@ -114,14 +117,14 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Snaps a position to this grid. /// - /// The original position in coordinate space local to this . - /// The snapped position in coordinate space local to this . + /// The original position in coordinate space local to this . + /// The snapped position in coordinate space local to this . public abstract Vector2 GetSnapPosition(Vector2 position); /// /// Retrieves the time at a snapped position. /// - /// The snapped position in coordinate space local to this . + /// The snapped position in coordinate space local to this . /// The time at the snapped position. public double GetSnapTime(Vector2 position) => startTime + (position - CentrePosition).Length / Velocity; From 510ce9345f79b3deee4f6ed1fc72e46ebed0ed0e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 17 Oct 2019 16:14:28 +0900 Subject: [PATCH 1803/2815] Fix potential blueprint nullrefs with the new structure --- .../Blueprints/HoldNoteSelectionBlueprint.cs | 32 +++++++++++-------- .../Compose/Components/BlueprintContainer.cs | 18 ++++++++--- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index d64c5dbc6a..3169a8c036 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -20,30 +20,36 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints private readonly IBindable direction = new Bindable(); - private readonly BodyPiece body; + [Resolved] + private OsuColour colours { get; set; } public HoldNoteSelectionBlueprint(DrawableHoldNote hold) : base(hold) { - InternalChildren = new Drawable[] - { - new HoldNoteNoteSelectionBlueprint(hold.Head), - new HoldNoteNoteSelectionBlueprint(hold.Tail), - body = new BodyPiece - { - AccentColour = Color4.Transparent - }, - }; } [BackgroundDependencyLoader] - private void load(OsuColour colours, IScrollingInfo scrollingInfo) + private void load(IScrollingInfo scrollingInfo) { - body.BorderColour = colours.Yellow; - direction.BindTo(scrollingInfo.Direction); } + protected override void LoadComplete() + { + base.LoadComplete(); + + InternalChildren = new Drawable[] + { + new HoldNoteNoteSelectionBlueprint(HitObject.Head), + new HoldNoteNoteSelectionBlueprint(HitObject.Tail), + new BodyPiece + { + AccentColour = Color4.Transparent, + BorderColour = colours.Yellow + }, + }; + } + protected override void Update() { base.Update(); diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 2de5ecf633..cb3d3b71fc 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -123,12 +123,20 @@ namespace osu.Game.Screens.Edit.Compose.Components if (blueprint == null) return; - blueprint.Selected += onBlueprintSelected; - blueprint.Deselected += onBlueprintDeselected; - blueprint.SelectionRequested += onSelectionRequested; - blueprint.DragRequested += onDragRequested; + if (hitObject.IsLoaded) + addBlueprint(); + else + hitObject.OnLoadComplete += _ => addBlueprint(); - selectionBlueprints.Add(blueprint); + void addBlueprint() + { + blueprint.Selected += onBlueprintSelected; + blueprint.Deselected += onBlueprintDeselected; + blueprint.SelectionRequested += onSelectionRequested; + blueprint.DragRequested += onDragRequested; + + selectionBlueprints.Add(blueprint); + } } private void removeBlueprintFor(DrawableHitObject hitObject) => removeBlueprintFor(hitObject.HitObject); From f3ed71d3361bd1eb81e23432e457f93c54caa3db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Oct 2019 16:36:47 +0900 Subject: [PATCH 1804/2815] Move scoring distance constant to a central/shared location --- osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs | 7 +------ osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 8 ++++++++ osu.Game.Rulesets.Osu/Objects/Slider.cs | 7 +------ 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs index 558993f8b2..f701712739 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs @@ -10,11 +10,6 @@ namespace osu.Game.Rulesets.Osu.Edit { public class OsuDistanceSnapGrid : CircularDistanceSnapGrid { - /// - /// Scoring distance with a speed-adjusted beat length of 1 second. - /// - private const float base_scoring_distance = 100; - public OsuDistanceSnapGrid(OsuHitObject hitObject) : base(hitObject, hitObject.StackedEndPosition) { @@ -25,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Edit TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(time); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(time); - double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; + double scoringDistance = OsuHitObject.BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; return (float)(scoringDistance / timingPoint.BeatLength); } diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 80e013fe68..b506c1f918 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -14,8 +14,16 @@ namespace osu.Game.Rulesets.Osu.Objects { public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition { + /// + /// The radius of hit objects (ie. the radius of a ). + /// public const float OBJECT_RADIUS = 64; + /// + /// Scoring distance with a speed-adjusted beat length of 1 second (ie. the speed slider balls move through their track). + /// + internal const float BASE_SCORING_DISTANCE = 100; + public double TimePreempt = 600; public double TimeFadeIn = 400; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 9bed123465..d98d72331a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -19,11 +19,6 @@ namespace osu.Game.Rulesets.Osu.Objects { public class Slider : OsuHitObject, IHasCurve { - /// - /// Scoring distance with a speed-adjusted beat length of 1 second. - /// - private const float base_scoring_distance = 100; - public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; public double Duration => EndTime - StartTime; @@ -123,7 +118,7 @@ namespace osu.Game.Rulesets.Osu.Objects TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); - double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; + double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; Velocity = scoringDistance / timingPoint.BeatLength; TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier; From c4cc960e1564944a18964c606b2b64b8f4d927de Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 17 Oct 2019 18:00:15 +0900 Subject: [PATCH 1805/2815] Fix mania hitobject selections not moving correctly --- .../Edit/ManiaSelectionHandler.cs | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index f576c43e52..2fba0639da 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -65,24 +65,27 @@ namespace osu.Game.Rulesets.Mania.Edit private void performDragMovement(MoveSelectionEvent moveEvent) { + float delta = moveEvent.InstantDelta.Y; + + // When scrolling downwards the anchor position is at the bottom of the screen, however the movement event assumes the anchor is at the top of the screen. + // This causes the delta to assume a positive hitobject position, and which can be corrected for by subtracting the parent height. + if (scrollingInfo.Direction.Value == ScrollingDirection.Down) + delta -= moveEvent.Blueprint.HitObject.Parent.DrawHeight; + foreach (var b in SelectedBlueprints) { var hitObject = b.HitObject; - var objectParent = (HitObjectContainer)hitObject.Parent; - // Using the hitobject position is required since AdjustPosition can be invoked multiple times per frame - // without the position having been updated by the parenting ScrollingHitObjectContainer - hitObject.Y += moveEvent.InstantDelta.Y; + // StartTime could be used to adjust the position if only one movement event was received per frame. + // However this is not the case and ScrollingHitObjectContainer performs movement in UpdateAfterChildren() so the position must also be updated to be valid for further movement events + hitObject.Y += delta; - float targetPosition; + float targetPosition = hitObject.Position.Y; - // If we're scrolling downwards, a position of 0 is actually further away from the hit target - // so we need to flip the vertical coordinate in the hitobject container's space + // The scrolling algorithm always assumes an anchor at the top of the screen, so the position must be flipped when scrolling downwards to reflect a top anchor if (scrollingInfo.Direction.Value == ScrollingDirection.Down) - targetPosition = -hitObject.Position.Y; - else - targetPosition = hitObject.Position.Y; + targetPosition = -targetPosition; objectParent.Remove(hitObject); From 38dcd42d0891fd3c2961277a32661ab7f4d2a974 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 17 Oct 2019 12:35:12 +0300 Subject: [PATCH 1806/2815] Parse voted comments --- .../Online/API/Requests/Responses/Comment.cs | 2 ++ .../API/Requests/Responses/CommentBundle.cs | 20 ++++++++++++++++ osu.Game/Overlays/Comments/DrawableComment.cs | 24 ++++++++++++++----- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs index 29abaa74e5..5510e9afff 100644 --- a/osu.Game/Online/API/Requests/Responses/Comment.cs +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -72,6 +72,8 @@ namespace osu.Game.Online.API.Requests.Responses public bool HasMessage => !string.IsNullOrEmpty(MessageHtml); + public bool IsVoted { get; set; } + public string GetMessage => HasMessage ? WebUtility.HtmlDecode(Regex.Replace(MessageHtml, @"<(.|\n)*?>", string.Empty)) : string.Empty; public int DeletedChildrenCount => ChildComments.Count(c => c.IsDeleted); diff --git a/osu.Game/Online/API/Requests/Responses/CommentBundle.cs b/osu.Game/Online/API/Requests/Responses/CommentBundle.cs index 7063581605..f910c738ac 100644 --- a/osu.Game/Online/API/Requests/Responses/CommentBundle.cs +++ b/osu.Game/Online/API/Requests/Responses/CommentBundle.cs @@ -47,6 +47,26 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"included_comments")] public List IncludedComments { get; set; } + private List userVotes; + + [JsonProperty(@"user_votes")] + public List UserVotes + { + get => userVotes; + set + { + userVotes = value; + value.ForEach(v => + { + Comments.ForEach(c => + { + if (v == c.Id) + c.IsVoted = true; + }); + }); + } + } + private List users; [JsonProperty(@"users")] diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 89abda92cf..3e9c6a5eca 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -15,6 +15,8 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Shapes; using System.Linq; using osu.Game.Online.Chat; +using osu.Framework.Allocation; +using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays.Comments { @@ -81,7 +83,7 @@ namespace osu.Game.Overlays.Comments Spacing = new Vector2(5, 0), Children = new Drawable[] { - votePill = new VotePill(comment.VotesCount) + votePill = new VotePill(comment) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -336,28 +338,38 @@ namespace osu.Game.Overlays.Comments private class VotePill : CircularContainer { - public VotePill(int count) + private readonly Box background; + private readonly Comment comment; + + public VotePill(Comment comment) { + this.comment = comment; + AutoSizeAxes = Axes.X; Height = 20; Masking = true; Children = new Drawable[] { - new Box + background = new Box { RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(0.05f) }, - new SpriteText + new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, Margin = new MarginPadding { Horizontal = margin }, Font = OsuFont.GetFont(size: 14), - Text = $"+{count}" + Text = $"+{comment.VotesCount}" } }; } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = comment.IsVoted ? colours.GreenLight : OsuColour.Gray(0.05f); + } } } } From 1f28c00594daa535678f651a5d16501347653648 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 17 Oct 2019 13:10:28 +0300 Subject: [PATCH 1807/2815] UI implementation --- osu.Game/Overlays/Comments/DrawableComment.cs | 68 +++++++++++++++++-- 1 file changed, 62 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 3e9c6a5eca..d1cae6592a 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -17,6 +17,10 @@ using System.Linq; using osu.Game.Online.Chat; using osu.Framework.Allocation; using osu.Game.Graphics.Sprites; +using osuTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Input.Events; +using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Comments { @@ -57,7 +61,7 @@ namespace osu.Game.Overlays.Comments { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(margin), + Padding = new MarginPadding(margin) { Left = margin + 5 }, Child = content = new GridContainer { RelativeSizeAxes = Axes.X, @@ -336,10 +340,15 @@ namespace osu.Game.Overlays.Comments } } - private class VotePill : CircularContainer + private class VotePill : Container, IHasAccentColour { + public Color4 AccentColour { get; set; } + private readonly Box background; + private readonly Box hoverLayer; private readonly Comment comment; + private readonly CircularContainer borderContainer; + private readonly SpriteText sideNumber; public VotePill(Comment comment) { @@ -347,12 +356,24 @@ namespace osu.Game.Overlays.Comments AutoSizeAxes = Axes.X; Height = 20; - Masking = true; Children = new Drawable[] { - background = new Box + borderContainer = new CircularContainer { RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + hoverLayer = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0 + } + } }, new OsuSpriteText { @@ -361,14 +382,49 @@ namespace osu.Game.Overlays.Comments Margin = new MarginPadding { Horizontal = margin }, Font = OsuFont.GetFont(size: 14), Text = $"+{comment.VotesCount}" - } + }, + sideNumber = new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreRight, + Text = "+1", + Font = OsuFont.GetFont(size: 14), + Margin = new MarginPadding { Right = 3 }, + Alpha = 0, + }, + new HoverClickSounds(), }; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - background.Colour = comment.IsVoted ? colours.GreenLight : OsuColour.Gray(0.05f); + AccentColour = borderContainer.BorderColour = sideNumber.Colour = colours.GreenLight; + background.Colour = comment.IsVoted ? AccentColour : OsuColour.Gray(0.05f); + hoverLayer.Colour = Color4.Black.Opacity(0.5f); + } + + protected override bool OnHover(HoverEvent e) + { + if (comment.IsVoted) + hoverLayer.Show(); + else + sideNumber.Show(); + + borderContainer.BorderThickness = 3; + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + + if (comment.IsVoted) + hoverLayer.Hide(); + else + sideNumber.Hide(); + + borderContainer.BorderThickness = 0; } } } From d3a8dfd5ff6287ba616b31030a9e5c643cffb5ad Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 17 Oct 2019 13:57:17 +0300 Subject: [PATCH 1808/2815] Implement LoadingButton component --- .../Graphics/UserInterface/LoadingButton.cs | 96 +++++++++++++ .../Graphics/UserInterface/ShowMoreButton.cs | 129 ++++++------------ 2 files changed, 135 insertions(+), 90 deletions(-) create mode 100644 osu.Game/Graphics/UserInterface/LoadingButton.cs diff --git a/osu.Game/Graphics/UserInterface/LoadingButton.cs b/osu.Game/Graphics/UserInterface/LoadingButton.cs new file mode 100644 index 0000000000..1557a90c4a --- /dev/null +++ b/osu.Game/Graphics/UserInterface/LoadingButton.cs @@ -0,0 +1,96 @@ +// 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.Containers; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; +using osuTK; + +namespace osu.Game.Graphics.UserInterface +{ + public abstract class LoadingButton : OsuHoverContainer + { + private const float fade_duration = 200; + + private bool isLoading; + + public bool IsLoading + { + get => isLoading; + set + { + isLoading = value; + + Enabled.Value = !isLoading; + + if (value) + { + loading.Show(); + content.FadeOut(fade_duration, Easing.OutQuint); + OnLoadingStart(); + } + else + { + loading.Hide(); + content.FadeIn(fade_duration, Easing.OutQuint); + OnLoadingFinished(); + } + } + } + + public Vector2 LoadingAnimationSize + { + get => loading.Size; + set => loading.Size = value; + } + + private readonly Container background; + private readonly LoadingAnimation loading; + private readonly Drawable content; + + public LoadingButton() + { + Child = background = CreateBackground(); + + background.AddRange(new Drawable[] + { + content = CreateContent(), + loading = new LoadingAnimation + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(12) + } + }); + } + + protected override bool OnClick(ClickEvent e) + { + if (!Enabled.Value) + return false; + + try + { + return base.OnClick(e); + } + finally + { + // run afterwards as this will disable this button. + IsLoading = true; + } + } + + protected virtual void OnLoadingStart() + { + } + + protected virtual void OnLoadingFinished() + { + } + + protected abstract Container CreateBackground(); + + protected abstract Drawable CreateContent(); + } +} diff --git a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs index 5296b9dd7f..b6b87c43f2 100644 --- a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs +++ b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs @@ -5,8 +5,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; @@ -14,10 +12,8 @@ using System.Collections.Generic; namespace osu.Game.Graphics.UserInterface { - public class ShowMoreButton : OsuHoverContainer + public class ShowMoreButton : LoadingButton { - private const float fade_duration = 200; - private Color4 chevronIconColour; protected Color4 ChevronIconColour @@ -32,100 +28,53 @@ namespace osu.Game.Graphics.UserInterface set => text.Text = value; } - private bool isLoading; - - public bool IsLoading - { - get => isLoading; - set - { - isLoading = value; - - Enabled.Value = !isLoading; - - if (value) - { - loading.Show(); - content.FadeOut(fade_duration, Easing.OutQuint); - } - else - { - loading.Hide(); - content.FadeIn(fade_duration, Easing.OutQuint); - } - } - } - - private readonly Box background; - private readonly LoadingAnimation loading; - private readonly FillFlowContainer content; - private readonly ChevronIcon leftChevron; - private readonly ChevronIcon rightChevron; - private readonly SpriteText text; - protected override IEnumerable EffectTargets => new[] { background }; + private ChevronIcon leftChevron; + private ChevronIcon rightChevron; + private SpriteText text; + private Box background; + public ShowMoreButton() { AutoSizeAxes = Axes.Both; + AddInternal(new CircularContainer + { + Masking = true, + Size = new Vector2(140, 30) + }); + } + + protected override Container CreateBackground() => new CircularContainer + { + Masking = true, + Size = new Vector2(140, 30), + Child = background = new Box + { + RelativeSizeAxes = Axes.Both, + } + }; + + protected override Drawable CreateContent() => new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(7), Children = new Drawable[] { - new CircularContainer + leftChevron = new ChevronIcon(), + text = new OsuSpriteText { - Masking = true, - Size = new Vector2(140, 30), - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - }, - content = new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(7), - Children = new Drawable[] - { - leftChevron = new ChevronIcon(), - text = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), - Text = "show more".ToUpper(), - }, - rightChevron = new ChevronIcon(), - } - }, - loading = new LoadingAnimation - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(12) - }, - } - } - }; - } - - protected override bool OnClick(ClickEvent e) - { - if (!Enabled.Value) - return false; - - try - { - return base.OnClick(e); + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Text = "show more".ToUpper(), + }, + rightChevron = new ChevronIcon(), } - finally - { - // run afterwards as this will disable this button. - IsLoading = true; - } - } + }; private class ChevronIcon : SpriteIcon { From a437ff74ccf17f9e5bd3d42af78f053a62495e6c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 17 Oct 2019 14:18:31 +0300 Subject: [PATCH 1809/2815] Move VotePill to it's own file --- .../Online/TestSceneCommentsContainer.cs | 3 +- .../Graphics/UserInterface/ShowMoreButton.cs | 5 - osu.Game/Overlays/Comments/DrawableComment.cs | 94 ------------ osu.Game/Overlays/Comments/VotePill.cs | 135 ++++++++++++++++++ 4 files changed, 137 insertions(+), 100 deletions(-) create mode 100644 osu.Game/Overlays/Comments/VotePill.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs index 436e80d6f5..86bd0ddd11 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -22,7 +22,8 @@ namespace osu.Game.Tests.Visual.Online typeof(HeaderButton), typeof(SortTabControl), typeof(ShowChildrenButton), - typeof(DeletedChildrenPlaceholder) + typeof(DeletedChildrenPlaceholder), + typeof(VotePill) }; protected override bool UseOnlineAPI => true; diff --git a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs index b6b87c43f2..31e9af55c4 100644 --- a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs +++ b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs @@ -38,11 +38,6 @@ namespace osu.Game.Graphics.UserInterface public ShowMoreButton() { AutoSizeAxes = Axes.Both; - AddInternal(new CircularContainer - { - Masking = true, - Size = new Vector2(140, 30) - }); } protected override Container CreateBackground() => new CircularContainer diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index d1cae6592a..bc53af09d3 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -15,12 +15,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Shapes; using System.Linq; using osu.Game.Online.Chat; -using osu.Framework.Allocation; -using osu.Game.Graphics.Sprites; -using osuTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Input.Events; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Comments { @@ -339,93 +333,5 @@ namespace osu.Game.Overlays.Comments return parentComment.HasMessage ? parentComment.GetMessage : parentComment.IsDeleted ? @"deleted" : string.Empty; } } - - private class VotePill : Container, IHasAccentColour - { - public Color4 AccentColour { get; set; } - - private readonly Box background; - private readonly Box hoverLayer; - private readonly Comment comment; - private readonly CircularContainer borderContainer; - private readonly SpriteText sideNumber; - - public VotePill(Comment comment) - { - this.comment = comment; - - AutoSizeAxes = Axes.X; - Height = 20; - Children = new Drawable[] - { - borderContainer = new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both - }, - hoverLayer = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0 - } - } - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Margin = new MarginPadding { Horizontal = margin }, - Font = OsuFont.GetFont(size: 14), - Text = $"+{comment.VotesCount}" - }, - sideNumber = new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreRight, - Text = "+1", - Font = OsuFont.GetFont(size: 14), - Margin = new MarginPadding { Right = 3 }, - Alpha = 0, - }, - new HoverClickSounds(), - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - AccentColour = borderContainer.BorderColour = sideNumber.Colour = colours.GreenLight; - background.Colour = comment.IsVoted ? AccentColour : OsuColour.Gray(0.05f); - hoverLayer.Colour = Color4.Black.Opacity(0.5f); - } - - protected override bool OnHover(HoverEvent e) - { - if (comment.IsVoted) - hoverLayer.Show(); - else - sideNumber.Show(); - - borderContainer.BorderThickness = 3; - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - base.OnHoverLost(e); - - if (comment.IsVoted) - hoverLayer.Hide(); - else - sideNumber.Hide(); - - borderContainer.BorderThickness = 0; - } - } } } diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs new file mode 100644 index 0000000000..79b7310c28 --- /dev/null +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -0,0 +1,135 @@ +// 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.Containers; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Allocation; +using osu.Game.Graphics.Sprites; +using osuTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Input.Events; +using osu.Game.Graphics.UserInterface; +using System.Collections.Generic; +using osuTK; + +namespace osu.Game.Overlays.Comments +{ + public class VotePill : LoadingButton, IHasAccentColour + { + public Color4 AccentColour { get; set; } + + protected override IEnumerable EffectTargets => null; + + private readonly Comment comment; + private Box background; + private Box hoverLayer; + private CircularContainer borderContainer; + private SpriteText sideNumber; + private OsuSpriteText votesCounter; + + public VotePill(Comment comment) + { + this.comment = comment; + votesCounter.Text = $"+{comment.VotesCount}"; + + AutoSizeAxes = Axes.X; + Height = 20; + LoadingAnimationSize = new Vector2(10); + + Action = () => + { + + }; + } + + protected override Container CreateBackground() => new Container + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Children = new Drawable[] + { + borderContainer = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + hoverLayer = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0 + } + } + }, + sideNumber = new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreRight, + Text = "+1", + Font = OsuFont.GetFont(size: 14), + Margin = new MarginPadding { Right = 3 }, + Alpha = 0, + }, + }, + }; + + protected override Drawable CreateContent() => votesCounter = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Horizontal = 10 }, + Font = OsuFont.GetFont(size: 14), + AlwaysPresent = true, + }; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = borderContainer.BorderColour = sideNumber.Colour = colours.GreenLight; + background.Colour = comment.IsVoted ? AccentColour : OsuColour.Gray(0.05f); + hoverLayer.Colour = Color4.Black.Opacity(0.5f); + } + + protected override void OnLoadingStart() + { + sideNumber.Hide(); + borderContainer.BorderThickness = 0; + } + + protected override bool OnHover(HoverEvent e) + { + if (comment.IsVoted) + hoverLayer.Show(); + + if (!IsLoading) + { + borderContainer.BorderThickness = 3; + + if (!comment.IsVoted) + sideNumber.Show(); + } + + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + + if (comment.IsVoted) + hoverLayer.Hide(); + else + sideNumber.Hide(); + + borderContainer.BorderThickness = 0; + } + } +} From 42cd4107a0396d8dd06e2acba1ca4ba659f2dd62 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 17 Oct 2019 15:04:30 +0300 Subject: [PATCH 1810/2815] Implement CommentVoteRequest and adjust UI --- .../Online/API/Requests/CommentVoteRequest.cs | 36 +++++++ osu.Game/Overlays/Comments/VotePill.cs | 96 ++++++++++++++----- 2 files changed, 106 insertions(+), 26 deletions(-) create mode 100644 osu.Game/Online/API/Requests/CommentVoteRequest.cs diff --git a/osu.Game/Online/API/Requests/CommentVoteRequest.cs b/osu.Game/Online/API/Requests/CommentVoteRequest.cs new file mode 100644 index 0000000000..06a3b1126e --- /dev/null +++ b/osu.Game/Online/API/Requests/CommentVoteRequest.cs @@ -0,0 +1,36 @@ +// 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.IO.Network; +using osu.Game.Online.API.Requests.Responses; +using System.Net.Http; + +namespace osu.Game.Online.API.Requests +{ + public class CommentVoteRequest : APIRequest + { + private readonly long id; + private readonly CommentVoteAction action; + + public CommentVoteRequest(long id, CommentVoteAction action) + { + this.id = id; + this.action = action; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + req.Method = action == CommentVoteAction.Vote ? HttpMethod.Post : HttpMethod.Delete; + return req; + } + + protected override string Target => $@"comments/{id}/vote"; + } + + public enum CommentVoteAction + { + Vote, + UnVote + } +} diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index 79b7310c28..cd50546b45 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -15,6 +15,10 @@ using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using System.Collections.Generic; using osuTK; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Framework.Bindables; +using System.Linq; namespace osu.Game.Overlays.Comments { @@ -24,26 +28,57 @@ namespace osu.Game.Overlays.Comments protected override IEnumerable EffectTargets => null; + [Resolved] + private IAPIProvider api { get; set; } + private readonly Comment comment; private Box background; private Box hoverLayer; private CircularContainer borderContainer; private SpriteText sideNumber; private OsuSpriteText votesCounter; + private CommentVoteRequest request; + + private readonly BindableBool isVoted = new BindableBool(); public VotePill(Comment comment) { this.comment = comment; - votesCounter.Text = $"+{comment.VotesCount}"; + setCount(comment.VotesCount); + + Action = onAction; AutoSizeAxes = Axes.X; Height = 20; LoadingAnimationSize = new Vector2(10); + } - Action = () => - { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = borderContainer.BorderColour = sideNumber.Colour = colours.GreenLight; + hoverLayer.Colour = Color4.Black.Opacity(0.5f); + } - }; + protected override void LoadComplete() + { + base.LoadComplete(); + isVoted.Value = comment.IsVoted; + isVoted.BindValueChanged(voted => background.Colour = voted.NewValue ? AccentColour : OsuColour.Gray(0.05f), true); + } + + private void onAction() + { + request = new CommentVoteRequest(comment.Id, isVoted.Value ? CommentVoteAction.UnVote : CommentVoteAction.Vote); + request.Success += onSuccess; + api.Queue(request); + } + + private void onSuccess(CommentBundle response) + { + isVoted.Value = !isVoted.Value; + setCount(response.Comments.First().VotesCount); + IsLoading = false; } protected override Container CreateBackground() => new Container @@ -90,46 +125,55 @@ namespace osu.Game.Overlays.Comments AlwaysPresent = true, }; - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - AccentColour = borderContainer.BorderColour = sideNumber.Colour = colours.GreenLight; - background.Colour = comment.IsVoted ? AccentColour : OsuColour.Gray(0.05f); - hoverLayer.Colour = Color4.Black.Opacity(0.5f); - } + protected override void OnLoadingStart() => onHoverLostAction(); - protected override void OnLoadingStart() + protected override void OnLoadingFinished() { - sideNumber.Hide(); - borderContainer.BorderThickness = 0; + if (IsHovered) + onHoverAction(); } protected override bool OnHover(HoverEvent e) { - if (comment.IsVoted) - hoverLayer.Show(); - - if (!IsLoading) - { - borderContainer.BorderThickness = 3; - - if (!comment.IsVoted) - sideNumber.Show(); - } - + onHoverAction(); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { + onHoverLostAction(); base.OnHoverLost(e); + } - if (comment.IsVoted) + private void onHoverLostAction() + { + if (isVoted.Value) hoverLayer.Hide(); else sideNumber.Hide(); borderContainer.BorderThickness = 0; } + + private void onHoverAction() + { + if (!IsLoading) + { + borderContainer.BorderThickness = 3; + + if (!isVoted.Value) + sideNumber.Show(); + else + hoverLayer.Show(); + } + } + + private void setCount(int count) => votesCounter.Text = $"+{count}"; + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + request?.Cancel(); + } } } From 6b196a6ce7548b180afebed93adfaf0d5062ef55 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 17 Oct 2019 15:24:51 +0300 Subject: [PATCH 1811/2815] CI fixes --- osu.Game/Graphics/UserInterface/LoadingButton.cs | 7 ++++--- osu.Game/Overlays/Comments/DrawableComment.cs | 10 ++++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/LoadingButton.cs b/osu.Game/Graphics/UserInterface/LoadingButton.cs index 1557a90c4a..46a4c70666 100644 --- a/osu.Game/Graphics/UserInterface/LoadingButton.cs +++ b/osu.Game/Graphics/UserInterface/LoadingButton.cs @@ -45,15 +45,16 @@ namespace osu.Game.Graphics.UserInterface set => loading.Size = value; } - private readonly Container background; private readonly LoadingAnimation loading; private readonly Drawable content; - public LoadingButton() + protected LoadingButton() { + Container background; + Child = background = CreateBackground(); - background.AddRange(new Drawable[] + background.AddRange(new[] { content = CreateContent(), loading = new LoadingAnimation diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index bc53af09d3..b376900b18 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -81,11 +81,17 @@ namespace osu.Game.Overlays.Comments Spacing = new Vector2(5, 0), Children = new Drawable[] { - votePill = new VotePill(comment) + new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, - AlwaysPresent = true, + Width = 40, + AutoSizeAxes = Axes.Y, + Child = votePill = new VotePill(comment) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + } }, new UpdateableAvatar(comment.User) { From a858e713f88e7f009c8a2269a72d41d432357eda Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 17 Oct 2019 15:40:09 +0300 Subject: [PATCH 1812/2815] Fix multiple spaces --- osu.Game/Overlays/Comments/DrawableComment.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index b376900b18..3fb9867f0e 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -87,7 +87,7 @@ namespace osu.Game.Overlays.Comments Origin = Anchor.Centre, Width = 40, AutoSizeAxes = Axes.Y, - Child = votePill = new VotePill(comment) + Child = votePill = new VotePill(comment) { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, From 9ee63a8c1abb260ccd196662c9e1e9705bb9f086 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 17 Oct 2019 16:28:32 +0300 Subject: [PATCH 1813/2815] Apply suggested changes --- osu.Game/Online/API/Requests/Responses/CommentBundle.cs | 6 +----- osu.Game/Overlays/Comments/VotePill.cs | 8 ++++---- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/CommentBundle.cs b/osu.Game/Online/API/Requests/Responses/CommentBundle.cs index f910c738ac..7db3126ade 100644 --- a/osu.Game/Online/API/Requests/Responses/CommentBundle.cs +++ b/osu.Game/Online/API/Requests/Responses/CommentBundle.cs @@ -47,15 +47,11 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"included_comments")] public List IncludedComments { get; set; } - private List userVotes; - [JsonProperty(@"user_votes")] - public List UserVotes + private List userVotes { - get => userVotes; set { - userVotes = value; value.ForEach(v => { Comments.ForEach(c => diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index cd50546b45..d8288c8ec4 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -40,11 +40,11 @@ namespace osu.Game.Overlays.Comments private CommentVoteRequest request; private readonly BindableBool isVoted = new BindableBool(); + private readonly BindableInt votesCount = new BindableInt(); public VotePill(Comment comment) { this.comment = comment; - setCount(comment.VotesCount); Action = onAction; @@ -64,7 +64,9 @@ namespace osu.Game.Overlays.Comments { base.LoadComplete(); isVoted.Value = comment.IsVoted; + votesCount.Value = comment.VotesCount; isVoted.BindValueChanged(voted => background.Colour = voted.NewValue ? AccentColour : OsuColour.Gray(0.05f), true); + votesCount.BindValueChanged(count => votesCounter.Text = $"+{count.NewValue}", true); } private void onAction() @@ -77,7 +79,7 @@ namespace osu.Game.Overlays.Comments private void onSuccess(CommentBundle response) { isVoted.Value = !isVoted.Value; - setCount(response.Comments.First().VotesCount); + votesCount.Value = response.Comments.Single().VotesCount; IsLoading = false; } @@ -168,8 +170,6 @@ namespace osu.Game.Overlays.Comments } } - private void setCount(int count) => votesCounter.Text = $"+{count}"; - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 22511e41e29ab2bea9b01f678296f91224d7d3c8 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 17 Oct 2019 23:20:01 +0300 Subject: [PATCH 1814/2815] Use received data to set isVoted bindable --- osu.Game/Overlays/Comments/VotePill.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index d8288c8ec4..d1a78cb368 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -78,8 +78,9 @@ namespace osu.Game.Overlays.Comments private void onSuccess(CommentBundle response) { - isVoted.Value = !isVoted.Value; - votesCount.Value = response.Comments.Single().VotesCount; + var receivedComment = response.Comments.Single(); + isVoted.Value = receivedComment.IsVoted; + votesCount.Value = receivedComment.VotesCount; IsLoading = false; } From e5b50b5e1fda974c7e47ec5be852ff0ec98bcf63 Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 17 Oct 2019 13:54:36 -0700 Subject: [PATCH 1815/2815] Fix slider bar regression when using arrows --- osu.Game/Graphics/UserInterface/FixedSearchTextBox.cs | 10 ++++++++++ osu.Game/Overlays/SettingsPanel.cs | 4 ++-- osu.Game/Screens/Select/FilterControl.cs | 9 ++------- 3 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 osu.Game/Graphics/UserInterface/FixedSearchTextBox.cs diff --git a/osu.Game/Graphics/UserInterface/FixedSearchTextBox.cs b/osu.Game/Graphics/UserInterface/FixedSearchTextBox.cs new file mode 100644 index 0000000000..0654746a6e --- /dev/null +++ b/osu.Game/Graphics/UserInterface/FixedSearchTextBox.cs @@ -0,0 +1,10 @@ +// 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.Graphics.UserInterface +{ + public class FixedSearchTextBox : SearchTextBox + { + public override bool HandleLeftRightArrows => false; + } +} diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index 37e7b62483..119b9a2b8c 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -37,7 +37,7 @@ namespace osu.Game.Overlays protected SettingsSectionsContainer SectionsContainer; - private SearchTextBox searchTextBox; + private FixedSearchTextBox searchTextBox; /// /// Provide a source for the toolbar height. @@ -80,7 +80,7 @@ namespace osu.Game.Overlays Masking = true, RelativeSizeAxes = Axes.Both, ExpandableHeader = CreateHeader(), - FixedHeader = searchTextBox = new SearchTextBox + FixedHeader = searchTextBox = new FixedSearchTextBox { RelativeSizeAxes = Axes.X, Origin = Anchor.TopCentre, diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 2c878f8d90..7aca11da2f 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -49,7 +49,7 @@ namespace osu.Game.Screens.Select return criteria; } - private readonly SongSelectTextBox searchTextBox; + private readonly FixedSearchTextBox searchTextBox; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || groupTabs.ReceivePositionalInputAt(screenSpacePos) || sortTabs.ReceivePositionalInputAt(screenSpacePos); @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Select Origin = Anchor.TopRight, Children = new Drawable[] { - searchTextBox = new SongSelectTextBox { RelativeSizeAxes = Axes.X }, + searchTextBox = new FixedSearchTextBox { RelativeSizeAxes = Axes.X }, new Box { RelativeSizeAxes = Axes.X, @@ -170,10 +170,5 @@ namespace osu.Game.Screens.Select } private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria()); - - private class SongSelectTextBox : SearchTextBox - { - public override bool HandleLeftRightArrows => false; - } } } From 9daafb46365b369b9a656c52127f89d1e15a0baa Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 18 Oct 2019 03:06:01 +0300 Subject: [PATCH 1816/2815] Simplify hover/unhover logic --- osu.Game/Overlays/Comments/VotePill.cs | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index d1a78cb368..5eade6fc46 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -128,7 +128,7 @@ namespace osu.Game.Overlays.Comments AlwaysPresent = true, }; - protected override void OnLoadingStart() => onHoverLostAction(); + protected override void OnLoadingStart() => updateDisplay(); protected override void OnLoadingFinished() { @@ -144,31 +144,27 @@ namespace osu.Game.Overlays.Comments protected override void OnHoverLost(HoverLostEvent e) { - onHoverLostAction(); + updateDisplay(); base.OnHoverLost(e); } - private void onHoverLostAction() + private void updateDisplay() { if (isVoted.Value) - hoverLayer.Hide(); - else + { + hoverLayer.FadeTo(IsHovered ? 1 : 0); sideNumber.Hide(); + } + else + sideNumber.FadeTo(IsHovered ? 1 : 0); - borderContainer.BorderThickness = 0; + borderContainer.BorderThickness = IsHovered ? 3 : 0; } private void onHoverAction() { if (!IsLoading) - { - borderContainer.BorderThickness = 3; - - if (!isVoted.Value) - sideNumber.Show(); - else - hoverLayer.Show(); - } + updateDisplay(); } protected override void Dispose(bool isDisposing) From 5ccdd2b203512f9a6cb00947546b5474bfcd46a2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Oct 2019 20:05:25 +0900 Subject: [PATCH 1817/2815] Mask the osu! beatsnap grid --- osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs index f701712739..bc0f76f000 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs @@ -13,6 +13,7 @@ namespace osu.Game.Rulesets.Osu.Edit public OsuDistanceSnapGrid(OsuHitObject hitObject) : base(hitObject, hitObject.StackedEndPosition) { + Masking = true; } protected override float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) From bc76a9cb8c4c80e352152d86fb9abf80f3eff284 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Oct 2019 20:07:11 +0900 Subject: [PATCH 1818/2815] Expose selection changed event from BlueprintContainer --- .../Compose/Components/BlueprintContainer.cs | 16 ++++++++++------ .../Edit/Compose/Components/SelectionHandler.cs | 4 ++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index cb3d3b71fc..ef1eb09e7c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.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 osu.Framework.Allocation; @@ -19,15 +20,14 @@ namespace osu.Game.Screens.Edit.Compose.Components { public class BlueprintContainer : CompositeDrawable { - private SelectionBlueprintContainer selectionBlueprints; + public event Action> SelectionChanged; + private SelectionBlueprintContainer selectionBlueprints; private Container placementBlueprintContainer; private PlacementBlueprint currentPlacement; private SelectionHandler selectionHandler; private InputManager inputManager; - private IEnumerable selections => selectionBlueprints.Children.Where(c => c.IsAlive); - [Resolved] private HitObjectComposer composer { get; set; } @@ -196,9 +196,9 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The rectangle to perform a selection on in screen-space coordinates. private void select(RectangleF rect) { - foreach (var blueprint in selections.ToList()) + foreach (var blueprint in selectionBlueprints) { - if (blueprint.IsPresent && rect.Contains(blueprint.SelectionPoint)) + if (blueprint.IsAlive && blueprint.IsPresent && rect.Contains(blueprint.SelectionPoint)) blueprint.Select(); else blueprint.Deselect(); @@ -208,18 +208,22 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Deselects all selected s. /// - private void deselectAll() => selections.ToList().ForEach(m => m.Deselect()); + private void deselectAll() => selectionHandler.SelectedBlueprints.ToList().ForEach(m => m.Deselect()); private void onBlueprintSelected(SelectionBlueprint blueprint) { selectionHandler.HandleSelected(blueprint); selectionBlueprints.ChangeChildDepth(blueprint, 1); + + SelectionChanged?.Invoke(selectionHandler.SelectedHitObjects); } private void onBlueprintDeselected(SelectionBlueprint blueprint) { selectionHandler.HandleDeselected(blueprint); selectionBlueprints.ChangeChildDepth(blueprint, 0); + + SelectionChanged?.Invoke(selectionHandler.SelectedHitObjects); } private void onSelectionRequested(SelectionBlueprint blueprint, InputState state) => selectionHandler.HandleSelectionRequested(blueprint, state); diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index c9e862d99e..f1467ff2c6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -26,10 +26,10 @@ namespace osu.Game.Screens.Edit.Compose.Components { public const float BORDER_RADIUS = 2; - protected IEnumerable SelectedBlueprints => selectedBlueprints; + public IEnumerable SelectedBlueprints => selectedBlueprints; private readonly List selectedBlueprints; - protected IEnumerable SelectedHitObjects => selectedBlueprints.Select(b => b.HitObject.HitObject); + public IEnumerable SelectedHitObjects => selectedBlueprints.Select(b => b.HitObject.HitObject); private Drawable outline; From d3e38f5e5aeeeb7c88ca2e7c5ff507c8dd82ba02 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Oct 2019 20:10:03 +0900 Subject: [PATCH 1819/2815] Make the editor beatmap protected --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 25 ++++++++++----------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index a267d7c44d..bf31b76dc4 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -30,12 +30,11 @@ namespace osu.Game.Rulesets.Edit where TObject : HitObject { protected IRulesetConfigManager Config { get; private set; } - + protected EditorBeatmap EditorBeatmap { get; private set; } protected readonly Ruleset Ruleset; private IWorkingBeatmap workingBeatmap; private Beatmap playableBeatmap; - private EditorBeatmap editorBeatmap; private IBeatmapProcessor beatmapProcessor; private DrawableEditRulesetWrapper drawableRulesetWrapper; @@ -129,14 +128,14 @@ namespace osu.Game.Rulesets.Edit beatmapProcessor = Ruleset.CreateBeatmapProcessor(playableBeatmap); - editorBeatmap = new EditorBeatmap(playableBeatmap); - editorBeatmap.HitObjectAdded += addHitObject; - editorBeatmap.HitObjectRemoved += removeHitObject; - editorBeatmap.StartTimeChanged += updateHitObject; + EditorBeatmap = new EditorBeatmap(playableBeatmap); + EditorBeatmap.HitObjectAdded += addHitObject; + EditorBeatmap.HitObjectRemoved += removeHitObject; + EditorBeatmap.StartTimeChanged += updateHitObject; var dependencies = new DependencyContainer(parent); - dependencies.CacheAs(editorBeatmap); - dependencies.CacheAs>(editorBeatmap); + dependencies.CacheAs(EditorBeatmap); + dependencies.CacheAs>(EditorBeatmap); Config = dependencies.Get().GetConfigFor(Ruleset); @@ -189,18 +188,18 @@ namespace osu.Game.Rulesets.Edit { } - public void EndPlacement(HitObject hitObject) => editorBeatmap.Add(hitObject); + public void EndPlacement(HitObject hitObject) => EditorBeatmap.Add(hitObject); - public void Delete(HitObject hitObject) => editorBeatmap.Remove(hitObject); + public void Delete(HitObject hitObject) => EditorBeatmap.Remove(hitObject); protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - if (editorBeatmap != null) + if (EditorBeatmap != null) { - editorBeatmap.HitObjectAdded -= addHitObject; - editorBeatmap.HitObjectRemoved -= removeHitObject; + EditorBeatmap.HitObjectAdded -= addHitObject; + EditorBeatmap.HitObjectRemoved -= removeHitObject; } } } From c4704f6a2993fc01aaf5e875f6d951cb6edaa210 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Oct 2019 20:20:07 +0900 Subject: [PATCH 1820/2815] Add beat snap grid to the composer --- .../Edit/OsuHitObjectComposer.cs | 28 ++++++++ osu.Game/Rulesets/Edit/HitObjectComposer.cs | 70 +++++++++++++++++-- 2 files changed, 92 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 1c040e9dee..a5590a999d 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -2,10 +2,12 @@ // 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.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; @@ -52,5 +54,31 @@ namespace osu.Game.Rulesets.Osu.Edit return base.CreateBlueprintFor(hitObject); } + + protected override DistanceSnapGrid CreateDistanceSnapGrid(IEnumerable selectedHitObjects) + { + var objects = selectedHitObjects.ToList(); + + if (objects.Count == 0) + { + var lastObject = EditorBeatmap.HitObjects.LastOrDefault(h => h.StartTime < EditorClock.CurrentTime); + + if (lastObject == null) + return null; + + return new OsuDistanceSnapGrid(lastObject); + } + else + { + double minTime = objects.Min(h => h.StartTime); + + var lastObject = EditorBeatmap.HitObjects.LastOrDefault(h => h.StartTime < minTime); + + if (lastObject == null) + return null; + + return new OsuDistanceSnapGrid(lastObject); + } + } } } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index bf31b76dc4..49ecea5fd0 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -33,12 +34,17 @@ namespace osu.Game.Rulesets.Edit protected EditorBeatmap EditorBeatmap { get; private set; } protected readonly Ruleset Ruleset; + [Resolved] + protected IFrameBasedClock EditorClock { get; private set; } + private IWorkingBeatmap workingBeatmap; private Beatmap playableBeatmap; private IBeatmapProcessor beatmapProcessor; private DrawableEditRulesetWrapper drawableRulesetWrapper; private BlueprintContainer blueprintContainer; + private Container distanceSnapGridContainer; + private DistanceSnapGrid distanceSnapGrid; private readonly List layerContainers = new List(); private InputManager inputManager; @@ -65,11 +71,13 @@ namespace osu.Game.Rulesets.Edit return; } - var layerBelowRuleset = drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer(); - layerBelowRuleset.Child = new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both }; + var layerBelowRuleset = drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChildren(new Drawable[] + { + distanceSnapGridContainer = new Container { RelativeSizeAxes = Axes.Both }, + new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both } + }); - var layerAboveRuleset = drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer(); - layerAboveRuleset.Child = blueprintContainer = new BlueprintContainer(); + var layerAboveRuleset = drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChild(blueprintContainer = new BlueprintContainer()); layerContainers.Add(layerBelowRuleset); layerContainers.Add(layerAboveRuleset); @@ -112,11 +120,13 @@ namespace osu.Game.Rulesets.Edit }; toolboxCollection.Items = - CompositionTools.Select(t => new RadioButton(t.Name, () => blueprintContainer.CurrentTool = t)) - .Prepend(new RadioButton("Select", () => blueprintContainer.CurrentTool = null)) + CompositionTools.Select(t => new RadioButton(t.Name, () => selectTool(t))) + .Prepend(new RadioButton("Select", () => selectTool(null))) .ToList(); toolboxCollection.Items[0].Select(); + + blueprintContainer.SelectionChanged += selectionChanged; } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -149,6 +159,14 @@ namespace osu.Game.Rulesets.Edit inputManager = GetContainingInputManager(); } + protected override void Update() + { + base.Update(); + + if (EditorClock.ElapsedFrameTime != 0 && blueprintContainer.CurrentTool != null) + showGridFor(Enumerable.Empty()); + } + protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); @@ -162,6 +180,38 @@ namespace osu.Game.Rulesets.Edit }); } + private void selectionChanged(IEnumerable selectedHitObjects) + { + var hitObjects = selectedHitObjects.ToArray(); + + if (!hitObjects.Any()) + distanceSnapGridContainer.Hide(); + else + showGridFor(hitObjects); + } + + private void selectTool(HitObjectCompositionTool tool) + { + blueprintContainer.CurrentTool = tool; + + if (tool == null) + distanceSnapGridContainer.Hide(); + else + showGridFor(Enumerable.Empty()); + } + + private void showGridFor(IEnumerable selectedHitObjects) + { + distanceSnapGridContainer.Clear(); + distanceSnapGrid = CreateDistanceSnapGrid(selectedHitObjects); + + if (distanceSnapGrid != null) + { + distanceSnapGridContainer.Child = distanceSnapGrid; + distanceSnapGridContainer.Show(); + } + } + private void addHitObject(HitObject hitObject) => updateHitObject(hitObject); private void removeHitObject(HitObject hitObject) @@ -232,5 +282,13 @@ namespace osu.Game.Rulesets.Edit /// Creates a which outlines s and handles movement of selections. /// public virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler(); + + /// + /// Creates the applicable for a selection. + /// + /// The selection. + /// The for . + [CanBeNull] + protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable selectedHitObjects) => null; } } From 1dc7c59853ae8fc5bc22c44bdb9fcc97a15b087d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Oct 2019 20:34:02 +0900 Subject: [PATCH 1821/2815] Implement selection position snapping --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 ++++ .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 8 ++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 49ecea5fd0..3f46a6deaa 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -242,6 +242,8 @@ namespace osu.Game.Rulesets.Edit public void Delete(HitObject hitObject) => EditorBeatmap.Remove(hitObject); + public override Vector2 GetSnappedPosition(Vector2 position) => beatSnapGrid?.GetSnapPosition(position) ?? position; + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -290,5 +292,7 @@ namespace osu.Game.Rulesets.Edit /// The for . [CanBeNull] protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable selectedHitObjects) => null; + + public abstract Vector2 GetSnappedPosition(Vector2 position); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index ef1eb09e7c..295b21a99e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -230,9 +230,13 @@ namespace osu.Game.Screens.Edit.Compose.Components private void onDragRequested(SelectionBlueprint blueprint, DragEvent dragEvent) { - var movePosition = blueprint.ScreenSpaceMovementStartPosition + dragEvent.ScreenSpaceMousePosition - dragEvent.ScreenSpaceMouseDownPosition; + HitObject draggedObject = blueprint.HitObject.HitObject; - selectionHandler.HandleMovement(new MoveSelectionEvent(blueprint, blueprint.ScreenSpaceMovementStartPosition, movePosition)); + Vector2 movePosition = blueprint.ScreenSpaceMovementStartPosition + dragEvent.ScreenSpaceMousePosition - dragEvent.ScreenSpaceMouseDownPosition; + Vector2 snappedPosition = composer.GetSnappedPosition(ToLocalSpace(movePosition)); + + // Move the hitobjects + selectionHandler.HandleMovement(new MoveSelectionEvent(blueprint, blueprint.ScreenSpaceMovementStartPosition, ToScreenSpace(snappedPosition))); } protected override void Dispose(bool isDisposing) From ba4402207adaa23d1954e91ce381e7516428f2d5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Oct 2019 20:34:16 +0900 Subject: [PATCH 1822/2815] Implement selection time snapping --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 ++++ .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 3f46a6deaa..0b2a9cd0fb 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -244,6 +244,8 @@ namespace osu.Game.Rulesets.Edit public override Vector2 GetSnappedPosition(Vector2 position) => beatSnapGrid?.GetSnapPosition(position) ?? position; + public override double GetSnappedTime(double startTime, Vector2 position) => beatSnapGrid?.GetSnapTime(position) ?? startTime; + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -294,5 +296,7 @@ namespace osu.Game.Rulesets.Edit protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable selectedHitObjects) => null; public abstract Vector2 GetSnappedPosition(Vector2 position); + + public abstract double GetSnappedTime(double startTime, Vector2 screenSpacePosition); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 295b21a99e..5d7f9ab788 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { @@ -237,6 +238,11 @@ namespace osu.Game.Screens.Edit.Compose.Components // Move the hitobjects selectionHandler.HandleMovement(new MoveSelectionEvent(blueprint, blueprint.ScreenSpaceMovementStartPosition, ToScreenSpace(snappedPosition))); + + // Apply the start time at the newly snapped-to position + double offset = composer.GetSnappedTime(draggedObject.StartTime, snappedPosition) - draggedObject.StartTime; + foreach (HitObject obj in selectionHandler.SelectedHitObjects) + obj.StartTime += offset; } protected override void Dispose(bool isDisposing) From b047e05d8618dab7e47c8d03c4af151ca53b1d8e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 18 Oct 2019 13:18:16 +0900 Subject: [PATCH 1823/2815] Fix bad variable names --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 0b2a9cd0fb..c0c4cccca3 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -242,9 +242,9 @@ namespace osu.Game.Rulesets.Edit public void Delete(HitObject hitObject) => EditorBeatmap.Remove(hitObject); - public override Vector2 GetSnappedPosition(Vector2 position) => beatSnapGrid?.GetSnapPosition(position) ?? position; + public override Vector2 GetSnappedPosition(Vector2 position) => distanceSnapGrid?.GetSnapPosition(position) ?? position; - public override double GetSnappedTime(double startTime, Vector2 position) => beatSnapGrid?.GetSnapTime(position) ?? startTime; + public override double GetSnappedTime(double startTime, Vector2 position) => distanceSnapGrid?.GetSnapTime(position) ?? startTime; protected override void Dispose(bool isDisposing) { From 9a896d52bf795b77159474deae5d7aa42cc4fa3e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 18 Oct 2019 13:18:41 +0900 Subject: [PATCH 1824/2815] Fix nested hitobjects not updating --- .../Objects/Drawables/DrawableHitObject.cs | 17 +++++++++++++++-- osu.Game/Rulesets/Objects/HitObject.cs | 8 ++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 99b0c07570..0a8648516e 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -77,6 +77,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public JudgementResult Result { get; private set; } + private Bindable startTimeBindable; private Bindable comboIndexBindable; public override bool RemoveWhenNotAlive => false; @@ -126,7 +127,10 @@ namespace osu.Game.Rulesets.Objects.Drawables { base.LoadComplete(); - Apply(HitObject); + HitObject.DefaultsApplied += onDefaultsApplied; + + startTimeBindable = HitObject.StartTimeBindable.GetBoundCopy(); + startTimeBindable.BindValueChanged(_ => updateState(ArmedState.Idle, true)); if (HitObject is IHasComboInformation combo) { @@ -135,9 +139,12 @@ namespace osu.Game.Rulesets.Objects.Drawables } updateState(ArmedState.Idle, true); + onDefaultsApplied(); } - protected void Apply(HitObject hitObject) + private void onDefaultsApplied() => apply(HitObject); + + private void apply(HitObject hitObject) { #pragma warning disable 618 // can be removed 20200417 if (GetType().GetMethod(nameof(AddNested), BindingFlags.NonPublic | BindingFlags.Instance)?.DeclaringType != typeof(DrawableHitObject)) @@ -493,6 +500,12 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// The that provides the scoring information. protected virtual JudgementResult CreateResult(Judgement judgement) => new JudgementResult(HitObject, judgement); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + HitObject.DefaultsApplied -= onDefaultsApplied; + } } public abstract class DrawableHitObject : DrawableHitObject diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index eb8652443f..6211fe50e6 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.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 JetBrains.Annotations; @@ -28,6 +29,11 @@ namespace osu.Game.Rulesets.Objects /// private const double control_point_leniency = 1; + /// + /// Invoked after has completed on this . + /// + public event Action DefaultsApplied; + public readonly Bindable StartTimeBindable = new Bindable(); /// @@ -113,6 +119,8 @@ namespace osu.Game.Rulesets.Objects foreach (var h in nestedHitObjects) h.ApplyDefaults(controlPointInfo, difficulty); + + DefaultsApplied?.Invoke(); } protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) From cb301a46612839070fcc0bd531939eae4be05458 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 18 Oct 2019 13:18:57 +0900 Subject: [PATCH 1825/2815] Improve performance of intra-frame updates/deletions --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 22 ++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index c0c4cccca3..a5b7991a52 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Logging; +using osu.Framework.Threading; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Configuration; @@ -23,6 +24,7 @@ using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.RadioButtons; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose.Components; +using osuTK; namespace osu.Game.Rulesets.Edit { @@ -212,19 +214,21 @@ namespace osu.Game.Rulesets.Edit } } + private ScheduledDelegate scheduledUpdate; + private void addHitObject(HitObject hitObject) => updateHitObject(hitObject); - private void removeHitObject(HitObject hitObject) - { - beatmapProcessor?.PreProcess(); - beatmapProcessor?.PostProcess(); - } + private void removeHitObject(HitObject hitObject) => updateHitObject(null); - private void updateHitObject(HitObject hitObject) + private void updateHitObject([CanBeNull] HitObject hitObject) { - beatmapProcessor?.PreProcess(); - hitObject.ApplyDefaults(playableBeatmap.ControlPointInfo, playableBeatmap.BeatmapInfo.BaseDifficulty); - beatmapProcessor?.PostProcess(); + scheduledUpdate?.Cancel(); + scheduledUpdate = Schedule(() => + { + beatmapProcessor?.PreProcess(); + hitObject?.ApplyDefaults(playableBeatmap.ControlPointInfo, playableBeatmap.BeatmapInfo.BaseDifficulty); + beatmapProcessor?.PostProcess(); + }); } public override IEnumerable HitObjects => drawableRulesetWrapper.Playfield.AllHitObjects; From 5d3d25d3b69060342deb8ba58dfe192735c809a7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 18 Oct 2019 13:24:25 +0900 Subject: [PATCH 1826/2815] Make method private for now --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 99b0c07570..18d45f3724 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Objects.Drawables { base.LoadComplete(); - Apply(HitObject); + apply(HitObject); if (HitObject is IHasComboInformation combo) { @@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Objects.Drawables updateState(ArmedState.Idle, true); } - protected void Apply(HitObject hitObject) + private void apply(HitObject hitObject) { #pragma warning disable 618 // can be removed 20200417 if (GetType().GetMethod(nameof(AddNested), BindingFlags.NonPublic | BindingFlags.Instance)?.DeclaringType != typeof(DrawableHitObject)) From 463079e1480d7fa17c779ad16a33b710f84fc46c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 18 Oct 2019 13:48:59 +0900 Subject: [PATCH 1827/2815] Implement placement snapping --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 1 + .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index a5b7991a52..a51728ba88 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -240,6 +240,7 @@ namespace osu.Game.Rulesets.Edit public void BeginPlacement(HitObject hitObject) { + hitObject.StartTime = GetSnappedTime(hitObject.StartTime, inputManager.CurrentState.Mouse.Position); } public void EndPlacement(HitObject hitObject) => EditorBeatmap.Add(hitObject); diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 5d7f9ab788..3bb4266563 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -152,7 +152,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { if (currentPlacement != null) { - currentPlacement.UpdatePosition(e.ScreenSpaceMousePosition); + updatePlacementPosition(e.ScreenSpaceMousePosition); return true; } @@ -187,10 +187,12 @@ namespace osu.Game.Screens.Edit.Compose.Components placementBlueprintContainer.Child = currentPlacement = blueprint; // Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame - blueprint.UpdatePosition(inputManager.CurrentState.Mouse.Position); + updatePlacementPosition(inputManager.CurrentState.Mouse.Position); } } + private void updatePlacementPosition(Vector2 screenSpacePosition) => currentPlacement.UpdatePosition(ToScreenSpace(composer.GetSnappedPosition(ToLocalSpace(screenSpacePosition)))); + /// /// Select all masks in a given rectangle selection area. /// From b30c84778f5ae5655decbb91ee6f79977dc0aaba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Oct 2019 16:13:01 +0900 Subject: [PATCH 1828/2815] Update WaveContainer to support framework changes --- osu.Game/Graphics/Containers/WaveContainer.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/WaveContainer.cs b/osu.Game/Graphics/Containers/WaveContainer.cs index c01674f5b4..8b87ddaa20 100644 --- a/osu.Game/Graphics/Containers/WaveContainer.cs +++ b/osu.Game/Graphics/Containers/WaveContainer.cs @@ -159,8 +159,15 @@ namespace osu.Game.Graphics.Containers Height = Parent.Parent.DrawSize.Y * 1.5f; } - protected override void PopIn() => this.MoveToY(FinalPosition, APPEAR_DURATION, easing_show); - protected override void PopOut() => this.MoveToY(Parent.Parent.DrawSize.Y, DISAPPEAR_DURATION, easing_hide); + protected override void PopIn() => Schedule(() => this.MoveToY(FinalPosition, APPEAR_DURATION, easing_show)); + + protected override void PopOut() + { + double duration = IsLoaded ? DISAPPEAR_DURATION : 0; + + // scheduling is required as parent may not be present at the time this is called. + Schedule(() => this.MoveToY(Parent.Parent.DrawSize.Y, duration, easing_hide)); + } } } } From 6b0976ff1e2e1d13747f0e6a06114ff52b056b26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Oct 2019 15:07:06 +0900 Subject: [PATCH 1829/2815] Remove a weird unicode charcter from file --- osu.Game/Overlays/AccountCreation/ScreenWarning.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs index be417f4aac..f91d2e3323 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs @@ -121,7 +121,7 @@ namespace osu.Game.Overlays.AccountCreation multiAccountExplanationText.AddText("? osu! has a policy of "); multiAccountExplanationText.AddText("one account per person!", cp => cp.Colour = colours.Yellow); multiAccountExplanationText.AddText(" Please be aware that creating more than one account per person may result in "); - multiAccountExplanationText.AddText("permanent deactivation of accounts", cp => cp.Colour = colours.Yellow); + multiAccountExplanationText.AddText("permanent deactivation of accounts", cp => cp.Colour = colours.Yellow); multiAccountExplanationText.AddText("."); furtherAssistance.AddText("Need further assistance? Contact us via our "); From 89f50b26f72f56f932df01a99f94f93de621da8e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 18 Oct 2019 17:32:11 +0900 Subject: [PATCH 1830/2815] Fix hitobject combo colour potentially not getting adjusted --- .../TestSceneHitObjectAccentColour.cs | 143 ++++++++++++++++++ .../Objects/Drawables/DrawableHitObject.cs | 2 +- 2 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs new file mode 100644 index 0000000000..6d7159a825 --- /dev/null +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs @@ -0,0 +1,143 @@ +// 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.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Textures; +using osu.Framework.Testing; +using osu.Game.Audio; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Skinning; +using osu.Game.Tests.Visual; +using osuTK.Graphics; + +namespace osu.Game.Tests.Gameplay +{ + [HeadlessTest] + public class TestSceneHitObjectAccentColour : OsuTestScene + { + private Container skinContainer; + + [SetUp] + public void Setup() => Schedule(() => Child = skinContainer = new SkinProvidingContainer(new TestSkin())); + + [Test] + public void TestChangeComboIndexBeforeLoad() + { + TestDrawableHitObject hitObject = null; + + AddStep("set combo and add hitobject", () => + { + hitObject = new TestDrawableHitObject(); + hitObject.HitObject.ComboIndex = 1; + + skinContainer.Add(hitObject); + }); + + AddAssert("combo colour is green", () => hitObject.AccentColour.Value == Color4.Green); + } + + [Test] + public void TestChangeComboIndexDuringLoad() + { + TestDrawableHitObject hitObject = null; + + AddStep("add hitobject and set combo", () => + { + skinContainer.Add(hitObject = new TestDrawableHitObject()); + hitObject.HitObject.ComboIndex = 1; + }); + + AddAssert("combo colour is green", () => hitObject.AccentColour.Value == Color4.Green); + } + + [Test] + public void TestChangeComboIndexAfterLoad() + { + TestDrawableHitObject hitObject = null; + + AddStep("add hitobject", () => skinContainer.Add(hitObject = new TestDrawableHitObject())); + AddAssert("combo colour is red", () => hitObject.AccentColour.Value == Color4.Red); + + AddStep("change combo", () => hitObject.HitObject.ComboIndex = 1); + AddAssert("combo colour is green", () => hitObject.AccentColour.Value == Color4.Green); + } + + private class TestDrawableHitObject : DrawableHitObject + { + public TestDrawableHitObject() + : base(new TestHitObjectWithCombo()) + { + } + } + + private class TestHitObjectWithCombo : HitObject, IHasComboInformation + { + public bool NewCombo { get; } = false; + public int ComboOffset { get; } = 0; + + public Bindable IndexInCurrentComboBindable { get; } = new Bindable(); + + public int IndexInCurrentCombo + { + get => IndexInCurrentComboBindable.Value; + set => IndexInCurrentComboBindable.Value = value; + } + + public Bindable ComboIndexBindable { get; } = new Bindable(); + + public int ComboIndex + { + get => ComboIndexBindable.Value; + set => ComboIndexBindable.Value = value; + } + + public Bindable LastInComboBindable { get; } = new Bindable(); + + public bool LastInCombo + { + get => LastInComboBindable.Value; + set => LastInComboBindable.Value = value; + } + } + + private class TestSkin : ISkin + { + public readonly List ComboColours = new List + { + Color4.Red, + Color4.Green + }; + + public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotImplementedException(); + + public Texture GetTexture(string componentName) => throw new NotImplementedException(); + + public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); + + public IBindable GetConfig(TLookup lookup) + { + switch (lookup) + { + case GlobalSkinConfiguration global: + switch (global) + { + case GlobalSkinConfiguration.ComboColours: + return SkinUtils.As(new Bindable>(ComboColours)); + } + + break; + } + + throw new NotImplementedException(); + } + } + } +} diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 7f3bfd3b5c..0948452ceb 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -128,7 +128,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (HitObject is IHasComboInformation combo) { comboIndexBindable = combo.ComboIndexBindable.GetBoundCopy(); - comboIndexBindable.BindValueChanged(_ => updateAccentColour()); + comboIndexBindable.BindValueChanged(_ => updateAccentColour(), true); } updateState(ArmedState.Idle, true); From 31313ec9e134f0a26f7847267126d89175d6817f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 18 Oct 2019 17:56:31 +0900 Subject: [PATCH 1831/2815] Fix potential nullref --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index a51728ba88..1ec36a59a8 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -240,7 +240,8 @@ namespace osu.Game.Rulesets.Edit public void BeginPlacement(HitObject hitObject) { - hitObject.StartTime = GetSnappedTime(hitObject.StartTime, inputManager.CurrentState.Mouse.Position); + if (distanceSnapGrid != null) + hitObject.StartTime = GetSnappedTime(hitObject.StartTime, distanceSnapGrid.ToLocalSpace(inputManager.CurrentState.Mouse.Position)); } public void EndPlacement(HitObject hitObject) => EditorBeatmap.Add(hitObject); From 71d45d41d1f85125dfae1171e3b5a2f954f4053d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Oct 2019 17:46:39 +0900 Subject: [PATCH 1832/2815] Add basic visualisation of different control point types --- .../ControlPoints/ControlPointInfo.cs | 6 + osu.Game/Screens/Edit/Timing/TimingScreen.cs | 133 +++++++++++++----- 2 files changed, 105 insertions(+), 34 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 855084ad02..68eb0ec6d1 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -36,6 +36,12 @@ namespace osu.Game.Beatmaps.ControlPoints [JsonProperty] public SortedList EffectPoints { get; private set; } = new SortedList(Comparer.Default); + public IReadOnlyList AllControlPoints => + TimingPoints + .Concat((IEnumerable)DifficultyPoints) + .Concat(SamplePoints) + .Concat(EffectPoints).ToArray(); + /// /// Finds the difficulty control point that is active at . /// diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index be7058ae08..97ec03e6dd 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -2,13 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Globalization; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Beatmaps; @@ -16,6 +16,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osuTK; namespace osu.Game.Screens.Edit.Timing { @@ -64,7 +65,7 @@ namespace osu.Game.Screens.Edit.Timing RelativeSizeAxes = Axes.Both, Child = new ControlPointTable { - ControlPoints = Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints + ControlPoints = Beatmap.Value.Beatmap.ControlPointInfo.AllControlPoints } } }; @@ -115,7 +116,7 @@ namespace osu.Game.Screens.Edit.Timing }); } - public IReadOnlyList ControlPoints + public IEnumerable ControlPoints { set { @@ -125,14 +126,15 @@ namespace osu.Game.Screens.Edit.Timing if (value?.Any() != true) return; - for (int i = 0; i < value.Count; i++) + var grouped = value.GroupBy(cp => cp.Time, cp => cp); + + foreach (var group in grouped) { - var cp = value[i]; - backgroundFlow.Add(new RowBackground { Action = () => controlPoint.Value = cp }); + backgroundFlow.Add(new RowBackground { Action = () => controlPoint.Value = group.First() }); } Columns = createHeaders(); - Content = value.Select((s, i) => createContent(i, s)).ToArray().ToRectangular(); + Content = grouped.Select((s, i) => createContent(i, s)).ToArray().ToRectangular(); } } @@ -141,41 +143,104 @@ namespace osu.Game.Screens.Edit.Timing var columns = new List { new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("offset", Anchor.Centre), - new TableColumn("BPM", Anchor.Centre), - new TableColumn("Meter", Anchor.Centre), - new TableColumn("Sample Set", Anchor.Centre), - new TableColumn("Volume", Anchor.Centre), + new TableColumn("time", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("Attributes", Anchor.Centre), }; return columns.ToArray(); } - private Drawable[] createContent(int index, ControlPoint controlPoint) + private Drawable[] createContent(int index, IGrouping controlPoints) => new Drawable[] { - return new Drawable[] + new OsuSpriteText { - new OsuSpriteText + Text = $"#{index + 1}", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), + Margin = new MarginPadding(10) + }, + new OsuSpriteText + { + Text = $"{controlPoints.Key:n0}ms", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + ChildrenEnumerable = controlPoints.Select(createAttribute).Where(c => c != null), + Padding = new MarginPadding(10), + Spacing = new Vector2(10) + }, + }; + + private Drawable createAttribute(ControlPoint controlPoint) + { + if (controlPoint.AutoGenerated) + return null; + + switch (controlPoint) + { + case TimingControlPoint timing: + return new RowAttribute("timing", $"{60000 / timing.BeatLength:n1}bpm {timing.TimeSignature}"); + + case DifficultyControlPoint difficulty: + + return new RowAttribute("difficulty", $"{difficulty.SpeedMultiplier:n2}x"); + + case EffectControlPoint effect: + return new RowAttribute("effect", $"{(effect.KiaiMode ? "Kiai " : "")}{(effect.OmitFirstBarLine ? "NoBarLine " : "")}"); + + case SampleControlPoint sample: + return new RowAttribute("sample", $"{sample.SampleBank} {sample.SampleVolume}%"); + } + + return null; + } + + private class RowAttribute : CompositeDrawable, IHasTooltip + { + private readonly string header; + private readonly string content; + + public RowAttribute(string header, string content) + { + this.header = header; + this.content = content; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AutoSizeAxes = Axes.X; + + Height = 20; + + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + + Masking = true; + CornerRadius = 5; + + InternalChildren = new Drawable[] { - Text = $"#{index + 1}", - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) - }, - new OsuSpriteText - { - Text = $"{controlPoint.Time}", - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) - }, - new OsuSpriteText - { - Text = $"{(controlPoint as TimingControlPoint)?.BeatLength.ToString(CultureInfo.InvariantCulture) ?? "-"}", - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) - }, - new OsuSpriteText - { - Text = $"{(controlPoint as TimingControlPoint)?.TimeSignature.ToString() ?? "-"}", - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) - }, - }; + new Box + { + Colour = colours.Yellow, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Padding = new MarginPadding(2), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Default.With(weight: FontWeight.SemiBold, size: 12), + Text = header, + Colour = colours.Gray3 + }, + }; + } + + public string TooltipText => content; } protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty); From ffec960b77960b855f9e8b63fc2a9b4e90814d2a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Oct 2019 17:59:54 +0900 Subject: [PATCH 1833/2815] Split out classes --- .../Screens/Edit/Timing/ControlPointTable.cs | 186 +++++++++++++++ osu.Game/Screens/Edit/Timing/RowAttribute.cs | 59 +++++ osu.Game/Screens/Edit/Timing/TimingScreen.cs | 219 ------------------ 3 files changed, 245 insertions(+), 219 deletions(-) create mode 100644 osu.Game/Screens/Edit/Timing/ControlPointTable.cs create mode 100644 osu.Game/Screens/Edit/Timing/RowAttribute.cs diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs new file mode 100644 index 0000000000..1ff88fb5b4 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -0,0 +1,186 @@ +// 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.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Screens.Edit.Timing +{ + public class ControlPointTable : TableContainer + { + private const float horizontal_inset = 20; + private const float row_height = 25; + private const int text_size = 14; + + private readonly FillFlowContainer backgroundFlow; + + [Resolved] + private Bindable controlPoint { get; set; } + + public ControlPointTable() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Padding = new MarginPadding { Horizontal = horizontal_inset }; + RowSize = new Dimension(GridSizeMode.Absolute, row_height); + + AddInternal(backgroundFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Depth = 1f, + Padding = new MarginPadding { Horizontal = -horizontal_inset }, + Margin = new MarginPadding { Top = row_height } + }); + } + + public IEnumerable ControlPoints + { + set + { + Content = null; + backgroundFlow.Clear(); + + if (value?.Any() != true) + return; + + var grouped = value.GroupBy(cp => cp.Time, cp => cp); + + foreach (var group in grouped) + { + backgroundFlow.Add(new RowBackground { Action = () => controlPoint.Value = group.First() }); + } + + Columns = createHeaders(); + Content = grouped.Select((s, i) => createContent(i, s)).ToArray().ToRectangular(); + } + } + + private TableColumn[] createHeaders() + { + var columns = new List + { + new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("time", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("Attributes", Anchor.Centre), + }; + + return columns.ToArray(); + } + + private Drawable[] createContent(int index, IGrouping controlPoints) => new Drawable[] + { + new OsuSpriteText + { + Text = $"#{index + 1}", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), + Margin = new MarginPadding(10) + }, + new OsuSpriteText + { + Text = $"{controlPoints.Key:n0}ms", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + ChildrenEnumerable = controlPoints.Select(createAttribute).Where(c => c != null), + Padding = new MarginPadding(10), + Spacing = new Vector2(10) + }, + }; + + private Drawable createAttribute(ControlPoint controlPoint) + { + if (controlPoint.AutoGenerated) + return null; + + switch (controlPoint) + { + case TimingControlPoint timing: + return new RowAttribute("timing", $"{60000 / timing.BeatLength:n1}bpm {timing.TimeSignature}"); + + case DifficultyControlPoint difficulty: + + return new RowAttribute("difficulty", $"{difficulty.SpeedMultiplier:n2}x"); + + case EffectControlPoint effect: + return new RowAttribute("effect", $"{(effect.KiaiMode ? "Kiai " : "")}{(effect.OmitFirstBarLine ? "NoBarLine " : "")}"); + + case SampleControlPoint sample: + return new RowAttribute("sample", $"{sample.SampleBank} {sample.SampleVolume}%"); + } + + return null; + } + + protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty); + + private class HeaderText : OsuSpriteText + { + public HeaderText(string text) + { + Text = text.ToUpper(); + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Black); + } + } + + public class RowBackground : OsuClickableContainer + { + private const int fade_duration = 100; + + private readonly Box hoveredBackground; + + public RowBackground() + { + RelativeSizeAxes = Axes.X; + Height = 25; + + AlwaysPresent = true; + + CornerRadius = 3; + Masking = true; + + Children = new Drawable[] + { + hoveredBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + hoveredBackground.Colour = colours.Blue; + } + + protected override bool OnHover(HoverEvent e) + { + hoveredBackground.FadeIn(fade_duration, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hoveredBackground.FadeOut(fade_duration, Easing.OutQuint); + base.OnHoverLost(e); + } + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/RowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttribute.cs new file mode 100644 index 0000000000..8716382142 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/RowAttribute.cs @@ -0,0 +1,59 @@ +// 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.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Timing +{ + public class RowAttribute : CompositeDrawable, IHasTooltip + { + private readonly string header; + private readonly string content; + + public RowAttribute(string header, string content) + { + this.header = header; + this.content = content; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AutoSizeAxes = Axes.X; + + Height = 20; + + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + + Masking = true; + CornerRadius = 5; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = colours.Yellow, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Padding = new MarginPadding(2), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Default.With(weight: FontWeight.SemiBold, size: 12), + Text = header, + Colour = colours.Gray3 + }, + }; + } + + public string TooltipText => content; + } +} diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 97ec03e6dd..3dda57becc 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -1,22 +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.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osuTK; namespace osu.Game.Screens.Edit.Timing { @@ -87,216 +80,4 @@ namespace osu.Game.Screens.Edit.Timing } } } - - public class ControlPointTable : TableContainer - { - private const float horizontal_inset = 20; - private const float row_height = 25; - private const int text_size = 14; - - private readonly FillFlowContainer backgroundFlow; - - [Resolved] - private Bindable controlPoint { get; set; } - - public ControlPointTable() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - Padding = new MarginPadding { Horizontal = horizontal_inset }; - RowSize = new Dimension(GridSizeMode.Absolute, row_height); - - AddInternal(backgroundFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Depth = 1f, - Padding = new MarginPadding { Horizontal = -horizontal_inset }, - Margin = new MarginPadding { Top = row_height } - }); - } - - public IEnumerable ControlPoints - { - set - { - Content = null; - backgroundFlow.Clear(); - - if (value?.Any() != true) - return; - - var grouped = value.GroupBy(cp => cp.Time, cp => cp); - - foreach (var group in grouped) - { - backgroundFlow.Add(new RowBackground { Action = () => controlPoint.Value = group.First() }); - } - - Columns = createHeaders(); - Content = grouped.Select((s, i) => createContent(i, s)).ToArray().ToRectangular(); - } - } - - private TableColumn[] createHeaders() - { - var columns = new List - { - new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("time", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("Attributes", Anchor.Centre), - }; - - return columns.ToArray(); - } - - private Drawable[] createContent(int index, IGrouping controlPoints) => new Drawable[] - { - new OsuSpriteText - { - Text = $"#{index + 1}", - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), - Margin = new MarginPadding(10) - }, - new OsuSpriteText - { - Text = $"{controlPoints.Key:n0}ms", - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - ChildrenEnumerable = controlPoints.Select(createAttribute).Where(c => c != null), - Padding = new MarginPadding(10), - Spacing = new Vector2(10) - }, - }; - - private Drawable createAttribute(ControlPoint controlPoint) - { - if (controlPoint.AutoGenerated) - return null; - - switch (controlPoint) - { - case TimingControlPoint timing: - return new RowAttribute("timing", $"{60000 / timing.BeatLength:n1}bpm {timing.TimeSignature}"); - - case DifficultyControlPoint difficulty: - - return new RowAttribute("difficulty", $"{difficulty.SpeedMultiplier:n2}x"); - - case EffectControlPoint effect: - return new RowAttribute("effect", $"{(effect.KiaiMode ? "Kiai " : "")}{(effect.OmitFirstBarLine ? "NoBarLine " : "")}"); - - case SampleControlPoint sample: - return new RowAttribute("sample", $"{sample.SampleBank} {sample.SampleVolume}%"); - } - - return null; - } - - private class RowAttribute : CompositeDrawable, IHasTooltip - { - private readonly string header; - private readonly string content; - - public RowAttribute(string header, string content) - { - this.header = header; - this.content = content; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - AutoSizeAxes = Axes.X; - - Height = 20; - - Anchor = Anchor.CentreLeft; - Origin = Anchor.CentreLeft; - - Masking = true; - CornerRadius = 5; - - InternalChildren = new Drawable[] - { - new Box - { - Colour = colours.Yellow, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Padding = new MarginPadding(2), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.Default.With(weight: FontWeight.SemiBold, size: 12), - Text = header, - Colour = colours.Gray3 - }, - }; - } - - public string TooltipText => content; - } - - protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty); - - private class HeaderText : OsuSpriteText - { - public HeaderText(string text) - { - Text = text.ToUpper(); - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Black); - } - } - - public class RowBackground : OsuClickableContainer - { - private const int fade_duration = 100; - - private readonly Box hoveredBackground; - - public RowBackground() - { - RelativeSizeAxes = Axes.X; - Height = 25; - - AlwaysPresent = true; - - CornerRadius = 3; - Masking = true; - - Children = new Drawable[] - { - hoveredBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - }, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - hoveredBackground.Colour = colours.Blue; - } - - protected override bool OnHover(HoverEvent e) - { - hoveredBackground.FadeIn(fade_duration, Easing.OutQuint); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - hoveredBackground.FadeOut(fade_duration, Easing.OutQuint); - base.OnHoverLost(e); - } - } - } } From 07286c0cfc106dfd49fb8370bfe5490ed2bb597f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Oct 2019 18:21:53 +0900 Subject: [PATCH 1834/2815] Fix editor's clock not being processed unless composer is loaded --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 3 ++- osu.Game/Screens/Edit/Editor.cs | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index a267d7c44d..038d6a320a 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -57,7 +57,8 @@ namespace osu.Game.Rulesets.Edit { drawableRulesetWrapper = new DrawableEditRulesetWrapper(CreateDrawableRuleset(Ruleset, workingBeatmap, Array.Empty())) { - Clock = framedClock + Clock = framedClock, + ProcessCustomClock = false }; } catch (Exception e) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 7f08c2f8b9..35408e4003 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -173,6 +173,12 @@ namespace osu.Game.Screens.Edit bottomBackground.Colour = colours.Gray2; } + protected override void Update() + { + base.Update(); + clock.ProcessFrame(); + } + protected override bool OnKeyDown(KeyDownEvent e) { switch (e.Key) From 5dd5a070e073779743e8b2862ef9b9dd18aa8598 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 18 Oct 2019 18:44:58 +0900 Subject: [PATCH 1835/2815] Show placement grid from hitobjects at the current time --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index a5590a999d..fcf2772219 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Edit if (objects.Count == 0) { - var lastObject = EditorBeatmap.HitObjects.LastOrDefault(h => h.StartTime < EditorClock.CurrentTime); + var lastObject = EditorBeatmap.HitObjects.LastOrDefault(h => h.StartTime <= EditorClock.CurrentTime); if (lastObject == null) return null; From 190a83da6ec098031c5a879976d28eb585a8a726 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 18 Oct 2019 19:04:08 +0900 Subject: [PATCH 1836/2815] Refresh the grid after a placement --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 1ec36a59a8..c769ccae41 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -244,7 +244,11 @@ namespace osu.Game.Rulesets.Edit hitObject.StartTime = GetSnappedTime(hitObject.StartTime, distanceSnapGrid.ToLocalSpace(inputManager.CurrentState.Mouse.Position)); } - public void EndPlacement(HitObject hitObject) => EditorBeatmap.Add(hitObject); + public void EndPlacement(HitObject hitObject) + { + EditorBeatmap.Add(hitObject); + showGridFor(Enumerable.Empty()); + } public void Delete(HitObject hitObject) => EditorBeatmap.Remove(hitObject); From ca957f0994cd8e459231ecde6085d939eca2d094 Mon Sep 17 00:00:00 2001 From: HoLLy Date: Sat, 19 Oct 2019 11:52:07 +0200 Subject: [PATCH 1837/2815] Hide gameplay cursor when resume overlay is shown --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 908ad1ce49..3d3e0fe49a 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -39,6 +39,7 @@ namespace osu.Game.Rulesets.Osu.UI public override void Show() { base.Show(); + GameplayCursor.ActiveCursor.Hide(); cursorScaleContainer.MoveTo(GameplayCursor.ActiveCursor.Position); clickToResumeCursor.Appear(); @@ -54,6 +55,7 @@ namespace osu.Game.Rulesets.Osu.UI { localCursorContainer?.Expire(); localCursorContainer = null; + GameplayCursor.ActiveCursor.Show(); base.Hide(); } From 68837d47df136a9ac8a9b1af9fb68bc62b2576ce Mon Sep 17 00:00:00 2001 From: HoLLy Date: Sat, 19 Oct 2019 12:15:31 +0200 Subject: [PATCH 1838/2815] Use local bindable instead of using BindValueChanged of external one --- .../UI/Cursor/OsuCursorContainer.cs | 13 ++++++------- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 8 ++++++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index 52e2e493d5..6433ced624 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -29,8 +29,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private readonly Drawable cursorTrail; - public IBindable CursorScale => cursorScale; - private Bindable cursorScale; + public Bindable CursorScale; private Bindable userCursorScale; private Bindable autoCursorScale; private readonly IBindable beatmap = new Bindable(); @@ -58,8 +57,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize); autoCursorScale.ValueChanged += _ => calculateScale(); - cursorScale = new Bindable(); - cursorScale.ValueChanged += e => ActiveCursor.Scale = cursorTrail.Scale = new Vector2(e.NewValue); + CursorScale = new Bindable(); + CursorScale.ValueChanged += e => ActiveCursor.Scale = cursorTrail.Scale = new Vector2(e.NewValue); calculateScale(); } @@ -74,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor scale *= 1f - 0.7f * (1f + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY; } - cursorScale.Value = scale; + CursorScale.Value = scale; } protected override void LoadComplete() @@ -130,13 +129,13 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor protected override void PopIn() { fadeContainer.FadeTo(1, 300, Easing.OutQuint); - ActiveCursor.ScaleTo(cursorScale.Value, 400, Easing.OutQuint); + ActiveCursor.ScaleTo(CursorScale.Value, 400, Easing.OutQuint); } protected override void PopOut() { fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint); - ActiveCursor.ScaleTo(cursorScale.Value * 0.8f, 450, Easing.OutQuint); + ActiveCursor.ScaleTo(CursorScale.Value * 0.8f, 450, Easing.OutQuint); } private class DefaultCursorTrail : CursorTrail diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 3d3e0fe49a..8347d255fa 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -1,8 +1,9 @@ -// 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; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -21,6 +22,7 @@ namespace osu.Game.Rulesets.Osu.UI private OsuClickToResumeCursor clickToResumeCursor; private OsuCursorContainer localCursorContainer; + private Bindable localCursorScale; public override CursorContainer LocalCursor => State.Value == Visibility.Visible ? localCursorContainer : null; @@ -47,7 +49,9 @@ namespace osu.Game.Rulesets.Osu.UI { Add(localCursorContainer = new OsuCursorContainer()); - localCursorContainer.CursorScale.BindValueChanged(scale => cursorScaleContainer.Scale = new Vector2(scale.NewValue), true); + localCursorScale = new Bindable(); + localCursorScale.BindTo(localCursorContainer.CursorScale); + localCursorScale.BindValueChanged(scale => cursorScaleContainer.Scale = new Vector2(scale.NewValue), true); } } From 7dc65ec9645e71bd790f5e157bf757ebfd6748c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 20 Oct 2019 23:32:49 +0900 Subject: [PATCH 1839/2815] Add missing required types --- osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs index be84ae2cf9..54dcdc8da5 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs @@ -1,6 +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 NUnit.Framework; using osu.Framework.Allocation; using osu.Game.Rulesets.Osu; @@ -11,6 +13,12 @@ namespace osu.Game.Tests.Visual.Editor [TestFixture] public class TestSceneTimingScreen : EditorClockTestScene { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ControlPointTable), + typeof(RowAttribute) + }; + [BackgroundDependencyLoader] private void load() { From 0fbba9a5e5c875f63a1e4afc7181aa4ee41895cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 20 Oct 2019 23:42:13 +0900 Subject: [PATCH 1840/2815] Split out more classes --- .../Visual/Editor/TestSceneTimingScreen.cs | 1 + .../Edit/Timing/ControlPointSettings.cs | 26 +++++++++++++++++++ osu.Game/Screens/Edit/Timing/TimingScreen.cs | 15 ----------- 3 files changed, 27 insertions(+), 15 deletions(-) create mode 100644 osu.Game/Screens/Edit/Timing/ControlPointSettings.cs diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs index 54dcdc8da5..3e227169e0 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs @@ -16,6 +16,7 @@ namespace osu.Game.Tests.Visual.Editor public override IReadOnlyList RequiredTypes => new[] { typeof(ControlPointTable), + typeof(ControlPointSettings), typeof(RowAttribute) }; diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs new file mode 100644 index 0000000000..2feced9738 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -0,0 +1,26 @@ +// 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.Game.Graphics; + +namespace osu.Game.Screens.Edit.Timing +{ + public class ControlPointSettings : CompositeDrawable + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.Both; + + InternalChild = new Box + { + Colour = colours.Gray3, + RelativeSizeAxes = Axes.Both, + }; + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 3dda57becc..9cbba6b155 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -64,20 +64,5 @@ namespace osu.Game.Screens.Edit.Timing }; } } - - public class ControlPointSettings : CompositeDrawable - { - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - RelativeSizeAxes = Axes.Both; - - InternalChild = new Box - { - Colour = colours.Gray3, - RelativeSizeAxes = Axes.Both, - }; - } - } } } From 80bf68c1081ed4dc90f1cc5c99c7810e8e34e597 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Oct 2019 00:06:38 +0900 Subject: [PATCH 1841/2815] Add control sections and hook up bindable control groups --- .../Edit/Timing/ControlPointSettings.cs | 130 +++++++++++++++++- .../Screens/Edit/Timing/ControlPointTable.cs | 4 +- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 3 +- 3 files changed, 131 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs index 2feced9738..dc4fb2a338 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -1,11 +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.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Edit.Timing { @@ -16,11 +25,126 @@ namespace osu.Game.Screens.Edit.Timing { RelativeSizeAxes = Axes.Both; - InternalChild = new Box + InternalChildren = new Drawable[] { - Colour = colours.Gray3, - RelativeSizeAxes = Axes.Both, + new Box + { + Colour = colours.Gray3, + RelativeSizeAxes = Axes.Both, + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(30), + Children = createSections() + }, + } }; } + + private IReadOnlyList createSections() => new Drawable[] + { + new TimingSection(), + new DifficultySection(), + new SampleSection(), + new EffectSection(), + }; + + private class TimingSection : Section + { + } + + private class DifficultySection : Section + { + } + + private class SampleSection : Section + { + } + + private class EffectSection : Section + { + } + + private class Section : Container + where T : ControlPoint + { + private const float header_height = 20; + + protected override Container Content { get; } + + [Resolved] + private Bindable> selectedPoints { get; set; } + + protected Section() + { + RelativeSizeAxes = Axes.X; + AutoSizeDuration = 200; + AutoSizeEasing = Easing.OutQuint; + AutoSizeAxes = Axes.Y; + + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = Color4.Gray, + RelativeSizeAxes = Axes.Both, + }, + new Container + { + RelativeSizeAxes = Axes.X, + Height = header_height, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = typeof(T).Name.Replace(typeof(ControlPoint).Name, string.Empty) + }, + } + }, + Content = new Container() + { + Y = header_height, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + Colour = Color4.DarkGray, + RelativeSizeAxes = Axes.X, + Height = 100, + }, + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedPoints.BindValueChanged(points => + { + var matching = points.NewValue?.OfType().Where(p => !p.AutoGenerated).FirstOrDefault(); + + if (matching != null) + { + Content.BypassAutoSizeAxes = Axes.None; + } + else + { + Content.BypassAutoSizeAxes = Axes.Y; + } + }, true); + } + } } } diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 1ff88fb5b4..5032e71fe8 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Edit.Timing private readonly FillFlowContainer backgroundFlow; [Resolved] - private Bindable controlPoint { get; set; } + private Bindable> selectedPoints { get; set; } public ControlPointTable() { @@ -60,7 +60,7 @@ namespace osu.Game.Screens.Edit.Timing foreach (var group in grouped) { - backgroundFlow.Add(new RowBackground { Action = () => controlPoint.Value = group.First() }); + backgroundFlow.Add(new RowBackground { Action = () => selectedPoints.Value = group }); } Columns = createHeaders(); diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 9cbba6b155..5da5d3f785 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.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.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -16,7 +17,7 @@ namespace osu.Game.Screens.Edit.Timing public class TimingScreen : EditorScreenWithTimeline { [Cached] - private readonly Bindable controlPoint = new Bindable(); + private Bindable> selectedPoints = new Bindable>(); protected override Drawable CreateMainContent() => new GridContainer { From aa910fb51970a77c3bfaa8db12d2b3028380ad80 Mon Sep 17 00:00:00 2001 From: miterosan Date: Sun, 20 Oct 2019 21:11:16 +0200 Subject: [PATCH 1842/2815] Revert "Load the rulesets lasily" This reverts commit 42d1379848fa03611cda80eb2336838ddf3165d3. --- osu.Game/Rulesets/RulesetStore.cs | 95 +++++++++++++++---------------- 1 file changed, 46 insertions(+), 49 deletions(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 97a90bf824..47aad43966 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -16,11 +16,17 @@ namespace osu.Game.Rulesets /// public class RulesetStore : DatabaseBackedStore { - private static readonly Lazy> loaded_assemblies = new Lazy>(() => loadRulesets()); + private static readonly Dictionary loaded_assemblies = new Dictionary(); static RulesetStore() { AppDomain.CurrentDomain.AssemblyResolve += currentDomain_AssemblyResolve; + + // On android in release configuration assemblies are loaded from the apk directly into memory. + // We cannot read assemblies from cwd, so should check loaded assemblies instead. + loadFromAppDomain(); + + loadFromDisk(); } public RulesetStore(IDatabaseContextFactory factory) @@ -48,7 +54,7 @@ namespace osu.Game.Rulesets /// public IEnumerable AvailableRulesets { get; private set; } - private static Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args) => loaded_assemblies.Value.Keys.FirstOrDefault(a => a.FullName == args.Name); + private static Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args) => loaded_assemblies.Keys.FirstOrDefault(a => a.FullName == args.Name); private const string ruleset_library_prefix = "osu.Game.Rulesets"; @@ -58,7 +64,7 @@ namespace osu.Game.Rulesets { var context = usage.Context; - var instances = loaded_assemblies.Value.Values.Select(r => (Ruleset)Activator.CreateInstance(r, (RulesetInfo)null)).ToList(); + var instances = loaded_assemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r, (RulesetInfo)null)).ToList(); //add all legacy modes in correct order foreach (var r in instances.Where(r => r.LegacyID != null).OrderBy(r => r.LegacyID)) @@ -107,39 +113,8 @@ namespace osu.Game.Rulesets } } - /// - /// Loads the rulesets that are in the current appdomain an in the current directory. - /// - /// The rulesets that were loaded. - private static Dictionary loadRulesets() + private static void loadFromAppDomain() { - var rulesets = new Dictionary(); - - foreach (var rulesetAssembly in getRulesetAssemblies()) - { - try - { - rulesets[rulesetAssembly] = rulesetAssembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset))); - } - catch (Exception e) - { - Logger.Error(e, $"Failed to add ruleset {rulesetAssembly}"); - } - } - - return rulesets; - } - - /// - /// Scans the current appdomain and current directory for ruleset assemblies. - /// Rulesets that were found in the current directory are automaticly loaded. - /// - /// The ruleset assemblies that were found in the current appdomain or in the current directory. - private static IEnumerable getRulesetAssemblies() - { - var rulesetAssemblies = new HashSet(); - - // load from appdomain foreach (var ruleset in AppDomain.CurrentDomain.GetAssemblies()) { string rulesetName = ruleset.GetName().Name; @@ -147,33 +122,55 @@ namespace osu.Game.Rulesets if (!rulesetName.StartsWith(ruleset_library_prefix, StringComparison.InvariantCultureIgnoreCase) || ruleset.GetName().Name.Contains("Tests")) continue; - rulesetAssemblies.Add(ruleset); + addRuleset(ruleset); } + } - // load from current directory + private static void loadFromDisk() + { try { string[] files = Directory.GetFiles(Environment.CurrentDirectory, $"{ruleset_library_prefix}.*.dll"); foreach (string file in files.Where(f => !Path.GetFileName(f).Contains("Tests"))) - { - try - { - rulesetAssemblies.Add(Assembly.LoadFrom(file)); - } - catch (Exception e) - { - Logger.Error(e, $"Failed to load ruleset assembly {Path.GetFileNameWithoutExtension(file)}"); - return null; - } - } + loadRulesetFromFile(file); } catch (Exception e) { Logger.Error(e, $"Could not load rulesets from directory {Environment.CurrentDirectory}"); } + } - return rulesetAssemblies; + private static void loadRulesetFromFile(string file) + { + var filename = Path.GetFileNameWithoutExtension(file); + + if (loaded_assemblies.Values.Any(t => t.Namespace == filename)) + return; + + try + { + addRuleset(Assembly.LoadFrom(file)); + } + catch (Exception e) + { + Logger.Error(e, $"Failed to load ruleset {filename}"); + } + } + + private static void addRuleset(Assembly assembly) + { + if (loaded_assemblies.ContainsKey(assembly)) + return; + + try + { + loaded_assemblies[assembly] = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset))); + } + catch (Exception e) + { + Logger.Error(e, $"Failed to add ruleset {assembly}"); + } } } } From 6e32557be3d36f8c867e95d1f7527fcc2bbb95ca Mon Sep 17 00:00:00 2001 From: miterosan Date: Sun, 20 Oct 2019 22:29:29 +0200 Subject: [PATCH 1843/2815] Fix spelling error --- osu.Android/OsuGameActivity.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index 41531617af..2e5fa59d20 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -18,7 +18,7 @@ namespace osu.Android { // The default current directory on android is '/'. // On some devices '/' maps to the app data directory. On others it maps to the root of the internal storage. - // In order to have a consitend current directory on all devices the full path of the app data directory is set as the current directory. + // In order to have a consistent current directory on all devices the full path of the app data directory is set as the current directory. System.Environment.CurrentDirectory = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal); base.OnCreate(savedInstanceState); From dc222b5e4d00b3eac3d6e46405731e2b1050642b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 21 Oct 2019 13:52:02 +0900 Subject: [PATCH 1844/2815] Add common path for duplicated code --- .../Objects/Drawables/DrawableHitObject.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 156d4178e2..b472c880c0 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -154,11 +154,7 @@ namespace osu.Game.Rulesets.Objects.Drawables { var drawableNested = CreateNestedHitObject(h) ?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}."); - drawableNested.OnNewResult += (d, r) => OnNewResult?.Invoke(d, r); - drawableNested.OnRevertResult += (d, r) => OnRevertResult?.Invoke(d, r); - drawableNested.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j); - - nestedHitObjects.Value.Add(drawableNested); + addNested(drawableNested); AddNestedHitObject(drawableNested); } } @@ -176,14 +172,7 @@ namespace osu.Game.Rulesets.Objects.Drawables ///
/// [Obsolete("Use AddNestedHitObject() / ClearNestedHitObjects() / CreateNestedHitObject() instead.")] // can be removed 20200417 - protected virtual void AddNested(DrawableHitObject h) - { - h.OnNewResult += (d, r) => OnNewResult?.Invoke(d, r); - h.OnRevertResult += (d, r) => OnRevertResult?.Invoke(d, r); - h.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j); - - nestedHitObjects.Value.Add(h); - } + protected virtual void AddNested(DrawableHitObject h) => addNested(h); /// /// Invoked by the base to remove all previously-added nested s. @@ -199,6 +188,17 @@ namespace osu.Game.Rulesets.Objects.Drawables /// The drawable representation for . protected virtual DrawableHitObject CreateNestedHitObject(HitObject hitObject) => null; + private void addNested(DrawableHitObject hitObject) + { + // Todo: Exists for legacy purposes, can be removed 20200417 + + hitObject.OnNewResult += (d, r) => OnNewResult?.Invoke(d, r); + hitObject.OnRevertResult += (d, r) => OnRevertResult?.Invoke(d, r); + hitObject.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j); + + nestedHitObjects.Value.Add(hitObject); + } + #region State / Transform Management /// From 74b6e691d82631c6f2ad8edc73db42d2490c748f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 21 Oct 2019 14:01:52 +0900 Subject: [PATCH 1845/2815] Remove unnecessary schedule --- .../Compose/Components/BlueprintContainer.cs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index cb3d3b71fc..2de5ecf633 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -123,20 +123,12 @@ namespace osu.Game.Screens.Edit.Compose.Components if (blueprint == null) return; - if (hitObject.IsLoaded) - addBlueprint(); - else - hitObject.OnLoadComplete += _ => addBlueprint(); + blueprint.Selected += onBlueprintSelected; + blueprint.Deselected += onBlueprintDeselected; + blueprint.SelectionRequested += onSelectionRequested; + blueprint.DragRequested += onDragRequested; - void addBlueprint() - { - blueprint.Selected += onBlueprintSelected; - blueprint.Deselected += onBlueprintDeselected; - blueprint.SelectionRequested += onSelectionRequested; - blueprint.DragRequested += onDragRequested; - - selectionBlueprints.Add(blueprint); - } + selectionBlueprints.Add(blueprint); } private void removeBlueprintFor(DrawableHitObject hitObject) => removeBlueprintFor(hitObject.HitObject); From fc7e4680a763d6cfeb1a615919be6b29400de3df Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 21 Oct 2019 14:08:28 +0900 Subject: [PATCH 1846/2815] Split on multiple lines --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 3bb4266563..039e324e4b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -191,7 +191,13 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - private void updatePlacementPosition(Vector2 screenSpacePosition) => currentPlacement.UpdatePosition(ToScreenSpace(composer.GetSnappedPosition(ToLocalSpace(screenSpacePosition)))); + private void updatePlacementPosition(Vector2 screenSpacePosition) + { + Vector2 snappedGridPosition = composer.GetSnappedPosition(ToLocalSpace(screenSpacePosition)); + Vector2 snappedScreenSpacePosition = ToScreenSpace(snappedGridPosition); + + currentPlacement.UpdatePosition(snappedScreenSpacePosition); + } /// /// Select all masks in a given rectangle selection area. From 96649e0a6a8d803cfdb68bce00f403ceaf59b83d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 21 Oct 2019 15:03:49 +0900 Subject: [PATCH 1847/2815] Fix selection blueprints not respecting stacking --- .../TestSceneHitCircleSelectionBlueprint.cs | 7 +++++++ osu.Game.Rulesets.Osu/Edit/Blueprints/BlueprintPiece.cs | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs index d4cdabdb07..cf24306623 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs @@ -52,6 +52,13 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("blueprint positioned over hitobject", () => blueprint.CirclePiece.Position == hitCircle.Position); } + [Test] + public void TestStackedHitObject() + { + AddStep("set stacking", () => hitCircle.StackHeight = 5); + AddAssert("blueprint positioned over hitobject", () => blueprint.CirclePiece.Position == hitCircle.StackedPosition); + } + private class TestBlueprint : HitCircleSelectionBlueprint { public new HitCirclePiece CirclePiece => base.CirclePiece; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/BlueprintPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/BlueprintPiece.cs index 95e926fdfa..b9c77d3f56 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/BlueprintPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/BlueprintPiece.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints /// The to reference properties from. public virtual void UpdateFrom(T hitObject) { - Position = hitObject.Position; + Position = hitObject.StackedPosition; } } } From 5f8d46f66628e9499ea424f811178e57de7406aa Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 21 Oct 2019 16:15:41 +0900 Subject: [PATCH 1848/2815] Fix sliders not moving with stacking change --- osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs | 15 +++++++++++++++ .../TestSceneSliderSelectionBlueprint.cs | 9 ++++++++- .../Objects/Drawables/DrawableSlider.cs | 3 +++ osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 10 ++++++++++ osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 ++ 5 files changed, 38 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index 6a4201f84d..4893ebfdd4 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -111,6 +111,21 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("Distance Overflow 1 Repeat", () => SetContents(() => testDistanceOverflow(1))); } + [Test] + public void TestChangeStackHeight() + { + DrawableSlider slider = null; + + AddStep("create slider", () => + { + slider = (DrawableSlider)createSlider(repeats: 1); + Add(slider); + }); + + AddStep("change stack height", () => slider.HitObject.StackHeight = 10); + AddAssert("body positioned correctly", () => slider.Position == slider.HitObject.StackedPosition); + } + private Drawable testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats); private Drawable testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs index ec23ec31b2..5df0b70f12 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs @@ -78,6 +78,13 @@ namespace osu.Game.Rulesets.Osu.Tests checkPositions(); } + [Test] + public void TestStackedHitObject() + { + AddStep("set stacking", () => slider.StackHeight = 5); + checkPositions(); + } + private void moveHitObject() { AddStep("move hitobject", () => @@ -88,7 +95,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void checkPositions() { - AddAssert("body positioned correctly", () => blueprint.BodyPiece.Position == slider.Position); + AddAssert("body positioned correctly", () => blueprint.BodyPiece.Position == slider.StackedPosition); AddAssert("head positioned correctly", () => Precision.AlmostEquals(blueprint.HeadBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.HeadCircle.ScreenSpaceDrawQuad.Centre)); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 9e8ad9851c..c0b0db0e99 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -31,6 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public readonly SliderBall Ball; private readonly IBindable positionBindable = new Bindable(); + private readonly IBindable stackHeightBindable = new Bindable(); private readonly IBindable scaleBindable = new Bindable(); private readonly IBindable pathBindable = new Bindable(); @@ -108,6 +109,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables config?.BindWith(OsuRulesetSetting.SnakingOutSliders, Body.SnakingOut); positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); + stackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); scaleBindable.BindValueChanged(scale => { updatePathRadius(); @@ -115,6 +117,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }); positionBindable.BindTo(HitObject.PositionBindable); + stackHeightBindable.BindTo(HitObject.StackHeightBindable); scaleBindable.BindTo(HitObject.ScaleBindable); pathBindable.BindTo(slider.PathBindable); diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index b506c1f918..0ba712a83f 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.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.Linq; using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; @@ -98,6 +99,15 @@ namespace osu.Game.Rulesets.Osu.Objects set => LastInComboBindable.Value = value; } + protected OsuHitObject() + { + StackHeightBindable.BindValueChanged(height => + { + foreach (var nested in NestedHitObjects.OfType()) + nested.StackHeight = height.NewValue; + }); + } + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index d98d72331a..010bf072e8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -163,6 +163,7 @@ namespace osu.Game.Rulesets.Osu.Objects { StartTime = e.Time, Position = Position, + StackHeight = StackHeight, Samples = getNodeSamples(0), SampleControlPoint = SampleControlPoint, }); @@ -176,6 +177,7 @@ namespace osu.Game.Rulesets.Osu.Objects { StartTime = e.Time, Position = EndPosition, + StackHeight = StackHeight }); break; From 38c2c328ff8586d85c03e389747cdac9f5a43f1d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 21 Oct 2019 17:04:56 +0900 Subject: [PATCH 1849/2815] Rename HitObject -> DrawableObject in selection blueprints --- .../Blueprints/HoldNoteSelectionBlueprint.cs | 16 ++++++++-------- .../Edit/Blueprints/ManiaSelectionBlueprint.cs | 16 ++++++++-------- .../Edit/Blueprints/NoteSelectionBlueprint.cs | 2 +- .../Edit/ManiaSelectionHandler.cs | 14 +++++++------- .../Edit/Masks/ManiaSelectionBlueprint.cs | 4 ++-- .../TestSceneHitCircleSelectionBlueprint.cs | 4 ++-- .../HitCircles/HitCircleSelectionBlueprint.cs | 4 ++-- .../Edit/Blueprints/OsuSelectionBlueprint.cs | 6 +++--- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 18 +++++++++--------- .../Compose/Components/BlueprintContainer.cs | 4 ++-- .../Compose/Components/MoveSelectionEvent.cs | 2 +- .../Compose/Components/SelectionHandler.cs | 4 ++-- 12 files changed, 47 insertions(+), 47 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index 3169a8c036..3a9eb1f043 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { public class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint { - public new DrawableHoldNote HitObject => (DrawableHoldNote)base.HitObject; + public new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject; private readonly IBindable direction = new Bindable(); @@ -40,8 +40,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints InternalChildren = new Drawable[] { - new HoldNoteNoteSelectionBlueprint(HitObject.Head), - new HoldNoteNoteSelectionBlueprint(HitObject.Tail), + new HoldNoteNoteSelectionBlueprint(DrawableObject.Head), + new HoldNoteNoteSelectionBlueprint(DrawableObject.Tail), new BodyPiece { AccentColour = Color4.Transparent, @@ -54,13 +54,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { base.Update(); - Size = HitObject.DrawSize + new Vector2(0, HitObject.Tail.DrawHeight); + Size = DrawableObject.DrawSize + new Vector2(0, DrawableObject.Tail.DrawHeight); // This is a side-effect of not matching the hitobject's anchors/origins, which is kinda hard to do // When scrolling upwards our origin is already at the top of the head note (which is the intended location), // but when scrolling downwards our origin is at the _bottom_ of the tail note (where we need to be at the _top_ of the tail note) if (direction.Value == ScrollingDirection.Down) - Y -= HitObject.Tail.DrawHeight; + Y -= DrawableObject.Tail.DrawHeight; } public override Quad SelectionQuad => ScreenSpaceDrawQuad; @@ -77,10 +77,10 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { base.Update(); - Anchor = HitObject.Anchor; - Origin = HitObject.Origin; + Anchor = DrawableObject.Anchor; + Origin = DrawableObject.Origin; - Position = HitObject.DrawPosition; + Position = DrawableObject.DrawPosition; } // Todo: This is temporary, since the note masks don't do anything special yet. In the future they will handle input. diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs index cc50459a0c..3bd7fb2d49 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints public Vector2 ScreenSpaceDragPosition { get; private set; } public Vector2 DragPosition { get; private set; } - public new DrawableManiaHitObject HitObject => (DrawableManiaHitObject)base.HitObject; + public new DrawableManiaHitObject DrawableObject => (DrawableManiaHitObject)base.DrawableObject; protected IClock EditorClock { get; private set; } @@ -28,8 +28,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints [Resolved] private IManiaHitObjectComposer composer { get; set; } - public ManiaSelectionBlueprint(DrawableHitObject hitObject) - : base(hitObject) + public ManiaSelectionBlueprint(DrawableHitObject drawableObject) + : base(drawableObject) { RelativeSizeAxes = Axes.None; } @@ -44,13 +44,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { base.Update(); - Position = Parent.ToLocalSpace(HitObject.ToScreenSpace(Vector2.Zero)); + Position = Parent.ToLocalSpace(DrawableObject.ToScreenSpace(Vector2.Zero)); } protected override bool OnMouseDown(MouseDownEvent e) { ScreenSpaceDragPosition = e.ScreenSpaceMousePosition; - DragPosition = HitObject.ToLocalSpace(e.ScreenSpaceMousePosition); + DragPosition = DrawableObject.ToLocalSpace(e.ScreenSpaceMousePosition); return base.OnMouseDown(e); } @@ -60,20 +60,20 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints var result = base.OnDrag(e); ScreenSpaceDragPosition = e.ScreenSpaceMousePosition; - DragPosition = HitObject.ToLocalSpace(e.ScreenSpaceMousePosition); + DragPosition = DrawableObject.ToLocalSpace(e.ScreenSpaceMousePosition); return result; } public override void Show() { - HitObject.AlwaysAlive = true; + DrawableObject.AlwaysAlive = true; base.Show(); } public override void Hide() { - HitObject.AlwaysAlive = false; + DrawableObject.AlwaysAlive = false; base.Hide(); } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs index d345b14e84..b83c4aa9aa 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { base.Update(); - Size = HitObject.DrawSize; + Size = DrawableObject.DrawSize; } } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 2fba0639da..732231b0d9 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Mania.Edit public override void HandleMovement(MoveSelectionEvent moveEvent) { var maniaBlueprint = (ManiaSelectionBlueprint)moveEvent.Blueprint; - int lastColumn = maniaBlueprint.HitObject.HitObject.Column; + int lastColumn = maniaBlueprint.DrawableObject.HitObject.Column; adjustOrigins(maniaBlueprint); performDragMovement(moveEvent); @@ -48,19 +48,19 @@ namespace osu.Game.Rulesets.Mania.Edit /// The that received the drag event. private void adjustOrigins(ManiaSelectionBlueprint reference) { - var referenceParent = (HitObjectContainer)reference.HitObject.Parent; + var referenceParent = (HitObjectContainer)reference.DrawableObject.Parent; - float offsetFromReferenceOrigin = reference.DragPosition.Y - reference.HitObject.OriginPosition.Y; + float offsetFromReferenceOrigin = reference.DragPosition.Y - reference.DrawableObject.OriginPosition.Y; float targetPosition = referenceParent.ToLocalSpace(reference.ScreenSpaceDragPosition).Y - offsetFromReferenceOrigin; // Flip the vertical coordinate space when scrolling downwards if (scrollingInfo.Direction.Value == ScrollingDirection.Down) targetPosition = targetPosition - referenceParent.DrawHeight; - float movementDelta = targetPosition - reference.HitObject.Position.Y; + float movementDelta = targetPosition - reference.DrawableObject.Position.Y; foreach (var b in SelectedBlueprints.OfType()) - b.HitObject.Y += movementDelta; + b.DrawableObject.Y += movementDelta; } private void performDragMovement(MoveSelectionEvent moveEvent) @@ -70,11 +70,11 @@ namespace osu.Game.Rulesets.Mania.Edit // When scrolling downwards the anchor position is at the bottom of the screen, however the movement event assumes the anchor is at the top of the screen. // This causes the delta to assume a positive hitobject position, and which can be corrected for by subtracting the parent height. if (scrollingInfo.Direction.Value == ScrollingDirection.Down) - delta -= moveEvent.Blueprint.HitObject.Parent.DrawHeight; + delta -= moveEvent.Blueprint.DrawableObject.Parent.DrawHeight; foreach (var b in SelectedBlueprints) { - var hitObject = b.HitObject; + var hitObject = b.DrawableObject; var objectParent = (HitObjectContainer)hitObject.Parent; // StartTime could be used to adjust the position if only one movement event was received per frame. diff --git a/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs index 30b0f09a94..ff8882124f 100644 --- a/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs @@ -9,8 +9,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Masks { public abstract class ManiaSelectionBlueprint : SelectionBlueprint { - protected ManiaSelectionBlueprint(DrawableHitObject hitObject) - : base(hitObject) + protected ManiaSelectionBlueprint(DrawableHitObject drawableObject) + : base(drawableObject) { RelativeSizeAxes = Axes.None; } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs index cf24306623..0ecce42e88 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs @@ -63,8 +63,8 @@ namespace osu.Game.Rulesets.Osu.Tests { public new HitCirclePiece CirclePiece => base.CirclePiece; - public TestBlueprint(DrawableHitCircle hitCircle) - : base(hitCircle) + public TestBlueprint(DrawableHitCircle drawableCircle) + : base(drawableCircle) { } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs index a191dba8ff..37e15664b3 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs @@ -11,8 +11,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles { protected readonly HitCirclePiece CirclePiece; - public HitCircleSelectionBlueprint(DrawableHitCircle hitCircle) - : base(hitCircle) + public HitCircleSelectionBlueprint(DrawableHitCircle drawableCircle) + : base(drawableCircle) { InternalChild = CirclePiece = new HitCirclePiece(); } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs index 2e4b990db8..a864257274 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs @@ -10,10 +10,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints public abstract class OsuSelectionBlueprint : SelectionBlueprint where T : OsuHitObject { - protected new T HitObject => (T)base.HitObject.HitObject; + protected T HitObject => (T)DrawableObject.HitObject; - protected OsuSelectionBlueprint(DrawableHitObject hitObject) - : base(hitObject) + protected OsuSelectionBlueprint(DrawableHitObject drawableObject) + : base(drawableObject) { } } diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 838984b223..3076ad081a 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -43,20 +43,20 @@ namespace osu.Game.Rulesets.Edit /// /// The which this applies to. /// - public readonly DrawableHitObject HitObject; + public readonly DrawableHitObject DrawableObject; /// - /// The screen-space position of prior to handling a movement event. + /// The screen-space position of prior to handling a movement event. /// internal Vector2 ScreenSpaceMovementStartPosition { get; private set; } - protected override bool ShouldBeAlive => (HitObject.IsAlive && HitObject.IsPresent) || State == SelectionState.Selected; + protected override bool ShouldBeAlive => (DrawableObject.IsAlive && DrawableObject.IsPresent) || State == SelectionState.Selected; public override bool HandlePositionalInput => ShouldBeAlive; public override bool RemoveWhenNotAlive => false; - protected SelectionBlueprint(DrawableHitObject hitObject) + protected SelectionBlueprint(DrawableHitObject drawableObject) { - HitObject = hitObject; + DrawableObject = drawableObject; RelativeSizeAxes = Axes.Both; @@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Edit public bool IsSelected => State == SelectionState.Selected; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObject.ReceivePositionalInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos); private bool selectionRequested; @@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Edit protected override bool OnDragStart(DragStartEvent e) { - ScreenSpaceMovementStartPosition = HitObject.ToScreenSpace(HitObject.OriginPosition); + ScreenSpaceMovementStartPosition = DrawableObject.ToScreenSpace(DrawableObject.OriginPosition); return true; } @@ -151,11 +151,11 @@ namespace osu.Game.Rulesets.Edit /// /// The screen-space point that causes this to be selected. /// - public virtual Vector2 SelectionPoint => HitObject.ScreenSpaceDrawQuad.Centre; + public virtual Vector2 SelectionPoint => DrawableObject.ScreenSpaceDrawQuad.Centre; /// /// The screen-space quad that outlines this for selections. /// - public virtual Quad SelectionQuad => HitObject.ScreenSpaceDrawQuad; + public virtual Quad SelectionQuad => DrawableObject.ScreenSpaceDrawQuad; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 2de5ecf633..5bc5dc8e42 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void removeBlueprintFor(HitObject hitObject) { - var blueprint = selectionBlueprints.Single(m => m.HitObject.HitObject == hitObject); + var blueprint = selectionBlueprints.Single(m => m.DrawableObject.HitObject == hitObject); if (blueprint == null) return; @@ -252,7 +252,7 @@ namespace osu.Game.Screens.Edit.Compose.Components return d; // Put earlier hitobjects towards the end of the list, so they handle input first - int i = y.HitObject.HitObject.StartTime.CompareTo(x.HitObject.HitObject.StartTime); + int i = y.DrawableObject.HitObject.StartTime.CompareTo(x.DrawableObject.HitObject.StartTime); return i == 0 ? CompareReverseChildID(x, y) : i; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs index 13945381bb..fe0a47aec8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs +++ b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs @@ -40,7 +40,7 @@ namespace osu.Game.Screens.Edit.Compose.Components ScreenSpaceStartPosition = screenSpaceStartPosition; ScreenSpacePosition = screenSpacePosition; - InstantDelta = Blueprint.HitObject.Parent.ToLocalSpace(ScreenSpacePosition) - Blueprint.HitObject.Position; + InstantDelta = Blueprint.DrawableObject.Parent.ToLocalSpace(ScreenSpacePosition) - Blueprint.DrawableObject.Position; } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index c9e862d99e..9cbc1f6977 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected IEnumerable SelectedBlueprints => selectedBlueprints; private readonly List selectedBlueprints; - protected IEnumerable SelectedHitObjects => selectedBlueprints.Select(b => b.HitObject.HitObject); + protected IEnumerable SelectedHitObjects => selectedBlueprints.Select(b => b.DrawableObject.HitObject); private Drawable outline; @@ -81,7 +81,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { case Key.Delete: foreach (var h in selectedBlueprints.ToList()) - placementHandler.Delete(h.HitObject.HitObject); + placementHandler.Delete(h.DrawableObject.HitObject); return true; } From c34d3362df6b7142e555dd869e8d458a7256b3a1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 21 Oct 2019 17:14:08 +0900 Subject: [PATCH 1850/2815] Fix hit circles selection area being too large --- .../HitCircles/HitCircleSelectionBlueprint.cs | 8 ++++++++ .../Objects/Drawables/DrawableHitCircle.cs | 18 ++++++++---------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs index 37e15664b3..093bae854e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs @@ -1,14 +1,18 @@ // 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.Primitives; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles { public class HitCircleSelectionBlueprint : OsuSelectionBlueprint { + protected new DrawableHitCircle DrawableObject => (DrawableHitCircle)base.DrawableObject; + protected readonly HitCirclePiece CirclePiece; public HitCircleSelectionBlueprint(DrawableHitCircle drawableCircle) @@ -23,5 +27,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles CirclePiece.UpdateFrom(HitObject); } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.HitArea.ReceivePositionalInputAt(screenSpacePos); + + public override Quad SelectionQuad => DrawableObject.HitArea.ScreenSpaceDrawQuad; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index bb227d76df..f74f2d7bc5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -24,14 +24,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly IBindable stackHeightBindable = new Bindable(); private readonly IBindable scaleBindable = new Bindable(); - public OsuAction? HitAction => hitArea.HitAction; + public OsuAction? HitAction => HitArea.HitAction; + public readonly HitReceptor HitArea; + public readonly SkinnableDrawable CirclePiece; private readonly Container scaleContainer; - private readonly HitArea hitArea; - - public SkinnableDrawable CirclePiece { get; } - public DrawableHitCircle(HitCircle h) : base(h) { @@ -48,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Anchor = Anchor.Centre, Children = new Drawable[] { - hitArea = new HitArea + HitArea = new HitReceptor { Hit = () => { @@ -69,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }, }; - Size = hitArea.DrawSize; + Size = HitArea.DrawSize; } [BackgroundDependencyLoader] @@ -153,7 +151,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Expire(true); - hitArea.HitAction = null; + HitArea.HitAction = null; break; case ArmedState.Miss: @@ -172,7 +170,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public Drawable ProxiedLayer => ApproachCircle; - private class HitArea : Drawable, IKeyBindingHandler + public class HitReceptor : Drawable, IKeyBindingHandler { // IsHovered is used public override bool HandlePositionalInput => true; @@ -181,7 +179,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public OsuAction? HitAction; - public HitArea() + public HitReceptor() { Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); From c8f4e8b52c3ba46d973cbed4875966fba6c68dc9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Oct 2019 17:24:32 +0900 Subject: [PATCH 1851/2815] 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 85766665a9..c5714caf4c 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -62,6 +62,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ab7c40116b..64172a0954 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 925a217a13..5d384888d2 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,8 +118,8 @@ - - + + From 9f004186d57e39f7d99f875c1ffa0bdfe39cce08 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Oct 2019 17:56:39 +0900 Subject: [PATCH 1852/2815] Ensure DrawableHitObject's HitObject is not null --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index b472c880c0..f305daf128 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; @@ -89,9 +90,9 @@ namespace osu.Game.Rulesets.Objects.Drawables public IBindable State => state; - protected DrawableHitObject(HitObject hitObject) + protected DrawableHitObject([NotNull] HitObject hitObject) { - HitObject = hitObject; + HitObject = hitObject ?? throw new ArgumentNullException(nameof(hitObject)); } [BackgroundDependencyLoader] From 1bf5f9313fc178a2dc78aa49e111ea0143e663fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Oct 2019 18:10:00 +0900 Subject: [PATCH 1853/2815] Fix failing test --- osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs index cbbf5b0c09..eaa8ca7ebb 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs @@ -239,7 +239,7 @@ namespace osu.Game.Rulesets.Taiko.Tests private class TestStrongNestedHit : DrawableStrongNestedHit { public TestStrongNestedHit(DrawableHitObject mainObject) - : base(null, mainObject) + : base(new StrongHitObject { StartTime = mainObject.HitObject.StartTime }, mainObject) { } From 0bf35faae8f5cede5647fbd3139a959c01ab4d04 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Oct 2019 19:25:46 +0900 Subject: [PATCH 1854/2815] Update incorrect reference --- osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 99a045e509..4001a0f33a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -231,7 +231,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void onDragRequested(SelectionBlueprint blueprint, DragEvent dragEvent) { - HitObject draggedObject = blueprint.HitObject.HitObject; + HitObject draggedObject = blueprint.DrawableObject.HitObject; Vector2 movePosition = blueprint.ScreenSpaceMovementStartPosition + dragEvent.ScreenSpaceMousePosition - dragEvent.ScreenSpaceMouseDownPosition; Vector2 snappedPosition = composer.GetSnappedPosition(ToLocalSpace(movePosition)); From 75f444e4311203c4901ef9d6ef2a4ed18e06d047 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 22 Oct 2019 00:44:58 +0300 Subject: [PATCH 1855/2815] Basic implementation --- osu.Game/Overlays/Chat/DrawableChannel.cs | 49 ++++++++++++++++++----- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index f831266b1b..34de149bc3 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -12,6 +12,8 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Online.Chat; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Overlays.Chat { @@ -74,17 +76,40 @@ namespace osu.Game.Overlays.Chat protected virtual ChatLine CreateChatLine(Message m) => new ChatLine(m); + protected virtual Drawable CreateDaySeparator(DateTimeOffset time) => new Container + { + Margin = new MarginPadding { Vertical = 5 }, + RelativeSizeAxes = Axes.X, + Height = 2, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + } + }; + private void newMessagesArrived(IEnumerable newMessages) { // Add up to last Channel.MAX_HISTORY messages var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MaxHistory)); - ChatLineFlow.AddRange(displayMessages.Select(CreateChatLine)); + var existingChatLines = getChatLines(); - if (scroll.IsScrolledToEnd(10) || !ChatLineFlow.Children.Any() || newMessages.Any(m => m is LocalMessage)) + Message lastMessage = existingChatLines.Any() ? existingChatLines.First().Message : null; + + displayMessages.ForEach(m => + { + if (lastMessage == null || lastMessage.Timestamp.ToLocalTime().Date != m.Timestamp.ToLocalTime().Date) + ChatLineFlow.Add(CreateDaySeparator(m.Timestamp)); + + ChatLineFlow.Add(CreateChatLine(m)); + lastMessage = m; + }); + + if (scroll.IsScrolledToEnd(10) || !existingChatLines.Any() || newMessages.Any(m => m is LocalMessage)) scrollToEnd(); - var staleMessages = ChatLineFlow.Children.Where(c => c.LifetimeEnd == double.MaxValue).ToArray(); + var staleMessages = existingChatLines.Where(c => c.LifetimeEnd == double.MaxValue).ToArray(); int count = staleMessages.Length - Channel.MaxHistory; for (int i = 0; i < count; i++) @@ -98,7 +123,7 @@ namespace osu.Game.Overlays.Chat private void pendingMessageResolved(Message existing, Message updated) { - var found = ChatLineFlow.Children.LastOrDefault(c => c.Message == existing); + var found = getChatLines().LastOrDefault(c => c.Message == existing); if (found != null) { @@ -112,19 +137,25 @@ namespace osu.Game.Overlays.Chat private void messageRemoved(Message removed) { - ChatLineFlow.Children.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); + getChatLines().FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); } + private IEnumerable getChatLines() => ChatLineFlow.Children.OfType(); + private void scrollToEnd() => ScheduleAfterChildren(() => scroll.ScrollToEnd()); - protected class ChatLineContainer : FillFlowContainer + protected class ChatLineContainer : FillFlowContainer { protected override int Compare(Drawable x, Drawable y) { - var xC = (ChatLine)x; - var yC = (ChatLine)y; + if (x is ChatLine && y is ChatLine) + { + var xC = (ChatLine)x; + var yC = (ChatLine)y; - return xC.Message.CompareTo(yC.Message); + return xC.Message.CompareTo(yC.Message); + } + return base.Compare(x, y); } } } From d19041fa5353d007b01f1610a46a244e820e60b6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 22 Oct 2019 01:30:37 +0300 Subject: [PATCH 1856/2815] Implement DaySeparator class --- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 8 ++ osu.Game/Overlays/Chat/DrawableChannel.cs | 84 ++++++++++++++++--- 2 files changed, 81 insertions(+), 11 deletions(-) diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index 8f39fb9006..a17b6ea170 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -124,6 +124,14 @@ namespace osu.Game.Online.Chat protected override ChatLine CreateChatLine(Message m) => CreateChatLineAction(m); + protected override DaySeparator CreateDaySeparator(DateTimeOffset time) => new DaySeparator(time) + { + Colour = Color4.White, + TextSize = 14, + LineHeight = 1, + Margin = new MarginPadding { Horizontal = 10 } + }; + public StandAloneDrawableChannel(Channel channel) : base(channel) { diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 34de149bc3..850c7146da 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -14,6 +14,9 @@ using osu.Game.Graphics.Cursor; using osu.Game.Online.Chat; using osu.Framework.Graphics.Shapes; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Chat { @@ -23,6 +26,9 @@ namespace osu.Game.Overlays.Chat protected ChatLineContainer ChatLineFlow; private OsuScrollContainer scroll; + [Resolved] + private OsuColour colours { get; set; } + public DrawableChannel(Channel channel) { Channel = channel; @@ -76,16 +82,9 @@ namespace osu.Game.Overlays.Chat protected virtual ChatLine CreateChatLine(Message m) => new ChatLine(m); - protected virtual Drawable CreateDaySeparator(DateTimeOffset time) => new Container + protected virtual DaySeparator CreateDaySeparator(DateTimeOffset time) => new DaySeparator(time) { - Margin = new MarginPadding { Vertical = 5 }, - RelativeSizeAxes = Axes.X, - Height = 2, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - } + Colour = colours.ChatBlue.Lighten(0.7f) }; private void newMessagesArrived(IEnumerable newMessages) @@ -95,11 +94,11 @@ namespace osu.Game.Overlays.Chat var existingChatLines = getChatLines(); - Message lastMessage = existingChatLines.Any() ? existingChatLines.First().Message : null; + Message lastMessage = existingChatLines.Any() ? existingChatLines.Last().Message : null; displayMessages.ForEach(m => { - if (lastMessage == null || lastMessage.Timestamp.ToLocalTime().Date != m.Timestamp.ToLocalTime().Date) + if (lastMessage == null || lastMessage.Timestamp.ToLocalTime().Day != m.Timestamp.ToLocalTime().Day) ChatLineFlow.Add(CreateDaySeparator(m.Timestamp)); ChatLineFlow.Add(CreateChatLine(m)); @@ -158,5 +157,68 @@ namespace osu.Game.Overlays.Chat return base.Compare(x, y); } } + + protected class DaySeparator : GridContainer + { + public float TextSize + { + get => text.Font.Size; + set => text.Font = text.Font.With(size: value); + } + + private float lineHeight = 2; + + public float LineHeight + { + get => LineHeight; + set { lineHeight = leftBox.Height = rightBox.Height = value; } + } + + private readonly SpriteText text; + private readonly Box leftBox; + private readonly Box rightBox; + + public DaySeparator(DateTimeOffset time) + { + Margin = new MarginPadding { Vertical = 10 }; + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + }; + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + }; + Content = new[] + { + new Drawable[] + { + leftBox = new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = lineHeight, + }, + text = new SpriteText + { + Margin = new MarginPadding { Horizontal = 10 }, + Text = time.ToLocalTime().ToString("dd MMM yyyy"), + }, + rightBox = new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = lineHeight, + }, + } + }; + } + } } } From a3ab6d33c14ad83cd44edd4f5eeb35ee097a580e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 22 Oct 2019 01:37:30 +0300 Subject: [PATCH 1857/2815] Add test --- .../Visual/Online/TestSceneStandAloneChatDisplay.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 3c5641fcd6..39c2fbfcc9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Game.Online.Chat; using osu.Game.Users; using osuTK; +using System; namespace osu.Game.Tests.Visual.Online { @@ -111,6 +112,13 @@ namespace osu.Game.Tests.Visual.Online Sender = longUsernameUser, Content = "Hi guys, my new username is lit!" })); + + AddStep("message with new date", () => testChannel.AddNewMessages(new Message(sequence++) + { + Sender = longUsernameUser, + Content = "Message from the future!", + Timestamp = DateTimeOffset.Now + })); } } } From bb7af1e39cf88a7dd9120251237af29fff60bdab Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 22 Oct 2019 01:45:04 +0300 Subject: [PATCH 1858/2815] Fix some margin/padding issues --- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 3 +- osu.Game/Overlays/Chat/DrawableChannel.cs | 71 ++++++++++--------- 2 files changed, 40 insertions(+), 34 deletions(-) diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index a17b6ea170..5b1d25b0d9 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -129,7 +129,8 @@ namespace osu.Game.Online.Chat Colour = Color4.White, TextSize = 14, LineHeight = 1, - Margin = new MarginPadding { Horizontal = 10 } + Padding = new MarginPadding { Horizontal = 10 }, + Margin = new MarginPadding { Vertical = 5 }, }; public StandAloneDrawableChannel(Channel channel) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 850c7146da..b26f974ec9 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -84,7 +84,8 @@ namespace osu.Game.Overlays.Chat protected virtual DaySeparator CreateDaySeparator(DateTimeOffset time) => new DaySeparator(time) { - Colour = colours.ChatBlue.Lighten(0.7f) + Margin = new MarginPadding { Vertical = 10 }, + Colour = colours.ChatBlue.Lighten(0.7f), }; private void newMessagesArrived(IEnumerable newMessages) @@ -158,7 +159,7 @@ namespace osu.Game.Overlays.Chat } } - protected class DaySeparator : GridContainer + protected class DaySeparator : Container { public float TextSize { @@ -180,42 +181,46 @@ namespace osu.Game.Overlays.Chat public DaySeparator(DateTimeOffset time) { - Margin = new MarginPadding { Vertical = 10 }; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - ColumnDimensions = new[] + Child = new GridContainer { - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - }; - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - }; - Content = new[] - { - new Drawable[] + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ColumnDimensions = new[] { - leftBox = new Box + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Height = lineHeight, - }, - text = new SpriteText - { - Margin = new MarginPadding { Horizontal = 10 }, - Text = time.ToLocalTime().ToString("dd MMM yyyy"), - }, - rightBox = new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Height = lineHeight, - }, + leftBox = new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = lineHeight, + }, + text = new SpriteText + { + Margin = new MarginPadding { Horizontal = 10 }, + Text = time.ToLocalTime().ToString("dd MMM yyyy"), + }, + rightBox = new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = lineHeight, + }, + } } }; } From 2896ed90e24cf774a573301c86798b2e3ba881c2 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 22 Oct 2019 01:55:26 +0300 Subject: [PATCH 1859/2815] Fix incorrect date comparison --- osu.Game/Overlays/Chat/DrawableChannel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index b26f974ec9..ece5beb7f8 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -99,7 +99,7 @@ namespace osu.Game.Overlays.Chat displayMessages.ForEach(m => { - if (lastMessage == null || lastMessage.Timestamp.ToLocalTime().Day != m.Timestamp.ToLocalTime().Day) + if (lastMessage == null || lastMessage.Timestamp.ToLocalTime().Date != m.Timestamp.ToLocalTime().Date) ChatLineFlow.Add(CreateDaySeparator(m.Timestamp)); ChatLineFlow.Add(CreateChatLine(m)); From f7924d3bad6ce574c1967ba5a956915f8e9be56e Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 21 Oct 2019 16:00:09 -0700 Subject: [PATCH 1860/2815] Rename "FixedSearchTextBox" to "SeekLimitedSearchTextBox" --- .../{FixedSearchTextBox.cs => SeekLimitedSearchTextBox.cs} | 2 +- osu.Game/Overlays/SettingsPanel.cs | 4 ++-- osu.Game/Screens/Select/FilterControl.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename osu.Game/Graphics/UserInterface/{FixedSearchTextBox.cs => SeekLimitedSearchTextBox.cs} (82%) diff --git a/osu.Game/Graphics/UserInterface/FixedSearchTextBox.cs b/osu.Game/Graphics/UserInterface/SeekLimitedSearchTextBox.cs similarity index 82% rename from osu.Game/Graphics/UserInterface/FixedSearchTextBox.cs rename to osu.Game/Graphics/UserInterface/SeekLimitedSearchTextBox.cs index 0654746a6e..6cd14c3263 100644 --- a/osu.Game/Graphics/UserInterface/FixedSearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/SeekLimitedSearchTextBox.cs @@ -3,7 +3,7 @@ namespace osu.Game.Graphics.UserInterface { - public class FixedSearchTextBox : SearchTextBox + public class SeekLimitedSearchTextBox : SearchTextBox { public override bool HandleLeftRightArrows => false; } diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index 119b9a2b8c..d028664fe0 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -37,7 +37,7 @@ namespace osu.Game.Overlays protected SettingsSectionsContainer SectionsContainer; - private FixedSearchTextBox searchTextBox; + private SeekLimitedSearchTextBox searchTextBox; /// /// Provide a source for the toolbar height. @@ -80,7 +80,7 @@ namespace osu.Game.Overlays Masking = true, RelativeSizeAxes = Axes.Both, ExpandableHeader = CreateHeader(), - FixedHeader = searchTextBox = new FixedSearchTextBox + FixedHeader = searchTextBox = new SeekLimitedSearchTextBox { RelativeSizeAxes = Axes.X, Origin = Anchor.TopCentre, diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 7aca11da2f..5b81303788 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -49,7 +49,7 @@ namespace osu.Game.Screens.Select return criteria; } - private readonly FixedSearchTextBox searchTextBox; + private readonly SeekLimitedSearchTextBox searchTextBox; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || groupTabs.ReceivePositionalInputAt(screenSpacePos) || sortTabs.ReceivePositionalInputAt(screenSpacePos); @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Select Origin = Anchor.TopRight, Children = new Drawable[] { - searchTextBox = new FixedSearchTextBox { RelativeSizeAxes = Axes.X }, + searchTextBox = new SeekLimitedSearchTextBox { RelativeSizeAxes = Axes.X }, new Box { RelativeSizeAxes = Axes.X, From e9ae838f465bbf719a07623818e26e5b7bf5329b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 22 Oct 2019 02:16:52 +0300 Subject: [PATCH 1861/2815] CI fixes --- osu.Game/Overlays/Chat/DrawableChannel.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index ece5beb7f8..3dd9f755d1 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -155,6 +155,7 @@ namespace osu.Game.Overlays.Chat return xC.Message.CompareTo(yC.Message); } + return base.Compare(x, y); } } @@ -171,7 +172,7 @@ namespace osu.Game.Overlays.Chat public float LineHeight { - get => LineHeight; + get => lineHeight; set { lineHeight = leftBox.Height = rightBox.Height = value; } } @@ -193,11 +194,8 @@ namespace osu.Game.Overlays.Chat new Dimension(GridSizeMode.AutoSize), new Dimension(), }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize), }, + Content = new[] { new Drawable[] { From b1eac6b400dd5bfb5ad4948e588b616a46a23f46 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 22 Oct 2019 03:11:19 +0300 Subject: [PATCH 1862/2815] Apply suggested changes --- osu.Game/Overlays/Chat/DrawableChannel.cs | 32 ++++++----------------- 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 3dd9f755d1..cfa56b5136 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.Chat public class DrawableChannel : Container { public readonly Channel Channel; - protected ChatLineContainer ChatLineFlow; + protected FillFlowContainer ChatLineFlow; private OsuScrollContainer scroll; [Resolved] @@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Chat // Some chat lines have effects that slightly protrude to the bottom, // which we do not want to mask away, hence the padding. Padding = new MarginPadding { Bottom = 5 }, - Child = ChatLineFlow = new ChatLineContainer + Child = ChatLineFlow = new FillFlowContainer { Padding = new MarginPadding { Left = 20, Right = 20 }, RelativeSizeAxes = Axes.X, @@ -93,9 +93,9 @@ namespace osu.Game.Overlays.Chat // Add up to last Channel.MAX_HISTORY messages var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MaxHistory)); - var existingChatLines = getChatLines(); + var existingChatLines = getChatLines; - Message lastMessage = existingChatLines.Any() ? existingChatLines.Last().Message : null; + Message lastMessage = existingChatLines.LastOrDefault()?.Message; displayMessages.ForEach(m => { @@ -123,7 +123,7 @@ namespace osu.Game.Overlays.Chat private void pendingMessageResolved(Message existing, Message updated) { - var found = getChatLines().LastOrDefault(c => c.Message == existing); + var found = getChatLines.LastOrDefault(c => c.Message == existing); if (found != null) { @@ -137,29 +137,13 @@ namespace osu.Game.Overlays.Chat private void messageRemoved(Message removed) { - getChatLines().FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); + getChatLines.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); } - private IEnumerable getChatLines() => ChatLineFlow.Children.OfType(); + private IEnumerable getChatLines => ChatLineFlow.Children.OfType(); private void scrollToEnd() => ScheduleAfterChildren(() => scroll.ScrollToEnd()); - protected class ChatLineContainer : FillFlowContainer - { - protected override int Compare(Drawable x, Drawable y) - { - if (x is ChatLine && y is ChatLine) - { - var xC = (ChatLine)x; - var yC = (ChatLine)y; - - return xC.Message.CompareTo(yC.Message); - } - - return base.Compare(x, y); - } - } - protected class DaySeparator : Container { public float TextSize @@ -173,7 +157,7 @@ namespace osu.Game.Overlays.Chat public float LineHeight { get => lineHeight; - set { lineHeight = leftBox.Height = rightBox.Height = value; } + set => lineHeight = leftBox.Height = rightBox.Height = value; } private readonly SpriteText text; From 09b2f11bd5c4ee7fd6ebf2320e0d107c53ac6f82 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 22 Oct 2019 03:14:20 +0300 Subject: [PATCH 1863/2815] Remove unused variable --- osu.Game/Overlays/Chat/DrawableChannel.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index cfa56b5136..85de23caaa 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -93,9 +93,7 @@ namespace osu.Game.Overlays.Chat // Add up to last Channel.MAX_HISTORY messages var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MaxHistory)); - var existingChatLines = getChatLines; - - Message lastMessage = existingChatLines.LastOrDefault()?.Message; + Message lastMessage = getChatLines.LastOrDefault()?.Message; displayMessages.ForEach(m => { @@ -106,10 +104,10 @@ namespace osu.Game.Overlays.Chat lastMessage = m; }); - if (scroll.IsScrolledToEnd(10) || !existingChatLines.Any() || newMessages.Any(m => m is LocalMessage)) + if (scroll.IsScrolledToEnd(10) || !getChatLines.Any() || newMessages.Any(m => m is LocalMessage)) scrollToEnd(); - var staleMessages = existingChatLines.Where(c => c.LifetimeEnd == double.MaxValue).ToArray(); + var staleMessages = getChatLines.Where(c => c.LifetimeEnd == double.MaxValue).ToArray(); int count = staleMessages.Length - Channel.MaxHistory; for (int i = 0; i < count; i++) From 5d0d83b6bf8b6a8546e8df4b0313199f439ca138 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Oct 2019 15:04:10 +0900 Subject: [PATCH 1864/2815] Add basic xmldoc --- osu.Game/Graphics/UserInterface/SeekLimitedSearchTextBox.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/SeekLimitedSearchTextBox.cs b/osu.Game/Graphics/UserInterface/SeekLimitedSearchTextBox.cs index 6cd14c3263..6a9e8a5b8c 100644 --- a/osu.Game/Graphics/UserInterface/SeekLimitedSearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/SeekLimitedSearchTextBox.cs @@ -3,6 +3,9 @@ namespace osu.Game.Graphics.UserInterface { + /// + /// A which does not handle left/right arrow keys for seeking. + /// public class SeekLimitedSearchTextBox : SearchTextBox { public override bool HandleLeftRightArrows => false; From 81e8b678d342c8a9f9f646974a9659790573cdea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Oct 2019 20:17:19 +0900 Subject: [PATCH 1865/2815] Update editor time when a new timing point is selected --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 5da5d3f785..ed045aca86 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -2,11 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; 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.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; @@ -19,6 +21,9 @@ namespace osu.Game.Screens.Edit.Timing [Cached] private Bindable> selectedPoints = new Bindable>(); + [Resolved] + private IAdjustableClock clock { get; set; } + protected override Drawable CreateMainContent() => new GridContainer { RelativeSizeAxes = Axes.Both, @@ -37,6 +42,13 @@ namespace osu.Game.Screens.Edit.Timing } }; + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedPoints.BindValueChanged(selected => { clock.Seek(selected.NewValue.First().Time); }); + } + public class ControlPointList : CompositeDrawable { [Resolved] From 4883844c4c390aa515fef26d4b730c8992d6fe57 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Oct 2019 20:57:56 +0900 Subject: [PATCH 1866/2815] Add basic information display for all types of control points --- .../Edit/Timing/ControlPointSettings.cs | 136 +++++++++++++++--- 1 file changed, 114 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs index dc4fb2a338..1ab22679d9 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -14,7 +14,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.Edit.Timing { @@ -40,7 +39,7 @@ namespace osu.Game.Screens.Edit.Timing RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(30), + Spacing = new Vector2(2), Children = createSections() }, } @@ -57,31 +56,123 @@ namespace osu.Game.Screens.Edit.Timing private class TimingSection : Section { + private OsuSpriteText bpm; + private OsuSpriteText timeSignature; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + bpm = new OsuSpriteText(), + timeSignature = new OsuSpriteText(), + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => + { + bpm.Text = $"BPM: {point.NewValue?.BeatLength}"; + timeSignature.Text = $"Signature: {point.NewValue?.TimeSignature}"; + }); + } } private class DifficultySection : Section { + private OsuSpriteText multiplier; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + multiplier = new OsuSpriteText(), + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier}"; }); + } } private class SampleSection : Section { + private OsuSpriteText bank; + private OsuSpriteText volume; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + bank = new OsuSpriteText(), + volume = new OsuSpriteText(), + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => + { + bank.Text = $"Bank: {point.NewValue?.SampleBank}"; + volume.Text = $"Volume: {point.NewValue?.SampleVolume}"; + }); + } } private class EffectSection : Section { + private OsuSpriteText kiai; + private OsuSpriteText omitBarLine; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + kiai = new OsuSpriteText(), + omitBarLine = new OsuSpriteText(), + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => + { + kiai.Text = $"Kiai: {point.NewValue?.KiaiMode}"; + omitBarLine.Text = $"Skip Bar Line: {point.NewValue?.OmitFirstBarLine}"; + }); + } } - private class Section : Container + private class Section : CompositeDrawable where T : ControlPoint { - private const float header_height = 20; + private OsuCheckbox checkbox; + private Container content; - protected override Container Content { get; } + protected FillFlowContainer Flow { get; private set; } + + protected Bindable ControlPoint { get; } = new Bindable(); + + private const float header_height = 20; [Resolved] private Bindable> selectedPoints { get; set; } - protected Section() + [BackgroundDependencyLoader] + private void load(OsuColour colours) { RelativeSizeAxes = Axes.X; AutoSizeDuration = 200; @@ -94,7 +185,7 @@ namespace osu.Game.Screens.Edit.Timing { new Box { - Colour = Color4.Gray, + Colour = colours.Gray1, RelativeSizeAxes = Axes.Both, }, new Container @@ -103,13 +194,13 @@ namespace osu.Game.Screens.Edit.Timing Height = header_height, Children = new Drawable[] { - new OsuSpriteText + checkbox = new OsuCheckbox { - Text = typeof(T).Name.Replace(typeof(ControlPoint).Name, string.Empty) - }, + LabelText = typeof(T).Name.Replace(typeof(ControlPoint).Name, string.Empty) + } } }, - Content = new Container() + content = new Container { Y = header_height, RelativeSizeAxes = Axes.X, @@ -118,9 +209,15 @@ namespace osu.Game.Screens.Edit.Timing { new Box { - Colour = Color4.DarkGray, + Colour = colours.Gray2, + RelativeSizeAxes = Axes.Both, + }, + Flow = new FillFlowContainer + { + Padding = new MarginPadding(10), RelativeSizeAxes = Axes.X, - Height = 100, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, }, } } @@ -133,17 +230,12 @@ namespace osu.Game.Screens.Edit.Timing selectedPoints.BindValueChanged(points => { - var matching = points.NewValue?.OfType().Where(p => !p.AutoGenerated).FirstOrDefault(); + ControlPoint.Value = points.NewValue?.OfType().Where(p => !p.AutoGenerated).FirstOrDefault(); - if (matching != null) - { - Content.BypassAutoSizeAxes = Axes.None; - } - else - { - Content.BypassAutoSizeAxes = Axes.Y; - } + checkbox.Current.Value = ControlPoint.Value != null; }, true); + + checkbox.Current.BindValueChanged(selected => { content.BypassAutoSizeAxes = selected.NewValue ? Axes.None : Axes.Y; }, true); } } } From 5e22eed131ca60093f816fa8a50a06a4536f8b85 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Oct 2019 21:50:21 +0900 Subject: [PATCH 1867/2815] Add add/remove buttons --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 53 +++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index ed045aca86..b6bd9ae61d 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -13,6 +14,8 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osuTK; namespace osu.Game.Screens.Edit.Timing { @@ -51,9 +54,14 @@ namespace osu.Game.Screens.Edit.Timing public class ControlPointList : CompositeDrawable { + private OsuButton deleteButton; + [Resolved] protected IBindable Beatmap { get; private set; } + [Resolved] + private Bindable> selectedPoints { get; set; } + [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -73,9 +81,52 @@ namespace osu.Game.Screens.Edit.Timing { ControlPoints = Beatmap.Value.Beatmap.ControlPointInfo.AllControlPoints } - } + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Direction = FillDirection.Horizontal, + Margin = new MarginPadding(10), + Spacing = new Vector2(5), + Children = new Drawable[] + { + deleteButton = new OsuButton + { + Text = "-", + Size = new Vector2(30, 30), + Action = delete, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + new OsuButton + { + Text = "+", + Action = addNew, + Size = new Vector2(30, 30), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + } + }, }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedPoints.BindValueChanged(selected => { deleteButton.Enabled.Value = selected.NewValue != null; }, true); + } + + private void delete() + { + } + + private void addNew() + { + } } } } From 0ba287a7fd31bcb39256722bbc42585fbad77b6d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Oct 2019 00:14:22 +0900 Subject: [PATCH 1868/2815] Rename variable --- osu.Game/Overlays/Chat/DrawableChannel.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 85de23caaa..20e82cea95 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -93,7 +93,7 @@ namespace osu.Game.Overlays.Chat // Add up to last Channel.MAX_HISTORY messages var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MaxHistory)); - Message lastMessage = getChatLines.LastOrDefault()?.Message; + Message lastMessage = chatLines.LastOrDefault()?.Message; displayMessages.ForEach(m => { @@ -104,10 +104,10 @@ namespace osu.Game.Overlays.Chat lastMessage = m; }); - if (scroll.IsScrolledToEnd(10) || !getChatLines.Any() || newMessages.Any(m => m is LocalMessage)) + if (scroll.IsScrolledToEnd(10) || !chatLines.Any() || newMessages.Any(m => m is LocalMessage)) scrollToEnd(); - var staleMessages = getChatLines.Where(c => c.LifetimeEnd == double.MaxValue).ToArray(); + var staleMessages = chatLines.Where(c => c.LifetimeEnd == double.MaxValue).ToArray(); int count = staleMessages.Length - Channel.MaxHistory; for (int i = 0; i < count; i++) @@ -121,7 +121,7 @@ namespace osu.Game.Overlays.Chat private void pendingMessageResolved(Message existing, Message updated) { - var found = getChatLines.LastOrDefault(c => c.Message == existing); + var found = chatLines.LastOrDefault(c => c.Message == existing); if (found != null) { @@ -135,10 +135,10 @@ namespace osu.Game.Overlays.Chat private void messageRemoved(Message removed) { - getChatLines.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); + chatLines.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); } - private IEnumerable getChatLines => ChatLineFlow.Children.OfType(); + private IEnumerable chatLines => ChatLineFlow.Children.OfType(); private void scrollToEnd() => ScheduleAfterChildren(() => scroll.ScrollToEnd()); From 3b4823abe7c687bbab7eba9a6af9675fdb9f2786 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Oct 2019 00:16:17 +0900 Subject: [PATCH 1869/2815] Use foreach --- osu.Game/Overlays/Chat/DrawableChannel.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 20e82cea95..f40f53852a 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -95,14 +95,14 @@ namespace osu.Game.Overlays.Chat Message lastMessage = chatLines.LastOrDefault()?.Message; - displayMessages.ForEach(m => + foreach (var message in displayMessages) { - if (lastMessage == null || lastMessage.Timestamp.ToLocalTime().Date != m.Timestamp.ToLocalTime().Date) - ChatLineFlow.Add(CreateDaySeparator(m.Timestamp)); + if (lastMessage == null || lastMessage.Timestamp.ToLocalTime().Date != message.Timestamp.ToLocalTime().Date) + ChatLineFlow.Add(CreateDaySeparator(message.Timestamp)); - ChatLineFlow.Add(CreateChatLine(m)); - lastMessage = m; - }); + ChatLineFlow.Add(CreateChatLine(message)); + lastMessage = message; + } if (scroll.IsScrolledToEnd(10) || !chatLines.Any() || newMessages.Any(m => m is LocalMessage)) scrollToEnd(); From e9aa7f32186a806a549968d6920e7ea871d5f0d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Oct 2019 00:24:08 +0900 Subject: [PATCH 1870/2815] Subclass and use yellow for stand-alone chat display --- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 27 +++++++++++++------ osu.Game/Overlays/Chat/DrawableChannel.cs | 1 - 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index 5b1d25b0d9..5ae453ceeb 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Chat; using osuTK.Graphics; @@ -124,14 +125,7 @@ namespace osu.Game.Online.Chat protected override ChatLine CreateChatLine(Message m) => CreateChatLineAction(m); - protected override DaySeparator CreateDaySeparator(DateTimeOffset time) => new DaySeparator(time) - { - Colour = Color4.White, - TextSize = 14, - LineHeight = 1, - Padding = new MarginPadding { Horizontal = 10 }, - Margin = new MarginPadding { Vertical = 5 }, - }; + protected override DaySeparator CreateDaySeparator(DateTimeOffset time) => new CustomDaySeparator(time); public StandAloneDrawableChannel(Channel channel) : base(channel) @@ -143,6 +137,23 @@ namespace osu.Game.Online.Chat { ChatLineFlow.Padding = new MarginPadding { Horizontal = 0 }; } + + private class CustomDaySeparator : DaySeparator + { + public CustomDaySeparator(DateTimeOffset time) : base(time) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.Yellow; + TextSize = 14; + LineHeight = 1; + Padding = new MarginPadding { Horizontal = 10 }; + Margin = new MarginPadding { Vertical = 5 }; + } + } } protected class StandAloneMessage : ChatLine diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index f40f53852a..6cdbfabe0f 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -13,7 +13,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Online.Chat; using osu.Framework.Graphics.Shapes; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.Sprites; From c3375071adbc1cbc9557789ca0ef6e1ae96b854c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Oct 2019 00:26:47 +0900 Subject: [PATCH 1871/2815] Fix formatting issue --- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index 5ae453ceeb..21d0bcc4bf 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -140,7 +140,8 @@ namespace osu.Game.Online.Chat private class CustomDaySeparator : DaySeparator { - public CustomDaySeparator(DateTimeOffset time) : base(time) + public CustomDaySeparator(DateTimeOffset time) + : base(time) { } From 8154cc1b1654b6766266b5c65ec4adb3c4ceaee5 Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 22 Oct 2019 14:40:56 -0700 Subject: [PATCH 1872/2815] Fix registration textboxes always focusing after pressing escape --- osu.Game/Overlays/AccountCreation/ScreenEntry.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs index e136fc1403..66ced88875 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs @@ -138,18 +138,12 @@ namespace osu.Game.Overlays.AccountCreation passwordTextBox.Current.ValueChanged += password => { characterCheckText.ForEach(s => s.Colour = password.NewValue.Length == 0 ? Color4.White : Interpolation.ValueAt(password.NewValue.Length, Color4.OrangeRed, Color4.YellowGreen, 0, 8, Easing.In)); }; } - protected override void Update() - { - base.Update(); - - if (host?.OnScreenKeyboardOverlapsGameWindow != true && !textboxes.Any(t => t.HasFocus)) - focusNextTextbox(); - } - public override void OnEntering(IScreen last) { base.OnEntering(last); processingOverlay.Hide(); + + focusNextTextbox(); } private void performRegistration() From c06f142433d547abe9d96bf082df5c36e8c8400b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Oct 2019 11:22:55 +0900 Subject: [PATCH 1873/2815] Fix some spacing and references --- osu.Game/Screens/Edit/Timing/ControlPointSettings.cs | 2 -- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 2 +- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs index 1ab22679d9..0974745294 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -13,7 +13,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osuTK; namespace osu.Game.Screens.Edit.Timing { @@ -39,7 +38,6 @@ namespace osu.Game.Screens.Edit.Timing RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(2), Children = createSections() }, } diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 5032e71fe8..777d34e75b 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Edit.Timing var columns = new List { new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("time", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("Time", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new TableColumn("Attributes", Anchor.Centre), }; diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index b6bd9ae61d..bd0a75f0b0 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.ComponentModel; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; From 7b7a87afa8369fcb659e18e5ae67f5c4f92bbce8 Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 22 Oct 2019 19:51:29 -0700 Subject: [PATCH 1874/2815] Put back mobile conditional --- osu.Game/Overlays/AccountCreation/ScreenEntry.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs index 66ced88875..6de14c51ee 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs @@ -143,7 +143,8 @@ namespace osu.Game.Overlays.AccountCreation base.OnEntering(last); processingOverlay.Hide(); - focusNextTextbox(); + if (host?.OnScreenKeyboardOverlapsGameWindow != true) + focusNextTextbox(); } private void performRegistration() From 851773a84228c8a45cb7853ea46dd4d77c23c1b3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Oct 2019 14:04:06 +0900 Subject: [PATCH 1875/2815] Apply adjustments for framework changes --- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 2 +- osu.Game/Overlays/Settings/SettingsSlider.cs | 4 ++-- osu.Game/Screens/Edit/BindableBeatDivisor.cs | 2 +- osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 5c706781e6..11aba80d76 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -17,7 +17,7 @@ using osu.Framework.Input.Events; namespace osu.Game.Graphics.UserInterface { public class OsuSliderBar : SliderBar, IHasTooltip, IHasAccentColour - where T : struct, IEquatable, IComparable, IConvertible + where T : struct, IEquatable, IComparable, IConvertible { /// /// Maximum number of decimal digits to be displayed in the tooltip. diff --git a/osu.Game/Overlays/Settings/SettingsSlider.cs b/osu.Game/Overlays/Settings/SettingsSlider.cs index fd96ea972a..20e08c0cd8 100644 --- a/osu.Game/Overlays/Settings/SettingsSlider.cs +++ b/osu.Game/Overlays/Settings/SettingsSlider.cs @@ -8,12 +8,12 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings { public class SettingsSlider : SettingsSlider> - where T : struct, IEquatable, IComparable, IConvertible + where T : struct, IEquatable, IComparable, IConvertible { } public class SettingsSlider : SettingsItem - where T : struct, IEquatable, IComparable, IConvertible + where T : struct, IEquatable, IComparable, IConvertible where U : OsuSliderBar, new() { protected override Drawable CreateControl() => new U diff --git a/osu.Game/Screens/Edit/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/BindableBeatDivisor.cs index 2aeb1ef04b..a724f354e7 100644 --- a/osu.Game/Screens/Edit/BindableBeatDivisor.cs +++ b/osu.Game/Screens/Edit/BindableBeatDivisor.cs @@ -10,7 +10,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.Edit { - public class BindableBeatDivisor : BindableNumber + public class BindableBeatDivisor : BindableInt { public static readonly int[] VALID_DIVISORS = { 1, 2, 3, 4, 6, 8, 12, 16 }; diff --git a/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs index 28fe1f35ca..c8e281195a 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs @@ -11,7 +11,7 @@ using osu.Game.Overlays.Settings; namespace osu.Game.Screens.Play.PlayerSettings { public class PlayerSliderBar : SettingsSlider - where T : struct, IEquatable, IComparable, IConvertible + where T : struct, IEquatable, IComparable, IConvertible { public OsuSliderBar Bar => (OsuSliderBar)Control; From e836364add4c833aa9bbc8841ddefe415a6fd137 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Oct 2019 15:13:52 +0900 Subject: [PATCH 1876/2815] 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 c5714caf4c..43c1302e54 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -62,6 +62,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 64172a0954..e898a001de 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 5d384888d2..656c60543e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,8 +118,8 @@ - - + + From 54f23cbd0da2a062f375dcfd1696bf0427718822 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Oct 2019 15:49:36 +0900 Subject: [PATCH 1877/2815] Update stacking test case values --- .../old-stacking-expected-conversion.json | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking-expected-conversion.json b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking-expected-conversion.json index b994cbd85a..004e7940d1 100644 --- a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking-expected-conversion.json +++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking-expected-conversion.json @@ -143,14 +143,14 @@ "Objects": [{ "StartTime": 34989, "EndTime": 34989, - "X": 163, - "Y": 138 + "X": 156.597382, + "Y": 131.597382 }, { "StartTime": 35018, "EndTime": 35018, - "X": 188, - "Y": 138 + "X": 181.597382, + "Y": 131.597382 } ] }, @@ -159,14 +159,14 @@ "Objects": [{ "StartTime": 35106, "EndTime": 35106, - "X": 163, - "Y": 138 + "X": 159.798691, + "Y": 134.798691 }, { "StartTime": 35135, "EndTime": 35135, - "X": 188, - "Y": 138 + "X": 184.798691, + "Y": 134.798691 } ] }, @@ -191,20 +191,20 @@ "Objects": [{ "StartTime": 35695, "EndTime": 35695, - "X": 166, - "Y": 76 + "X": 162.798691, + "Y": 72.79869 }, { "StartTime": 35871, "EndTime": 35871, - "X": 240.99855, - "Y": 75.53417 + "X": 237.797241, + "Y": 72.33286 }, { "StartTime": 36011, "EndTime": 36011, - "X": 315.9971, - "Y": 75.0683441 + "X": 312.795776, + "Y": 71.8670349 } ] }, @@ -235,20 +235,20 @@ "Objects": [{ "StartTime": 36518, "EndTime": 36518, - "X": 166, - "Y": 76 + "X": 169.201309, + "Y": 79.20131 }, { "StartTime": 36694, "EndTime": 36694, - "X": 240.99855, - "Y": 75.53417 + "X": 244.19986, + "Y": 78.73548 }, { "StartTime": 36834, "EndTime": 36834, - "X": 315.9971, - "Y": 75.0683441 + "X": 319.198425, + "Y": 78.26965 } ] }, @@ -257,20 +257,20 @@ "Objects": [{ "StartTime": 36929, "EndTime": 36929, - "X": 315, - "Y": 75 + "X": 324.603943, + "Y": 84.6039352 }, { "StartTime": 37105, "EndTime": 37105, - "X": 240.001526, - "Y": 75.47769 + "X": 249.605469, + "Y": 85.08163 }, { "StartTime": 37245, "EndTime": 37245, - "X": 165.003052, - "Y": 75.95539 + "X": 174.607, + "Y": 85.5593262 } ] } From b903edca4581574a30eb4760eeb908df64a9317f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Oct 2019 16:03:16 +0900 Subject: [PATCH 1878/2815] Don't snap slider control point placement --- .../Blueprints/Sliders/SliderPlacementBlueprint.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 2fb18bf8ba..2fe294a7e0 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; +using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; @@ -28,6 +29,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private readonly List segments = new List(); private Vector2 cursor; + private InputManager inputManager; private PlacementState state; @@ -52,6 +54,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders setState(PlacementState.Initial); } + protected override void LoadComplete() + { + base.LoadComplete(); + inputManager = GetContainingInputManager(); + } + public override void UpdatePosition(Vector2 screenSpacePosition) { switch (state) @@ -61,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders break; case PlacementState.Body: - cursor = ToLocalSpace(screenSpacePosition) - HitObject.Position; + cursor = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position; break; } } From 64682611bbd547336f61e3fe7bd42594e802c7bd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Oct 2019 16:39:14 +0900 Subject: [PATCH 1879/2815] Fix distance snapping grid not updating on scroll --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 51155ce3fd..5b8f6663e7 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -162,12 +162,17 @@ namespace osu.Game.Rulesets.Edit inputManager = GetContainingInputManager(); } + private double lastGridUpdateTime; + protected override void Update() { base.Update(); - if (EditorClock.ElapsedFrameTime != 0 && blueprintContainer.CurrentTool != null) + if (EditorClock.CurrentTime != lastGridUpdateTime && blueprintContainer.CurrentTool != null) + { showGridFor(Enumerable.Empty()); + lastGridUpdateTime = EditorClock.CurrentTime; + } } protected override void UpdateAfterChildren() @@ -212,6 +217,8 @@ namespace osu.Game.Rulesets.Edit { distanceSnapGridContainer.Child = distanceSnapGrid; distanceSnapGridContainer.Show(); + + lastGridUpdateTime = EditorClock.CurrentTime; } } From 97383b4a37aeac057ab11b4baeb7176356e01d05 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Oct 2019 16:58:56 +0900 Subject: [PATCH 1880/2815] Show centre point of distance snap grid --- .../TestSceneOsuDistanceSnapGrid.cs | 7 +++++++ .../Components/CircularDistanceSnapGrid.cs | 17 +++++++++++++++++ .../Edit/Compose/Components/DistanceSnapGrid.cs | 10 +++++----- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs index da7708081b..6b8daa531f 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs @@ -2,6 +2,7 @@ // 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.Graphics; @@ -14,6 +15,7 @@ using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Tests.Visual; using osuTK; using osuTK.Graphics; @@ -25,6 +27,11 @@ namespace osu.Game.Rulesets.Osu.Tests private const double beat_length = 100; private static readonly Vector2 grid_position = new Vector2(512, 384); + public override IReadOnlyList RequiredTypes => new[] + { + typeof(CircularDistanceSnapGrid) + }; + [Cached(typeof(IEditorBeatmap))] private readonly EditorBeatmap editorBeatmap; diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index 3cbf926d4f..17b2eedb6e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Game.Rulesets.Objects; using osuTK; @@ -18,6 +19,22 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override void CreateContent(Vector2 centrePosition) { + AddInternal(new Box + { + Origin = Anchor.Centre, + Position = centrePosition, + Width = 2, + Height = Math.Min(10, DistanceSpacing * 2), + }); + + AddInternal(new Box + { + Origin = Anchor.Centre, + Position = centrePosition, + Width = Math.Min(10, DistanceSpacing * 2), + Height = 2, + }); + float dx = Math.Max(centrePosition.X, DrawWidth - centrePosition.X); float dy = Math.Max(centrePosition.Y, DrawHeight - centrePosition.Y); float maxDistance = new Vector2(dx, dy).Length; diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 299e78b7c0..096ff0a6dd 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -36,15 +36,15 @@ namespace osu.Game.Screens.Edit.Compose.Components /// protected readonly Vector2 CentrePosition; + [Resolved] + protected OsuColour Colours { get; private set; } + [Resolved] private IEditorBeatmap beatmap { get; set; } [Resolved] private BindableBeatDivisor beatDivisor { get; set; } - [Resolved] - private OsuColour colours { get; set; } - private readonly Cached gridCache = new Cached(); private readonly HitObject hitObject; @@ -136,7 +136,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected ColourInfo GetColourForBeatIndex(int index) { int beat = (index + 1) % beatDivisor.Value; - ColourInfo colour = colours.Gray5; + ColourInfo colour = Colours.Gray5; for (int i = 0; i < BindableBeatDivisor.VALID_DIVISORS.Length; i++) { @@ -144,7 +144,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if ((beat * divisor) % beatDivisor.Value == 0) { - colour = BindableBeatDivisor.GetColourFor(divisor, colours); + colour = BindableBeatDivisor.GetColourFor(divisor, Colours); break; } } From c9fec50f6321017afe59b832e8ff7783e120b161 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Oct 2019 17:00:13 +0900 Subject: [PATCH 1881/2815] Remove unnecessary whitespace --- .../Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index 17b2eedb6e..8188197022 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -38,7 +38,6 @@ namespace osu.Game.Screens.Edit.Compose.Components float dx = Math.Max(centrePosition.X, DrawWidth - centrePosition.X); float dy = Math.Max(centrePosition.Y, DrawHeight - centrePosition.Y); float maxDistance = new Vector2(dx, dy).Length; - int requiredCircles = (int)(maxDistance / DistanceSpacing); for (int i = 0; i < requiredCircles; i++) From 2c9b11cdfd2b898f271147e27ae5395d926687d8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Oct 2019 17:49:21 +0900 Subject: [PATCH 1882/2815] Move variable outside of if block --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 5b8f6663e7..7e7c260360 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -217,9 +217,9 @@ namespace osu.Game.Rulesets.Edit { distanceSnapGridContainer.Child = distanceSnapGrid; distanceSnapGridContainer.Show(); - - lastGridUpdateTime = EditorClock.CurrentTime; } + + lastGridUpdateTime = EditorClock.CurrentTime; } private ScheduledDelegate scheduledUpdate; From c03fa01fd9194f66b2e8c9733546aafddabdcb5c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Oct 2019 17:51:16 +0900 Subject: [PATCH 1883/2815] Remove unnecessary set --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 7e7c260360..6396301add 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -169,10 +169,7 @@ namespace osu.Game.Rulesets.Edit base.Update(); if (EditorClock.CurrentTime != lastGridUpdateTime && blueprintContainer.CurrentTool != null) - { showGridFor(Enumerable.Empty()); - lastGridUpdateTime = EditorClock.CurrentTime; - } } protected override void UpdateAfterChildren() From f61d7e4fbec3dd022eaec9593cbdfcfc9b2df510 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Oct 2019 17:56:09 +0900 Subject: [PATCH 1884/2815] Add smoothing and tidy code a touch --- .../Components/CircularDistanceSnapGrid.cs | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index 8188197022..a644e51c13 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -19,20 +19,27 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override void CreateContent(Vector2 centrePosition) { - AddInternal(new Box - { - Origin = Anchor.Centre, - Position = centrePosition, - Width = 2, - Height = Math.Min(10, DistanceSpacing * 2), - }); + const float crosshair_thickness = 1; + const float crosshair_max_size = 10; - AddInternal(new Box + AddRangeInternal(new[] { - Origin = Anchor.Centre, - Position = centrePosition, - Width = Math.Min(10, DistanceSpacing * 2), - Height = 2, + new Box + { + Origin = Anchor.Centre, + Position = centrePosition, + Width = crosshair_thickness, + EdgeSmoothness = new Vector2(1), + Height = Math.Min(crosshair_max_size, DistanceSpacing * 2), + }, + new Box + { + Origin = Anchor.Centre, + Position = centrePosition, + EdgeSmoothness = new Vector2(1), + Width = Math.Min(crosshair_max_size, DistanceSpacing * 2), + Height = crosshair_thickness, + } }); float dx = Math.Max(centrePosition.X, DrawWidth - centrePosition.X); From 436941cda34b7f6dcbd9ca95a872056bc94e24a9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Oct 2019 17:58:44 +0900 Subject: [PATCH 1885/2815] Add comment --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 2fe294a7e0..b7b8d0af88 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -69,6 +69,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders break; case PlacementState.Body: + // The given screen-space position may have been externally snapped, but the unsnapped position from the input manager + // is used instead since snapping control points doesn't make much sense cursor = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position; break; } From cef2318cf55861fc1c54ce14d868b28457372e90 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Oct 2019 18:37:57 +0900 Subject: [PATCH 1886/2815] Move drag box drag handling to BlueprintContainer --- .../Compose/Components/BlueprintContainer.cs | 28 ++++++++++++++++--- .../Edit/Compose/Components/DragBox.cs | 21 +------------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 4001a0f33a..c390ffe3f2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -23,6 +23,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { public event Action> SelectionChanged; + private DragBox dragBox; private SelectionBlueprintContainer selectionBlueprints; private Container placementBlueprintContainer; private PlacementBlueprint currentPlacement; @@ -46,12 +47,9 @@ namespace osu.Game.Screens.Edit.Compose.Components selectionHandler = composer.CreateSelectionHandler(); selectionHandler.DeselectAll = deselectAll; - var dragBox = new DragBox(select); - dragBox.DragEnd += () => selectionHandler.UpdateVisibility(); - InternalChildren = new[] { - dragBox, + dragBox = new DragBox(select), selectionHandler, selectionBlueprints = new SelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }, placementBlueprintContainer = new Container { RelativeSizeAxes = Axes.Both }, @@ -229,6 +227,28 @@ namespace osu.Game.Screens.Edit.Compose.Components private void onSelectionRequested(SelectionBlueprint blueprint, InputState state) => selectionHandler.HandleSelectionRequested(blueprint, state); + protected override bool OnDragStart(DragStartEvent e) + { + if (!selectionHandler.SelectedBlueprints.Any(b => b.IsHovered)) + dragBox.FadeIn(250, Easing.OutQuint); + + return true; + } + + protected override bool OnDrag(DragEvent e) + { + dragBox.UpdateDrag(e); + return true; + } + + protected override bool OnDragEnd(DragEndEvent e) + { + dragBox.FadeOut(250, Easing.OutQuint); + selectionHandler.UpdateVisibility(); + + return true; + } + private void onDragRequested(SelectionBlueprint blueprint, DragEvent dragEvent) { HitObject draggedObject = blueprint.DrawableObject.HitObject; diff --git a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs index 143615148a..7d892aa889 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs @@ -19,11 +19,6 @@ namespace osu.Game.Screens.Edit.Compose.Components { private readonly Action performSelection; - /// - /// Invoked when the drag selection has finished. - /// - public event Action DragEnd; - private Drawable box; /// @@ -55,13 +50,7 @@ namespace osu.Game.Screens.Edit.Compose.Components }; } - protected override bool OnDragStart(DragStartEvent e) - { - this.FadeIn(250, Easing.OutQuint); - return true; - } - - protected override bool OnDrag(DragEvent e) + public void UpdateDrag(DragEvent e) { var dragPosition = e.ScreenSpaceMousePosition; var dragStartPosition = e.ScreenSpaceMouseDownPosition; @@ -78,14 +67,6 @@ namespace osu.Game.Screens.Edit.Compose.Components box.Size = bottomRight - topLeft; performSelection?.Invoke(dragRectangle); - return true; - } - - protected override bool OnDragEnd(DragEndEvent e) - { - this.FadeOut(250, Easing.OutQuint); - DragEnd?.Invoke(); - return true; } } } From b310fd9d44503c7f889efc8357b76bb885a03367 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 23 Oct 2019 13:39:42 +0300 Subject: [PATCH 1887/2815] Adjust naming inside the LoadingButton --- .../Graphics/UserInterface/LoadingButton.cs | 18 +++++++++--------- .../Graphics/UserInterface/ShowMoreButton.cs | 4 ++-- osu.Game/Overlays/Comments/VotePill.cs | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/LoadingButton.cs b/osu.Game/Graphics/UserInterface/LoadingButton.cs index 46a4c70666..c177431c78 100644 --- a/osu.Game/Graphics/UserInterface/LoadingButton.cs +++ b/osu.Game/Graphics/UserInterface/LoadingButton.cs @@ -27,13 +27,13 @@ namespace osu.Game.Graphics.UserInterface if (value) { loading.Show(); - content.FadeOut(fade_duration, Easing.OutQuint); + text.FadeOut(fade_duration, Easing.OutQuint); OnLoadingStart(); } else { loading.Hide(); - content.FadeIn(fade_duration, Easing.OutQuint); + text.FadeIn(fade_duration, Easing.OutQuint); OnLoadingFinished(); } } @@ -46,17 +46,17 @@ namespace osu.Game.Graphics.UserInterface } private readonly LoadingAnimation loading; - private readonly Drawable content; + private readonly Drawable text; protected LoadingButton() { - Container background; + Container content; - Child = background = CreateBackground(); + Child = content = CreateContent(); - background.AddRange(new[] + content.AddRange(new[] { - content = CreateContent(), + text = CreateText(), loading = new LoadingAnimation { Anchor = Anchor.Centre, @@ -90,8 +90,8 @@ namespace osu.Game.Graphics.UserInterface { } - protected abstract Container CreateBackground(); + protected abstract Container CreateContent(); - protected abstract Drawable CreateContent(); + protected abstract Drawable CreateText(); } } diff --git a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs index 31e9af55c4..407e102ca5 100644 --- a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs +++ b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs @@ -40,7 +40,7 @@ namespace osu.Game.Graphics.UserInterface AutoSizeAxes = Axes.Both; } - protected override Container CreateBackground() => new CircularContainer + protected override Container CreateContent() => new CircularContainer { Masking = true, Size = new Vector2(140, 30), @@ -50,7 +50,7 @@ namespace osu.Game.Graphics.UserInterface } }; - protected override Drawable CreateContent() => new FillFlowContainer + protected override Drawable CreateText() => new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index 5eade6fc46..532fd8d905 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -84,7 +84,7 @@ namespace osu.Game.Overlays.Comments IsLoading = false; } - protected override Container CreateBackground() => new Container + protected override Container CreateContent() => new Container { RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, @@ -119,7 +119,7 @@ namespace osu.Game.Overlays.Comments }, }; - protected override Drawable CreateContent() => votesCounter = new OsuSpriteText + protected override Drawable CreateText() => votesCounter = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, From 714c89faa49272b4b5bbb0c275a842e05ddc85c9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Oct 2019 18:58:15 +0900 Subject: [PATCH 1888/2815] Move selection drag events to BlueprintContainer --- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 22 ------- .../Compose/Components/BlueprintContainer.cs | 64 ++++++++++++------- 2 files changed, 42 insertions(+), 44 deletions(-) diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 3076ad081a..8a6e8f6128 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -35,21 +35,11 @@ namespace osu.Game.Rulesets.Edit /// public event Action SelectionRequested; - /// - /// Invoked when this has requested drag. - /// - public event Action DragRequested; - /// /// The which this applies to. /// public readonly DrawableHitObject DrawableObject; - /// - /// The screen-space position of prior to handling a movement event. - /// - internal Vector2 ScreenSpaceMovementStartPosition { get; private set; } - protected override bool ShouldBeAlive => (DrawableObject.IsAlive && DrawableObject.IsPresent) || State == SelectionState.Selected; public override bool HandlePositionalInput => ShouldBeAlive; public override bool RemoveWhenNotAlive => false; @@ -136,18 +126,6 @@ namespace osu.Game.Rulesets.Edit return base.OnClick(e); } - protected override bool OnDragStart(DragStartEvent e) - { - ScreenSpaceMovementStartPosition = DrawableObject.ToScreenSpace(DrawableObject.OriginPosition); - return true; - } - - protected override bool OnDrag(DragEvent e) - { - DragRequested?.Invoke(this, e); - return true; - } - /// /// The screen-space point that causes this to be selected. /// diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index c390ffe3f2..2f401ede38 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -109,7 +110,6 @@ namespace osu.Game.Screens.Edit.Compose.Components blueprint.Selected -= onBlueprintSelected; blueprint.Deselected -= onBlueprintDeselected; blueprint.SelectionRequested -= onSelectionRequested; - blueprint.DragRequested -= onDragRequested; selectionBlueprints.Remove(blueprint); } @@ -125,7 +125,6 @@ namespace osu.Game.Screens.Edit.Compose.Components blueprint.Selected += onBlueprintSelected; blueprint.Deselected += onBlueprintDeselected; blueprint.SelectionRequested += onSelectionRequested; - blueprint.DragRequested += onDragRequested; selectionBlueprints.Add(blueprint); } @@ -227,9 +226,18 @@ namespace osu.Game.Screens.Edit.Compose.Components private void onSelectionRequested(SelectionBlueprint blueprint, InputState state) => selectionHandler.HandleSelectionRequested(blueprint, state); + private Vector2? screenSpaceMovementStartPosition; + private SelectionBlueprint movementBlueprint; + protected override bool OnDragStart(DragStartEvent e) { - if (!selectionHandler.SelectedBlueprints.Any(b => b.IsHovered)) + if (selectionHandler.SelectedBlueprints.Any(b => b.IsHovered)) + { + // The earliest hitobject is used for drag-movement/snapping + movementBlueprint = selectionHandler.SelectedBlueprints.OrderBy(b => b.DrawableObject.HitObject.StartTime).First(); + screenSpaceMovementStartPosition = movementBlueprint.DrawableObject.ToScreenSpace(movementBlueprint.DrawableObject.OriginPosition); + } + else dragBox.FadeIn(250, Easing.OutQuint); return true; @@ -237,34 +245,46 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnDrag(DragEvent e) { - dragBox.UpdateDrag(e); + if (movementBlueprint != null) + { + Debug.Assert(screenSpaceMovementStartPosition != null); + + Vector2 startPosition = screenSpaceMovementStartPosition.Value; + HitObject draggedObject = movementBlueprint.DrawableObject.HitObject; + + Vector2 movePosition = startPosition + e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition; + Vector2 snappedPosition = composer.GetSnappedPosition(ToLocalSpace(movePosition)); + + // Move the hitobjects + selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, startPosition, ToScreenSpace(snappedPosition))); + + // Apply the start time at the newly snapped-to position + double offset = composer.GetSnappedTime(draggedObject.StartTime, snappedPosition) - draggedObject.StartTime; + foreach (HitObject obj in selectionHandler.SelectedHitObjects) + obj.StartTime += offset; + } + else + dragBox.UpdateDrag(e); + return true; } protected override bool OnDragEnd(DragEndEvent e) { - dragBox.FadeOut(250, Easing.OutQuint); - selectionHandler.UpdateVisibility(); + if (movementBlueprint != null) + { + screenSpaceMovementStartPosition = null; + movementBlueprint = null; + } + else + { + dragBox.FadeOut(250, Easing.OutQuint); + selectionHandler.UpdateVisibility(); + } return true; } - private void onDragRequested(SelectionBlueprint blueprint, DragEvent dragEvent) - { - HitObject draggedObject = blueprint.DrawableObject.HitObject; - - Vector2 movePosition = blueprint.ScreenSpaceMovementStartPosition + dragEvent.ScreenSpaceMousePosition - dragEvent.ScreenSpaceMouseDownPosition; - Vector2 snappedPosition = composer.GetSnappedPosition(ToLocalSpace(movePosition)); - - // Move the hitobjects - selectionHandler.HandleMovement(new MoveSelectionEvent(blueprint, blueprint.ScreenSpaceMovementStartPosition, ToScreenSpace(snappedPosition))); - - // Apply the start time at the newly snapped-to position - double offset = composer.GetSnappedTime(draggedObject.StartTime, snappedPosition) - draggedObject.StartTime; - foreach (HitObject obj in selectionHandler.SelectedHitObjects) - obj.StartTime += offset; - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From e04c77178cfa94d7e89a6e0527b749114c4a41ba Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Oct 2019 14:58:02 +0900 Subject: [PATCH 1889/2815] Move selection events to BlueprintContainer --- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 35 ------------------- .../Compose/Components/BlueprintContainer.cs | 21 +++++++++-- 2 files changed, 19 insertions(+), 37 deletions(-) diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 8a6e8f6128..44f38acfd4 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -6,8 +6,6 @@ using osu.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; -using osu.Framework.Input.Events; -using osu.Framework.Input.States; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Objects.Drawables; using osuTK; @@ -29,12 +27,6 @@ namespace osu.Game.Rulesets.Edit /// public event Action Deselected; - /// - /// Invoked when this has requested selection. - /// Will fire even if already selected. Does not actually perform selection. - /// - public event Action SelectionRequested; - /// /// The which this applies to. /// @@ -99,33 +91,6 @@ namespace osu.Game.Rulesets.Edit public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos); - private bool selectionRequested; - - protected override bool OnMouseDown(MouseDownEvent e) - { - selectionRequested = false; - - if (State == SelectionState.NotSelected) - { - SelectionRequested?.Invoke(this, e.CurrentState); - selectionRequested = true; - } - - return IsSelected; - } - - protected override bool OnClick(ClickEvent e) - { - if (State == SelectionState.Selected && !selectionRequested) - { - selectionRequested = true; - SelectionRequested?.Invoke(this, e.CurrentState); - return true; - } - - return base.OnClick(e); - } - /// /// The screen-space point that causes this to be selected. /// diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 2f401ede38..089f1bde9c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -109,7 +109,6 @@ namespace osu.Game.Screens.Edit.Compose.Components blueprint.Selected -= onBlueprintSelected; blueprint.Deselected -= onBlueprintDeselected; - blueprint.SelectionRequested -= onSelectionRequested; selectionBlueprints.Remove(blueprint); } @@ -124,15 +123,31 @@ namespace osu.Game.Screens.Edit.Compose.Components blueprint.Selected += onBlueprintSelected; blueprint.Deselected += onBlueprintDeselected; - blueprint.SelectionRequested += onSelectionRequested; selectionBlueprints.Add(blueprint); } private void removeBlueprintFor(DrawableHitObject hitObject) => removeBlueprintFor(hitObject.HitObject); + protected override bool OnMouseDown(MouseDownEvent e) + { + foreach (SelectionBlueprint blueprint in selectionBlueprints.AliveBlueprints) + { + if (blueprint.IsHovered) + { + selectionHandler.HandleSelectionRequested(blueprint, e.CurrentState); + break; + } + } + + return true; + } + protected override bool OnClick(ClickEvent e) { + if (selectionBlueprints.AliveBlueprints.Any(b => b.IsHovered)) + return true; + deselectAll(); return true; } @@ -298,6 +313,8 @@ namespace osu.Game.Screens.Edit.Compose.Components private class SelectionBlueprintContainer : Container { + public IEnumerable AliveBlueprints => AliveInternalChildren.Cast(); + protected override int Compare(Drawable x, Drawable y) { if (!(x is SelectionBlueprint xBlueprint) || !(y is SelectionBlueprint yBlueprint)) From f128e99fb2a0e16043730d05164620aeeab362e1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Oct 2019 15:07:04 +0900 Subject: [PATCH 1890/2815] Remove unused methods --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 089f1bde9c..a2893c735e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Input; using osu.Framework.Input.Events; -using osu.Framework.Input.States; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; @@ -127,8 +126,6 @@ namespace osu.Game.Screens.Edit.Compose.Components selectionBlueprints.Add(blueprint); } - private void removeBlueprintFor(DrawableHitObject hitObject) => removeBlueprintFor(hitObject.HitObject); - protected override bool OnMouseDown(MouseDownEvent e) { foreach (SelectionBlueprint blueprint in selectionBlueprints.AliveBlueprints) @@ -239,8 +236,6 @@ namespace osu.Game.Screens.Edit.Compose.Components SelectionChanged?.Invoke(selectionHandler.SelectedHitObjects); } - private void onSelectionRequested(SelectionBlueprint blueprint, InputState state) => selectionHandler.HandleSelectionRequested(blueprint, state); - private Vector2? screenSpaceMovementStartPosition; private SelectionBlueprint movementBlueprint; From a07e5a269b4d5b6dd0b23432779026e82847d371 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Oct 2019 15:11:54 +0900 Subject: [PATCH 1891/2815] Extract drag events into multiple methods --- .../Compose/Components/BlueprintContainer.cs | 109 ++++++++++++------ 1 file changed, 72 insertions(+), 37 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index a2893c735e..e442e1b0b2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -160,6 +160,33 @@ namespace osu.Game.Screens.Edit.Compose.Components return base.OnMouseMove(e); } + protected override bool OnDragStart(DragStartEvent e) + { + if (!beginSelectionMovement()) + dragBox.FadeIn(250, Easing.OutQuint); + + return true; + } + + protected override bool OnDrag(DragEvent e) + { + if (!moveCurrentSelection(e)) + dragBox.UpdateDrag(e); + + return true; + } + + protected override bool OnDragEnd(DragEndEvent e) + { + if (!finishSelectionMovement()) + { + dragBox.FadeOut(250, Easing.OutQuint); + selectionHandler.UpdateVisibility(); + } + + return true; + } + protected override void Update() { base.Update(); @@ -239,58 +266,66 @@ namespace osu.Game.Screens.Edit.Compose.Components private Vector2? screenSpaceMovementStartPosition; private SelectionBlueprint movementBlueprint; - protected override bool OnDragStart(DragStartEvent e) + /// + /// Attempts to begin the movement of any selected blueprints. + /// + /// Whether movement began. + private bool beginSelectionMovement() { - if (selectionHandler.SelectedBlueprints.Any(b => b.IsHovered)) - { - // The earliest hitobject is used for drag-movement/snapping - movementBlueprint = selectionHandler.SelectedBlueprints.OrderBy(b => b.DrawableObject.HitObject.StartTime).First(); - screenSpaceMovementStartPosition = movementBlueprint.DrawableObject.ToScreenSpace(movementBlueprint.DrawableObject.OriginPosition); - } - else - dragBox.FadeIn(250, Easing.OutQuint); + Debug.Assert(movementBlueprint == null); + + // Any selected blueprints can begin the movement of the group, however only the earliest hitobject is used for movement + if (!selectionHandler.SelectedBlueprints.Any(b => b.IsHovered)) + return false; + + // Movement is tracked from the blueprint of the earliest hitobject, since it only makes sense to distance snap from that hitobject + movementBlueprint = selectionHandler.SelectedBlueprints.OrderBy(b => b.DrawableObject.HitObject.StartTime).First(); + screenSpaceMovementStartPosition = movementBlueprint.DrawableObject.ToScreenSpace(movementBlueprint.DrawableObject.OriginPosition); return true; } - protected override bool OnDrag(DragEvent e) + /// + /// Moves the current selected blueprints. + /// + /// The defining the movement event. + /// Whether a movement was active. + private bool moveCurrentSelection(DragEvent e) { - if (movementBlueprint != null) - { - Debug.Assert(screenSpaceMovementStartPosition != null); + if (movementBlueprint == null) + return false; - Vector2 startPosition = screenSpaceMovementStartPosition.Value; - HitObject draggedObject = movementBlueprint.DrawableObject.HitObject; + Debug.Assert(screenSpaceMovementStartPosition != null); - Vector2 movePosition = startPosition + e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition; - Vector2 snappedPosition = composer.GetSnappedPosition(ToLocalSpace(movePosition)); + Vector2 startPosition = screenSpaceMovementStartPosition.Value; + HitObject draggedObject = movementBlueprint.DrawableObject.HitObject; - // Move the hitobjects - selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, startPosition, ToScreenSpace(snappedPosition))); + // The final movement position, relative to screenSpaceMovementStartPosition + Vector2 movePosition = startPosition + e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition; + Vector2 snappedPosition = composer.GetSnappedPosition(ToLocalSpace(movePosition)); - // Apply the start time at the newly snapped-to position - double offset = composer.GetSnappedTime(draggedObject.StartTime, snappedPosition) - draggedObject.StartTime; - foreach (HitObject obj in selectionHandler.SelectedHitObjects) - obj.StartTime += offset; - } - else - dragBox.UpdateDrag(e); + // Move the hitobjects + selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, startPosition, ToScreenSpace(snappedPosition))); + + // Apply the start time at the newly snapped-to position + double offset = composer.GetSnappedTime(draggedObject.StartTime, snappedPosition) - draggedObject.StartTime; + foreach (HitObject obj in selectionHandler.SelectedHitObjects) + obj.StartTime += offset; return true; } - protected override bool OnDragEnd(DragEndEvent e) + /// + /// Finishes the current movement of selected blueprints. + /// + /// Whether a movement was active. + private bool finishSelectionMovement() { - if (movementBlueprint != null) - { - screenSpaceMovementStartPosition = null; - movementBlueprint = null; - } - else - { - dragBox.FadeOut(250, Easing.OutQuint); - selectionHandler.UpdateVisibility(); - } + if (movementBlueprint == null) + return false; + + screenSpaceMovementStartPosition = null; + movementBlueprint = null; return true; } From 8e4a21bee756c610c61e4be2d1737b6d5d2aa7f7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Oct 2019 15:58:22 +0900 Subject: [PATCH 1892/2815] Separate out mouse down/click/up handling --- .../Compose/Components/BlueprintContainer.cs | 61 ++++++++++++++++--- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index e442e1b0b2..123fdc78e6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Input; using osu.Framework.Input.Events; +using osu.Framework.Input.States; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; @@ -128,27 +129,27 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnMouseDown(MouseDownEvent e) { - foreach (SelectionBlueprint blueprint in selectionBlueprints.AliveBlueprints) - { - if (blueprint.IsHovered) - { - selectionHandler.HandleSelectionRequested(blueprint, e.CurrentState); - break; - } - } - + beginClickSelection(e); return true; } protected override bool OnClick(ClickEvent e) { - if (selectionBlueprints.AliveBlueprints.Any(b => b.IsHovered)) + // clickSelectionBegan will be true if a mouse down occurred on the blueprint but the click event was received outside of the blueprint + // otherwise, deselection should only occur if the click event did not occur on top of a selected blueprint + if (clickSelectionBegan || selectionHandler.SelectedBlueprints.Any(b => b.IsHovered)) return true; deselectAll(); return true; } + protected override bool OnMouseUp(MouseUpEvent e) + { + endClickSelection(); + return true; + } + protected override bool OnMouseMove(MouseMoveEvent e) { if (currentPlacement != null) @@ -227,6 +228,40 @@ namespace osu.Game.Screens.Edit.Compose.Components currentPlacement.UpdatePosition(snappedScreenSpacePosition); } + #region Selection + + /// + /// Whether a blueprint was selected by a previous click event. + /// + private bool clickSelectionBegan; + + /// + /// Attempts to select any hovered blueprints. + /// + /// The input event that triggered this selection. + private void beginClickSelection(UIEvent e) + { + Debug.Assert(!clickSelectionBegan); + + foreach (SelectionBlueprint blueprint in selectionBlueprints.AliveBlueprints) + { + if (blueprint.IsHovered) + { + selectionHandler.HandleSelectionRequested(blueprint, e.CurrentState); + clickSelectionBegan = true; + break; + } + } + } + + /// + /// Finishes the current blueprint selection. + /// + private void endClickSelection() + { + clickSelectionBegan = false; + } + /// /// Select all masks in a given rectangle selection area. /// @@ -263,6 +298,10 @@ namespace osu.Game.Screens.Edit.Compose.Components SelectionChanged?.Invoke(selectionHandler.SelectedHitObjects); } + #endregion + + #region Selection Movement + private Vector2? screenSpaceMovementStartPosition; private SelectionBlueprint movementBlueprint; @@ -330,6 +369,8 @@ namespace osu.Game.Screens.Edit.Compose.Components return true; } + #endregion + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 45bd91f63fd10fd9c617fb6082f64166f90db160 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Oct 2019 16:14:29 +0900 Subject: [PATCH 1893/2815] Add special cases for click-selection --- .../Compose/Components/BlueprintContainer.cs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 123fdc78e6..8d87af6931 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Input; using osu.Framework.Input.Events; -using osu.Framework.Input.States; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; @@ -135,9 +134,9 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnClick(ClickEvent e) { - // clickSelectionBegan will be true if a mouse down occurred on the blueprint but the click event was received outside of the blueprint - // otherwise, deselection should only occur if the click event did not occur on top of a selected blueprint - if (clickSelectionBegan || selectionHandler.SelectedBlueprints.Any(b => b.IsHovered)) + // Deselection should only occur if no selected blueprints are hovered + // A special case for when a blueprint was selected via this click is added since OnClick() may occur outside the hitobject and should not trigger deselection + if (endClickSelection() || selectionHandler.SelectedBlueprints.Any(b => b.IsHovered)) return true; deselectAll(); @@ -146,7 +145,8 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnMouseUp(MouseUpEvent e) { - endClickSelection(); + // Special case for when a drag happened instead of a click + Schedule(() => endClickSelection()); return true; } @@ -257,9 +257,14 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Finishes the current blueprint selection. /// - private void endClickSelection() + /// Whether a click selection was active. + private bool endClickSelection() { + if (!clickSelectionBegan) + return false; + clickSelectionBegan = false; + return true; } /// @@ -313,8 +318,9 @@ namespace osu.Game.Screens.Edit.Compose.Components { Debug.Assert(movementBlueprint == null); - // Any selected blueprints can begin the movement of the group, however only the earliest hitobject is used for movement - if (!selectionHandler.SelectedBlueprints.Any(b => b.IsHovered)) + // Any selected blueprint that is hovered can begin the movement of the group, however only the earliest hitobject is used for movement + // A special case is added for when a click selection occurred before the drag + if (!clickSelectionBegan && !selectionHandler.SelectedBlueprints.Any(b => b.IsHovered)) return false; // Movement is tracked from the blueprint of the earliest hitobject, since it only makes sense to distance snap from that hitobject From fb88001c0ed0476c767bab32182bd8eac3f7018c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Oct 2019 16:17:48 +0900 Subject: [PATCH 1894/2815] Reorder blueprint addition/removal + add regions --- .../Compose/Components/BlueprintContainer.cs | 82 ++++++++++--------- 1 file changed, 45 insertions(+), 37 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 8d87af6931..d3cd0b1d65 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -89,43 +89,6 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - private void addBlueprintFor(HitObject hitObject) - { - var drawable = composer.HitObjects.FirstOrDefault(d => d.HitObject == hitObject); - if (drawable == null) - return; - - addBlueprintFor(drawable); - } - - private void removeBlueprintFor(HitObject hitObject) - { - var blueprint = selectionBlueprints.Single(m => m.DrawableObject.HitObject == hitObject); - if (blueprint == null) - return; - - blueprint.Deselect(); - - blueprint.Selected -= onBlueprintSelected; - blueprint.Deselected -= onBlueprintDeselected; - - selectionBlueprints.Remove(blueprint); - } - - private void addBlueprintFor(DrawableHitObject hitObject) - { - refreshTool(); - - var blueprint = composer.CreateBlueprintFor(hitObject); - if (blueprint == null) - return; - - blueprint.Selected += onBlueprintSelected; - blueprint.Deselected += onBlueprintDeselected; - - selectionBlueprints.Add(blueprint); - } - protected override bool OnMouseDown(MouseDownEvent e) { beginClickSelection(e); @@ -201,6 +164,49 @@ namespace osu.Game.Screens.Edit.Compose.Components } } + #region Blueprint Addition/Removal + + private void addBlueprintFor(HitObject hitObject) + { + var drawable = composer.HitObjects.FirstOrDefault(d => d.HitObject == hitObject); + if (drawable == null) + return; + + addBlueprintFor(drawable); + } + + private void removeBlueprintFor(HitObject hitObject) + { + var blueprint = selectionBlueprints.Single(m => m.DrawableObject.HitObject == hitObject); + if (blueprint == null) + return; + + blueprint.Deselect(); + + blueprint.Selected -= onBlueprintSelected; + blueprint.Deselected -= onBlueprintDeselected; + + selectionBlueprints.Remove(blueprint); + } + + private void addBlueprintFor(DrawableHitObject hitObject) + { + refreshTool(); + + var blueprint = composer.CreateBlueprintFor(hitObject); + if (blueprint == null) + return; + + blueprint.Selected += onBlueprintSelected; + blueprint.Deselected += onBlueprintDeselected; + + selectionBlueprints.Add(blueprint); + } + + #endregion + + #region Placement + /// /// Refreshes the current placement tool. /// @@ -228,6 +234,8 @@ namespace osu.Game.Screens.Edit.Compose.Components currentPlacement.UpdatePosition(snappedScreenSpacePosition); } + #endregion + #region Selection /// From 7a71352684566cfa1649753181981160a9b203d4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Oct 2019 17:22:14 +0900 Subject: [PATCH 1895/2815] Fix drag box being positioned incorrectly for 1 frame --- osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs | 3 +++ osu.Game/Screens/Edit/Compose/Components/DragBox.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index d3cd0b1d65..30f0f94128 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -127,7 +127,10 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnDragStart(DragStartEvent e) { if (!beginSelectionMovement()) + { + dragBox.UpdateDrag(e); dragBox.FadeIn(250, Easing.OutQuint); + } return true; } diff --git a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs index 7d892aa889..2a510e74fd 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs @@ -50,7 +50,7 @@ namespace osu.Game.Screens.Edit.Compose.Components }; } - public void UpdateDrag(DragEvent e) + public void UpdateDrag(MouseButtonEvent e) { var dragPosition = e.ScreenSpaceMousePosition; var dragStartPosition = e.ScreenSpaceMouseDownPosition; From f45f17339c9d6c4d4c7d61020cbcfdd8ad090ac3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Oct 2019 18:09:20 +0900 Subject: [PATCH 1896/2815] Implement slider path distance snapping --- .../Blueprints/Sliders/SliderPlacementBlueprint.cs | 11 +++++++++-- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 ++++ .../Edit/Compose/Components/DistanceSnapGrid.cs | 7 +++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index b7b8d0af88..e1478a062c 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -33,6 +33,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private PlacementState state; + [Resolved] + private HitObjectComposer composer { get; set; } + public SliderPlacementBlueprint() : base(new Objects.Slider()) { @@ -131,8 +134,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void updateSlider() { - var newControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray(); - HitObject.Path = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints); + Vector2[] newControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray(); + + var unsnappedPath = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints); + var snappedDistance = composer.GetSnappedDistance((float)unsnappedPath.Distance); + + HitObject.Path = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints, snappedDistance); bodyPiece.UpdateFrom(HitObject); headCirclePiece.UpdateFrom(HitObject.HeadCircle); diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 6396301add..f9d2734e80 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -261,6 +261,8 @@ namespace osu.Game.Rulesets.Edit public override double GetSnappedTime(double startTime, Vector2 position) => distanceSnapGrid?.GetSnapTime(position) ?? startTime; + public override float GetSnappedDistance(float distance) => distanceSnapGrid?.GetSnapDistance(distance) ?? distance; + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -313,5 +315,7 @@ namespace osu.Game.Rulesets.Edit public abstract Vector2 GetSnappedPosition(Vector2 position); public abstract double GetSnappedTime(double startTime, Vector2 screenSpacePosition); + + public abstract float GetSnappedDistance(float distance); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 096ff0a6dd..3bedff79f5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -128,6 +128,13 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The time at the snapped position. public double GetSnapTime(Vector2 position) => startTime + (position - CentrePosition).Length / Velocity; + /// + /// Snaps a distance by the snap distance of this grid. + /// + /// The distance to snap. + /// The snapped distance. + public float GetSnapDistance(float distance) => (int)(distance / DistanceSpacing) * DistanceSpacing; + /// /// Retrieves the applicable colour for a beat index. /// From d83b9ef0e4078688dd1b27cb243b4c147144de96 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Oct 2019 18:17:54 +0900 Subject: [PATCH 1897/2815] Rename grid snapping methods --- osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs | 4 ++-- osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs | 6 +++--- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 6 +++--- .../Edit/Compose/Components/CircularDistanceSnapGrid.cs | 2 +- .../Screens/Edit/Compose/Components/DistanceSnapGrid.cs | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs index 6b8daa531f..fddbcea374 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs @@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void assertSnappedDistance(float expectedDistance) => AddAssert($"snap distance = {expectedDistance}", () => { - Vector2 snappedPosition = grid.GetSnapPosition(grid.ToLocalSpace(InputManager.CurrentState.Mouse.Position)); + Vector2 snappedPosition = grid.GetSnappedPosition(grid.ToLocalSpace(InputManager.CurrentState.Mouse.Position)); float distance = Vector2.Distance(snappedPosition, grid_position); return Precision.AlmostEquals(expectedDistance, distance); @@ -160,7 +160,7 @@ namespace osu.Game.Rulesets.Osu.Tests Colour = Color4.SlateGray }, grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }), - new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnapPosition(grid.ToLocalSpace(v)) } + new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)) } }; }); } diff --git a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs index a9e5930478..54d910fdcf 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs @@ -106,10 +106,10 @@ namespace osu.Game.Tests.Visual.Editor Vector2 snapPosition = Vector2.Zero; AddStep("get first tick position", () => snapPosition = grid_position + new Vector2((float)beat_length, 0)); - AddAssert("snap time is 1 beat away", () => Precision.AlmostEquals(beat_length, grid.GetSnapTime(snapPosition), 0.01)); + AddAssert("snap time is 1 beat away", () => Precision.AlmostEquals(beat_length, grid.GetSnappedTime(snapPosition), 0.01)); createGrid(g => g.Velocity = 2, "with velocity = 2"); - AddAssert("snap time is now 0.5 beats away", () => Precision.AlmostEquals(beat_length / 2, grid.GetSnapTime(snapPosition), 0.01)); + AddAssert("snap time is now 0.5 beats away", () => Precision.AlmostEquals(beat_length / 2, grid.GetSnappedTime(snapPosition), 0.01)); } private void createGrid(Action func = null, string description = null) @@ -206,7 +206,7 @@ namespace osu.Game.Tests.Visual.Editor protected override float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) => Velocity; - public override Vector2 GetSnapPosition(Vector2 screenSpacePosition) + public override Vector2 GetSnappedPosition(Vector2 screenSpacePosition) => Vector2.Zero; } } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index f9d2734e80..3c24c3dd1f 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -257,11 +257,11 @@ namespace osu.Game.Rulesets.Edit public void Delete(HitObject hitObject) => EditorBeatmap.Remove(hitObject); - public override Vector2 GetSnappedPosition(Vector2 position) => distanceSnapGrid?.GetSnapPosition(position) ?? position; + public override Vector2 GetSnappedPosition(Vector2 position) => distanceSnapGrid?.GetSnappedPosition(position) ?? position; - public override double GetSnappedTime(double startTime, Vector2 position) => distanceSnapGrid?.GetSnapTime(position) ?? startTime; + public override double GetSnappedTime(double startTime, Vector2 position) => distanceSnapGrid?.GetSnappedTime(position) ?? startTime; - public override float GetSnappedDistance(float distance) => distanceSnapGrid?.GetSnapDistance(distance) ?? distance; + public override float GetSnappedDistance(float distance) => distanceSnapGrid?.GetSnappedDistance(distance) ?? distance; protected override void Dispose(bool isDisposing) { diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index a644e51c13..381ae9f927 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -63,7 +63,7 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - public override Vector2 GetSnapPosition(Vector2 position) + public override Vector2 GetSnappedPosition(Vector2 position) { Vector2 direction = position - CentrePosition; diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 3bedff79f5..50c69ae561 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -119,21 +119,21 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// The original position in coordinate space local to this . /// The snapped position in coordinate space local to this . - public abstract Vector2 GetSnapPosition(Vector2 position); + public abstract Vector2 GetSnappedPosition(Vector2 position); /// /// Retrieves the time at a snapped position. /// /// The snapped position in coordinate space local to this . /// The time at the snapped position. - public double GetSnapTime(Vector2 position) => startTime + (position - CentrePosition).Length / Velocity; + public double GetSnappedTime(Vector2 position) => startTime + (position - CentrePosition).Length / Velocity; /// /// Snaps a distance by the snap distance of this grid. /// /// The distance to snap. /// The snapped distance. - public float GetSnapDistance(float distance) => (int)(distance / DistanceSpacing) * DistanceSpacing; + public float GetSnappedDistance(float distance) => (int)(distance / DistanceSpacing) * DistanceSpacing; /// /// Retrieves the applicable colour for a beat index. From a969914d6ed0be5aadee0d3c40e43d0e640a5319 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Oct 2019 18:24:22 +0900 Subject: [PATCH 1898/2815] Mention coordinate space --- osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 50c69ae561..250a0abef5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -131,7 +131,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Snaps a distance by the snap distance of this grid. /// - /// The distance to snap. + /// The distance to snap in coordinate space local to this . /// The snapped distance. public float GetSnappedDistance(float distance) => (int)(distance / DistanceSpacing) * DistanceSpacing; From 0af5706db6f02c68a15302cf3b9fc0cf9b3a25a2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Oct 2019 19:02:59 +0900 Subject: [PATCH 1899/2815] Snap path during control point movement --- .../Sliders/Components/PathControlPointPiece.cs | 6 ++++-- .../Components/PathControlPointVisualiser.cs | 6 +++++- .../Sliders/SliderPlacementBlueprint.cs | 2 +- .../Sliders/SliderSelectionBlueprint.cs | 17 ++++++++++++++++- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 3aec7c2872..7afb8fcf49 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.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.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -8,7 +9,6 @@ using osu.Framework.Graphics.Lines; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osuTK; @@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { public class PathControlPointPiece : BlueprintPiece { + public Action ControlPointsChanged; + private readonly Slider slider; private readonly int index; @@ -96,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (isSegmentSeparatorWithPrevious) newControlPoints[index - 1] = newControlPoints[index]; - slider.Path = new SliderPath(slider.Path.Type, newControlPoints); + ControlPointsChanged?.Invoke(newControlPoints); return true; } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 24fcc460d1..0385824b27 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -1,14 +1,18 @@ // 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.Containers; using osu.Game.Rulesets.Osu.Objects; +using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { public class PathControlPointVisualiser : CompositeDrawable { + public Action ControlPointsChanged; + private readonly Slider slider; private readonly Container pieces; @@ -25,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components base.Update(); while (slider.Path.ControlPoints.Length > pieces.Count) - pieces.Add(new PathControlPointPiece(slider, pieces.Count)); + pieces.Add(new PathControlPointPiece(slider, pieces.Count) { ControlPointsChanged = c => ControlPointsChanged?.Invoke(c) }); while (slider.Path.ControlPoints.Length < pieces.Count) pieces.Remove(pieces[pieces.Count - 1]); } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index e1478a062c..761c2961ea 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders bodyPiece = new SliderBodyPiece(), headCirclePiece = new HitCirclePiece(), tailCirclePiece = new HitCirclePiece(), - new PathControlPointVisualiser(HitObject), + new PathControlPointVisualiser(HitObject) { ControlPointsChanged = _ => updateSlider() }, }; setState(PlacementState.Initial); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index fdeffc6f8a..a62c3cbb9f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -1,7 +1,11 @@ // 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.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -15,6 +19,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected readonly SliderCircleSelectionBlueprint HeadBlueprint; protected readonly SliderCircleSelectionBlueprint TailBlueprint; + [Resolved] + private HitObjectComposer composer { get; set; } + public SliderSelectionBlueprint(DrawableSlider slider) : base(slider) { @@ -25,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders BodyPiece = new SliderBodyPiece(), HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start), TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End), - new PathControlPointVisualiser(sliderObject), + new PathControlPointVisualiser(sliderObject) { ControlPointsChanged = onNewControlPoints }, }; } @@ -36,6 +43,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders BodyPiece.UpdateFrom(HitObject); } + private void onNewControlPoints(Vector2[] controlPoints) + { + var unsnappedPath = new SliderPath(controlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, controlPoints); + var snappedDistance = composer.GetSnappedDistance((float)unsnappedPath.Distance); + + HitObject.Path = new SliderPath(unsnappedPath.Type, controlPoints, snappedDistance); + } + public override Vector2 SelectionPoint => HeadBlueprint.SelectionPoint; protected virtual SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new SliderCircleSelectionBlueprint(slider, position); From b7af4acdbf21bd3c3e521b1b12abb63d53341ea6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Oct 2019 19:04:00 +0900 Subject: [PATCH 1900/2815] Allow not having a composer --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 4 ++-- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 761c2961ea..400a1fea14 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private PlacementState state; - [Resolved] + [Resolved(CanBeNull = true)] private HitObjectComposer composer { get; set; } public SliderPlacementBlueprint() @@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders Vector2[] newControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray(); var unsnappedPath = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints); - var snappedDistance = composer.GetSnappedDistance((float)unsnappedPath.Distance); + var snappedDistance = composer?.GetSnappedDistance((float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance; HitObject.Path = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints, snappedDistance); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index a62c3cbb9f..a90ed677ff 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected readonly SliderCircleSelectionBlueprint HeadBlueprint; protected readonly SliderCircleSelectionBlueprint TailBlueprint; - [Resolved] + [Resolved(CanBeNull = true)] private HitObjectComposer composer { get; set; } public SliderSelectionBlueprint(DrawableSlider slider) @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void onNewControlPoints(Vector2[] controlPoints) { var unsnappedPath = new SliderPath(controlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, controlPoints); - var snappedDistance = composer.GetSnappedDistance((float)unsnappedPath.Distance); + var snappedDistance = composer?.GetSnappedDistance((float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance; HitObject.Path = new SliderPath(unsnappedPath.Type, controlPoints, snappedDistance); } From a6458fdeab45510f6cf92956cfbd038237009bb6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Oct 2019 19:04:24 +0900 Subject: [PATCH 1901/2815] Re-use slider type --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 400a1fea14..02923203b1 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -139,7 +139,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders var unsnappedPath = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints); var snappedDistance = composer?.GetSnappedDistance((float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance; - HitObject.Path = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints, snappedDistance); + HitObject.Path = new SliderPath(unsnappedPath.Type, newControlPoints, snappedDistance); bodyPiece.UpdateFrom(HitObject); headCirclePiece.UpdateFrom(HitObject.HeadCircle); From a89ea78a7a54caa26d2882f12cdb7e086e4f937f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Oct 2019 00:34:33 +0200 Subject: [PATCH 1902/2815] Add extended testing for Markdown links While reviewing #6542 it became apparent that there was another Markdown link format variant, used in comments that came from the web API, called the "inline link" style. It allows to specify the tooltip title within the actual URL portion, as such: [link text](https://osu.ppy.sh "tooltip text") Add tests with a couple of easy and trickier examples of such a format. Moreover, add a new edge case of a Markdown link with a link inside the display text, which during tests was detected to be problematic. --- osu.Game.Tests/Chat/MessageFormatterTests.cs | 48 ++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index 9b4a90e9a9..1cbd083f0d 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -273,6 +273,54 @@ namespace osu.Game.Tests.Chat Assert.AreEqual(21, result.Links[0].Length); } + [Test] + public void TestMarkdownFormatLinkWithInlineTitle() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "I haven't seen [this link format](https://osu.ppy.sh \"osu!\") before..." }); + + Assert.AreEqual("I haven't seen this link format before...", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); + Assert.AreEqual(15, result.Links[0].Index); + Assert.AreEqual(16, result.Links[0].Length); + } + + [Test] + public void TestMarkdownFormatLinkWithInlineTitleAndEscapedQuotes() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "I haven't seen [this link format](https://osu.ppy.sh \"inner quote \\\" just to confuse \") before..." }); + + Assert.AreEqual("I haven't seen this link format before...", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); + Assert.AreEqual(15, result.Links[0].Index); + Assert.AreEqual(16, result.Links[0].Length); + } + + [Test] + public void TestMarkdownFormatLinkWithUrlInTextAndInlineTitle() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "I haven't seen [https://osu.ppy.sh](https://osu.ppy.sh \"https://osu.ppy.sh\") before..." }); + + Assert.AreEqual("I haven't seen https://osu.ppy.sh before...", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); + Assert.AreEqual(15, result.Links[0].Index); + Assert.AreEqual(18, result.Links[0].Length); + } + + [Test] + public void TestMarkdownFormatLinkWithMisleadingUrlInText() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "I haven't seen [https://google.com](https://osu.ppy.sh) before..." }); + + Assert.AreEqual("I haven't seen https://google.com before...", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); + Assert.AreEqual(15, result.Links[0].Index); + Assert.AreEqual(18, result.Links[0].Length); + } + [Test] public void TestChannelLink() { From 24b71605227c247a37937dacafeaf2d600007760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Oct 2019 00:40:58 +0200 Subject: [PATCH 1903/2815] Add support for parsing Markdown inline links Extend the Markdown parsing regex to allow parsing so-called inline links. Within the parenthesis () part of the Markdown URL syntax, introduce a new capturing group: ( \s+ // whitespace between actual URL and inline title (? // start of "title" named group "" // opening double quote (doubled inside @ string) ( [^""] // any character but a double quote | // or (?<=\\) // the next character should be preceded by a \ "" // a double quote )* // zero or more times "" // closing double quote ) )? // the whole group is optional This allows for parsing the inline links as-provided by web. Correctness is displayed by the passing tests. --- osu.Game/Online/Chat/MessageFormatter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 24d17612ee..749d24ee75 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -20,7 +20,7 @@ namespace osu.Game.Online.Chat private static readonly Regex new_link_regex = new Regex(@"\[(?<url>[a-z]+://[^ ]+) (?<text>(((?<=\\)[\[\]])|[^\[\]])*(((?<open>\[)(((?<=\\)[\[\]])|[^\[\]])*)+((?<close-open>\])(((?<=\\)[\[\]])|[^\[\]])*)+)*(?(open)(?!)))\]"); // [test](https://osu.ppy.sh/b/1234) -> test (https://osu.ppy.sh/b/1234) aka correct markdown format - private static readonly Regex markdown_link_regex = new Regex(@"\[(?<text>(((?<=\\)[\[\]])|[^\[\]])*(((?<open>\[)(((?<=\\)[\[\]])|[^\[\]])*)+((?<close-open>\])(((?<=\\)[\[\]])|[^\[\]])*)+)*(?(open)(?!)))\]\((?<url>[a-z]+://[^ ]+)\)"); + private static readonly Regex markdown_link_regex = new Regex(@"\[(?<text>(((?<=\\)[\[\]])|[^\[\]])*(((?<open>\[)(((?<=\\)[\[\]])|[^\[\]])*)+((?<close-open>\])(((?<=\\)[\[\]])|[^\[\]])*)+)*(?(open)(?!)))\]\((?<url>[a-z]+://[^ ]+)(\s+(?<title>""([^""]|(?<=\\)"")*""))?\)"); // advanced, RFC-compatible regular expression that matches any possible URL, *but* allows certain invalid characters that are widely used // This is in the format (<required>, [optional]): From cbd99cc767085bf79f17682e2ffe2845330d39f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= <dach.bartlomiej@gmail.com> Date: Tue, 22 Oct 2019 01:01:37 +0200 Subject: [PATCH 1904/2815] Resolve link-in-link edge case Testing with #6542 surfaced a crash scenario, caused by formatted links that had URLs in the display text, for example [mean example - https://osu.ppy.sh](https://osu.ppy.sh) In that case the outer Markdown link would get picked up once, and then reduced to the link text when looking for other links, leading to it being picked up again the second time when the raw link is found. Add a check in the raw link parsing path that ensures that the found URL is not a part of a bigger, pre-existing link. --- osu.Game.Tests/Chat/MessageFormatterTests.cs | 12 ++++++++++++ osu.Game/Online/Chat/MessageFormatter.cs | 18 +++++++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index 1cbd083f0d..7988c6b96d 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -309,6 +309,18 @@ namespace osu.Game.Tests.Chat Assert.AreEqual(18, result.Links[0].Length); } + [Test] + public void TestMarkdownFormatLinkWithUrlAndTextInTitle() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "I haven't seen [oh no, text here! https://osu.ppy.sh](https://osu.ppy.sh) before..." }); + + Assert.AreEqual("I haven't seen oh no, text here! https://osu.ppy.sh before...", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); + Assert.AreEqual(15, result.Links[0].Index); + Assert.AreEqual(36, result.Links[0].Length); + } + [Test] public void TestMarkdownFormatLinkWithMisleadingUrlInText() { diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 749d24ee75..d77920c97d 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -95,11 +95,17 @@ namespace osu.Game.Online.Chat foreach (Match m in regex.Matches(result.Text, startIndex)) { var index = m.Index; - var link = m.Groups["link"].Value; - var indexLength = link.Length; + var linkText = m.Groups["link"].Value; + var indexLength = linkText.Length; - var details = getLinkDetails(link); - result.Links.Add(new Link(link, index, indexLength, details.Action, details.Argument)); + var details = getLinkDetails(linkText); + var link = new Link(linkText, index, indexLength, details.Action, details.Argument); + + // sometimes an already-processed formatted link can reduce to a simple URL, too + // (example: [mean example - https://osu.ppy.sh](https://osu.ppy.sh)) + // therefore we need to check if any of the pre-existing links contains the raw one we found + if (result.Links.All(existingLink => !existingLink.Contains(link))) + result.Links.Add(link); } } @@ -292,6 +298,8 @@ namespace osu.Game.Online.Chat Argument = argument; } + public bool Contains(Link otherLink) => otherLink.Index >= Index && otherLink.Index + otherLink.Length <= Index + Length; + public int CompareTo(Link otherLink) => Index > otherLink.Index ? 1 : -1; } } From 85769982a01fb95da961bb5d2c23abfaa8669380 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski <megaman9919@gmail.com> Date: Thu, 24 Oct 2019 17:49:34 +0300 Subject: [PATCH 1905/2815] Refactor LoadingButton --- .../Graphics/UserInterface/LoadingButton.cs | 26 +++------- .../Graphics/UserInterface/ShowMoreButton.cs | 49 +++++++++++-------- osu.Game/Overlays/Comments/VotePill.cs | 29 ++++++----- 3 files changed, 54 insertions(+), 50 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/LoadingButton.cs b/osu.Game/Graphics/UserInterface/LoadingButton.cs index c177431c78..49ec18ce8e 100644 --- a/osu.Game/Graphics/UserInterface/LoadingButton.cs +++ b/osu.Game/Graphics/UserInterface/LoadingButton.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osuTK; @@ -11,8 +10,6 @@ namespace osu.Game.Graphics.UserInterface { public abstract class LoadingButton : OsuHoverContainer { - private const float fade_duration = 200; - private bool isLoading; public bool IsLoading @@ -27,14 +24,12 @@ namespace osu.Game.Graphics.UserInterface if (value) { loading.Show(); - text.FadeOut(fade_duration, Easing.OutQuint); - OnLoadingStart(); + OnLoadStarted(); } else { loading.Hide(); - text.FadeIn(fade_duration, Easing.OutQuint); - OnLoadingFinished(); + OnLoadFinished(); } } } @@ -46,17 +41,12 @@ namespace osu.Game.Graphics.UserInterface } private readonly LoadingAnimation loading; - private readonly Drawable text; protected LoadingButton() { - Container content; - - Child = content = CreateContent(); - - content.AddRange(new[] + AddRange(new[] { - text = CreateText(), + CreateContent(), loading = new LoadingAnimation { Anchor = Anchor.Centre, @@ -82,16 +72,14 @@ namespace osu.Game.Graphics.UserInterface } } - protected virtual void OnLoadingStart() + protected virtual void OnLoadStarted() { } - protected virtual void OnLoadingFinished() + protected virtual void OnLoadFinished() { } - protected abstract Container CreateContent(); - - protected abstract Drawable CreateText(); + protected abstract Drawable CreateContent(); } } diff --git a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs index 407e102ca5..4931a6aed6 100644 --- a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs +++ b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs @@ -14,6 +14,8 @@ namespace osu.Game.Graphics.UserInterface { public class ShowMoreButton : LoadingButton { + private const int duration = 200; + private Color4 chevronIconColour; protected Color4 ChevronIconColour @@ -34,43 +36,50 @@ namespace osu.Game.Graphics.UserInterface private ChevronIcon rightChevron; private SpriteText text; private Box background; + private FillFlowContainer textContainer; public ShowMoreButton() { AutoSizeAxes = Axes.Both; } - protected override Container CreateContent() => new CircularContainer + protected override Drawable CreateContent() => new CircularContainer { Masking = true, Size = new Vector2(140, 30), - Child = background = new Box - { - RelativeSizeAxes = Axes.Both, - } - }; - - protected override Drawable CreateText() => new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(7), Children = new Drawable[] { - leftChevron = new ChevronIcon(), - text = new OsuSpriteText + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + textContainer = new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), - Text = "show more".ToUpper(), - }, - rightChevron = new ChevronIcon(), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(7), + Children = new Drawable[] + { + leftChevron = new ChevronIcon(), + text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Text = "show more".ToUpper(), + }, + rightChevron = new ChevronIcon(), + } + } } }; + protected override void OnLoadStarted() => textContainer.FadeOut(duration, Easing.OutQuint); + + protected override void OnLoadFinished() => textContainer.FadeIn(duration, Easing.OutQuint); + private class ChevronIcon : SpriteIcon { private const int icon_size = 8; diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index 532fd8d905..e8d9013fd9 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -24,6 +24,8 @@ namespace osu.Game.Overlays.Comments { public class VotePill : LoadingButton, IHasAccentColour { + private const int duration = 200; + public Color4 AccentColour { get; set; } protected override IEnumerable<Drawable> EffectTargets => null; @@ -84,7 +86,7 @@ namespace osu.Game.Overlays.Comments IsLoading = false; } - protected override Container CreateContent() => new Container + protected override Drawable CreateContent() => new Container { RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, @@ -116,22 +118,27 @@ namespace osu.Game.Overlays.Comments Margin = new MarginPadding { Right = 3 }, Alpha = 0, }, + votesCounter = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Horizontal = 10 }, + Font = OsuFont.GetFont(size: 14), + AlwaysPresent = true, + } }, }; - protected override Drawable CreateText() => votesCounter = new OsuSpriteText + protected override void OnLoadStarted() { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Margin = new MarginPadding { Horizontal = 10 }, - Font = OsuFont.GetFont(size: 14), - AlwaysPresent = true, - }; + votesCounter.FadeOut(duration, Easing.OutQuint); + updateDisplay(); + } - protected override void OnLoadingStart() => updateDisplay(); - - protected override void OnLoadingFinished() + protected override void OnLoadFinished() { + votesCounter.FadeIn(duration, Easing.OutQuint); + if (IsHovered) onHoverAction(); } From 661dfbefaf55e0c9b238d86274e792ed06b3df61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= <dach.bartlomiej@gmail.com> Date: Fri, 25 Oct 2019 00:20:44 +0200 Subject: [PATCH 1906/2815] Change containment check to overlap Due to scenarios wherein a formatted link ended up as part of a larger raw link after parsing, change the containment check to an overlap check and add appropriate tests for these edge cases. --- osu.Game.Tests/Chat/MessageFormatterTests.cs | 30 ++++++++++++++++++++ osu.Game/Online/Chat/MessageFormatter.cs | 4 +-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index 7988c6b96d..fbb0416c45 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -333,6 +333,36 @@ namespace osu.Game.Tests.Chat Assert.AreEqual(18, result.Links[0].Length); } + [Test] + public void TestMarkdownFormatLinkThatContractsIntoLargerLink() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "super broken https://[osu.ppy](https://reddit.com).sh/" }); + + Assert.AreEqual("super broken https://osu.ppy.sh/", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://reddit.com", result.Links[0].Url); + Assert.AreEqual(21, result.Links[0].Index); + Assert.AreEqual(7, result.Links[0].Length); + } + + [Test] + public void TestMarkdownFormatLinkDirectlyNextToRawLink() + { + // the raw link has a port at the end of it, so that the raw link regex terminates at the port and doesn't consume display text from the formatted one + Message result = MessageFormatter.FormatMessage(new Message { Content = "https://localhost:8080[https://osu.ppy.sh](https://osu.ppy.sh) should be two links" }); + + Assert.AreEqual("https://localhost:8080https://osu.ppy.sh should be two links", result.DisplayContent); + Assert.AreEqual(2, result.Links.Count); + + Assert.AreEqual("https://localhost:8080", result.Links[0].Url); + Assert.AreEqual(0, result.Links[0].Index); + Assert.AreEqual(22, result.Links[0].Length); + + Assert.AreEqual("https://osu.ppy.sh", result.Links[1].Url); + Assert.AreEqual(22, result.Links[1].Index); + Assert.AreEqual(18, result.Links[1].Length); + } + [Test] public void TestChannelLink() { diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index d77920c97d..3ffff281f8 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -104,7 +104,7 @@ namespace osu.Game.Online.Chat // sometimes an already-processed formatted link can reduce to a simple URL, too // (example: [mean example - https://osu.ppy.sh](https://osu.ppy.sh)) // therefore we need to check if any of the pre-existing links contains the raw one we found - if (result.Links.All(existingLink => !existingLink.Contains(link))) + if (result.Links.All(existingLink => !existingLink.Overlaps(link))) result.Links.Add(link); } } @@ -298,7 +298,7 @@ namespace osu.Game.Online.Chat Argument = argument; } - public bool Contains(Link otherLink) => otherLink.Index >= Index && otherLink.Index + otherLink.Length <= Index + Length; + public bool Overlaps(Link otherLink) => Index < otherLink.Index + otherLink.Length && otherLink.Index < Index + Length; public int CompareTo(Link otherLink) => Index > otherLink.Index ? 1 : -1; } From 07f7944fc61f2fb907385cf4e9de4b2b7e575482 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Fri, 25 Oct 2019 12:22:19 +0900 Subject: [PATCH 1907/2815] Fix DateTime display sizing on results screen --- .../Screens/Ranking/Pages/ScoreResultsPage.cs | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs index 7c35742ff6..af47003682 100644 --- a/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs +++ b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs @@ -253,9 +253,7 @@ namespace osu.Game.Screens.Ranking.Pages { this.date = date; - AutoSizeAxes = Axes.Y; - - Width = 140; + AutoSizeAxes = Axes.Both; Masking = true; CornerRadius = 5; @@ -271,22 +269,26 @@ namespace osu.Game.Screens.Ranking.Pages RelativeSizeAxes = Axes.Both, Colour = colours.Gray6, }, - new OsuSpriteText + new FillFlowContainer { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Text = date.ToShortDateString(), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, Padding = new MarginPadding { Horizontal = 10, Vertical = 5 }, - Colour = Color4.White, + Spacing = new Vector2(5), + Children = new[] + { + new OsuSpriteText + { + Text = date.ToShortDateString(), + Colour = Color4.White, + }, + new OsuSpriteText + { + Text = date.ToShortTimeString(), + Colour = Color4.White, + } + } }, - new OsuSpriteText - { - Origin = Anchor.CentreRight, - Anchor = Anchor.CentreRight, - Text = date.ToShortTimeString(), - Padding = new MarginPadding { Horizontal = 10, Vertical = 5 }, - Colour = Color4.White, - } }; } } From e5b5d286fd4aa21cc077bbc99a9892fe0d2deec1 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Fri, 25 Oct 2019 12:48:34 +0900 Subject: [PATCH 1908/2815] Increase spacing to closer match the design --- osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs index af47003682..56ae069a26 100644 --- a/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs +++ b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs @@ -274,7 +274,7 @@ namespace osu.Game.Screens.Ranking.Pages AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Padding = new MarginPadding { Horizontal = 10, Vertical = 5 }, - Spacing = new Vector2(5), + Spacing = new Vector2(10), Children = new[] { new OsuSpriteText From 607b4d874a262185d105b6fcb0588498e121de46 Mon Sep 17 00:00:00 2001 From: smoogipoo <smoogipoo@smgi.me> Date: Fri, 25 Oct 2019 12:34:49 +0900 Subject: [PATCH 1909/2815] Refactor flow of snapping through HitObjectComposer --- .../TestSceneOsuDistanceSnapGrid.cs | 7 +- .../Sliders/SliderPlacementBlueprint.cs | 2 +- .../Sliders/SliderSelectionBlueprint.cs | 2 +- .../Edit/OsuDistanceSnapGrid.cs | 12 --- .../Editor/TestSceneDistanceSnapGrid.cs | 34 ++++---- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 87 +++++++++++++++++-- .../Compose/Components/BlueprintContainer.cs | 8 +- .../Components/CircularDistanceSnapGrid.cs | 11 ++- .../Compose/Components/DistanceSnapGrid.cs | 54 +++--------- 9 files changed, 128 insertions(+), 89 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs index fddbcea374..7b28cc9c50 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs @@ -142,10 +142,9 @@ namespace osu.Game.Rulesets.Osu.Tests private void assertSnappedDistance(float expectedDistance) => AddAssert($"snap distance = {expectedDistance}", () => { - Vector2 snappedPosition = grid.GetSnappedPosition(grid.ToLocalSpace(InputManager.CurrentState.Mouse.Position)); - float distance = Vector2.Distance(snappedPosition, grid_position); + Vector2 snappedPosition = grid.GetSnappedPosition(grid.ToLocalSpace(InputManager.CurrentState.Mouse.Position)).position; - return Precision.AlmostEquals(expectedDistance, distance); + return Precision.AlmostEquals(expectedDistance, Vector2.Distance(snappedPosition, grid_position)); }); private void createGrid() @@ -160,7 +159,7 @@ namespace osu.Game.Rulesets.Osu.Tests Colour = Color4.SlateGray }, grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }), - new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)) } + new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position } }; }); } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 02923203b1..6f5309c2c2 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders Vector2[] newControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray(); var unsnappedPath = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints); - var snappedDistance = composer?.GetSnappedDistance((float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance; + var snappedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance; HitObject.Path = new SliderPath(unsnappedPath.Type, newControlPoints, snappedDistance); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index a90ed677ff..9ee456e791 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void onNewControlPoints(Vector2[] controlPoints) { var unsnappedPath = new SliderPath(controlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, controlPoints); - var snappedDistance = composer?.GetSnappedDistance((float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance; + var snappedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance; HitObject.Path = new SliderPath(unsnappedPath.Type, controlPoints, snappedDistance); } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs index bc0f76f000..79cd51a7f4 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; @@ -15,15 +13,5 @@ namespace osu.Game.Rulesets.Osu.Edit { Masking = true; } - - protected override float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) - { - TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(time); - DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(time); - - double scoringDistance = OsuHitObject.BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; - - return (float)(scoringDistance / timingPoint.BeatLength); - } } } diff --git a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs index 54d910fdcf..a67ba4d9e4 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs @@ -7,7 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.MathUtils; -using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; @@ -93,23 +92,27 @@ namespace osu.Game.Tests.Visual.Editor [TestCase(2)] public void TestGridVelocity(float velocity) { - createGrid(g => g.Velocity = velocity); + // Todo: - float expectedDistance = (float)beat_length * velocity; - AddAssert($"spacing is {expectedDistance}", () => Precision.AlmostEquals(grid.DistanceSpacing, expectedDistance)); + // createGrid(g => g.Velocity = velocity); + // + // float expectedDistance = (float)beat_length * velocity; + // AddAssert($"spacing is {expectedDistance}", () => Precision.AlmostEquals(grid.DistanceSpacing, expectedDistance)); } [Test] public void TestGetSnappedTime() { - createGrid(); + //Todo: - Vector2 snapPosition = Vector2.Zero; - AddStep("get first tick position", () => snapPosition = grid_position + new Vector2((float)beat_length, 0)); - AddAssert("snap time is 1 beat away", () => Precision.AlmostEquals(beat_length, grid.GetSnappedTime(snapPosition), 0.01)); - - createGrid(g => g.Velocity = 2, "with velocity = 2"); - AddAssert("snap time is now 0.5 beats away", () => Precision.AlmostEquals(beat_length / 2, grid.GetSnappedTime(snapPosition), 0.01)); + // createGrid(); + // + // Vector2 snapPosition = Vector2.Zero; + // AddStep("get first tick position", () => snapPosition = grid_position + new Vector2((float)beat_length, 0)); + // AddAssert("snap time is 1 beat away", () => Precision.AlmostEquals(beat_length, grid.GetSnappedPosition(snapPosition).time, 0.01)); + // + // createGrid(g => g.Velocity = 2, "with velocity = 2"); + // AddAssert("snap time is now 0.5 beats away", () => Precision.AlmostEquals(beat_length / 2, grid.GetSnappedPosition(snapPosition).time, 0.01)); } private void createGrid(Action<TestDistanceSnapGrid> func = null, string description = null) @@ -132,8 +135,6 @@ namespace osu.Game.Tests.Visual.Editor private class TestDistanceSnapGrid : DistanceSnapGrid { - public new float Velocity = 1; - public new float DistanceSpacing => base.DistanceSpacing; public TestDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) @@ -203,11 +204,8 @@ namespace osu.Game.Tests.Visual.Editor } } - protected override float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) - => Velocity; - - public override Vector2 GetSnappedPosition(Vector2 screenSpacePosition) - => Vector2.Zero; + public override (Vector2 position, double time) GetSnappedPosition(Vector2 screenSpacePosition) + => (Vector2.Zero, 0); } } } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 3c24c3dd1f..beb0d38216 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -14,6 +14,7 @@ using osu.Framework.Logging; using osu.Framework.Threading; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; @@ -39,6 +40,9 @@ namespace osu.Game.Rulesets.Edit [Resolved] protected IFrameBasedClock EditorClock { get; private set; } + [Resolved] + private BindableBeatDivisor beatDivisor { get; set; } + private IWorkingBeatmap workingBeatmap; private Beatmap<TObject> playableBeatmap; private IBeatmapProcessor beatmapProcessor; @@ -246,7 +250,7 @@ namespace osu.Game.Rulesets.Edit public void BeginPlacement(HitObject hitObject) { if (distanceSnapGrid != null) - hitObject.StartTime = GetSnappedTime(hitObject.StartTime, distanceSnapGrid.ToLocalSpace(inputManager.CurrentState.Mouse.Position)); + hitObject.StartTime = GetSnappedPosition(distanceSnapGrid.ToLocalSpace(inputManager.CurrentState.Mouse.Position), hitObject.StartTime).time; } public void EndPlacement(HitObject hitObject) @@ -257,11 +261,45 @@ namespace osu.Game.Rulesets.Edit public void Delete(HitObject hitObject) => EditorBeatmap.Remove(hitObject); - public override Vector2 GetSnappedPosition(Vector2 position) => distanceSnapGrid?.GetSnappedPosition(position) ?? position; + public override (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => distanceSnapGrid?.GetSnappedPosition(position) ?? (position, time); - public override double GetSnappedTime(double startTime, Vector2 position) => distanceSnapGrid?.GetSnappedTime(position) ?? startTime; + public override float GetBeatSnapDistanceAt(double referenceTime) + { + DifficultyControlPoint difficultyPoint = EditorBeatmap.ControlPointInfo.DifficultyPointAt(referenceTime); + return (float)(100 * EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / beatDivisor.Value); + } - public override float GetSnappedDistance(float distance) => distanceSnapGrid?.GetSnappedDistance(distance) ?? distance; + public override float DurationToDistance(double referenceTime, double duration) + { + double beatLength = EditorBeatmap.ControlPointInfo.TimingPointAt(referenceTime).BeatLength / beatDivisor.Value; + return (float)(duration / beatLength * GetBeatSnapDistanceAt(referenceTime)); + } + + public override double DistanceToDuration(double referenceTime, float distance) + { + double beatLength = EditorBeatmap.ControlPointInfo.TimingPointAt(referenceTime).BeatLength / beatDivisor.Value; + return distance / GetBeatSnapDistanceAt(referenceTime) * beatLength; + } + + public override double GetSnappedDurationFromDistance(double referenceTime, float distance) + => beatSnap(referenceTime, DistanceToDuration(referenceTime, distance)); + + public override float GetSnappedDistanceFromDistance(double referenceTime, float distance) + => DurationToDistance(referenceTime, beatSnap(referenceTime, DistanceToDuration(referenceTime, distance))); + + /// <summary> + /// Snaps a duration to the closest beat of a timing point applicable at the reference time. + /// </summary> + /// <param name="referenceTime">The time of the timing point which <paramref name="duration"/> resides in.</param> + /// <param name="duration">The duration to snap.</param> + /// <returns>A value that represents <paramref name="duration"/> snapped to the closest beat of the timing point.</returns> + private double beatSnap(double referenceTime, double duration) + { + double beatLength = EditorBeatmap.ControlPointInfo.TimingPointAt(referenceTime).BeatLength / beatDivisor.Value; + + // A 1ms offset prevents rounding errors due to minute variations in duration + return (int)((duration + 1) / beatLength) * beatLength; + } protected override void Dispose(bool isDisposing) { @@ -312,10 +350,45 @@ namespace osu.Game.Rulesets.Edit [CanBeNull] protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable<HitObject> selectedHitObjects) => null; - public abstract Vector2 GetSnappedPosition(Vector2 position); + public abstract (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time); - public abstract double GetSnappedTime(double startTime, Vector2 screenSpacePosition); + /// <summary> + /// Retrieves the distance between two points within a timing point that are one beat length apart. + /// </summary> + /// <param name="referenceTime">The time of the timing point.</param> + /// <returns>The distance between two points residing in the timing point that are one beat length apart.</returns> + public abstract float GetBeatSnapDistanceAt(double referenceTime); - public abstract float GetSnappedDistance(float distance); + /// <summary> + /// Converts a duration to a distance. + /// </summary> + /// <param name="referenceTime">The time of the timing point which <paramref name="duration"/> resides in.</param> + /// <param name="duration">The duration to convert.</param> + /// <returns>A value that represents <paramref name="duration"/> as a distance in the timing point.</returns> + public abstract float DurationToDistance(double referenceTime, double duration); + + /// <summary> + /// Converts a distance to a duration. + /// </summary> + /// <param name="referenceTime">The time of the timing point which <paramref name="distance"/> resides in.</param> + /// <param name="distance">The distance to convert.</param> + /// <returns>A value that represents <paramref name="distance"/> as a duration in the timing point.</returns> + public abstract double DistanceToDuration(double referenceTime, float distance); + + /// <summary> + /// Converts a distance to a snapped duration. + /// </summary> + /// <param name="referenceTime">The time of the timing point which <paramref name="distance"/> resides in.</param> + /// <param name="distance">The distance to convert.</param> + /// <returns>A value that represents <paramref name="distance"/> as a duration snapped to the closest beat of the timing point.</returns> + public abstract double GetSnappedDurationFromDistance(double referenceTime, float distance); + + /// <summary> + /// Converts an unsnapped distance to a snapped distance. + /// </summary> + /// <param name="referenceTime">The time of the timing point which <paramref name="distance"/> resides in.</param> + /// <param name="distance">The distance to convert.</param> + /// <returns>A value that represents <paramref name="distance"/> snapped to the closest beat of the timing point.</returns> + public abstract float GetSnappedDistanceFromDistance(double referenceTime, float distance); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 4001a0f33a..1bfd4c454b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -185,7 +185,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void updatePlacementPosition(Vector2 screenSpacePosition) { - Vector2 snappedGridPosition = composer.GetSnappedPosition(ToLocalSpace(screenSpacePosition)); + Vector2 snappedGridPosition = composer.GetSnappedPosition(ToLocalSpace(screenSpacePosition), 0).position; Vector2 snappedScreenSpacePosition = ToScreenSpace(snappedGridPosition); currentPlacement.UpdatePosition(snappedScreenSpacePosition); @@ -232,15 +232,15 @@ namespace osu.Game.Screens.Edit.Compose.Components private void onDragRequested(SelectionBlueprint blueprint, DragEvent dragEvent) { HitObject draggedObject = blueprint.DrawableObject.HitObject; - Vector2 movePosition = blueprint.ScreenSpaceMovementStartPosition + dragEvent.ScreenSpaceMousePosition - dragEvent.ScreenSpaceMouseDownPosition; - Vector2 snappedPosition = composer.GetSnappedPosition(ToLocalSpace(movePosition)); + + (Vector2 snappedPosition, double snappedTime) = composer.GetSnappedPosition(ToLocalSpace(movePosition), draggedObject.StartTime); // Move the hitobjects selectionHandler.HandleMovement(new MoveSelectionEvent(blueprint, blueprint.ScreenSpaceMovementStartPosition, ToScreenSpace(snappedPosition))); // Apply the start time at the newly snapped-to position - double offset = composer.GetSnappedTime(draggedObject.StartTime, snappedPosition) - draggedObject.StartTime; + double offset = snappedTime - draggedObject.StartTime; foreach (HitObject obj in selectionHandler.SelectedHitObjects) obj.StartTime += offset; } diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index 381ae9f927..7e9e6e2290 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -2,9 +2,11 @@ // 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.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osuTK; @@ -12,6 +14,9 @@ namespace osu.Game.Screens.Edit.Compose.Components { public abstract class CircularDistanceSnapGrid : DistanceSnapGrid { + [Resolved] + private HitObjectComposer composer { get; set; } + protected CircularDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) : base(hitObject, centrePosition) { @@ -63,7 +68,7 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - public override Vector2 GetSnappedPosition(Vector2 position) + public override (Vector2 position, double time) GetSnappedPosition(Vector2 position) { Vector2 direction = position - CentrePosition; @@ -76,7 +81,9 @@ namespace osu.Game.Screens.Edit.Compose.Components int radialCount = Math.Max(1, (int)Math.Round(distance / radius)); Vector2 normalisedDirection = direction * new Vector2(1f / distance); - return CentrePosition + normalisedDirection * radialCount * radius; + Vector2 snappedPosition = CentrePosition + normalisedDirection * radialCount * radius; + + return (snappedPosition, StartTime + composer.GetSnappedDurationFromDistance(StartTime, (snappedPosition - CentrePosition).Length)); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 250a0abef5..9eaccc5ac3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -6,9 +6,8 @@ using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osuTK; @@ -20,16 +19,16 @@ namespace osu.Game.Screens.Edit.Compose.Components /// </summary> public abstract class DistanceSnapGrid : CompositeDrawable { - /// <summary> - /// The velocity of the beatmap at the point of placement in pixels per millisecond. - /// </summary> - protected double Velocity { get; private set; } - /// <summary> /// The spacing between each tick of the beat snapping grid. /// </summary> protected float DistanceSpacing { get; private set; } + /// <summary> + /// The snapping time at <see cref="CentrePosition"/>. + /// </summary> + protected double StartTime { get; private set; } + /// <summary> /// The position which the grid is centred on. /// The first beat snapping tick is located at <see cref="CentrePosition"/> + <see cref="DistanceSpacing"/> in the desired direction. @@ -45,25 +44,24 @@ namespace osu.Game.Screens.Edit.Compose.Components [Resolved] private BindableBeatDivisor beatDivisor { get; set; } + [Resolved] + private HitObjectComposer composer { get; set; } + private readonly Cached gridCache = new Cached(); private readonly HitObject hitObject; - private double startTime; - private double beatLength; - protected DistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) { this.hitObject = hitObject; - this.CentrePosition = centrePosition; + CentrePosition = centrePosition; RelativeSizeAxes = Axes.Both; } [BackgroundDependencyLoader] private void load() { - startTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime; - beatLength = beatmap.ControlPointInfo.TimingPointAt(startTime).BeatLength; + StartTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime; } protected override void LoadComplete() @@ -75,8 +73,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void updateSpacing() { - Velocity = GetVelocity(startTime, beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); - DistanceSpacing = (float)(beatLength / beatDivisor.Value * Velocity); + DistanceSpacing = composer.GetBeatSnapDistanceAt(StartTime); gridCache.Invalidate(); } @@ -105,35 +102,12 @@ namespace osu.Game.Screens.Edit.Compose.Components /// </summary> protected abstract void CreateContent(Vector2 centrePosition); - /// <summary> - /// Retrieves the velocity of gameplay at a point in time in pixels per millisecond. - /// </summary> - /// <param name="time">The time to retrieve the velocity at.</param> - /// <param name="controlPointInfo">The beatmap's <see cref="ControlPointInfo"/> at the point in time.</param> - /// <param name="difficulty">The beatmap's <see cref="BeatmapDifficulty"/> at the point in time.</param> - /// <returns>The velocity.</returns> - protected abstract float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty); - /// <summary> /// Snaps a position to this grid. /// </summary> /// <param name="position">The original position in coordinate space local to this <see cref="DistanceSnapGrid"/>.</param> - /// <returns>The snapped position in coordinate space local to this <see cref="DistanceSnapGrid"/>.</returns> - public abstract Vector2 GetSnappedPosition(Vector2 position); - - /// <summary> - /// Retrieves the time at a snapped position. - /// </summary> - /// <param name="position">The snapped position in coordinate space local to this <see cref="DistanceSnapGrid"/>.</param> - /// <returns>The time at the snapped position.</returns> - public double GetSnappedTime(Vector2 position) => startTime + (position - CentrePosition).Length / Velocity; - - /// <summary> - /// Snaps a distance by the snap distance of this grid. - /// </summary> - /// <param name="distance">The distance to snap in coordinate space local to this <see cref="DistanceSnapGrid"/>.</param> - /// <returns>The snapped distance.</returns> - public float GetSnappedDistance(float distance) => (int)(distance / DistanceSpacing) * DistanceSpacing; + /// <returns>A tuple containing the snapped position in coordinate space local to this <see cref="DistanceSnapGrid"/> and the respective time value.</returns> + public abstract (Vector2 position, double time) GetSnappedPosition(Vector2 position); /// <summary> /// Retrieves the applicable colour for a beat index. From 4ca6a5a0cc4fb841c35171dde5affcef9c0786fa Mon Sep 17 00:00:00 2001 From: smoogipoo <smoogipoo@smgi.me> Date: Fri, 25 Oct 2019 16:50:21 +0900 Subject: [PATCH 1910/2815] Interface the distance snap provider --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 37 +------------- .../Rulesets/Edit/IDistanceSnapProvider.cs | 51 +++++++++++++++++++ .../Compose/Components/DistanceSnapGrid.cs | 4 +- 3 files changed, 55 insertions(+), 37 deletions(-) create mode 100644 osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index beb0d38216..5922bfba78 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -314,7 +314,8 @@ namespace osu.Game.Rulesets.Edit } [Cached(typeof(HitObjectComposer))] - public abstract class HitObjectComposer : CompositeDrawable + [Cached(typeof(IDistanceSnapProvider))] + public abstract class HitObjectComposer : CompositeDrawable, IDistanceSnapProvider { internal HitObjectComposer() { @@ -351,44 +352,10 @@ namespace osu.Game.Rulesets.Edit protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable<HitObject> selectedHitObjects) => null; public abstract (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time); - - /// <summary> - /// Retrieves the distance between two points within a timing point that are one beat length apart. - /// </summary> - /// <param name="referenceTime">The time of the timing point.</param> - /// <returns>The distance between two points residing in the timing point that are one beat length apart.</returns> public abstract float GetBeatSnapDistanceAt(double referenceTime); - - /// <summary> - /// Converts a duration to a distance. - /// </summary> - /// <param name="referenceTime">The time of the timing point which <paramref name="duration"/> resides in.</param> - /// <param name="duration">The duration to convert.</param> - /// <returns>A value that represents <paramref name="duration"/> as a distance in the timing point.</returns> public abstract float DurationToDistance(double referenceTime, double duration); - - /// <summary> - /// Converts a distance to a duration. - /// </summary> - /// <param name="referenceTime">The time of the timing point which <paramref name="distance"/> resides in.</param> - /// <param name="distance">The distance to convert.</param> - /// <returns>A value that represents <paramref name="distance"/> as a duration in the timing point.</returns> public abstract double DistanceToDuration(double referenceTime, float distance); - - /// <summary> - /// Converts a distance to a snapped duration. - /// </summary> - /// <param name="referenceTime">The time of the timing point which <paramref name="distance"/> resides in.</param> - /// <param name="distance">The distance to convert.</param> - /// <returns>A value that represents <paramref name="distance"/> as a duration snapped to the closest beat of the timing point.</returns> public abstract double GetSnappedDurationFromDistance(double referenceTime, float distance); - - /// <summary> - /// Converts an unsnapped distance to a snapped distance. - /// </summary> - /// <param name="referenceTime">The time of the timing point which <paramref name="distance"/> resides in.</param> - /// <param name="distance">The distance to convert.</param> - /// <returns>A value that represents <paramref name="distance"/> snapped to the closest beat of the timing point.</returns> public abstract float GetSnappedDistanceFromDistance(double referenceTime, float distance); } } diff --git a/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs new file mode 100644 index 0000000000..c6e61f68da --- /dev/null +++ b/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs @@ -0,0 +1,51 @@ +// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osuTK; + +namespace osu.Game.Rulesets.Edit +{ + public interface IDistanceSnapProvider + { + (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time); + + /// <summary> + /// Retrieves the distance between two points within a timing point that are one beat length apart. + /// </summary> + /// <param name="referenceTime">The time of the timing point.</param> + /// <returns>The distance between two points residing in the timing point that are one beat length apart.</returns> + float GetBeatSnapDistanceAt(double referenceTime); + + /// <summary> + /// Converts a duration to a distance. + /// </summary> + /// <param name="referenceTime">The time of the timing point which <paramref name="duration"/> resides in.</param> + /// <param name="duration">The duration to convert.</param> + /// <returns>A value that represents <paramref name="duration"/> as a distance in the timing point.</returns> + float DurationToDistance(double referenceTime, double duration); + + /// <summary> + /// Converts a distance to a duration. + /// </summary> + /// <param name="referenceTime">The time of the timing point which <paramref name="distance"/> resides in.</param> + /// <param name="distance">The distance to convert.</param> + /// <returns>A value that represents <paramref name="distance"/> as a duration in the timing point.</returns> + double DistanceToDuration(double referenceTime, float distance); + + /// <summary> + /// Converts a distance to a snapped duration. + /// </summary> + /// <param name="referenceTime">The time of the timing point which <paramref name="distance"/> resides in.</param> + /// <param name="distance">The distance to convert.</param> + /// <returns>A value that represents <paramref name="distance"/> as a duration snapped to the closest beat of the timing point.</returns> + double GetSnappedDurationFromDistance(double referenceTime, float distance); + + /// <summary> + /// Converts an unsnapped distance to a snapped distance. + /// </summary> + /// <param name="referenceTime">The time of the timing point which <paramref name="distance"/> resides in.</param> + /// <param name="distance">The distance to convert.</param> + /// <returns>A value that represents <paramref name="distance"/> snapped to the closest beat of the timing point.</returns> + float GetSnappedDistanceFromDistance(double referenceTime, float distance); + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 9eaccc5ac3..d6ee6063ef 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private BindableBeatDivisor beatDivisor { get; set; } [Resolved] - private HitObjectComposer composer { get; set; } + private IDistanceSnapProvider snapProvider { get; set; } private readonly Cached gridCache = new Cached(); private readonly HitObject hitObject; @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void updateSpacing() { - DistanceSpacing = composer.GetBeatSnapDistanceAt(StartTime); + DistanceSpacing = snapProvider.GetBeatSnapDistanceAt(StartTime); gridCache.Invalidate(); } From ae011e8ee82db586ae808a0b8c10d6d44242f77e Mon Sep 17 00:00:00 2001 From: smoogipoo <smoogipoo@smgi.me> Date: Fri, 25 Oct 2019 17:25:46 +0900 Subject: [PATCH 1911/2815] Fix distance snap grid test scenes --- .../TestSceneOsuDistanceSnapGrid.cs | 99 +++++---------- .../Editor/TestSceneDistanceSnapGrid.cs | 117 +++++------------- .../Components/CircularDistanceSnapGrid.cs | 7 +- .../Compose/Components/DistanceSnapGrid.cs | 8 +- 4 files changed, 63 insertions(+), 168 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs index 7b28cc9c50..b6907cf7c0 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.MathUtils; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Objects; @@ -38,26 +39,34 @@ namespace osu.Game.Rulesets.Osu.Tests [Cached] private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); - private TestOsuDistanceSnapGrid grid; + [Cached(typeof(IDistanceSnapProvider))] + private readonly SnapProvider snapProvider = new SnapProvider(); + + private readonly TestOsuDistanceSnapGrid grid; public TestSceneOsuDistanceSnapGrid() { editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap()); - createGrid(); + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.SlateGray + }, + grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }), + new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position } + }; } [SetUp] public void Setup() => Schedule(() => { - Clear(); - editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1; editorBeatmap.ControlPointInfo.DifficultyPoints.Clear(); editorBeatmap.ControlPointInfo.TimingPoints.Clear(); editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); - - beatDivisor.Value = 1; }); [TestCase(1)] @@ -71,53 +80,11 @@ namespace osu.Game.Rulesets.Osu.Tests public void TestBeatDivisor(int divisor) { AddStep($"set beat divisor = {divisor}", () => beatDivisor.Value = divisor); - createGrid(); - } - - [TestCase(100, 100)] - [TestCase(200, 100)] - public void TestBeatLength(float beatLength, float expectedSpacing) - { - AddStep($"set beat length = {beatLength}", () => - { - editorBeatmap.ControlPointInfo.TimingPoints.Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beatLength }); - }); - - createGrid(); - AddAssert($"spacing = {expectedSpacing}", () => Precision.AlmostEquals(expectedSpacing, grid.DistanceSpacing)); - } - - [TestCase(0.5f, 50)] - [TestCase(1, 100)] - [TestCase(1.5f, 150)] - public void TestSpeedMultiplier(float multiplier, float expectedSpacing) - { - AddStep($"set speed multiplier = {multiplier}", () => - { - editorBeatmap.ControlPointInfo.DifficultyPoints.Clear(); - editorBeatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = multiplier }); - }); - - createGrid(); - AddAssert($"spacing = {expectedSpacing}", () => Precision.AlmostEquals(expectedSpacing, grid.DistanceSpacing)); - } - - [TestCase(0.5f, 50)] - [TestCase(1, 100)] - [TestCase(1.5f, 150)] - public void TestSliderMultiplier(float multiplier, float expectedSpacing) - { - AddStep($"set speed multiplier = {multiplier}", () => editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = multiplier); - createGrid(); - AddAssert($"spacing = {expectedSpacing}", () => Precision.AlmostEquals(expectedSpacing, grid.DistanceSpacing)); } [Test] public void TestCursorInCentre() { - createGrid(); - AddStep("move mouse to centre", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position))); assertSnappedDistance((float)beat_length); } @@ -125,8 +92,6 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestCursorBeforeMovementPoint() { - createGrid(); - AddStep("move mouse to just before movement point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 1.49f))); assertSnappedDistance((float)beat_length); } @@ -134,8 +99,6 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestCursorAfterMovementPoint() { - createGrid(); - AddStep("move mouse to just after movement point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 1.51f))); assertSnappedDistance((float)beat_length * 2); } @@ -147,23 +110,6 @@ namespace osu.Game.Rulesets.Osu.Tests return Precision.AlmostEquals(expectedDistance, Vector2.Distance(snappedPosition, grid_position)); }); - private void createGrid() - { - AddStep("create grid", () => - { - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.SlateGray - }, - grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }), - new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position } - }; - }); - } - private class SnappingCursorContainer : CompositeDrawable { public Func<Vector2, Vector2> GetSnapPosition; @@ -212,5 +158,20 @@ namespace osu.Game.Rulesets.Osu.Tests { } } + + private class SnapProvider : IDistanceSnapProvider + { + public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => (position, time); + + public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length; + + public float DurationToDistance(double referenceTime, double duration) => 0; + + public double DistanceToDuration(double referenceTime, float distance) => 0; + + public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0; + + public float GetSnappedDistanceFromDistance(double referenceTime, float distance) => 0; + } } } diff --git a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs index a67ba4d9e4..558e1106a7 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs @@ -1,13 +1,12 @@ // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; -using osu.Framework.MathUtils; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Objects; @@ -26,27 +25,25 @@ namespace osu.Game.Tests.Visual.Editor [Cached(typeof(IEditorBeatmap))] private readonly EditorBeatmap<OsuHitObject> editorBeatmap; - private TestDistanceSnapGrid grid; + [Cached(typeof(IDistanceSnapProvider))] + private readonly SnapProvider snapProvider = new SnapProvider(); public TestSceneDistanceSnapGrid() { editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap()); editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); - createGrid(); + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.SlateGray + }, + new TestDistanceSnapGrid(new HitObject(), grid_position) + }; } - [SetUp] - public void Setup() => Schedule(() => - { - Clear(); - - editorBeatmap.ControlPointInfo.TimingPoints.Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); - - BeatDivisor.Value = 1; - }); - [TestCase(1)] [TestCase(2)] [TestCase(3)] @@ -55,82 +52,9 @@ namespace osu.Game.Tests.Visual.Editor [TestCase(8)] [TestCase(12)] [TestCase(16)] - public void TestInitialBeatDivisor(int divisor) + public void TestBeatDivisor(int divisor) { AddStep($"set beat divisor = {divisor}", () => BeatDivisor.Value = divisor); - createGrid(); - - float expectedDistance = (float)beat_length / divisor; - AddAssert($"spacing is {expectedDistance}", () => Precision.AlmostEquals(grid.DistanceSpacing, expectedDistance)); - } - - [Test] - public void TestChangeBeatDivisor() - { - createGrid(); - AddStep("set beat divisor = 2", () => BeatDivisor.Value = 2); - - const float expected_distance = (float)beat_length / 2; - AddAssert($"spacing is {expected_distance}", () => Precision.AlmostEquals(grid.DistanceSpacing, expected_distance)); - } - - [TestCase(100)] - [TestCase(200)] - public void TestBeatLength(double beatLength) - { - AddStep($"set beat length = {beatLength}", () => - { - editorBeatmap.ControlPointInfo.TimingPoints.Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beatLength }); - }); - - createGrid(); - AddAssert($"spacing is {beatLength}", () => Precision.AlmostEquals(grid.DistanceSpacing, beatLength)); - } - - [TestCase(1)] - [TestCase(2)] - public void TestGridVelocity(float velocity) - { - // Todo: - - // createGrid(g => g.Velocity = velocity); - // - // float expectedDistance = (float)beat_length * velocity; - // AddAssert($"spacing is {expectedDistance}", () => Precision.AlmostEquals(grid.DistanceSpacing, expectedDistance)); - } - - [Test] - public void TestGetSnappedTime() - { - //Todo: - - // createGrid(); - // - // Vector2 snapPosition = Vector2.Zero; - // AddStep("get first tick position", () => snapPosition = grid_position + new Vector2((float)beat_length, 0)); - // AddAssert("snap time is 1 beat away", () => Precision.AlmostEquals(beat_length, grid.GetSnappedPosition(snapPosition).time, 0.01)); - // - // createGrid(g => g.Velocity = 2, "with velocity = 2"); - // AddAssert("snap time is now 0.5 beats away", () => Precision.AlmostEquals(beat_length / 2, grid.GetSnappedPosition(snapPosition).time, 0.01)); - } - - private void createGrid(Action<TestDistanceSnapGrid> func = null, string description = null) - { - AddStep($"create grid {description ?? string.Empty}", () => - { - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.SlateGray - }, - grid = new TestDistanceSnapGrid(new HitObject(), grid_position) - }; - - func?.Invoke(grid); - }); } private class TestDistanceSnapGrid : DistanceSnapGrid @@ -207,5 +131,20 @@ namespace osu.Game.Tests.Visual.Editor public override (Vector2 position, double time) GetSnappedPosition(Vector2 screenSpacePosition) => (Vector2.Zero, 0); } + + private class SnapProvider : IDistanceSnapProvider + { + public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => (position, time); + + public float GetBeatSnapDistanceAt(double referenceTime) => 10; + + public float DurationToDistance(double referenceTime, double duration) => 0; + + public double DistanceToDuration(double referenceTime, float distance) => 0; + + public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0; + + public float GetSnappedDistanceFromDistance(double referenceTime, float distance) => 0; + } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index 7e9e6e2290..f45115e1e4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -2,11 +2,9 @@ // 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.Shapes; using osu.Framework.Graphics.UserInterface; -using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osuTK; @@ -14,9 +12,6 @@ namespace osu.Game.Screens.Edit.Compose.Components { public abstract class CircularDistanceSnapGrid : DistanceSnapGrid { - [Resolved] - private HitObjectComposer composer { get; set; } - protected CircularDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) : base(hitObject, centrePosition) { @@ -83,7 +78,7 @@ namespace osu.Game.Screens.Edit.Compose.Components Vector2 normalisedDirection = direction * new Vector2(1f / distance); Vector2 snappedPosition = CentrePosition + normalisedDirection * radialCount * radius; - return (snappedPosition, StartTime + composer.GetSnappedDurationFromDistance(StartTime, (snappedPosition - CentrePosition).Length)); + return (snappedPosition, StartTime + SnapProvider.GetSnappedDurationFromDistance(StartTime, (snappedPosition - CentrePosition).Length)); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index d6ee6063ef..193474093f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -38,15 +38,15 @@ namespace osu.Game.Screens.Edit.Compose.Components [Resolved] protected OsuColour Colours { get; private set; } + [Resolved] + protected IDistanceSnapProvider SnapProvider { get; private set; } + [Resolved] private IEditorBeatmap beatmap { get; set; } [Resolved] private BindableBeatDivisor beatDivisor { get; set; } - [Resolved] - private IDistanceSnapProvider snapProvider { get; set; } - private readonly Cached gridCache = new Cached(); private readonly HitObject hitObject; @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void updateSpacing() { - DistanceSpacing = snapProvider.GetBeatSnapDistanceAt(StartTime); + DistanceSpacing = SnapProvider.GetBeatSnapDistanceAt(StartTime); gridCache.Invalidate(); } From ccc45dea206a183a127f9ad41116c27774183ff1 Mon Sep 17 00:00:00 2001 From: smoogipoo <smoogipoo@smgi.me> Date: Fri, 25 Oct 2019 18:19:26 +0900 Subject: [PATCH 1912/2815] Add hitobject composer snapping test --- ...tSceneHitObjectComposerDistanceSnapping.cs | 195 ++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs diff --git a/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs new file mode 100644 index 0000000000..e6b0cf992a --- /dev/null +++ b/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs @@ -0,0 +1,195 @@ +// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Edit; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Editor +{ + [HeadlessTest] + public class TestSceneHitObjectComposerDistanceSnapping : EditorClockTestScene + { + private TestHitObjectComposer composer; + + [SetUp] + public void Setup() => Schedule(() => + { + Child = composer = new TestHitObjectComposer(); + + BeatDivisor.Value = 1; + + composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1; + composer.EditorBeatmap.ControlPointInfo.DifficultyPoints.Clear(); + composer.EditorBeatmap.ControlPointInfo.TimingPoints.Clear(); + + composer.EditorBeatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = 1 }); + composer.EditorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = 1000 }); + }); + + [TestCase(1)] + [TestCase(2)] + public void TestSliderMultiplier(float multiplier) + { + AddStep($"set multiplier = {multiplier}", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = multiplier); + + assertSnapDistance(100 * multiplier); + } + + [TestCase(1)] + [TestCase(2)] + public void TestSpeedMultiplier(float multiplier) + { + AddStep($"set multiplier = {multiplier}", () => + { + composer.EditorBeatmap.ControlPointInfo.DifficultyPoints.Clear(); + composer.EditorBeatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = multiplier }); + }); + + assertSnapDistance(100 * multiplier); + } + + [TestCase(1)] + [TestCase(2)] + public void TestBeatDivisor(int divisor) + { + AddStep($"set divisor = {divisor}", () => BeatDivisor.Value = divisor); + + assertSnapDistance(100f / divisor); + } + + [Test] + public void TestConvertDurationToDistance() + { + assertDurationToDistance(500, 50); + assertDurationToDistance(1000, 100); + + AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2); + + assertDurationToDistance(500, 100); + assertDurationToDistance(1000, 200); + + AddStep("set beat length = 500", () => + { + composer.EditorBeatmap.ControlPointInfo.TimingPoints.Clear(); + composer.EditorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = 500 }); + }); + + assertDurationToDistance(500, 200); + assertDurationToDistance(1000, 400); + } + + [Test] + public void TestConvertDistanceToDuration() + { + assertDistanceToDuration(50, 500); + assertDistanceToDuration(100, 1000); + + AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2); + + assertDistanceToDuration(100, 500); + assertDistanceToDuration(200, 1000); + + AddStep("set beat length = 500", () => + { + composer.EditorBeatmap.ControlPointInfo.TimingPoints.Clear(); + composer.EditorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = 500 }); + }); + + assertDistanceToDuration(200, 500); + assertDistanceToDuration(400, 1000); + } + + [Test] + public void TestGetSnappedDurationFromDistance() + { + assertSnappedDuration(50, 0); + assertSnappedDuration(100, 1000); + assertSnappedDuration(150, 1000); + assertSnappedDuration(200, 2000); + assertSnappedDuration(250, 2000); + + AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2); + + assertSnappedDuration(50, 0); + assertSnappedDuration(100, 0); + assertSnappedDuration(150, 0); + assertSnappedDuration(200, 1000); + assertSnappedDuration(250, 1000); + + AddStep("set beat length = 500", () => + { + composer.EditorBeatmap.ControlPointInfo.TimingPoints.Clear(); + composer.EditorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = 500 }); + }); + + assertSnappedDuration(50, 0); + assertSnappedDuration(100, 0); + assertSnappedDuration(150, 0); + assertSnappedDuration(200, 500); + assertSnappedDuration(250, 500); + assertSnappedDuration(400, 1000); + } + + [Test] + public void GetSnappedDistanceFromDistance() + { + assertSnappedDistance(50, 0); + assertSnappedDistance(100, 100); + assertSnappedDistance(150, 100); + assertSnappedDistance(200, 200); + assertSnappedDistance(250, 200); + + AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2); + + assertSnappedDistance(50, 0); + assertSnappedDistance(100, 0); + assertSnappedDistance(150, 0); + assertSnappedDistance(200, 200); + assertSnappedDistance(250, 200); + + AddStep("set beat length = 500", () => + { + composer.EditorBeatmap.ControlPointInfo.TimingPoints.Clear(); + composer.EditorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = 500 }); + }); + + assertSnappedDistance(50, 0); + assertSnappedDistance(100, 0); + assertSnappedDistance(150, 0); + assertSnappedDistance(200, 200); + assertSnappedDistance(250, 200); + assertSnappedDistance(400, 400); + } + + private void assertSnapDistance(float expectedDistance) + => AddAssert($"distance is {expectedDistance}", () => composer.GetBeatSnapDistanceAt(0) == expectedDistance); + + private void assertDurationToDistance(double duration, float expectedDistance) + => AddAssert($"duration = {duration} -> distance = {expectedDistance}", () => composer.DurationToDistance(0, duration) == expectedDistance); + + private void assertDistanceToDuration(float distance, double expectedDuration) + => AddAssert($"distance = {distance} -> duration = {expectedDuration}", () => composer.DistanceToDuration(0, distance) == expectedDuration); + + private void assertSnappedDuration(float distance, double expectedDuration) + => AddAssert($"distance = {distance} -> duration = {expectedDuration} (snapped)", () => composer.GetSnappedDurationFromDistance(0, distance) == expectedDuration); + + private void assertSnappedDistance(float distance, float expectedDistance) + => AddAssert($"distance = {distance} -> distance = {expectedDistance} (snapped)", () => composer.GetSnappedDistanceFromDistance(0, distance) == expectedDistance); + + private class TestHitObjectComposer : OsuHitObjectComposer + { + public new EditorBeatmap<OsuHitObject> EditorBeatmap => base.EditorBeatmap; + + public TestHitObjectComposer() + : base(new OsuRuleset()) + { + } + } + } +} From da6ee05dd689646d5c4831d9c1d7cc149754d184 Mon Sep 17 00:00:00 2001 From: smoogipoo <smoogipoo@smgi.me> Date: Fri, 25 Oct 2019 18:37:44 +0900 Subject: [PATCH 1913/2815] Fix not being able to drag non-snaked sliders --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index fdeffc6f8a..ae0492ac3e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -38,6 +38,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders public override Vector2 SelectionPoint => HeadBlueprint.SelectionPoint; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos); + protected virtual SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new SliderCircleSelectionBlueprint(slider, position); } } From a9ec6b256266f6c2c1c579ac61bac1d72ada66ca Mon Sep 17 00:00:00 2001 From: smoogipoo <smoogipoo@smgi.me> Date: Fri, 25 Oct 2019 19:00:10 +0900 Subject: [PATCH 1914/2815] Fix testcase failure --- osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs index 0ea73fb3de..b7c7028b52 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs @@ -22,7 +22,7 @@ using osuTK; namespace osu.Game.Tests.Visual.Editor { [TestFixture] - public class TestSceneHitObjectComposer : OsuTestScene + public class TestSceneHitObjectComposer : EditorClockTestScene { public override IReadOnlyList<Type> RequiredTypes => new[] { From e38b7cb169767b0d637cfcb166ed919161aa3087 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Fri, 25 Oct 2019 17:00:56 +0900 Subject: [PATCH 1915/2815] Replace local Equatable implementations with abstract EquivalentTo --- osu.Game/Beatmaps/ControlPoints/ControlPoint.cs | 12 +++++++++--- .../Beatmaps/ControlPoints/DifficultyControlPoint.cs | 8 +++----- .../Beatmaps/ControlPoints/EffectControlPoint.cs | 10 ++++------ .../Beatmaps/ControlPoints/SampleControlPoint.cs | 9 ++++----- .../Beatmaps/ControlPoints/TimingControlPoint.cs | 9 ++++----- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 8 ++++---- 6 files changed, 28 insertions(+), 28 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index abe7e5e803..0081fab46a 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -5,7 +5,7 @@ using System; namespace osu.Game.Beatmaps.ControlPoints { - public class ControlPoint : IComparable<ControlPoint>, IEquatable<ControlPoint> + public abstract class ControlPoint : IComparable<ControlPoint>, IEquatable<ControlPoint> { /// <summary> /// The time at which the control point takes effect. @@ -19,7 +19,13 @@ namespace osu.Game.Beatmaps.ControlPoints public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time); - public bool Equals(ControlPoint other) - => Time.Equals(other?.Time); + /// <summary> + /// Whether this control point is equivalent to another, ignoring time. + /// </summary> + /// <param name="other">Another control point to compare with.</param> + /// <returns>Whether equivalent.</returns> + public abstract bool EquivalentTo(ControlPoint other); + + public bool Equals(ControlPoint other) => Time.Equals(other?.Time) && EquivalentTo(other); } } diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index a3e3121575..42651fd0ca 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -1,12 +1,11 @@ // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osuTK; namespace osu.Game.Beatmaps.ControlPoints { - public class DifficultyControlPoint : ControlPoint, IEquatable<DifficultyControlPoint> + public class DifficultyControlPoint : ControlPoint { /// <summary> /// The speed multiplier at this control point. @@ -19,8 +18,7 @@ namespace osu.Game.Beatmaps.ControlPoints private double speedMultiplier = 1; - public bool Equals(DifficultyControlPoint other) - => base.Equals(other) - && SpeedMultiplier.Equals(other?.SpeedMultiplier); + public override bool EquivalentTo(ControlPoint other) => + other is DifficultyControlPoint otherTyped && otherTyped.SpeedMultiplier.Equals(speedMultiplier); } } diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs index 354d86dc13..928f2a51ad 100644 --- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs @@ -1,11 +1,9 @@ // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; - namespace osu.Game.Beatmaps.ControlPoints { - public class EffectControlPoint : ControlPoint, IEquatable<EffectControlPoint> + public class EffectControlPoint : ControlPoint { /// <summary> /// Whether this control point enables Kiai mode. @@ -17,8 +15,8 @@ namespace osu.Game.Beatmaps.ControlPoints /// </summary> public bool OmitFirstBarLine; - public bool Equals(EffectControlPoint other) - => base.Equals(other) - && KiaiMode == other?.KiaiMode && OmitFirstBarLine == other.OmitFirstBarLine; + public override bool EquivalentTo(ControlPoint other) => + other is EffectControlPoint otherTyped && + KiaiMode == otherTyped.KiaiMode && OmitFirstBarLine == otherTyped.OmitFirstBarLine; } } diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index 7bc7a9056d..35eefebca4 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -1,12 +1,11 @@ // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Game.Audio; namespace osu.Game.Beatmaps.ControlPoints { - public class SampleControlPoint : ControlPoint, IEquatable<SampleControlPoint> + public class SampleControlPoint : ControlPoint { public const string DEFAULT_BANK = "normal"; @@ -45,8 +44,8 @@ namespace osu.Game.Beatmaps.ControlPoints return newSampleInfo; } - public bool Equals(SampleControlPoint other) - => base.Equals(other) - && string.Equals(SampleBank, other?.SampleBank) && SampleVolume == other?.SampleVolume; + public override bool EquivalentTo(ControlPoint other) => + other is SampleControlPoint otherTyped && + string.Equals(SampleBank, otherTyped?.SampleBank) && SampleVolume == otherTyped?.SampleVolume; } } diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index ccb8a92b3a..03b188929b 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -1,13 +1,12 @@ // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osuTK; using osu.Game.Beatmaps.Timing; namespace osu.Game.Beatmaps.ControlPoints { - public class TimingControlPoint : ControlPoint, IEquatable<TimingControlPoint> + public class TimingControlPoint : ControlPoint { /// <summary> /// The time signature at this control point. @@ -27,8 +26,8 @@ namespace osu.Game.Beatmaps.ControlPoints private double beatLength = DEFAULT_BEAT_LENGTH; - public bool Equals(TimingControlPoint other) - => base.Equals(other) - && TimeSignature == other?.TimeSignature && beatLength.Equals(other.beatLength); + public override bool EquivalentTo(ControlPoint other) => + other is TimingControlPoint otherTyped + && TimeSignature == otherTyped?.TimeSignature && beatLength.Equals(otherTyped.beatLength); } } diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 83d20da458..26f7209be6 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -189,7 +189,7 @@ namespace osu.Game.Beatmaps.Formats Foreground = 3 } - internal class LegacySampleControlPoint : SampleControlPoint, IEquatable<LegacySampleControlPoint> + internal class LegacySampleControlPoint : SampleControlPoint { public int CustomSampleBank; @@ -203,9 +203,9 @@ namespace osu.Game.Beatmaps.Formats return baseInfo; } - public bool Equals(LegacySampleControlPoint other) - => base.Equals(other) - && CustomSampleBank == other?.CustomSampleBank; + public override bool EquivalentTo(ControlPoint other) => + base.EquivalentTo(other) + && CustomSampleBank == ((LegacySampleControlPoint)other).CustomSampleBank; } } } From 94ffe03e6e71701e723db6196532dde41fd1c254 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Fri, 25 Oct 2019 16:19:06 +0900 Subject: [PATCH 1916/2815] Group timing points --- .../TestSceneDrawableHitObjects.cs | 2 +- .../TestSceneOsuDistanceSnapGrid.cs | 13 +- .../TestSceneSlider.cs | 2 +- .../TestSceneSliderInput.cs | 6 +- .../TestSceneTaikoPlayfield.cs | 6 +- .../Audio/DrumSampleMapping.cs | 7 +- .../Editor/TestSceneDistanceSnapGrid.cs | 10 +- .../Editor/TestSceneEditorSeekSnapping.cs | 20 ++- .../TestSceneDrawableScrollingRuleset.cs | 34 ++--- .../TestSceneBeatSyncedContainer.cs | 4 +- .../Beatmaps/ControlPoints/ControlPoint.cs | 6 +- .../ControlPoints/ControlPointGroup.cs | 48 +++++++ .../ControlPoints/ControlPointInfo.cs | 130 ++++++++++++++++-- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 92 ++----------- .../Containers/BeatSyncedContainer.cs | 2 - osu.Game/Screens/Edit/EditorClock.cs | 5 +- 16 files changed, 233 insertions(+), 154 deletions(-) create mode 100644 osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs index 7a9b61c60c..0369b6db4e 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Catch.Tests private void load() { var controlPointInfo = new ControlPointInfo(); - controlPointInfo.TimingPoints.Add(new TimingControlPoint()); + controlPointInfo.Add(0, new TimingControlPoint()); WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs index 6b8daa531f..b66123e628 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs @@ -53,9 +53,8 @@ namespace osu.Game.Rulesets.Osu.Tests Clear(); editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1; - editorBeatmap.ControlPointInfo.DifficultyPoints.Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); + editorBeatmap.ControlPointInfo.Clear(); + editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); beatDivisor.Value = 1; }); @@ -80,8 +79,8 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep($"set beat length = {beatLength}", () => { - editorBeatmap.ControlPointInfo.TimingPoints.Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beatLength }); + editorBeatmap.ControlPointInfo.Clear(); + editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beatLength }); }); createGrid(); @@ -95,8 +94,8 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep($"set speed multiplier = {multiplier}", () => { - editorBeatmap.ControlPointInfo.DifficultyPoints.Clear(); - editorBeatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = multiplier }); + editorBeatmap.ControlPointInfo.Clear(); + editorBeatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = multiplier }); }); createGrid(); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index 4893ebfdd4..a955911bd5 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -308,7 +308,7 @@ namespace osu.Game.Rulesets.Osu.Tests private Drawable createDrawable(Slider slider, float circleSize, double speedMultiplier) { var cpi = new ControlPointInfo(); - cpi.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = speedMultiplier }); + cpi.Add(0, new DifficultyControlPoint { SpeedMultiplier = speedMultiplier }); slider.ApplyDefaults(cpi, new BeatmapDifficulty { CircleSize = circleSize, SliderTickRate = 3 }); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index 2eb783233a..5f75cbabec 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -313,10 +313,6 @@ namespace osu.Game.Rulesets.Osu.Tests }, 25), } }, - ControlPointInfo = - { - DifficultyPoints = { new DifficultyControlPoint { SpeedMultiplier = 0.1f } } - }, BeatmapInfo = { BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 }, @@ -324,6 +320,8 @@ namespace osu.Game.Rulesets.Osu.Tests }, }); + Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f }); + var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); p.OnLoadComplete += _ => diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs index eaa8ca7ebb..8522a42739 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Taiko.Tests AddStep("Reset height", () => changePlayfieldSize(6)); var controlPointInfo = new ControlPointInfo(); - controlPointInfo.TimingPoints.Add(new TimingControlPoint()); + controlPointInfo.Add(0, new TimingControlPoint()); WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap { @@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Taiko.Tests HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Good : HitResult.Great; var cpi = new ControlPointInfo(); - cpi.EffectPoints.Add(new EffectControlPoint { KiaiMode = kiai }); + cpi.Add(0, new EffectControlPoint { KiaiMode = kiai }); Hit hit = new Hit(); hit.ApplyDefaults(cpi, new BeatmapDifficulty()); @@ -157,7 +157,7 @@ namespace osu.Game.Rulesets.Taiko.Tests HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Good : HitResult.Great; var cpi = new ControlPointInfo(); - cpi.EffectPoints.Add(new EffectControlPoint { KiaiMode = kiai }); + cpi.Add(0, new EffectControlPoint { KiaiMode = kiai }); Hit hit = new Hit(); hit.ApplyDefaults(cpi, new BeatmapDifficulty()); diff --git a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs index ad2596931d..aaf113f216 100644 --- a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs +++ b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs @@ -19,12 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Audio { this.controlPoints = controlPoints; - IEnumerable<SampleControlPoint> samplePoints; - if (controlPoints.SamplePoints.Count == 0) - // Get the default sample point - samplePoints = new[] { controlPoints.SamplePointAt(double.MinValue) }; - else - samplePoints = controlPoints.SamplePoints; + IEnumerable<SampleControlPoint> samplePoints = controlPoints.SamplePoints.Count == 0 ? new[] { controlPoints.SamplePointAt(double.MinValue) } : controlPoints.SamplePoints; foreach (var s in samplePoints) { diff --git a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs index a9e5930478..07646fdb78 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Editor public TestSceneDistanceSnapGrid() { editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap()); - editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); + editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); createGrid(); } @@ -42,8 +42,8 @@ namespace osu.Game.Tests.Visual.Editor { Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); + editorBeatmap.ControlPointInfo.Clear(); + editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); BeatDivisor.Value = 1; }); @@ -81,8 +81,8 @@ namespace osu.Game.Tests.Visual.Editor { AddStep($"set beat length = {beatLength}", () => { - editorBeatmap.ControlPointInfo.TimingPoints.Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beatLength }); + editorBeatmap.ControlPointInfo.Clear(); + editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beatLength }); }); createGrid(); diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorSeekSnapping.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorSeekSnapping.cs index b997d6aaeb..3118e0cabe 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorSeekSnapping.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorSeekSnapping.cs @@ -28,18 +28,7 @@ namespace osu.Game.Tests.Visual.Editor { var testBeatmap = new Beatmap { - ControlPointInfo = new ControlPointInfo - { - TimingPoints = - { - new TimingControlPoint { Time = 0, BeatLength = 200 }, - new TimingControlPoint { Time = 100, BeatLength = 400 }, - new TimingControlPoint { Time = 175, BeatLength = 800 }, - new TimingControlPoint { Time = 350, BeatLength = 200 }, - new TimingControlPoint { Time = 450, BeatLength = 100 }, - new TimingControlPoint { Time = 500, BeatLength = 307.69230769230802 } - } - }, + ControlPointInfo = new ControlPointInfo(), HitObjects = { new HitCircle { StartTime = 0 }, @@ -47,6 +36,13 @@ namespace osu.Game.Tests.Visual.Editor } }; + testBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 200 }); + testBeatmap.ControlPointInfo.Add(100, new TimingControlPoint { BeatLength = 400 }); + testBeatmap.ControlPointInfo.Add(175, new TimingControlPoint { BeatLength = 800 }); + testBeatmap.ControlPointInfo.Add(350, new TimingControlPoint { BeatLength = 200 }); + testBeatmap.ControlPointInfo.Add(450, new TimingControlPoint { BeatLength = 100 }); + testBeatmap.ControlPointInfo.Add(500, new TimingControlPoint { BeatLength = 307.69230769230802 }); + Beatmap.Value = CreateWorkingBeatmap(testBeatmap); Child = new TimingPointVisualiser(testBeatmap, 5000) { Clock = Clock }; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index dcab964d6d..684e79b3f5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -47,7 +47,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestRelativeBeatLengthScaleSingleTimingPoint() { - var beatmap = createBeatmap(new TimingControlPoint { BeatLength = time_range / 2 }); + var beatmap = createBeatmap(); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range / 2 }); createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true); @@ -61,10 +62,10 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestRelativeBeatLengthScaleTimingPointBeyondEndDoesNotBecomeDominant() { - var beatmap = createBeatmap( - new TimingControlPoint { BeatLength = time_range / 2 }, - new TimingControlPoint { Time = 12000, BeatLength = time_range }, - new TimingControlPoint { Time = 100000, BeatLength = time_range }); + var beatmap = createBeatmap(); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range / 2 }); + beatmap.ControlPointInfo.Add(12000, new TimingControlPoint { BeatLength = time_range }); + beatmap.ControlPointInfo.Add(100000, new TimingControlPoint { BeatLength = time_range }); createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true); @@ -75,9 +76,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestRelativeBeatLengthScaleFromSecondTimingPoint() { - var beatmap = createBeatmap( - new TimingControlPoint { BeatLength = time_range }, - new TimingControlPoint { Time = 3 * time_range, BeatLength = time_range / 2 }); + var beatmap = createBeatmap(); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range }); + beatmap.ControlPointInfo.Add(3 * time_range, new TimingControlPoint { BeatLength = time_range / 2 }); createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true); @@ -97,9 +98,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestNonRelativeScale() { - var beatmap = createBeatmap( - new TimingControlPoint { BeatLength = time_range }, - new TimingControlPoint { Time = 3 * time_range, BeatLength = time_range / 2 }); + var beatmap = createBeatmap(); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range }); + beatmap.ControlPointInfo.Add(3 * time_range, new TimingControlPoint { BeatLength = time_range / 2 }); createTest(beatmap); @@ -119,7 +120,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestSliderMultiplierDoesNotAffectRelativeBeatLength() { - var beatmap = createBeatmap(new TimingControlPoint { BeatLength = time_range }); + var beatmap = createBeatmap(); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range }); beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2; createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true); @@ -132,7 +134,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestSliderMultiplierAffectsNonRelativeBeatLength() { - var beatmap = createBeatmap(new TimingControlPoint { BeatLength = time_range }); + var beatmap = createBeatmap(); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range }); beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2; createTest(beatmap); @@ -154,14 +157,11 @@ namespace osu.Game.Tests.Visual.Gameplay /// Creates an <see cref="IBeatmap"/>, containing 10 hitobjects and user-provided timing points. /// The hitobjects are spaced <see cref="time_range"/> milliseconds apart. /// </summary> - /// <param name="timingControlPoints">The timing points to add to the beatmap.</param> /// <returns>The <see cref="IBeatmap"/>.</returns> - private IBeatmap createBeatmap(params TimingControlPoint[] timingControlPoints) + private IBeatmap createBeatmap() { var beatmap = new Beatmap<HitObject> { BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo } }; - beatmap.ControlPointInfo.TimingPoints.AddRange(timingControlPoints); - for (int i = 0; i < 10; i++) beatmap.HitObjects.Add(new HitObject { StartTime = i * time_range }); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index d84ffa0d93..b6df559686 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Internal; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Track; @@ -10,7 +11,6 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Lists; using osu.Framework.Timing; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; @@ -153,7 +153,7 @@ namespace osu.Game.Tests.Visual.UserInterface }; } - private SortedList<TimingControlPoint> timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints; + private IReadOnlyList<TimingControlPoint> timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints; private TimingControlPoint getNextTimingPoint(TimingControlPoint current) { diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index 0081fab46a..6288c1460f 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -10,13 +10,17 @@ namespace osu.Game.Beatmaps.ControlPoints /// <summary> /// The time at which the control point takes effect. /// </summary> - public double Time; + public double Time => controlPointGroup?.Time ?? 0; /// <summary> /// Whether this timing point was generated internally, as opposed to parsed from the underlying beatmap. /// </summary> internal bool AutoGenerated; + private ControlPointGroup controlPointGroup; + + public void AttachGroup(ControlPointGroup pointGroup) => this.controlPointGroup = pointGroup; + public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time); /// <summary> diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs new file mode 100644 index 0000000000..c4b990675e --- /dev/null +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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; + +namespace osu.Game.Beatmaps.ControlPoints +{ + public class ControlPointGroup : IComparable<ControlPointGroup> + { + public event Action<ControlPoint> ItemAdded; + public event Action<ControlPoint> ItemRemoved; + + /// <summary> + /// The time at which the control point takes effect. + /// </summary> + public double Time { get; } + + public IReadOnlyList<ControlPoint> ControlPoints => controlPoints; + + private readonly List<ControlPoint> controlPoints = new List<ControlPoint>(); + + public ControlPointGroup(double time) + { + Time = time; + } + + public int CompareTo(ControlPointGroup other) => Time.CompareTo(other.Time); + + public void Add(ControlPoint point) + { + point.AttachGroup(this); + + foreach (var existing in controlPoints.Where(p => p.GetType() == point.GetType()).ToArray()) + Remove(existing); + + controlPoints.Add(point); + ItemAdded?.Invoke(point); + } + + public void Remove(ControlPoint point) + { + controlPoints.Remove(point); + ItemRemoved?.Invoke(point); + } + } +} diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 68eb0ec6d1..2175eccaa2 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -12,35 +12,50 @@ namespace osu.Game.Beatmaps.ControlPoints [Serializable] public class ControlPointInfo { + /// <summary> + /// Control point groups. + /// </summary> + [JsonProperty] + public IReadOnlyList<ControlPointGroup> Groups => groups; + + private readonly SortedList<ControlPointGroup> groups = new SortedList<ControlPointGroup>(Comparer<ControlPointGroup>.Default); + /// <summary> /// All timing points. /// </summary> [JsonProperty] - public SortedList<TimingControlPoint> TimingPoints { get; private set; } = new SortedList<TimingControlPoint>(Comparer<TimingControlPoint>.Default); + public IReadOnlyList<TimingControlPoint> TimingPoints => timingPoints; + + private readonly SortedList<TimingControlPoint> timingPoints = new SortedList<TimingControlPoint>(Comparer<TimingControlPoint>.Default); /// <summary> /// All difficulty points. /// </summary> [JsonProperty] - public SortedList<DifficultyControlPoint> DifficultyPoints { get; private set; } = new SortedList<DifficultyControlPoint>(Comparer<DifficultyControlPoint>.Default); + public IReadOnlyList<DifficultyControlPoint> DifficultyPoints => difficultyPoints; + + private readonly SortedList<DifficultyControlPoint> difficultyPoints = new SortedList<DifficultyControlPoint>(Comparer<DifficultyControlPoint>.Default); /// <summary> /// All sound points. /// </summary> [JsonProperty] - public SortedList<SampleControlPoint> SamplePoints { get; private set; } = new SortedList<SampleControlPoint>(Comparer<SampleControlPoint>.Default); + public IReadOnlyList<SampleControlPoint> SamplePoints => samplePoints; + + private readonly SortedList<SampleControlPoint> samplePoints = new SortedList<SampleControlPoint>(Comparer<SampleControlPoint>.Default); /// <summary> /// All effect points. /// </summary> [JsonProperty] - public SortedList<EffectControlPoint> EffectPoints { get; private set; } = new SortedList<EffectControlPoint>(Comparer<EffectControlPoint>.Default); + public IReadOnlyList<EffectControlPoint> EffectPoints => effectPoints; - public IReadOnlyList<ControlPoint> AllControlPoints => - TimingPoints - .Concat((IEnumerable<ControlPoint>)DifficultyPoints) - .Concat(SamplePoints) - .Concat(EffectPoints).ToArray(); + private readonly SortedList<EffectControlPoint> effectPoints = new SortedList<EffectControlPoint>(Comparer<EffectControlPoint>.Default); + + /// <summary> + /// All control points, of all types. + /// </summary> + public IEnumerable<ControlPoint> AllControlPoints => Groups.SelectMany(g => g.ControlPoints); /// <summary> /// Finds the difficulty control point that is active at <paramref name="time"/>. @@ -70,6 +85,28 @@ namespace osu.Game.Beatmaps.ControlPoints /// <returns>The timing control point.</returns> public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time, TimingPoints.Count > 0 ? TimingPoints[0] : null); + /// <summary> + /// Finds the closest <see cref="ControlPoint"/> of the same type as <see cref="referencePoint"/> that is active at <paramref name="time"/>. + /// </summary> + /// <param name="time">The time to find the timing control point at.</param> + /// <param name="referencePoint">A reference point to infer type.</param> + /// <returns>The timing control point.</returns> + public ControlPoint SimilarPointAt(double time, ControlPoint referencePoint) + { + switch (referencePoint) + { + case TimingControlPoint _: return TimingPointAt(time); + + case EffectControlPoint _: return EffectPointAt(time); + + case SampleControlPoint _: return SamplePointAt(time); + + case DifficultyControlPoint _: return DifficultyPointAt(time); + } + + return null; + } + /// <summary> /// Finds the maximum BPM represented by any timing control point. /// </summary> @@ -98,7 +135,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// <param name="time">The time to find the control point at.</param> /// <param name="prePoint">The control point to use when <paramref name="time"/> is before any control points. If null, a new control point will be constructed.</param> /// <returns>The active control point at <paramref name="time"/>.</returns> - private T binarySearch<T>(SortedList<T> list, double time, T prePoint = null) + private T binarySearch<T>(IReadOnlyList<T> list, double time, T prePoint = null) where T : ControlPoint, new() { if (list == null) @@ -131,5 +168,78 @@ namespace osu.Game.Beatmaps.ControlPoints // l will be the first control point with Time > time, but we want the one before it return list[l - 1]; } + + public void Add(double time, ControlPoint newPoint, bool force = false) + { + if (!force && SimilarPointAt(time, newPoint)?.EquivalentTo(newPoint) == true) + return; + + GroupAt(time, true).Add(newPoint); + } + + public ControlPointGroup GroupAt(double time, bool createIfNotExisting) + { + var existing = Groups.FirstOrDefault(g => g.Time == time); + + if (existing != null) + return existing; + + if (createIfNotExisting) + { + var newGroup = new ControlPointGroup(time); + newGroup.ItemAdded += groupItemAdded; + newGroup.ItemRemoved += groupItemRemoved; + groups.Add(newGroup); + return newGroup; + } + + return null; + } + + private void groupItemRemoved(ControlPoint obj) + { + switch (obj) + { + case TimingControlPoint typed: + timingPoints.Remove(typed); + break; + + case EffectControlPoint typed: + effectPoints.Remove(typed); + break; + + case SampleControlPoint typed: + samplePoints.Remove(typed); + break; + + case DifficultyControlPoint typed: + difficultyPoints.Remove(typed); + break; + } + } + + private void groupItemAdded(ControlPoint obj) + { + switch (obj) + { + case TimingControlPoint typed: + timingPoints.Add(typed); + break; + + case EffectControlPoint typed: + effectPoints.Add(typed); + break; + + case SampleControlPoint typed: + samplePoints.Add(typed); + break; + + case DifficultyControlPoint typed: + difficultyPoints.Add(typed); + break; + } + } + + public void Clear() => groups.Clear(); } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 786b7611b5..61f70a8c27 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -369,31 +369,29 @@ namespace osu.Game.Beatmaps.Formats if (timingChange) { var controlPoint = CreateTimingControlPoint(); - controlPoint.Time = time; controlPoint.BeatLength = beatLength; controlPoint.TimeSignature = timeSignature; - handleTimingControlPoint(controlPoint); + beatmap.ControlPointInfo.Add(time, controlPoint); + } + else + { + beatmap.ControlPointInfo.Add(time, new DifficultyControlPoint + { + SpeedMultiplier = speedMultiplier, + AutoGenerated = timingChange + }); } - handleDifficultyControlPoint(new DifficultyControlPoint + beatmap.ControlPointInfo.Add(time, new EffectControlPoint { - Time = time, - SpeedMultiplier = speedMultiplier, - AutoGenerated = timingChange - }); - - handleEffectControlPoint(new EffectControlPoint - { - Time = time, KiaiMode = kiaiMode, OmitFirstBarLine = omitFirstBarSignature, AutoGenerated = timingChange }); - handleSampleControlPoint(new LegacySampleControlPoint + beatmap.ControlPointInfo.Add(time, new LegacySampleControlPoint { - Time = time, SampleBank = stringSampleSet, SampleVolume = sampleVolume, CustomSampleBank = customSampleBank, @@ -401,74 +399,6 @@ namespace osu.Game.Beatmaps.Formats }); } - private void handleTimingControlPoint(TimingControlPoint newPoint) - { - var existing = beatmap.ControlPointInfo.TimingPointAt(newPoint.Time); - - if (existing.Time == newPoint.Time) - { - // autogenerated points should not replace non-autogenerated. - // this allows for incorrectly ordered timing points to still be correctly handled. - if (newPoint.AutoGenerated && !existing.AutoGenerated) - return; - - beatmap.ControlPointInfo.TimingPoints.Remove(existing); - } - - beatmap.ControlPointInfo.TimingPoints.Add(newPoint); - } - - private void handleDifficultyControlPoint(DifficultyControlPoint newPoint) - { - var existing = beatmap.ControlPointInfo.DifficultyPointAt(newPoint.Time); - - if (existing.Time == newPoint.Time) - { - // autogenerated points should not replace non-autogenerated. - // this allows for incorrectly ordered timing points to still be correctly handled. - if (newPoint.AutoGenerated && !existing.AutoGenerated) - return; - - beatmap.ControlPointInfo.DifficultyPoints.Remove(existing); - } - - beatmap.ControlPointInfo.DifficultyPoints.Add(newPoint); - } - - private void handleEffectControlPoint(EffectControlPoint newPoint) - { - var existing = beatmap.ControlPointInfo.EffectPointAt(newPoint.Time); - - if (existing.Time == newPoint.Time) - { - // autogenerated points should not replace non-autogenerated. - // this allows for incorrectly ordered timing points to still be correctly handled. - if (newPoint.AutoGenerated && !existing.AutoGenerated) - return; - - beatmap.ControlPointInfo.EffectPoints.Remove(existing); - } - - beatmap.ControlPointInfo.EffectPoints.Add(newPoint); - } - - private void handleSampleControlPoint(SampleControlPoint newPoint) - { - var existing = beatmap.ControlPointInfo.SamplePointAt(newPoint.Time); - - if (existing.Time == newPoint.Time) - { - // autogenerated points should not replace non-autogenerated. - // this allows for incorrectly ordered timing points to still be correctly handled. - if (newPoint.AutoGenerated && !existing.AutoGenerated) - return; - - beatmap.ControlPointInfo.SamplePoints.Remove(existing); - } - - beatmap.ControlPointInfo.SamplePoints.Add(newPoint); - } - private void handleHitObject(string line) { // If the ruleset wasn't specified, assume the osu!standard ruleset. diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 370d044ba4..2832a70518 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -105,12 +105,10 @@ namespace osu.Game.Graphics.Containers { BeatLength = default_beat_length, AutoGenerated = true, - Time = 0 }; defaultEffect = new EffectControlPoint { - Time = 0, AutoGenerated = true, KiaiMode = false, OmitFirstBarLine = false diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 24fb561f04..1cfb123f25 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using Microsoft.EntityFrameworkCore.Internal; using osu.Framework.MathUtils; using osu.Framework.Timing; using osu.Game.Beatmaps; @@ -57,7 +58,7 @@ namespace osu.Game.Screens.Edit // Depending on beatSnapLength, we may snap to a beat that is beyond timingPoint's end time, but we want to instead snap to // the next timing point's start time - var nextTimingPoint = ControlPointInfo.TimingPoints.Find(t => t.Time > timingPoint.Time); + var nextTimingPoint = ControlPointInfo.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time); if (position > nextTimingPoint?.Time) position = nextTimingPoint.Time; @@ -124,7 +125,7 @@ namespace osu.Game.Screens.Edit if (seekTime < timingPoint.Time && timingPoint != ControlPointInfo.TimingPoints.First()) seekTime = timingPoint.Time; - var nextTimingPoint = ControlPointInfo.TimingPoints.Find(t => t.Time > timingPoint.Time); + var nextTimingPoint = ControlPointInfo.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time); if (seekTime > nextTimingPoint?.Time) seekTime = nextTimingPoint.Time; From da6769f0fcd9966a24624ec97451a16dd01fc187 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Fri, 25 Oct 2019 17:00:23 +0900 Subject: [PATCH 1917/2815] Remove necessity of AutoGenerated flag --- .../Formats/LegacyBeatmapDecoderTest.cs | 10 +-- .../Beatmaps/ControlPoints/ControlPoint.cs | 7 +-- .../ControlPoints/ControlPointInfo.cs | 2 +- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 63 ++++++++++++++----- .../Containers/BeatSyncedContainer.cs | 2 - .../Edit/Timing/ControlPointSettings.cs | 2 +- .../Screens/Edit/Timing/ControlPointTable.cs | 3 - 7 files changed, 56 insertions(+), 33 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index de516d3142..c50250159e 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -167,9 +167,9 @@ namespace osu.Game.Tests.Beatmaps.Formats var controlPoints = beatmap.ControlPointInfo; Assert.AreEqual(4, controlPoints.TimingPoints.Count); - Assert.AreEqual(42, controlPoints.DifficultyPoints.Count); - Assert.AreEqual(42, controlPoints.SamplePoints.Count); - Assert.AreEqual(42, controlPoints.EffectPoints.Count); + Assert.AreEqual(5, controlPoints.DifficultyPoints.Count); + Assert.AreEqual(34, controlPoints.SamplePoints.Count); + Assert.AreEqual(8, controlPoints.EffectPoints.Count); var timingPoint = controlPoints.TimingPointAt(0); Assert.AreEqual(956, timingPoint.Time); @@ -191,7 +191,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); difficultyPoint = controlPoints.DifficultyPointAt(48428); - Assert.AreEqual(48428, difficultyPoint.Time); + Assert.AreEqual(0, difficultyPoint.Time); Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); difficultyPoint = controlPoints.DifficultyPointAt(116999); @@ -224,7 +224,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsFalse(effectPoint.OmitFirstBarLine); effectPoint = controlPoints.EffectPointAt(119637); - Assert.AreEqual(119637, effectPoint.Time); + Assert.AreEqual(95901, effectPoint.Time); Assert.IsFalse(effectPoint.KiaiMode); Assert.IsFalse(effectPoint.OmitFirstBarLine); } diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index 6288c1460f..0861e00d8d 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -12,11 +12,6 @@ namespace osu.Game.Beatmaps.ControlPoints /// </summary> public double Time => controlPointGroup?.Time ?? 0; - /// <summary> - /// Whether this timing point was generated internally, as opposed to parsed from the underlying beatmap. - /// </summary> - internal bool AutoGenerated; - private ControlPointGroup controlPointGroup; public void AttachGroup(ControlPointGroup pointGroup) => this.controlPointGroup = pointGroup; diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 2175eccaa2..7e9c8844f0 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -177,7 +177,7 @@ namespace osu.Game.Beatmaps.ControlPoints GroupAt(time, true).Add(newPoint); } - public ControlPointGroup GroupAt(double time, bool createIfNotExisting) + public ControlPointGroup GroupAt(double time, bool createIfNotExisting = false) { var existing = Groups.FirstOrDefault(g => g.Time == time); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 61f70a8c27..5589aecc19 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.IO; using System.Linq; using osu.Framework.IO.File; @@ -50,6 +51,8 @@ namespace osu.Game.Beatmaps.Formats base.ParseStreamInto(stream, beatmap); + flushPendingPoints(); + // Objects may be out of order *only* if a user has manually edited an .osu file. // Unfortunately there are ranked maps in this state (example: https://osu.ppy.sh/s/594828). // OrderBy is used to guarantee that the parsing order of hitobjects with equal start times is maintained (stably-sorted) @@ -369,34 +372,64 @@ namespace osu.Game.Beatmaps.Formats if (timingChange) { var controlPoint = CreateTimingControlPoint(); + controlPoint.BeatLength = beatLength; controlPoint.TimeSignature = timeSignature; - beatmap.ControlPointInfo.Add(time, controlPoint); - } - else - { - beatmap.ControlPointInfo.Add(time, new DifficultyControlPoint - { - SpeedMultiplier = speedMultiplier, - AutoGenerated = timingChange - }); + addControlPoint(time, controlPoint, true); } - beatmap.ControlPointInfo.Add(time, new EffectControlPoint + addControlPoint(time, new DifficultyControlPoint + { + SpeedMultiplier = speedMultiplier, + }, timingChange); + + addControlPoint(time, new EffectControlPoint { KiaiMode = kiaiMode, OmitFirstBarLine = omitFirstBarSignature, - AutoGenerated = timingChange - }); + }, timingChange); - beatmap.ControlPointInfo.Add(time, new LegacySampleControlPoint + addControlPoint(time, new LegacySampleControlPoint { SampleBank = stringSampleSet, SampleVolume = sampleVolume, CustomSampleBank = customSampleBank, - AutoGenerated = timingChange - }); + }, timingChange); + + // To handle the scenario where a non-timing line shares the same time value as a subsequent timing line but + // appears earlier in the file, we buffer non-timing control points and rewrite them *after* control points from the timing line + // with the same time value (allowing them to overwrite as necessary). + // + // The expected outcome is that we prefer the non-timing line's adjustments over the timing line's adjustments when time is equal. + if (timingChange) + flushPendingPoints(); + } + + private readonly List<ControlPoint> pendingControlPoints = new List<ControlPoint>(); + private double pendingControlPointsTime; + + private void addControlPoint(double time, ControlPoint point, bool timingChange) + { + if (timingChange) + { + beatmap.ControlPointInfo.Add(time, point); + return; + } + + if (time != pendingControlPointsTime) + flushPendingPoints(); + + pendingControlPoints.Add(point); + pendingControlPointsTime = time; + } + + private void flushPendingPoints() + { + foreach (var p in pendingControlPoints) + beatmap.ControlPointInfo.Add(pendingControlPointsTime, p); + + pendingControlPoints.Clear(); } private void handleHitObject(string line) diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 2832a70518..2e76ab964f 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -104,12 +104,10 @@ namespace osu.Game.Graphics.Containers defaultTiming = new TimingControlPoint { BeatLength = default_beat_length, - AutoGenerated = true, }; defaultEffect = new EffectControlPoint { - AutoGenerated = true, KiaiMode = false, OmitFirstBarLine = false }; diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs index 0974745294..ce72d7c191 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -228,7 +228,7 @@ namespace osu.Game.Screens.Edit.Timing selectedPoints.BindValueChanged(points => { - ControlPoint.Value = points.NewValue?.OfType<T>().Where(p => !p.AutoGenerated).FirstOrDefault(); + ControlPoint.Value = points.NewValue?.OfType<T>().FirstOrDefault(); checkbox.Current.Value = ControlPoint.Value != null; }, true); diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 777d34e75b..597200e54c 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -105,9 +105,6 @@ namespace osu.Game.Screens.Edit.Timing private Drawable createAttribute(ControlPoint controlPoint) { - if (controlPoint.AutoGenerated) - return null; - switch (controlPoint) { case TimingControlPoint timing: From e2f2638212524bfa0d95971af8b0a458c404006e Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Fri, 25 Oct 2019 17:00:56 +0900 Subject: [PATCH 1918/2815] Replace local Equatable implementations with abstract EquivalentTo --- osu.Game/Beatmaps/ControlPoints/ControlPoint.cs | 12 +++++++++--- .../Beatmaps/ControlPoints/DifficultyControlPoint.cs | 8 +++----- .../Beatmaps/ControlPoints/EffectControlPoint.cs | 10 ++++------ .../Beatmaps/ControlPoints/SampleControlPoint.cs | 9 ++++----- .../Beatmaps/ControlPoints/TimingControlPoint.cs | 9 ++++----- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 8 ++++---- 6 files changed, 28 insertions(+), 28 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index abe7e5e803..0081fab46a 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -5,7 +5,7 @@ using System; namespace osu.Game.Beatmaps.ControlPoints { - public class ControlPoint : IComparable<ControlPoint>, IEquatable<ControlPoint> + public abstract class ControlPoint : IComparable<ControlPoint>, IEquatable<ControlPoint> { /// <summary> /// The time at which the control point takes effect. @@ -19,7 +19,13 @@ namespace osu.Game.Beatmaps.ControlPoints public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time); - public bool Equals(ControlPoint other) - => Time.Equals(other?.Time); + /// <summary> + /// Whether this control point is equivalent to another, ignoring time. + /// </summary> + /// <param name="other">Another control point to compare with.</param> + /// <returns>Whether equivalent.</returns> + public abstract bool EquivalentTo(ControlPoint other); + + public bool Equals(ControlPoint other) => Time.Equals(other?.Time) && EquivalentTo(other); } } diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index a3e3121575..42651fd0ca 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -1,12 +1,11 @@ // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osuTK; namespace osu.Game.Beatmaps.ControlPoints { - public class DifficultyControlPoint : ControlPoint, IEquatable<DifficultyControlPoint> + public class DifficultyControlPoint : ControlPoint { /// <summary> /// The speed multiplier at this control point. @@ -19,8 +18,7 @@ namespace osu.Game.Beatmaps.ControlPoints private double speedMultiplier = 1; - public bool Equals(DifficultyControlPoint other) - => base.Equals(other) - && SpeedMultiplier.Equals(other?.SpeedMultiplier); + public override bool EquivalentTo(ControlPoint other) => + other is DifficultyControlPoint otherTyped && otherTyped.SpeedMultiplier.Equals(speedMultiplier); } } diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs index 354d86dc13..928f2a51ad 100644 --- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs @@ -1,11 +1,9 @@ // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; - namespace osu.Game.Beatmaps.ControlPoints { - public class EffectControlPoint : ControlPoint, IEquatable<EffectControlPoint> + public class EffectControlPoint : ControlPoint { /// <summary> /// Whether this control point enables Kiai mode. @@ -17,8 +15,8 @@ namespace osu.Game.Beatmaps.ControlPoints /// </summary> public bool OmitFirstBarLine; - public bool Equals(EffectControlPoint other) - => base.Equals(other) - && KiaiMode == other?.KiaiMode && OmitFirstBarLine == other.OmitFirstBarLine; + public override bool EquivalentTo(ControlPoint other) => + other is EffectControlPoint otherTyped && + KiaiMode == otherTyped.KiaiMode && OmitFirstBarLine == otherTyped.OmitFirstBarLine; } } diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index 7bc7a9056d..35eefebca4 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -1,12 +1,11 @@ // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Game.Audio; namespace osu.Game.Beatmaps.ControlPoints { - public class SampleControlPoint : ControlPoint, IEquatable<SampleControlPoint> + public class SampleControlPoint : ControlPoint { public const string DEFAULT_BANK = "normal"; @@ -45,8 +44,8 @@ namespace osu.Game.Beatmaps.ControlPoints return newSampleInfo; } - public bool Equals(SampleControlPoint other) - => base.Equals(other) - && string.Equals(SampleBank, other?.SampleBank) && SampleVolume == other?.SampleVolume; + public override bool EquivalentTo(ControlPoint other) => + other is SampleControlPoint otherTyped && + string.Equals(SampleBank, otherTyped?.SampleBank) && SampleVolume == otherTyped?.SampleVolume; } } diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index ccb8a92b3a..03b188929b 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -1,13 +1,12 @@ // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osuTK; using osu.Game.Beatmaps.Timing; namespace osu.Game.Beatmaps.ControlPoints { - public class TimingControlPoint : ControlPoint, IEquatable<TimingControlPoint> + public class TimingControlPoint : ControlPoint { /// <summary> /// The time signature at this control point. @@ -27,8 +26,8 @@ namespace osu.Game.Beatmaps.ControlPoints private double beatLength = DEFAULT_BEAT_LENGTH; - public bool Equals(TimingControlPoint other) - => base.Equals(other) - && TimeSignature == other?.TimeSignature && beatLength.Equals(other.beatLength); + public override bool EquivalentTo(ControlPoint other) => + other is TimingControlPoint otherTyped + && TimeSignature == otherTyped?.TimeSignature && beatLength.Equals(otherTyped.beatLength); } } diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 83d20da458..26f7209be6 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -189,7 +189,7 @@ namespace osu.Game.Beatmaps.Formats Foreground = 3 } - internal class LegacySampleControlPoint : SampleControlPoint, IEquatable<LegacySampleControlPoint> + internal class LegacySampleControlPoint : SampleControlPoint { public int CustomSampleBank; @@ -203,9 +203,9 @@ namespace osu.Game.Beatmaps.Formats return baseInfo; } - public bool Equals(LegacySampleControlPoint other) - => base.Equals(other) - && CustomSampleBank == other?.CustomSampleBank; + public override bool EquivalentTo(ControlPoint other) => + base.EquivalentTo(other) + && CustomSampleBank == ((LegacySampleControlPoint)other).CustomSampleBank; } } } From e987db37ec9c8cf86936359259ca93875951506d Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Fri, 25 Oct 2019 19:48:01 +0900 Subject: [PATCH 1919/2815] Add grouping of ControlPoints --- .../TestSceneDrawableHitObjects.cs | 2 +- .../TestSceneOsuDistanceSnapGrid.cs | 13 +- .../TestSceneSlider.cs | 2 +- .../TestSceneSliderInput.cs | 6 +- .../TestSceneTaikoPlayfield.cs | 6 +- .../Audio/DrumSampleMapping.cs | 7 +- .../Editor/TestSceneDistanceSnapGrid.cs | 10 +- .../Editor/TestSceneEditorSeekSnapping.cs | 20 ++- .../TestSceneDrawableScrollingRuleset.cs | 34 ++--- .../TestSceneBeatSyncedContainer.cs | 4 +- .../Beatmaps/ControlPoints/ControlPoint.cs | 6 +- .../ControlPoints/ControlPointGroup.cs | 48 +++++++ .../ControlPoints/ControlPointInfo.cs | 126 +++++++++++++++++- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 92 ++----------- .../Containers/BeatSyncedContainer.cs | 2 - osu.Game/Screens/Edit/EditorClock.cs | 5 +- 16 files changed, 234 insertions(+), 149 deletions(-) create mode 100644 osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs index 7a9b61c60c..0369b6db4e 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Catch.Tests private void load() { var controlPointInfo = new ControlPointInfo(); - controlPointInfo.TimingPoints.Add(new TimingControlPoint()); + controlPointInfo.Add(0, new TimingControlPoint()); WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs index 6b8daa531f..b66123e628 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs @@ -53,9 +53,8 @@ namespace osu.Game.Rulesets.Osu.Tests Clear(); editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1; - editorBeatmap.ControlPointInfo.DifficultyPoints.Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); + editorBeatmap.ControlPointInfo.Clear(); + editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); beatDivisor.Value = 1; }); @@ -80,8 +79,8 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep($"set beat length = {beatLength}", () => { - editorBeatmap.ControlPointInfo.TimingPoints.Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beatLength }); + editorBeatmap.ControlPointInfo.Clear(); + editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beatLength }); }); createGrid(); @@ -95,8 +94,8 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep($"set speed multiplier = {multiplier}", () => { - editorBeatmap.ControlPointInfo.DifficultyPoints.Clear(); - editorBeatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = multiplier }); + editorBeatmap.ControlPointInfo.Clear(); + editorBeatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = multiplier }); }); createGrid(); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index 4893ebfdd4..a955911bd5 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -308,7 +308,7 @@ namespace osu.Game.Rulesets.Osu.Tests private Drawable createDrawable(Slider slider, float circleSize, double speedMultiplier) { var cpi = new ControlPointInfo(); - cpi.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = speedMultiplier }); + cpi.Add(0, new DifficultyControlPoint { SpeedMultiplier = speedMultiplier }); slider.ApplyDefaults(cpi, new BeatmapDifficulty { CircleSize = circleSize, SliderTickRate = 3 }); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index 2eb783233a..5f75cbabec 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -313,10 +313,6 @@ namespace osu.Game.Rulesets.Osu.Tests }, 25), } }, - ControlPointInfo = - { - DifficultyPoints = { new DifficultyControlPoint { SpeedMultiplier = 0.1f } } - }, BeatmapInfo = { BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 }, @@ -324,6 +320,8 @@ namespace osu.Game.Rulesets.Osu.Tests }, }); + Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f }); + var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); p.OnLoadComplete += _ => diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs index eaa8ca7ebb..8522a42739 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Taiko.Tests AddStep("Reset height", () => changePlayfieldSize(6)); var controlPointInfo = new ControlPointInfo(); - controlPointInfo.TimingPoints.Add(new TimingControlPoint()); + controlPointInfo.Add(0, new TimingControlPoint()); WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap { @@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Taiko.Tests HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Good : HitResult.Great; var cpi = new ControlPointInfo(); - cpi.EffectPoints.Add(new EffectControlPoint { KiaiMode = kiai }); + cpi.Add(0, new EffectControlPoint { KiaiMode = kiai }); Hit hit = new Hit(); hit.ApplyDefaults(cpi, new BeatmapDifficulty()); @@ -157,7 +157,7 @@ namespace osu.Game.Rulesets.Taiko.Tests HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Good : HitResult.Great; var cpi = new ControlPointInfo(); - cpi.EffectPoints.Add(new EffectControlPoint { KiaiMode = kiai }); + cpi.Add(0, new EffectControlPoint { KiaiMode = kiai }); Hit hit = new Hit(); hit.ApplyDefaults(cpi, new BeatmapDifficulty()); diff --git a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs index ad2596931d..aaf113f216 100644 --- a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs +++ b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs @@ -19,12 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Audio { this.controlPoints = controlPoints; - IEnumerable<SampleControlPoint> samplePoints; - if (controlPoints.SamplePoints.Count == 0) - // Get the default sample point - samplePoints = new[] { controlPoints.SamplePointAt(double.MinValue) }; - else - samplePoints = controlPoints.SamplePoints; + IEnumerable<SampleControlPoint> samplePoints = controlPoints.SamplePoints.Count == 0 ? new[] { controlPoints.SamplePointAt(double.MinValue) } : controlPoints.SamplePoints; foreach (var s in samplePoints) { diff --git a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs index a9e5930478..07646fdb78 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Editor public TestSceneDistanceSnapGrid() { editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap()); - editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); + editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); createGrid(); } @@ -42,8 +42,8 @@ namespace osu.Game.Tests.Visual.Editor { Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); + editorBeatmap.ControlPointInfo.Clear(); + editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); BeatDivisor.Value = 1; }); @@ -81,8 +81,8 @@ namespace osu.Game.Tests.Visual.Editor { AddStep($"set beat length = {beatLength}", () => { - editorBeatmap.ControlPointInfo.TimingPoints.Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beatLength }); + editorBeatmap.ControlPointInfo.Clear(); + editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beatLength }); }); createGrid(); diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorSeekSnapping.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorSeekSnapping.cs index b997d6aaeb..3118e0cabe 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorSeekSnapping.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorSeekSnapping.cs @@ -28,18 +28,7 @@ namespace osu.Game.Tests.Visual.Editor { var testBeatmap = new Beatmap { - ControlPointInfo = new ControlPointInfo - { - TimingPoints = - { - new TimingControlPoint { Time = 0, BeatLength = 200 }, - new TimingControlPoint { Time = 100, BeatLength = 400 }, - new TimingControlPoint { Time = 175, BeatLength = 800 }, - new TimingControlPoint { Time = 350, BeatLength = 200 }, - new TimingControlPoint { Time = 450, BeatLength = 100 }, - new TimingControlPoint { Time = 500, BeatLength = 307.69230769230802 } - } - }, + ControlPointInfo = new ControlPointInfo(), HitObjects = { new HitCircle { StartTime = 0 }, @@ -47,6 +36,13 @@ namespace osu.Game.Tests.Visual.Editor } }; + testBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 200 }); + testBeatmap.ControlPointInfo.Add(100, new TimingControlPoint { BeatLength = 400 }); + testBeatmap.ControlPointInfo.Add(175, new TimingControlPoint { BeatLength = 800 }); + testBeatmap.ControlPointInfo.Add(350, new TimingControlPoint { BeatLength = 200 }); + testBeatmap.ControlPointInfo.Add(450, new TimingControlPoint { BeatLength = 100 }); + testBeatmap.ControlPointInfo.Add(500, new TimingControlPoint { BeatLength = 307.69230769230802 }); + Beatmap.Value = CreateWorkingBeatmap(testBeatmap); Child = new TimingPointVisualiser(testBeatmap, 5000) { Clock = Clock }; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index dcab964d6d..684e79b3f5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -47,7 +47,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestRelativeBeatLengthScaleSingleTimingPoint() { - var beatmap = createBeatmap(new TimingControlPoint { BeatLength = time_range / 2 }); + var beatmap = createBeatmap(); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range / 2 }); createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true); @@ -61,10 +62,10 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestRelativeBeatLengthScaleTimingPointBeyondEndDoesNotBecomeDominant() { - var beatmap = createBeatmap( - new TimingControlPoint { BeatLength = time_range / 2 }, - new TimingControlPoint { Time = 12000, BeatLength = time_range }, - new TimingControlPoint { Time = 100000, BeatLength = time_range }); + var beatmap = createBeatmap(); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range / 2 }); + beatmap.ControlPointInfo.Add(12000, new TimingControlPoint { BeatLength = time_range }); + beatmap.ControlPointInfo.Add(100000, new TimingControlPoint { BeatLength = time_range }); createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true); @@ -75,9 +76,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestRelativeBeatLengthScaleFromSecondTimingPoint() { - var beatmap = createBeatmap( - new TimingControlPoint { BeatLength = time_range }, - new TimingControlPoint { Time = 3 * time_range, BeatLength = time_range / 2 }); + var beatmap = createBeatmap(); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range }); + beatmap.ControlPointInfo.Add(3 * time_range, new TimingControlPoint { BeatLength = time_range / 2 }); createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true); @@ -97,9 +98,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestNonRelativeScale() { - var beatmap = createBeatmap( - new TimingControlPoint { BeatLength = time_range }, - new TimingControlPoint { Time = 3 * time_range, BeatLength = time_range / 2 }); + var beatmap = createBeatmap(); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range }); + beatmap.ControlPointInfo.Add(3 * time_range, new TimingControlPoint { BeatLength = time_range / 2 }); createTest(beatmap); @@ -119,7 +120,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestSliderMultiplierDoesNotAffectRelativeBeatLength() { - var beatmap = createBeatmap(new TimingControlPoint { BeatLength = time_range }); + var beatmap = createBeatmap(); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range }); beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2; createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true); @@ -132,7 +134,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestSliderMultiplierAffectsNonRelativeBeatLength() { - var beatmap = createBeatmap(new TimingControlPoint { BeatLength = time_range }); + var beatmap = createBeatmap(); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range }); beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2; createTest(beatmap); @@ -154,14 +157,11 @@ namespace osu.Game.Tests.Visual.Gameplay /// Creates an <see cref="IBeatmap"/>, containing 10 hitobjects and user-provided timing points. /// The hitobjects are spaced <see cref="time_range"/> milliseconds apart. /// </summary> - /// <param name="timingControlPoints">The timing points to add to the beatmap.</param> /// <returns>The <see cref="IBeatmap"/>.</returns> - private IBeatmap createBeatmap(params TimingControlPoint[] timingControlPoints) + private IBeatmap createBeatmap() { var beatmap = new Beatmap<HitObject> { BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo } }; - beatmap.ControlPointInfo.TimingPoints.AddRange(timingControlPoints); - for (int i = 0; i < 10; i++) beatmap.HitObjects.Add(new HitObject { StartTime = i * time_range }); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index d84ffa0d93..b6df559686 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Internal; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Track; @@ -10,7 +11,6 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Lists; using osu.Framework.Timing; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; @@ -153,7 +153,7 @@ namespace osu.Game.Tests.Visual.UserInterface }; } - private SortedList<TimingControlPoint> timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints; + private IReadOnlyList<TimingControlPoint> timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints; private TimingControlPoint getNextTimingPoint(TimingControlPoint current) { diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index 0081fab46a..6288c1460f 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -10,13 +10,17 @@ namespace osu.Game.Beatmaps.ControlPoints /// <summary> /// The time at which the control point takes effect. /// </summary> - public double Time; + public double Time => controlPointGroup?.Time ?? 0; /// <summary> /// Whether this timing point was generated internally, as opposed to parsed from the underlying beatmap. /// </summary> internal bool AutoGenerated; + private ControlPointGroup controlPointGroup; + + public void AttachGroup(ControlPointGroup pointGroup) => this.controlPointGroup = pointGroup; + public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time); /// <summary> diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs new file mode 100644 index 0000000000..c4b990675e --- /dev/null +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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; + +namespace osu.Game.Beatmaps.ControlPoints +{ + public class ControlPointGroup : IComparable<ControlPointGroup> + { + public event Action<ControlPoint> ItemAdded; + public event Action<ControlPoint> ItemRemoved; + + /// <summary> + /// The time at which the control point takes effect. + /// </summary> + public double Time { get; } + + public IReadOnlyList<ControlPoint> ControlPoints => controlPoints; + + private readonly List<ControlPoint> controlPoints = new List<ControlPoint>(); + + public ControlPointGroup(double time) + { + Time = time; + } + + public int CompareTo(ControlPointGroup other) => Time.CompareTo(other.Time); + + public void Add(ControlPoint point) + { + point.AttachGroup(this); + + foreach (var existing in controlPoints.Where(p => p.GetType() == point.GetType()).ToArray()) + Remove(existing); + + controlPoints.Add(point); + ItemAdded?.Invoke(point); + } + + public void Remove(ControlPoint point) + { + controlPoints.Remove(point); + ItemRemoved?.Invoke(point); + } + } +} diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 855084ad02..6a760343c3 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -12,29 +12,50 @@ namespace osu.Game.Beatmaps.ControlPoints [Serializable] public class ControlPointInfo { + /// <summary> + /// Control point groups. + /// </summary> + [JsonProperty] + public IReadOnlyList<ControlPointGroup> Groups => groups; + + private readonly SortedList<ControlPointGroup> groups = new SortedList<ControlPointGroup>(Comparer<ControlPointGroup>.Default); + /// <summary> /// All timing points. /// </summary> [JsonProperty] - public SortedList<TimingControlPoint> TimingPoints { get; private set; } = new SortedList<TimingControlPoint>(Comparer<TimingControlPoint>.Default); + public IReadOnlyList<TimingControlPoint> TimingPoints => timingPoints; + + private readonly SortedList<TimingControlPoint> timingPoints = new SortedList<TimingControlPoint>(Comparer<TimingControlPoint>.Default); /// <summary> /// All difficulty points. /// </summary> [JsonProperty] - public SortedList<DifficultyControlPoint> DifficultyPoints { get; private set; } = new SortedList<DifficultyControlPoint>(Comparer<DifficultyControlPoint>.Default); + public IReadOnlyList<DifficultyControlPoint> DifficultyPoints => difficultyPoints; + + private readonly SortedList<DifficultyControlPoint> difficultyPoints = new SortedList<DifficultyControlPoint>(Comparer<DifficultyControlPoint>.Default); /// <summary> /// All sound points. /// </summary> [JsonProperty] - public SortedList<SampleControlPoint> SamplePoints { get; private set; } = new SortedList<SampleControlPoint>(Comparer<SampleControlPoint>.Default); + public IReadOnlyList<SampleControlPoint> SamplePoints => samplePoints; + + private readonly SortedList<SampleControlPoint> samplePoints = new SortedList<SampleControlPoint>(Comparer<SampleControlPoint>.Default); /// <summary> /// All effect points. /// </summary> [JsonProperty] - public SortedList<EffectControlPoint> EffectPoints { get; private set; } = new SortedList<EffectControlPoint>(Comparer<EffectControlPoint>.Default); + public IReadOnlyList<EffectControlPoint> EffectPoints => effectPoints; + + private readonly SortedList<EffectControlPoint> effectPoints = new SortedList<EffectControlPoint>(Comparer<EffectControlPoint>.Default); + + /// <summary> + /// All control points, of all types. + /// </summary> + public IEnumerable<ControlPoint> AllControlPoints => Groups.SelectMany(g => g.ControlPoints).ToArray(); /// <summary> /// Finds the difficulty control point that is active at <paramref name="time"/>. @@ -64,6 +85,28 @@ namespace osu.Game.Beatmaps.ControlPoints /// <returns>The timing control point.</returns> public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time, TimingPoints.Count > 0 ? TimingPoints[0] : null); + /// <summary> + /// Finds the closest <see cref="ControlPoint"/> of the same type as <see cref="referencePoint"/> that is active at <paramref name="time"/>. + /// </summary> + /// <param name="time">The time to find the timing control point at.</param> + /// <param name="referencePoint">A reference point to infer type.</param> + /// <returns>The timing control point.</returns> + public ControlPoint SimilarPointAt(double time, ControlPoint referencePoint) + { + switch (referencePoint) + { + case TimingControlPoint _: return TimingPointAt(time); + + case EffectControlPoint _: return EffectPointAt(time); + + case SampleControlPoint _: return SamplePointAt(time); + + case DifficultyControlPoint _: return DifficultyPointAt(time); + } + + return null; + } + /// <summary> /// Finds the maximum BPM represented by any timing control point. /// </summary> @@ -92,7 +135,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// <param name="time">The time to find the control point at.</param> /// <param name="prePoint">The control point to use when <paramref name="time"/> is before any control points. If null, a new control point will be constructed.</param> /// <returns>The active control point at <paramref name="time"/>.</returns> - private T binarySearch<T>(SortedList<T> list, double time, T prePoint = null) + private T binarySearch<T>(IReadOnlyList<T> list, double time, T prePoint = null) where T : ControlPoint, new() { if (list == null) @@ -125,5 +168,78 @@ namespace osu.Game.Beatmaps.ControlPoints // l will be the first control point with Time > time, but we want the one before it return list[l - 1]; } + + public void Add(double time, ControlPoint newPoint, bool force = false) + { + if (!force && SimilarPointAt(time, newPoint)?.EquivalentTo(newPoint) == true) + return; + + GroupAt(time, true).Add(newPoint); + } + + public ControlPointGroup GroupAt(double time, bool createIfNotExisting) + { + var existing = Groups.FirstOrDefault(g => g.Time == time); + + if (existing != null) + return existing; + + if (createIfNotExisting) + { + var newGroup = new ControlPointGroup(time); + newGroup.ItemAdded += groupItemAdded; + newGroup.ItemRemoved += groupItemRemoved; + groups.Add(newGroup); + return newGroup; + } + + return null; + } + + private void groupItemRemoved(ControlPoint obj) + { + switch (obj) + { + case TimingControlPoint typed: + timingPoints.Remove(typed); + break; + + case EffectControlPoint typed: + effectPoints.Remove(typed); + break; + + case SampleControlPoint typed: + samplePoints.Remove(typed); + break; + + case DifficultyControlPoint typed: + difficultyPoints.Remove(typed); + break; + } + } + + private void groupItemAdded(ControlPoint obj) + { + switch (obj) + { + case TimingControlPoint typed: + timingPoints.Add(typed); + break; + + case EffectControlPoint typed: + effectPoints.Add(typed); + break; + + case SampleControlPoint typed: + samplePoints.Add(typed); + break; + + case DifficultyControlPoint typed: + difficultyPoints.Add(typed); + break; + } + } + + public void Clear() => groups.Clear(); } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 786b7611b5..61f70a8c27 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -369,31 +369,29 @@ namespace osu.Game.Beatmaps.Formats if (timingChange) { var controlPoint = CreateTimingControlPoint(); - controlPoint.Time = time; controlPoint.BeatLength = beatLength; controlPoint.TimeSignature = timeSignature; - handleTimingControlPoint(controlPoint); + beatmap.ControlPointInfo.Add(time, controlPoint); + } + else + { + beatmap.ControlPointInfo.Add(time, new DifficultyControlPoint + { + SpeedMultiplier = speedMultiplier, + AutoGenerated = timingChange + }); } - handleDifficultyControlPoint(new DifficultyControlPoint + beatmap.ControlPointInfo.Add(time, new EffectControlPoint { - Time = time, - SpeedMultiplier = speedMultiplier, - AutoGenerated = timingChange - }); - - handleEffectControlPoint(new EffectControlPoint - { - Time = time, KiaiMode = kiaiMode, OmitFirstBarLine = omitFirstBarSignature, AutoGenerated = timingChange }); - handleSampleControlPoint(new LegacySampleControlPoint + beatmap.ControlPointInfo.Add(time, new LegacySampleControlPoint { - Time = time, SampleBank = stringSampleSet, SampleVolume = sampleVolume, CustomSampleBank = customSampleBank, @@ -401,74 +399,6 @@ namespace osu.Game.Beatmaps.Formats }); } - private void handleTimingControlPoint(TimingControlPoint newPoint) - { - var existing = beatmap.ControlPointInfo.TimingPointAt(newPoint.Time); - - if (existing.Time == newPoint.Time) - { - // autogenerated points should not replace non-autogenerated. - // this allows for incorrectly ordered timing points to still be correctly handled. - if (newPoint.AutoGenerated && !existing.AutoGenerated) - return; - - beatmap.ControlPointInfo.TimingPoints.Remove(existing); - } - - beatmap.ControlPointInfo.TimingPoints.Add(newPoint); - } - - private void handleDifficultyControlPoint(DifficultyControlPoint newPoint) - { - var existing = beatmap.ControlPointInfo.DifficultyPointAt(newPoint.Time); - - if (existing.Time == newPoint.Time) - { - // autogenerated points should not replace non-autogenerated. - // this allows for incorrectly ordered timing points to still be correctly handled. - if (newPoint.AutoGenerated && !existing.AutoGenerated) - return; - - beatmap.ControlPointInfo.DifficultyPoints.Remove(existing); - } - - beatmap.ControlPointInfo.DifficultyPoints.Add(newPoint); - } - - private void handleEffectControlPoint(EffectControlPoint newPoint) - { - var existing = beatmap.ControlPointInfo.EffectPointAt(newPoint.Time); - - if (existing.Time == newPoint.Time) - { - // autogenerated points should not replace non-autogenerated. - // this allows for incorrectly ordered timing points to still be correctly handled. - if (newPoint.AutoGenerated && !existing.AutoGenerated) - return; - - beatmap.ControlPointInfo.EffectPoints.Remove(existing); - } - - beatmap.ControlPointInfo.EffectPoints.Add(newPoint); - } - - private void handleSampleControlPoint(SampleControlPoint newPoint) - { - var existing = beatmap.ControlPointInfo.SamplePointAt(newPoint.Time); - - if (existing.Time == newPoint.Time) - { - // autogenerated points should not replace non-autogenerated. - // this allows for incorrectly ordered timing points to still be correctly handled. - if (newPoint.AutoGenerated && !existing.AutoGenerated) - return; - - beatmap.ControlPointInfo.SamplePoints.Remove(existing); - } - - beatmap.ControlPointInfo.SamplePoints.Add(newPoint); - } - private void handleHitObject(string line) { // If the ruleset wasn't specified, assume the osu!standard ruleset. diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 370d044ba4..2832a70518 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -105,12 +105,10 @@ namespace osu.Game.Graphics.Containers { BeatLength = default_beat_length, AutoGenerated = true, - Time = 0 }; defaultEffect = new EffectControlPoint { - Time = 0, AutoGenerated = true, KiaiMode = false, OmitFirstBarLine = false diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 24fb561f04..1cfb123f25 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using Microsoft.EntityFrameworkCore.Internal; using osu.Framework.MathUtils; using osu.Framework.Timing; using osu.Game.Beatmaps; @@ -57,7 +58,7 @@ namespace osu.Game.Screens.Edit // Depending on beatSnapLength, we may snap to a beat that is beyond timingPoint's end time, but we want to instead snap to // the next timing point's start time - var nextTimingPoint = ControlPointInfo.TimingPoints.Find(t => t.Time > timingPoint.Time); + var nextTimingPoint = ControlPointInfo.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time); if (position > nextTimingPoint?.Time) position = nextTimingPoint.Time; @@ -124,7 +125,7 @@ namespace osu.Game.Screens.Edit if (seekTime < timingPoint.Time && timingPoint != ControlPointInfo.TimingPoints.First()) seekTime = timingPoint.Time; - var nextTimingPoint = ControlPointInfo.TimingPoints.Find(t => t.Time > timingPoint.Time); + var nextTimingPoint = ControlPointInfo.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time); if (seekTime > nextTimingPoint?.Time) seekTime = nextTimingPoint.Time; From 8baf569f5976680d4e730a84a144e8bd53466579 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Fri, 25 Oct 2019 19:58:42 +0900 Subject: [PATCH 1920/2815] Remove necessity of AutoGenerated flag --- .../Formats/LegacyBeatmapDecoderTest.cs | 10 +-- .../Beatmaps/ControlPoints/ControlPoint.cs | 7 +-- .../ControlPoints/ControlPointInfo.cs | 2 +- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 63 ++++++++++++++----- .../Containers/BeatSyncedContainer.cs | 2 - 5 files changed, 55 insertions(+), 29 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index de516d3142..c50250159e 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -167,9 +167,9 @@ namespace osu.Game.Tests.Beatmaps.Formats var controlPoints = beatmap.ControlPointInfo; Assert.AreEqual(4, controlPoints.TimingPoints.Count); - Assert.AreEqual(42, controlPoints.DifficultyPoints.Count); - Assert.AreEqual(42, controlPoints.SamplePoints.Count); - Assert.AreEqual(42, controlPoints.EffectPoints.Count); + Assert.AreEqual(5, controlPoints.DifficultyPoints.Count); + Assert.AreEqual(34, controlPoints.SamplePoints.Count); + Assert.AreEqual(8, controlPoints.EffectPoints.Count); var timingPoint = controlPoints.TimingPointAt(0); Assert.AreEqual(956, timingPoint.Time); @@ -191,7 +191,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); difficultyPoint = controlPoints.DifficultyPointAt(48428); - Assert.AreEqual(48428, difficultyPoint.Time); + Assert.AreEqual(0, difficultyPoint.Time); Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); difficultyPoint = controlPoints.DifficultyPointAt(116999); @@ -224,7 +224,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsFalse(effectPoint.OmitFirstBarLine); effectPoint = controlPoints.EffectPointAt(119637); - Assert.AreEqual(119637, effectPoint.Time); + Assert.AreEqual(95901, effectPoint.Time); Assert.IsFalse(effectPoint.KiaiMode); Assert.IsFalse(effectPoint.OmitFirstBarLine); } diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index 6288c1460f..0861e00d8d 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -12,11 +12,6 @@ namespace osu.Game.Beatmaps.ControlPoints /// </summary> public double Time => controlPointGroup?.Time ?? 0; - /// <summary> - /// Whether this timing point was generated internally, as opposed to parsed from the underlying beatmap. - /// </summary> - internal bool AutoGenerated; - private ControlPointGroup controlPointGroup; public void AttachGroup(ControlPointGroup pointGroup) => this.controlPointGroup = pointGroup; diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 6a760343c3..b7bb993fc0 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -177,7 +177,7 @@ namespace osu.Game.Beatmaps.ControlPoints GroupAt(time, true).Add(newPoint); } - public ControlPointGroup GroupAt(double time, bool createIfNotExisting) + public ControlPointGroup GroupAt(double time, bool createIfNotExisting = false) { var existing = Groups.FirstOrDefault(g => g.Time == time); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 61f70a8c27..5589aecc19 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.IO; using System.Linq; using osu.Framework.IO.File; @@ -50,6 +51,8 @@ namespace osu.Game.Beatmaps.Formats base.ParseStreamInto(stream, beatmap); + flushPendingPoints(); + // Objects may be out of order *only* if a user has manually edited an .osu file. // Unfortunately there are ranked maps in this state (example: https://osu.ppy.sh/s/594828). // OrderBy is used to guarantee that the parsing order of hitobjects with equal start times is maintained (stably-sorted) @@ -369,34 +372,64 @@ namespace osu.Game.Beatmaps.Formats if (timingChange) { var controlPoint = CreateTimingControlPoint(); + controlPoint.BeatLength = beatLength; controlPoint.TimeSignature = timeSignature; - beatmap.ControlPointInfo.Add(time, controlPoint); - } - else - { - beatmap.ControlPointInfo.Add(time, new DifficultyControlPoint - { - SpeedMultiplier = speedMultiplier, - AutoGenerated = timingChange - }); + addControlPoint(time, controlPoint, true); } - beatmap.ControlPointInfo.Add(time, new EffectControlPoint + addControlPoint(time, new DifficultyControlPoint + { + SpeedMultiplier = speedMultiplier, + }, timingChange); + + addControlPoint(time, new EffectControlPoint { KiaiMode = kiaiMode, OmitFirstBarLine = omitFirstBarSignature, - AutoGenerated = timingChange - }); + }, timingChange); - beatmap.ControlPointInfo.Add(time, new LegacySampleControlPoint + addControlPoint(time, new LegacySampleControlPoint { SampleBank = stringSampleSet, SampleVolume = sampleVolume, CustomSampleBank = customSampleBank, - AutoGenerated = timingChange - }); + }, timingChange); + + // To handle the scenario where a non-timing line shares the same time value as a subsequent timing line but + // appears earlier in the file, we buffer non-timing control points and rewrite them *after* control points from the timing line + // with the same time value (allowing them to overwrite as necessary). + // + // The expected outcome is that we prefer the non-timing line's adjustments over the timing line's adjustments when time is equal. + if (timingChange) + flushPendingPoints(); + } + + private readonly List<ControlPoint> pendingControlPoints = new List<ControlPoint>(); + private double pendingControlPointsTime; + + private void addControlPoint(double time, ControlPoint point, bool timingChange) + { + if (timingChange) + { + beatmap.ControlPointInfo.Add(time, point); + return; + } + + if (time != pendingControlPointsTime) + flushPendingPoints(); + + pendingControlPoints.Add(point); + pendingControlPointsTime = time; + } + + private void flushPendingPoints() + { + foreach (var p in pendingControlPoints) + beatmap.ControlPointInfo.Add(pendingControlPointsTime, p); + + pendingControlPoints.Clear(); } private void handleHitObject(string line) diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 2832a70518..2e76ab964f 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -104,12 +104,10 @@ namespace osu.Game.Graphics.Containers defaultTiming = new TimingControlPoint { BeatLength = default_beat_length, - AutoGenerated = true, }; defaultEffect = new EffectControlPoint { - AutoGenerated = true, KiaiMode = false, OmitFirstBarLine = false }; From e39016bf011d0cf43d3132b798cc5240645dc640 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Fri, 25 Oct 2019 20:04:27 +0900 Subject: [PATCH 1921/2815] Fix known non-nulls --- osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs | 2 +- osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index 35eefebca4..07f5aa6c90 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -46,6 +46,6 @@ namespace osu.Game.Beatmaps.ControlPoints public override bool EquivalentTo(ControlPoint other) => other is SampleControlPoint otherTyped && - string.Equals(SampleBank, otherTyped?.SampleBank) && SampleVolume == otherTyped?.SampleVolume; + string.Equals(SampleBank, otherTyped.SampleBank) && SampleVolume == otherTyped.SampleVolume; } } diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index 03b188929b..f8c84c79dd 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -28,6 +28,6 @@ namespace osu.Game.Beatmaps.ControlPoints public override bool EquivalentTo(ControlPoint other) => other is TimingControlPoint otherTyped - && TimeSignature == otherTyped?.TimeSignature && beatLength.Equals(otherTyped.beatLength); + && TimeSignature == otherTyped.TimeSignature && beatLength.Equals(otherTyped.beatLength); } } From b8efc59cdcd2be6d4ca8cbe9466221c61b70672c Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Fri, 25 Oct 2019 20:13:22 +0900 Subject: [PATCH 1922/2815] Update UI components to use new grouping --- .../Edit/Timing/ControlPointSettings.cs | 4 ++-- .../Screens/Edit/Timing/ControlPointTable.cs | 18 ++++++++---------- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 10 ++++------ 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs index ce72d7c191..470750b860 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -167,7 +167,7 @@ namespace osu.Game.Screens.Edit.Timing private const float header_height = 20; [Resolved] - private Bindable<IEnumerable<ControlPoint>> selectedPoints { get; set; } + private Bindable<ControlPointGroup> selectedPoints { get; set; } [BackgroundDependencyLoader] private void load(OsuColour colours) @@ -228,7 +228,7 @@ namespace osu.Game.Screens.Edit.Timing selectedPoints.BindValueChanged(points => { - ControlPoint.Value = points.NewValue?.OfType<T>().FirstOrDefault(); + ControlPoint.Value = points.NewValue?.ControlPoints.OfType<T>().FirstOrDefault(); checkbox.Current.Value = ControlPoint.Value != null; }, true); diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 597200e54c..aca74d4a20 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Edit.Timing private readonly FillFlowContainer backgroundFlow; [Resolved] - private Bindable<IEnumerable<ControlPoint>> selectedPoints { get; set; } + private Bindable<ControlPointGroup> selectedGroup { get; set; } public ControlPointTable() { @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Edit.Timing }); } - public IEnumerable<ControlPoint> ControlPoints + public IEnumerable<ControlPointGroup> ControlGroups { set { @@ -56,15 +56,13 @@ namespace osu.Game.Screens.Edit.Timing if (value?.Any() != true) return; - var grouped = value.GroupBy(cp => cp.Time, cp => cp); - - foreach (var group in grouped) + foreach (var group in value) { - backgroundFlow.Add(new RowBackground { Action = () => selectedPoints.Value = group }); + backgroundFlow.Add(new RowBackground { Action = () => selectedGroup.Value = group }); } Columns = createHeaders(); - Content = grouped.Select((s, i) => createContent(i, s)).ToArray().ToRectangular(); + Content = value.Select((g, i) => createContent(i, g)).ToArray().ToRectangular(); } } @@ -80,7 +78,7 @@ namespace osu.Game.Screens.Edit.Timing return columns.ToArray(); } - private Drawable[] createContent(int index, IGrouping<double, ControlPoint> controlPoints) => new Drawable[] + private Drawable[] createContent(int index, ControlPointGroup group) => new Drawable[] { new OsuSpriteText { @@ -90,14 +88,14 @@ namespace osu.Game.Screens.Edit.Timing }, new OsuSpriteText { - Text = $"{controlPoints.Key:n0}ms", + Text = $"{group.Time:n0}ms", Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) }, new FillFlowContainer { RelativeSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - ChildrenEnumerable = controlPoints.Select(createAttribute).Where(c => c != null), + ChildrenEnumerable = group.ControlPoints.Select(createAttribute).Where(c => c != null), Padding = new MarginPadding(10), Spacing = new Vector2(10) }, diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index bd0a75f0b0..a9fd038829 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -21,7 +19,7 @@ namespace osu.Game.Screens.Edit.Timing public class TimingScreen : EditorScreenWithTimeline { [Cached] - private Bindable<IEnumerable<ControlPoint>> selectedPoints = new Bindable<IEnumerable<ControlPoint>>(); + private Bindable<ControlPointGroup> selectedPoints = new Bindable<ControlPointGroup>(); [Resolved] private IAdjustableClock clock { get; set; } @@ -48,7 +46,7 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - selectedPoints.BindValueChanged(selected => { clock.Seek(selected.NewValue.First().Time); }); + selectedPoints.BindValueChanged(selected => { clock.Seek(selected.NewValue.Time); }); } public class ControlPointList : CompositeDrawable @@ -59,7 +57,7 @@ namespace osu.Game.Screens.Edit.Timing protected IBindable<WorkingBeatmap> Beatmap { get; private set; } [Resolved] - private Bindable<IEnumerable<ControlPoint>> selectedPoints { get; set; } + private Bindable<ControlPointGroup> selectedPoints { get; set; } [BackgroundDependencyLoader] private void load(OsuColour colours) @@ -78,7 +76,7 @@ namespace osu.Game.Screens.Edit.Timing RelativeSizeAxes = Axes.Both, Child = new ControlPointTable { - ControlPoints = Beatmap.Value.Beatmap.ControlPointInfo.AllControlPoints + ControlGroups = Beatmap.Value.Beatmap.ControlPointInfo.Groups } }, new FillFlowContainer From c031aeb14c95431f9f7cc5e875b4a22248e0070f Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Sat, 26 Oct 2019 00:06:05 +0900 Subject: [PATCH 1923/2815] Fix inspection --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 61f70a8c27..b5f763fc8d 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -379,7 +379,7 @@ namespace osu.Game.Beatmaps.Formats beatmap.ControlPointInfo.Add(time, new DifficultyControlPoint { SpeedMultiplier = speedMultiplier, - AutoGenerated = timingChange + AutoGenerated = false }); } From d25f7f4c275997c168659ce9f0143eb830531083 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Sat, 26 Oct 2019 01:19:23 +0900 Subject: [PATCH 1924/2815] Correctly clear other lists --- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 6a760343c3..8f7777daff 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -240,6 +240,13 @@ namespace osu.Game.Beatmaps.ControlPoints } } - public void Clear() => groups.Clear(); + public void Clear() + { + groups.Clear(); + timingPoints.Clear(); + difficultyPoints.Clear(); + samplePoints.Clear(); + effectPoints.Clear(); + } } } From b0e21c2749d24d58de0b4c92569244bdeed77460 Mon Sep 17 00:00:00 2001 From: nwabear <iambeardaniel@gmail.com> Date: Fri, 25 Oct 2019 14:57:49 -0500 Subject: [PATCH 1925/2815] Fixed Issue #6442 --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 7 +++++++ osu.Game/Screens/Play/Player.cs | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index d5b3df27df..d37f053486 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -239,6 +239,11 @@ namespace osu.Game.Rulesets.UI continueResume(); } + public override void CancelResume() + { + ResumeOverlay.Hide(); + } + /// <summary> /// Creates and adds the visual representation of a <see cref="TObject"/> to this <see cref="DrawableRuleset{TObject}"/>. /// </summary> @@ -453,6 +458,8 @@ namespace osu.Game.Rulesets.UI /// <param name="continueResume">The action to run when resuming is to be completed.</param> public abstract void RequestResume(Action continueResume); + public abstract void CancelResume(); + /// <summary> /// Create a <see cref="ScoreProcessor"/> for the associated ruleset and link with this /// <see cref="DrawableRuleset"/>. diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 0b363eac4d..7eccde4555 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -443,6 +443,11 @@ namespace osu.Game.Screens.Play { if (!canPause) return; + if (IsResuming) + { + DrawableRuleset.CancelResume(); + } + IsResuming = false; GameplayClockContainer.Stop(); PauseOverlay.Show(); From f8354eefc4e058b98fc9d834bc0be2bc441db1e4 Mon Sep 17 00:00:00 2001 From: nwabear <iambeardaniel@gmail.com> Date: Fri, 25 Oct 2019 16:49:18 -0500 Subject: [PATCH 1926/2815] Added null check in the CancelResume method --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index d37f053486..d3e1118625 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -241,7 +241,7 @@ namespace osu.Game.Rulesets.UI public override void CancelResume() { - ResumeOverlay.Hide(); + ResumeOverlay?.Hide(); } /// <summary> From 7100319858fd510e07eff7538c45d321ffd8ec99 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Sat, 26 Oct 2019 08:31:41 +0900 Subject: [PATCH 1927/2815] Fix incorrect control point retrieval in non-lookup cases --- .../ControlPoints/ControlPointInfo.cs | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 8f7777daff..0122ee5cdc 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -62,28 +62,28 @@ namespace osu.Game.Beatmaps.ControlPoints /// </summary> /// <param name="time">The time to find the difficulty control point at.</param> /// <returns>The difficulty control point.</returns> - public DifficultyControlPoint DifficultyPointAt(double time) => binarySearch(DifficultyPoints, time); + public DifficultyControlPoint DifficultyPointAt(double time) => binarySearchWithFallback(DifficultyPoints, time); /// <summary> /// Finds the effect control point that is active at <paramref name="time"/>. /// </summary> /// <param name="time">The time to find the effect control point at.</param> /// <returns>The effect control point.</returns> - public EffectControlPoint EffectPointAt(double time) => binarySearch(EffectPoints, time); + public EffectControlPoint EffectPointAt(double time) => binarySearchWithFallback(EffectPoints, time); /// <summary> /// Finds the sound control point that is active at <paramref name="time"/>. /// </summary> /// <param name="time">The time to find the sound control point at.</param> /// <returns>The sound control point.</returns> - public SampleControlPoint SamplePointAt(double time) => binarySearch(SamplePoints, time, SamplePoints.Count > 0 ? SamplePoints[0] : null); + public SampleControlPoint SamplePointAt(double time) => binarySearchWithFallback(SamplePoints, time, SamplePoints.Count > 0 ? SamplePoints[0] : null); /// <summary> /// Finds the timing control point that is active at <paramref name="time"/>. /// </summary> /// <param name="time">The time to find the timing control point at.</param> /// <returns>The timing control point.</returns> - public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time, TimingPoints.Count > 0 ? TimingPoints[0] : null); + public TimingControlPoint TimingPointAt(double time) => binarySearchWithFallback(TimingPoints, time, TimingPoints.Count > 0 ? TimingPoints[0] : null); /// <summary> /// Finds the closest <see cref="ControlPoint"/> of the same type as <see cref="referencePoint"/> that is active at <paramref name="time"/>. @@ -95,13 +95,13 @@ namespace osu.Game.Beatmaps.ControlPoints { switch (referencePoint) { - case TimingControlPoint _: return TimingPointAt(time); + case TimingControlPoint _: return binarySearch(TimingPoints, time); - case EffectControlPoint _: return EffectPointAt(time); + case EffectControlPoint _: return binarySearch(EffectPoints, time); - case SampleControlPoint _: return SamplePointAt(time); + case SampleControlPoint _: return binarySearch(SamplePoints, time); - case DifficultyControlPoint _: return DifficultyPointAt(time); + case DifficultyControlPoint _: return binarySearch(DifficultyPoints, time); } return null; @@ -130,22 +130,35 @@ namespace osu.Game.Beatmaps.ControlPoints /// <summary> /// Binary searches one of the control point lists to find the active control point at <paramref name="time"/>. + /// Includes logic for returning a specific point when no matching point is found. /// </summary> /// <param name="list">The list to search.</param> /// <param name="time">The time to find the control point at.</param> /// <param name="prePoint">The control point to use when <paramref name="time"/> is before any control points. If null, a new control point will be constructed.</param> - /// <returns>The active control point at <paramref name="time"/>.</returns> - private T binarySearch<T>(IReadOnlyList<T> list, double time, T prePoint = null) + /// <returns>The active control point at <paramref name="time"/>, or a fallback <see cref="ControlPoint"/> if none found.</returns> + private T binarySearchWithFallback<T>(IReadOnlyList<T> list, double time, T prePoint = null) where T : ControlPoint, new() + { + return binarySearch(list, time) ?? prePoint ?? new T(); + } + + /// <summary> + /// Binary searches one of the control point lists to find the active control point at <paramref name="time"/>. + /// </summary> + /// <param name="list">The list to search.</param> + /// <param name="time">The time to find the control point at.</param> + /// <returns>The active control point at <paramref name="time"/>.</returns> + private T binarySearch<T>(IReadOnlyList<T> list, double time) + where T : ControlPoint { if (list == null) throw new ArgumentNullException(nameof(list)); if (list.Count == 0) - return new T(); + return null; if (time < list[0].Time) - return prePoint ?? new T(); + return null; if (time >= list[list.Count - 1].Time) return list[list.Count - 1]; From a724909c251b3175dd33db24904dca582043a00e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= <dach.bartlomiej@gmail.com> Date: Sat, 26 Oct 2019 01:43:33 +0200 Subject: [PATCH 1928/2815] Add temporary mobile report issue template Due to an overwhelming amount of mobile reports that are not actively being worked on (neither by the core team, due to more pressing priorities, nor by external contributors) and take up considerable time to manage, add an issue template that aims to enforce a temporary moratorium on accepting mobile issues. --- .github/ISSUE_TEMPLATE/mobile-issues.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/mobile-issues.md diff --git a/.github/ISSUE_TEMPLATE/mobile-issues.md b/.github/ISSUE_TEMPLATE/mobile-issues.md new file mode 100644 index 0000000000..f41562f532 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/mobile-issues.md @@ -0,0 +1,7 @@ +--- +name: Mobile Report +about: ⚠ Due to current development priorities we are currently not accepting mobile reports. +--- + +⚠ **PLEASE READ** ⚠: Due to prioritising finishing the client for desktop first we are not accepting reports related to mobile platforms for the time being. +Please check back in the future when the focus of development shifts towards mobile! From dca8de5e6be35d875fc7f33780a68560a768c807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= <dach.bartlomiej@gmail.com> Date: Sat, 26 Oct 2019 02:06:39 +0200 Subject: [PATCH 1929/2815] Rephrase template description --- .github/ISSUE_TEMPLATE/mobile-issues.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/mobile-issues.md b/.github/ISSUE_TEMPLATE/mobile-issues.md index f41562f532..6938f18097 100644 --- a/.github/ISSUE_TEMPLATE/mobile-issues.md +++ b/.github/ISSUE_TEMPLATE/mobile-issues.md @@ -1,6 +1,6 @@ --- name: Mobile Report -about: ⚠ Due to current development priorities we are currently not accepting mobile reports. +about: ⚠ Due to current development priorities we are not accepting mobile reports at this time. --- ⚠ **PLEASE READ** ⚠: Due to prioritising finishing the client for desktop first we are not accepting reports related to mobile platforms for the time being. From d6a49b9e93eecd41c6c6e4fc91b599b93050b6bf Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Sat, 26 Oct 2019 10:25:13 +0900 Subject: [PATCH 1930/2815] Add back autogeneration rules Will be removed in https://github.com/ppy/osu/pull/6604 --- .../Beatmaps/ControlPoints/ControlPointGroup.cs | 13 +++++++++++-- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 3 --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 12 +++++------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs index c4b990675e..8b71fc1897 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs @@ -30,10 +30,19 @@ namespace osu.Game.Beatmaps.ControlPoints public void Add(ControlPoint point) { - point.AttachGroup(this); + var existing = controlPoints.FirstOrDefault(p => p.GetType() == point.GetType()); + + if (existing != null) + { + // autogenerated points should not replace non-autogenerated. + // this allows for incorrectly ordered timing points to still be correctly handled. + if (point.AutoGenerated && !existing.AutoGenerated) + return; - foreach (var existing in controlPoints.Where(p => p.GetType() == point.GetType()).ToArray()) Remove(existing); + } + + point.AttachGroup(this); controlPoints.Add(point); ItemAdded?.Invoke(point); diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 0122ee5cdc..d6db4c8d10 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -184,9 +184,6 @@ namespace osu.Game.Beatmaps.ControlPoints public void Add(double time, ControlPoint newPoint, bool force = false) { - if (!force && SimilarPointAt(time, newPoint)?.EquivalentTo(newPoint) == true) - return; - GroupAt(time, true).Add(newPoint); } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index b5f763fc8d..24422199e5 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -374,14 +374,12 @@ namespace osu.Game.Beatmaps.Formats beatmap.ControlPointInfo.Add(time, controlPoint); } - else + + beatmap.ControlPointInfo.Add(time, new DifficultyControlPoint { - beatmap.ControlPointInfo.Add(time, new DifficultyControlPoint - { - SpeedMultiplier = speedMultiplier, - AutoGenerated = false - }); - } + SpeedMultiplier = speedMultiplier, + AutoGenerated = timingChange + }); beatmap.ControlPointInfo.Add(time, new EffectControlPoint { From 8ccff0e9cf6e42f21a23b324bc12cc6bea4105fd Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Sat, 26 Oct 2019 11:20:07 +0900 Subject: [PATCH 1931/2815] temp --- .../Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 6 +++--- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index c50250159e..7317771bac 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -167,9 +167,9 @@ namespace osu.Game.Tests.Beatmaps.Formats var controlPoints = beatmap.ControlPointInfo; Assert.AreEqual(4, controlPoints.TimingPoints.Count); - Assert.AreEqual(5, controlPoints.DifficultyPoints.Count); + Assert.AreEqual(6, controlPoints.DifficultyPoints.Count); Assert.AreEqual(34, controlPoints.SamplePoints.Count); - Assert.AreEqual(8, controlPoints.EffectPoints.Count); + Assert.AreEqual(9, controlPoints.EffectPoints.Count); var timingPoint = controlPoints.TimingPointAt(0); Assert.AreEqual(956, timingPoint.Time); @@ -191,7 +191,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); difficultyPoint = controlPoints.DifficultyPointAt(48428); - Assert.AreEqual(0, difficultyPoint.Time); + Assert.AreEqual(956, difficultyPoint.Time); Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); difficultyPoint = controlPoints.DifficultyPointAt(116999); diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 24e3c3d8ca..f8c6d3aa3b 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -95,13 +95,13 @@ namespace osu.Game.Beatmaps.ControlPoints { switch (referencePoint) { - case TimingControlPoint _: return TimingPointAt(time); + case TimingControlPoint _: return binarySearch(TimingPoints, time); - case EffectControlPoint _: return EffectPointAt(time); + case EffectControlPoint _: return binarySearch(EffectPoints, time); - case SampleControlPoint _: return SamplePointAt(time); + case SampleControlPoint _: return binarySearch(SamplePoints, time); - case DifficultyControlPoint _: return DifficultyPointAt(time); + case DifficultyControlPoint _: return binarySearch(DifficultyPoints, time); } return null; From 4290a71f442902dfb21248aa5e214d73b07f99d3 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Sat, 26 Oct 2019 11:38:05 +0900 Subject: [PATCH 1932/2815] Add special case for timing points Timing points can't fallback to defaults and must be added at least once. --- .../ControlPoints/ControlPointInfo.cs | 57 +++++++++++-------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 24e3c3d8ca..3927f46530 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -85,28 +85,6 @@ namespace osu.Game.Beatmaps.ControlPoints /// <returns>The timing control point.</returns> public TimingControlPoint TimingPointAt(double time) => binarySearchWithFallback(TimingPoints, time, TimingPoints.Count > 0 ? TimingPoints[0] : null); - /// <summary> - /// Finds the closest <see cref="ControlPoint"/> of the same type as <see cref="referencePoint"/> that is active at <paramref name="time"/>. - /// </summary> - /// <param name="time">The time to find the timing control point at.</param> - /// <param name="referencePoint">A reference point to infer type.</param> - /// <returns>The timing control point.</returns> - public ControlPoint SimilarPointAt(double time, ControlPoint referencePoint) - { - switch (referencePoint) - { - case TimingControlPoint _: return TimingPointAt(time); - - case EffectControlPoint _: return EffectPointAt(time); - - case SampleControlPoint _: return SamplePointAt(time); - - case DifficultyControlPoint _: return DifficultyPointAt(time); - } - - return null; - } - /// <summary> /// Finds the maximum BPM represented by any timing control point. /// </summary> @@ -184,7 +162,7 @@ namespace osu.Game.Beatmaps.ControlPoints public void Add(double time, ControlPoint newPoint, bool force = false) { - if (!force && SimilarPointAt(time, newPoint)?.EquivalentTo(newPoint) == true) + if (!force && checkAlreadyExisting(time, newPoint)) return; GroupAt(time, true).Add(newPoint); @@ -209,6 +187,39 @@ namespace osu.Game.Beatmaps.ControlPoints return null; } + /// <summary> + /// Check whether <see cref="newPoint"/> should be added. + /// </summary> + /// <param name="time">The time to find the timing control point at.</param> + /// <param name="newPoint">A point to be added.</param> + /// <returns>Whether the new point should be added.</returns> + private bool checkAlreadyExisting(double time, ControlPoint newPoint) + { + ControlPoint existing = null; + + switch (newPoint) + { + case TimingControlPoint _: + // Timing points are a special case and need to be added regardless of fallback availability. + existing = binarySearch(TimingPoints, time); + break; + + case EffectControlPoint _: + existing = EffectPointAt(time); + break; + + case SampleControlPoint _: + existing = SamplePointAt(time); + break; + + case DifficultyControlPoint _: + existing = DifficultyPointAt(time); + break; + } + + return existing?.EquivalentTo(newPoint) == true; + } + private void groupItemRemoved(ControlPoint obj) { switch (obj) From 654890776d31d75a5bcab4761341752890b39908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= <dach.bartlomiej@gmail.com> Date: Sat, 26 Oct 2019 14:56:29 +0200 Subject: [PATCH 1933/2815] Add exemption for potential code contributors Add an exemption clause allowing potential code contributors to submit issues if they state they would like to work on them, and note that mobile-related pull requests are still accepted. Suggested-by: Dean Herbert <pe@ppy.sh> --- .github/ISSUE_TEMPLATE/mobile-issues.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/mobile-issues.md b/.github/ISSUE_TEMPLATE/mobile-issues.md index 6938f18097..f171e80b8b 100644 --- a/.github/ISSUE_TEMPLATE/mobile-issues.md +++ b/.github/ISSUE_TEMPLATE/mobile-issues.md @@ -1,7 +1,8 @@ --- name: Mobile Report -about: ⚠ Due to current development priorities we are not accepting mobile reports at this time. +about: ⚠ Due to current development priorities we are not accepting mobile reports at this time (unless you're willing to fix them yourself!) --- -⚠ **PLEASE READ** ⚠: Due to prioritising finishing the client for desktop first we are not accepting reports related to mobile platforms for the time being. -Please check back in the future when the focus of development shifts towards mobile! +⚠ **PLEASE READ** ⚠: Due to prioritising finishing the client for desktop first we are not accepting reports related to mobile platforms for the time being, unless you're willing to fix them. +If you'd like to report a problem or suggest a feature and then work on it, feel free to open an issue and highlight that you'd like to address it yourself in the issue body; mobile pull requests are also welcome. +Otherwise, please check back in the future when the focus of development shifts towards mobile! From 9e2e87c8d13cf147b720825a5ff266c024cabf72 Mon Sep 17 00:00:00 2001 From: nwabear <iambeardaniel@gmail.com> Date: Sat, 26 Oct 2019 14:29:52 -0500 Subject: [PATCH 1934/2815] added visual tests added small commenting added xmldoc for CancelResume(); --- .../Visual/Gameplay/TestScenePause.cs | 18 ++++++++++++++++++ osu.Game/Rulesets/UI/DrawableRuleset.cs | 5 +++++ 2 files changed, 23 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 2df22df659..64022b2410 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -69,6 +69,24 @@ namespace osu.Game.Tests.Visual.Gameplay confirmClockRunning(true); } + [Test] + public void TestPauseWithResumeOverlay() + { + AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre)); + AddUntilStep("wait for hitobjects", () => Player.ScoreProcessor.Health.Value < 1); + + pauseAndConfirm(); + + resume(); + confirmClockRunning(false); + confirmPauseOverlayShown(false); + + pauseAndConfirm(); + + AddUntilStep("resume overlay is not active", () => Player.DrawableRuleset.ResumeOverlay.State.Value == Visibility.Hidden); + confirmPaused(); + } + [Test] public void TestResumeWithResumeOverlaySkipped() { diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index d3e1118625..44e2e60be7 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -239,8 +239,10 @@ namespace osu.Game.Rulesets.UI continueResume(); } + public override void CancelResume() { + // called if the user pauses while the resume overlay is open ResumeOverlay?.Hide(); } @@ -458,6 +460,9 @@ namespace osu.Game.Rulesets.UI /// <param name="continueResume">The action to run when resuming is to be completed.</param> public abstract void RequestResume(Action continueResume); + /// <summary> + /// Invoked when the user requests to pause while the resume overlay is active. + /// </summary> public abstract void CancelResume(); /// <summary> From e35931fdfc7b6c94d401ab0076ce3e80f2edffcd Mon Sep 17 00:00:00 2001 From: nwabear <iambeardaniel@gmail.com> Date: Sat, 26 Oct 2019 14:33:59 -0500 Subject: [PATCH 1935/2815] removed blank line --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 44e2e60be7..e005eea831 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -239,7 +239,6 @@ namespace osu.Game.Rulesets.UI continueResume(); } - public override void CancelResume() { // called if the user pauses while the resume overlay is open From 814b520e5e7bc41a99adbc927b7021f4c6741a6d Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Sun, 27 Oct 2019 11:35:45 +0900 Subject: [PATCH 1936/2815] Avoid potential mis-cast in comparison --- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 26f7209be6..a5a4380d4a 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -204,8 +204,8 @@ namespace osu.Game.Beatmaps.Formats } public override bool EquivalentTo(ControlPoint other) => - base.EquivalentTo(other) - && CustomSampleBank == ((LegacySampleControlPoint)other).CustomSampleBank; + base.EquivalentTo(other) && other is LegacySampleControlPoint otherTyped && + CustomSampleBank == otherTyped.CustomSampleBank; } } } From 93b003eb5adf1fdb2c31a6e6af2809e2fc9e315a Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Sun, 27 Oct 2019 13:31:23 +0900 Subject: [PATCH 1937/2815] Add selected row state --- .../Screens/Edit/Timing/ControlPointTable.cs | 54 +++++++++++++++++-- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index aca74d4a20..df1f6daf4e 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -15,6 +15,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Edit.Timing { @@ -58,7 +59,7 @@ namespace osu.Game.Screens.Edit.Timing foreach (var group in value) { - backgroundFlow.Add(new RowBackground { Action = () => selectedGroup.Value = group }); + backgroundFlow.Add(new RowBackground(group)); } Columns = createHeaders(); @@ -135,12 +136,17 @@ namespace osu.Game.Screens.Edit.Timing public class RowBackground : OsuClickableContainer { + private readonly ControlPointGroup controlGroup; private const int fade_duration = 100; private readonly Box hoveredBackground; - public RowBackground() + [Resolved] + private Bindable<ControlPointGroup> selectedGroup { get; set; } + + public RowBackground(ControlPointGroup controlGroup) { + this.controlGroup = controlGroup; RelativeSizeAxes = Axes.X; Height = 25; @@ -157,25 +163,63 @@ namespace osu.Game.Screens.Edit.Timing Alpha = 0, }, }; + + Action = () => selectedGroup.Value = controlGroup; } + private Color4 colourHover; + private Color4 colourSelected; + [BackgroundDependencyLoader] private void load(OsuColour colours) { - hoveredBackground.Colour = colours.Blue; + hoveredBackground.Colour = colourHover = colours.BlueDarker; + colourSelected = colours.YellowDarker; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedGroup.BindValueChanged(group => { Selected = controlGroup == group.NewValue; }); + } + + private bool selected; + + protected bool Selected + { + get => selected; + set + { + if (value == selected) + return; + + selected = value; + updateState(); + } } protected override bool OnHover(HoverEvent e) { - hoveredBackground.FadeIn(fade_duration, Easing.OutQuint); + updateState(); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - hoveredBackground.FadeOut(fade_duration, Easing.OutQuint); + updateState(); base.OnHoverLost(e); } + + private void updateState() + { + hoveredBackground.FadeColour(selected ? colourSelected : colourHover, 450, Easing.OutQuint); + + if (selected || IsHovered) + hoveredBackground.FadeIn(fade_duration, Easing.OutQuint); + else + hoveredBackground.FadeOut(fade_duration, Easing.OutQuint); + } } } } From de69665a4652f2f0595bb612c3c53570348522df Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Sun, 27 Oct 2019 14:17:59 +0900 Subject: [PATCH 1938/2815] Reduce horizontal spacing of attributes --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index df1f6daf4e..8d5774cd09 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -98,7 +98,7 @@ namespace osu.Game.Screens.Edit.Timing Direction = FillDirection.Horizontal, ChildrenEnumerable = group.ControlPoints.Select(createAttribute).Where(c => c != null), Padding = new MarginPadding(10), - Spacing = new Vector2(10) + Spacing = new Vector2(2) }, }; From 0fba272e78dc1501847d8a97c8a38a69e126b695 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Sun, 27 Oct 2019 15:19:36 +0900 Subject: [PATCH 1939/2815] Add the ability to add new ControlPoint types to existing groups --- .../Visual/Editor/TestSceneTimingScreen.cs | 5 + .../ControlPoints/ControlPointGroup.cs | 6 +- .../Edit/Timing/ControlPointSettings.cs | 190 ------------------ .../Screens/Edit/Timing/ControlPointTable.cs | 64 ++++-- .../Screens/Edit/Timing/DifficultySection.cs | 37 ++++ osu.Game/Screens/Edit/Timing/EffectSection.cs | 44 ++++ osu.Game/Screens/Edit/Timing/SampleSection.cs | 44 ++++ osu.Game/Screens/Edit/Timing/Section.cs | 126 ++++++++++++ osu.Game/Screens/Edit/Timing/TimingSection.cs | 44 ++++ 9 files changed, 346 insertions(+), 214 deletions(-) create mode 100644 osu.Game/Screens/Edit/Timing/DifficultySection.cs create mode 100644 osu.Game/Screens/Edit/Timing/EffectSection.cs create mode 100644 osu.Game/Screens/Edit/Timing/SampleSection.cs create mode 100644 osu.Game/Screens/Edit/Timing/Section.cs create mode 100644 osu.Game/Screens/Edit/Timing/TimingSection.cs diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs index 3e227169e0..121853d8d0 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs @@ -17,6 +17,11 @@ namespace osu.Game.Tests.Visual.Editor { typeof(ControlPointTable), typeof(ControlPointSettings), + typeof(Section<>), + typeof(TimingSection), + typeof(EffectSection), + typeof(SampleSection), + typeof(DifficultySection), typeof(RowAttribute) }; diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs index d57baf25be..cb73ce884e 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs @@ -2,8 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; +using osu.Framework.Bindables; namespace osu.Game.Beatmaps.ControlPoints { @@ -17,9 +17,9 @@ namespace osu.Game.Beatmaps.ControlPoints /// </summary> public double Time { get; } - public IReadOnlyList<ControlPoint> ControlPoints => controlPoints; + public IBindableList<ControlPoint> ControlPoints => controlPoints; - private readonly List<ControlPoint> controlPoints = new List<ControlPoint>(); + private readonly BindableList<ControlPoint> controlPoints = new BindableList<ControlPoint>(); public ControlPointGroup(double time) { diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs index 470750b860..e1182d9fa4 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -2,17 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Screens.Edit.Timing { @@ -51,190 +46,5 @@ namespace osu.Game.Screens.Edit.Timing new SampleSection(), new EffectSection(), }; - - private class TimingSection : Section<TimingControlPoint> - { - private OsuSpriteText bpm; - private OsuSpriteText timeSignature; - - [BackgroundDependencyLoader] - private void load() - { - Flow.AddRange(new[] - { - bpm = new OsuSpriteText(), - timeSignature = new OsuSpriteText(), - }); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => - { - bpm.Text = $"BPM: {point.NewValue?.BeatLength}"; - timeSignature.Text = $"Signature: {point.NewValue?.TimeSignature}"; - }); - } - } - - private class DifficultySection : Section<DifficultyControlPoint> - { - private OsuSpriteText multiplier; - - [BackgroundDependencyLoader] - private void load() - { - Flow.AddRange(new[] - { - multiplier = new OsuSpriteText(), - }); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier}"; }); - } - } - - private class SampleSection : Section<SampleControlPoint> - { - private OsuSpriteText bank; - private OsuSpriteText volume; - - [BackgroundDependencyLoader] - private void load() - { - Flow.AddRange(new[] - { - bank = new OsuSpriteText(), - volume = new OsuSpriteText(), - }); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => - { - bank.Text = $"Bank: {point.NewValue?.SampleBank}"; - volume.Text = $"Volume: {point.NewValue?.SampleVolume}"; - }); - } - } - - private class EffectSection : Section<EffectControlPoint> - { - private OsuSpriteText kiai; - private OsuSpriteText omitBarLine; - - [BackgroundDependencyLoader] - private void load() - { - Flow.AddRange(new[] - { - kiai = new OsuSpriteText(), - omitBarLine = new OsuSpriteText(), - }); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => - { - kiai.Text = $"Kiai: {point.NewValue?.KiaiMode}"; - omitBarLine.Text = $"Skip Bar Line: {point.NewValue?.OmitFirstBarLine}"; - }); - } - } - - private class Section<T> : CompositeDrawable - where T : ControlPoint - { - private OsuCheckbox checkbox; - private Container content; - - protected FillFlowContainer Flow { get; private set; } - - protected Bindable<T> ControlPoint { get; } = new Bindable<T>(); - - private const float header_height = 20; - - [Resolved] - private Bindable<ControlPointGroup> selectedPoints { get; set; } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - RelativeSizeAxes = Axes.X; - AutoSizeDuration = 200; - AutoSizeEasing = Easing.OutQuint; - AutoSizeAxes = Axes.Y; - - Masking = true; - - InternalChildren = new Drawable[] - { - new Box - { - Colour = colours.Gray1, - RelativeSizeAxes = Axes.Both, - }, - new Container - { - RelativeSizeAxes = Axes.X, - Height = header_height, - Children = new Drawable[] - { - checkbox = new OsuCheckbox - { - LabelText = typeof(T).Name.Replace(typeof(ControlPoint).Name, string.Empty) - } - } - }, - content = new Container - { - Y = header_height, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - new Box - { - Colour = colours.Gray2, - RelativeSizeAxes = Axes.Both, - }, - Flow = new FillFlowContainer - { - Padding = new MarginPadding(10), - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - }, - } - } - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - selectedPoints.BindValueChanged(points => - { - ControlPoint.Value = points.NewValue?.ControlPoints.OfType<T>().FirstOrDefault(); - - checkbox.Current.Value = ControlPoint.Value != null; - }, true); - - checkbox.Current.BindValueChanged(selected => { content.BypassAutoSizeAxes = selected.NewValue ? Axes.None : Axes.Y; }, true); - } - } } } diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 8d5774cd09..9c11443199 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -92,35 +92,57 @@ namespace osu.Game.Screens.Edit.Timing Text = $"{group.Time:n0}ms", Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - ChildrenEnumerable = group.ControlPoints.Select(createAttribute).Where(c => c != null), - Padding = new MarginPadding(10), - Spacing = new Vector2(2) - }, + new ControlGroupAttributes(group), }; - private Drawable createAttribute(ControlPoint controlPoint) + private class ControlGroupAttributes : CompositeDrawable { - switch (controlPoint) + private readonly IBindableList<ControlPoint> controlPoints; + + private readonly FillFlowContainer fill; + + public ControlGroupAttributes(ControlPointGroup group) { - case TimingControlPoint timing: - return new RowAttribute("timing", $"{60000 / timing.BeatLength:n1}bpm {timing.TimeSignature}"); + InternalChild = fill = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding(10), + Spacing = new Vector2(2) + }; - case DifficultyControlPoint difficulty: + controlPoints = group.ControlPoints.GetBoundCopy(); + controlPoints.ItemsAdded += _ => createChildren(); + controlPoints.ItemsRemoved += _ => createChildren(); - return new RowAttribute("difficulty", $"{difficulty.SpeedMultiplier:n2}x"); - - case EffectControlPoint effect: - return new RowAttribute("effect", $"{(effect.KiaiMode ? "Kiai " : "")}{(effect.OmitFirstBarLine ? "NoBarLine " : "")}"); - - case SampleControlPoint sample: - return new RowAttribute("sample", $"{sample.SampleBank} {sample.SampleVolume}%"); + createChildren(); } - return null; + private void createChildren() + { + fill.ChildrenEnumerable = controlPoints.Select(createAttribute).Where(c => c != null); + } + + private Drawable createAttribute(ControlPoint controlPoint) + { + switch (controlPoint) + { + case TimingControlPoint timing: + return new RowAttribute("timing", $"{60000 / timing.BeatLength:n1}bpm {timing.TimeSignature}"); + + case DifficultyControlPoint difficulty: + + return new RowAttribute("difficulty", $"{difficulty.SpeedMultiplier:n2}x"); + + case EffectControlPoint effect: + return new RowAttribute("effect", $"{(effect.KiaiMode ? "Kiai " : "")}{(effect.OmitFirstBarLine ? "NoBarLine " : "")}"); + + case SampleControlPoint sample: + return new RowAttribute("sample", $"{sample.SampleBank} {sample.SampleVolume}%"); + } + + return null; + } } protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty); diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs new file mode 100644 index 0000000000..150c11f5ee --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/DifficultySection.cs @@ -0,0 +1,37 @@ +using osu.Framework.Allocation; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Timing +{ + internal class DifficultySection : Section<DifficultyControlPoint> + { + private OsuSpriteText multiplier; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + multiplier = new OsuSpriteText(), + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier}"; }); + } + + protected override DifficultyControlPoint CreatePoint() + { + var reference = Beatmap.Value.Beatmap.ControlPointInfo.DifficultyPointAt(SelectedGroup.Value.Time); + + return new DifficultyControlPoint + { + SpeedMultiplier = reference.SpeedMultiplier, + }; + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/EffectSection.cs b/osu.Game/Screens/Edit/Timing/EffectSection.cs new file mode 100644 index 0000000000..ff8817147a --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/EffectSection.cs @@ -0,0 +1,44 @@ +using osu.Framework.Allocation; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Timing +{ + internal class EffectSection : Section<EffectControlPoint> + { + private OsuSpriteText kiai; + private OsuSpriteText omitBarLine; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + kiai = new OsuSpriteText(), + omitBarLine = new OsuSpriteText(), + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => + { + kiai.Text = $"Kiai: {point.NewValue?.KiaiMode}"; + omitBarLine.Text = $"Skip Bar Line: {point.NewValue?.OmitFirstBarLine}"; + }); + } + + protected override EffectControlPoint CreatePoint() + { + var reference = Beatmap.Value.Beatmap.ControlPointInfo.EffectPointAt(SelectedGroup.Value.Time); + + return new EffectControlPoint + { + KiaiMode = reference.KiaiMode, + OmitFirstBarLine = reference.OmitFirstBarLine + }; + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs new file mode 100644 index 0000000000..0d6bc74057 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/SampleSection.cs @@ -0,0 +1,44 @@ +using osu.Framework.Allocation; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Timing +{ + internal class SampleSection : Section<SampleControlPoint> + { + private OsuSpriteText bank; + private OsuSpriteText volume; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + bank = new OsuSpriteText(), + volume = new OsuSpriteText(), + }); + } + + protected override SampleControlPoint CreatePoint() + { + var reference = Beatmap.Value.Beatmap.ControlPointInfo.SamplePointAt(SelectedGroup.Value.Time); + + return new SampleControlPoint + { + SampleBank = reference.SampleBank, + SampleVolume = reference.SampleVolume, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => + { + bank.Text = $"Bank: {point.NewValue?.SampleBank}"; + volume.Text = $"Volume: {point.NewValue?.SampleVolume}"; + }); + } + } +} \ No newline at end of file diff --git a/osu.Game/Screens/Edit/Timing/Section.cs b/osu.Game/Screens/Edit/Timing/Section.cs new file mode 100644 index 0000000000..c6140ff497 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/Section.cs @@ -0,0 +1,126 @@ +// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Edit.Timing +{ + internal abstract class Section<T> : CompositeDrawable + where T : ControlPoint + { + private OsuCheckbox checkbox; + private Container content; + + protected FillFlowContainer Flow { get; private set; } + + protected Bindable<T> ControlPoint { get; } = new Bindable<T>(); + + private const float header_height = 20; + + [Resolved] + protected IBindable<WorkingBeatmap> Beatmap { get; private set; } + + [Resolved] + protected Bindable<ControlPointGroup> SelectedGroup { get; private set; } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.X; + AutoSizeDuration = 200; + AutoSizeEasing = Easing.OutQuint; + AutoSizeAxes = Axes.Y; + + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = colours.Gray1, + RelativeSizeAxes = Axes.Both, + }, + new Container + { + RelativeSizeAxes = Axes.X, + Height = header_height, + Children = new Drawable[] + { + checkbox = new OsuCheckbox + { + LabelText = typeof(T).Name.Replace(typeof(ControlPoint).Name, string.Empty) + } + } + }, + content = new Container + { + Y = header_height, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + Colour = colours.Gray2, + RelativeSizeAxes = Axes.Both, + }, + Flow = new FillFlowContainer + { + Padding = new MarginPadding(10), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + }, + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + checkbox.Current.BindValueChanged(selected => + { + if (selected.NewValue) + { + if (SelectedGroup.Value == null) + { + checkbox.Current.Value = false; + return; + } + + if (ControlPoint.Value == null) + SelectedGroup.Value.Add(ControlPoint.Value = CreatePoint()); + } + else + { + if (ControlPoint.Value != null) + { + SelectedGroup.Value.Remove(ControlPoint.Value); + ControlPoint.Value = null; + } + } + + content.BypassAutoSizeAxes = selected.NewValue ? Axes.None : Axes.Y; + }, true); + + SelectedGroup.BindValueChanged(points => + { + ControlPoint.Value = points.NewValue?.ControlPoints.OfType<T>().FirstOrDefault(); + checkbox.Current.Value = ControlPoint.Value != null; + }, true); + } + + protected abstract T CreatePoint(); + } +} diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs new file mode 100644 index 0000000000..dcbb6f8bbf --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -0,0 +1,44 @@ +using osu.Framework.Allocation; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Timing +{ + internal class TimingSection : Section<TimingControlPoint> + { + private OsuSpriteText bpm; + private OsuSpriteText timeSignature; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + bpm = new OsuSpriteText(), + timeSignature = new OsuSpriteText(), + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => + { + bpm.Text = $"BPM: {point.NewValue?.BeatLength}"; + timeSignature.Text = $"Signature: {point.NewValue?.TimeSignature}"; + }); + } + + protected override TimingControlPoint CreatePoint() + { + var reference = Beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(SelectedGroup.Value.Time); + + return new TimingControlPoint + { + BeatLength = reference.BeatLength, + TimeSignature = reference.TimeSignature + }; + } + } +} From 73369ae6130680101aeaf6216307b13e9b76f564 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Sun, 27 Oct 2019 16:13:24 +0900 Subject: [PATCH 1940/2815] Add the ability to add/remove groups --- .../ControlPoints/ControlPointInfo.cs | 19 +++++++++++-- .../Screens/Edit/Timing/ControlPointTable.cs | 2 +- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 27 ++++++++++++++----- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index e165adedea..4f83e14eaf 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; +using osu.Framework.Bindables; using osu.Framework.Lists; namespace osu.Game.Beatmaps.ControlPoints @@ -16,9 +17,9 @@ namespace osu.Game.Beatmaps.ControlPoints /// Control point groups. /// </summary> [JsonProperty] - public IReadOnlyList<ControlPointGroup> Groups => groups; + public IBindableList<ControlPointGroup> Groups => groups; - private readonly SortedList<ControlPointGroup> groups = new SortedList<ControlPointGroup>(Comparer<ControlPointGroup>.Default); + private readonly BindableList<ControlPointGroup> groups = new BindableList<ControlPointGroup>(); /// <summary> /// All timing points. @@ -294,5 +295,19 @@ namespace osu.Game.Beatmaps.ControlPoints samplePoints.Clear(); effectPoints.Clear(); } + + public ControlPointGroup CreateGroup(double time) + { + var newGroup = new ControlPointGroup(time); + + int i = groups.BinarySearch(newGroup); + if (i < 0) i = ~i; + + groups.Insert(i, newGroup); + + return newGroup; + } + + public void RemoveGroup(ControlPointGroup group) => groups.Remove(group); } } diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 9c11443199..729e631d87 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -203,7 +203,7 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - selectedGroup.BindValueChanged(group => { Selected = controlGroup == group.NewValue; }); + selectedGroup.BindValueChanged(group => { Selected = controlGroup == group.NewValue; }, true); } private bool selected; diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index a9fd038829..2b1c8f8497 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -52,12 +52,18 @@ namespace osu.Game.Screens.Edit.Timing public class ControlPointList : CompositeDrawable { private OsuButton deleteButton; + private ControlPointTable table; + + private IBindableList<ControlPointGroup> controlGroups; + + [Resolved] + protected IFrameBasedClock EditorClock { get; private set; } [Resolved] protected IBindable<WorkingBeatmap> Beatmap { get; private set; } [Resolved] - private Bindable<ControlPointGroup> selectedPoints { get; set; } + private Bindable<ControlPointGroup> selectedGroup { get; set; } [BackgroundDependencyLoader] private void load(OsuColour colours) @@ -74,10 +80,7 @@ namespace osu.Game.Screens.Edit.Timing new OsuScrollContainer { RelativeSizeAxes = Axes.Both, - Child = new ControlPointTable - { - ControlGroups = Beatmap.Value.Beatmap.ControlPointInfo.Groups - } + Child = table = new ControlPointTable(), }, new FillFlowContainer { @@ -114,15 +117,27 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - selectedPoints.BindValueChanged(selected => { deleteButton.Enabled.Value = selected.NewValue != null; }, true); + selectedGroup.BindValueChanged(selected => { deleteButton.Enabled.Value = selected.NewValue != null; }, true); + + controlGroups = Beatmap.Value.Beatmap.ControlPointInfo.Groups.GetBoundCopy(); + controlGroups.ItemsAdded += _ => createContent(); + controlGroups.ItemsRemoved += _ => createContent(); + createContent(); } + private void createContent() => table.ControlGroups = controlGroups; + private void delete() { + if (selectedGroup.Value == null) + return; + + Beatmap.Value.Beatmap.ControlPointInfo.RemoveGroup(selectedGroup.Value); } private void addNew() { + selectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.CreateGroup(EditorClock.CurrentTime); } } } From 81b5d7b79fd668463a7d1c616a66da5a77ec4dee Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Sun, 27 Oct 2019 16:30:05 +0900 Subject: [PATCH 1941/2815] Select another group after deleting selected --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 2b1c8f8497..2e9164c60f 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -57,7 +58,7 @@ namespace osu.Game.Screens.Edit.Timing private IBindableList<ControlPointGroup> controlGroups; [Resolved] - protected IFrameBasedClock EditorClock { get; private set; } + private IFrameBasedClock clock { get; set; } [Resolved] protected IBindable<WorkingBeatmap> Beatmap { get; private set; } @@ -133,11 +134,13 @@ namespace osu.Game.Screens.Edit.Timing return; Beatmap.Value.Beatmap.ControlPointInfo.RemoveGroup(selectedGroup.Value); + + selectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.Groups.FirstOrDefault(g => g.Time >= clock.CurrentTime); } private void addNew() { - selectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.CreateGroup(EditorClock.CurrentTime); + selectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.CreateGroup(clock.CurrentTime); } } } From 0179586f7811a1d71209d61cb676a0dd856cb7d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Sun, 27 Oct 2019 16:31:23 +0900 Subject: [PATCH 1942/2815] Disallow inserting a group if one already exists with the current time value --- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 4f83e14eaf..6418ef984b 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -301,9 +301,11 @@ namespace osu.Game.Beatmaps.ControlPoints var newGroup = new ControlPointGroup(time); int i = groups.BinarySearch(newGroup); - if (i < 0) i = ~i; - groups.Insert(i, newGroup); + if (i > 0) + return groups[i]; + + groups.Insert(~i, newGroup); return newGroup; } From 022cc139528eb581029d7dcf0a7935ef34d0350a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= <dach.bartlomiej@gmail.com> Date: Sun, 27 Oct 2019 22:55:46 +0100 Subject: [PATCH 1943/2815] Add beatmap carousel item sorting stability test Add visual test to ensure sorting stability when sorting criteria are applied in the beatmap carousel. --- .../SongSelect/TestSceneBeatmapCarousel.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index f87d6ebebb..8b82567a8d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -245,6 +245,28 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert($"Check #{set_count} is at bottom", () => carousel.BeatmapSets.Last().Metadata.Title.EndsWith($"#{set_count}!")); } + [Test] + public void TestSortingStability() + { + var sets = new List<BeatmapSetInfo>(); + + for (int i = 0; i < 20; i++) + { + var set = createTestBeatmapSet(i); + set.Metadata.Artist = "same artist"; + set.Metadata.Title = "same title"; + sets.Add(set); + } + + loadBeatmaps(sets); + + AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false)); + AddAssert("Items remain in original order", () => carousel.BeatmapSets.Select((set, index) => set.ID == index).All(b => b)); + + AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false)); + AddAssert("Items remain in original order", () => carousel.BeatmapSets.Select((set, index) => set.ID == index).All(b => b)); + } + [Test] public void TestSortingWithFiltered() { From c8d3dd0e5a62e126eb9e2d7701aec7e731a5effb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= <dach.bartlomiej@gmail.com> Date: Sun, 27 Oct 2019 23:14:14 +0100 Subject: [PATCH 1944/2815] Make carousel item sorting stable Migrate beatmap carousel item sorting from List<T>.Sort() to IEnumerable<T>.OrderBy(), as the second variant is documented to be a stable sorting algorithm. This allows for eliminating unnecessary movement of carousel items occurring whenever any set of items is tied when changing sorting criteria. --- .../Screens/Select/Carousel/CarouselGroup.cs | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index 09b728abeb..b32561eb88 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -1,7 +1,9 @@ // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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; namespace osu.Game.Screens.Select.Carousel { @@ -81,12 +83,9 @@ namespace osu.Game.Screens.Select.Carousel { base.Filter(criteria); - var children = new List<CarouselItem>(InternalChildren); - - children.ForEach(c => c.Filter(criteria)); - children.Sort((x, y) => x.CompareTo(criteria, y)); - - InternalChildren = children; + InternalChildren.ForEach(c => c.Filter(criteria)); + // IEnumerable<T>.OrderBy() is used instead of List<T>.Sort() to ensure sorting stability + InternalChildren = InternalChildren.OrderBy(c => c, new CriteriaComparer(criteria)).ToList(); } protected virtual void ChildItemStateChanged(CarouselItem item, CarouselItemState value) @@ -104,5 +103,23 @@ namespace osu.Game.Screens.Select.Carousel State.Value = CarouselItemState.Selected; } } + + private class CriteriaComparer : IComparer<CarouselItem> + { + private readonly FilterCriteria criteria; + + public CriteriaComparer(FilterCriteria criteria) + { + this.criteria = criteria; + } + + public int Compare(CarouselItem x, CarouselItem y) + { + if (x != null && y != null) + return x.CompareTo(criteria, y); + + throw new ArgumentNullException(); + } + } } } From 29e20bc8d2796d8ac5cb3f8a53dc684ec67263d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Mon, 28 Oct 2019 10:45:11 +0900 Subject: [PATCH 1945/2815] Add xmldoc and combine GroupAt / CreateGroup --- .../ControlPoints/ControlPointInfo.cs | 174 ++++++++---------- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 2 +- 2 files changed, 79 insertions(+), 97 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 6418ef984b..c3e2b469ae 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -14,7 +14,7 @@ namespace osu.Game.Beatmaps.ControlPoints public class ControlPointInfo { /// <summary> - /// Control point groups. + /// All control points grouped by time. /// </summary> [JsonProperty] public IBindableList<ControlPointGroup> Groups => groups; @@ -86,28 +86,6 @@ namespace osu.Game.Beatmaps.ControlPoints /// <returns>The timing control point.</returns> public TimingControlPoint TimingPointAt(double time) => binarySearchWithFallback(TimingPoints, time, TimingPoints.Count > 0 ? TimingPoints[0] : null); - /// <summary> - /// Finds the closest <see cref="ControlPoint"/> of the same type as <see cref="referencePoint"/> that is active at <paramref name="time"/>. - /// </summary> - /// <param name="time">The time to find the timing control point at.</param> - /// <param name="referencePoint">A reference point to infer type.</param> - /// <returns>The timing control point.</returns> - public ControlPoint SimilarPointAt(double time, ControlPoint referencePoint) - { - switch (referencePoint) - { - case TimingControlPoint _: return binarySearch(TimingPoints, time); - - case EffectControlPoint _: return binarySearch(EffectPoints, time); - - case SampleControlPoint _: return binarySearch(SamplePoints, time); - - case DifficultyControlPoint _: return binarySearch(DifficultyPoints, time); - } - - return null; - } - /// <summary> /// Finds the maximum BPM represented by any timing control point. /// </summary> @@ -129,6 +107,62 @@ namespace osu.Game.Beatmaps.ControlPoints public double BPMMode => 60000 / (TimingPoints.GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).FirstOrDefault()?.FirstOrDefault() ?? new TimingControlPoint()).BeatLength; + /// <summary> + /// Remove all <see cref="ControlPointGroup"/>s and return to a pristine state. + /// </summary> + public void Clear() + { + groups.Clear(); + timingPoints.Clear(); + difficultyPoints.Clear(); + samplePoints.Clear(); + effectPoints.Clear(); + } + + /// <summary> + /// Add a new <see cref="ControlPoint"/>. Note that the provided control point may not be added if the correct state is already present at the provided time. + /// </summary> + /// <param name="time">The time at which the control point should be added.</param> + /// <param name="controlPoint">The control point to add.</param> + /// <returns>Whether the control point was added.</returns> + public bool Add(double time, ControlPoint controlPoint) + { + if (checkAlreadyExisting(time, controlPoint)) + return false; + + GroupAt(time, true).Add(controlPoint); + return true; + } + + public ControlPointGroup GroupAt(double time, bool addIfNotExisting = false) + { + var newGroup = new ControlPointGroup(time); + + int i = groups.BinarySearch(newGroup); + + if (i >= 0) + return groups[i]; + + if (addIfNotExisting) + { + newGroup.ItemAdded += groupItemAdded; + newGroup.ItemRemoved += groupItemRemoved; + + groups.Insert(~i, newGroup); + return newGroup; + } + + return null; + } + + public void RemoveGroup(ControlPointGroup group) + { + group.ItemAdded -= groupItemAdded; + group.ItemRemoved -= groupItemRemoved; + + groups.Remove(group); + } + /// <summary> /// Binary searches one of the control point lists to find the active control point at <paramref name="time"/>. /// Includes logic for returning a specific point when no matching point is found. @@ -183,33 +217,6 @@ namespace osu.Game.Beatmaps.ControlPoints return list[l - 1]; } - public void Add(double time, ControlPoint newPoint, bool force = false) - { - if (!force && checkAlreadyExisting(time, newPoint)) - return; - - GroupAt(time, true).Add(newPoint); - } - - public ControlPointGroup GroupAt(double time, bool createIfNotExisting = false) - { - var existing = Groups.FirstOrDefault(g => g.Time == time); - - if (existing != null) - return existing; - - if (createIfNotExisting) - { - var newGroup = new ControlPointGroup(time); - newGroup.ItemAdded += groupItemAdded; - newGroup.ItemRemoved += groupItemRemoved; - groups.Add(newGroup); - return newGroup; - } - - return null; - } - /// <summary> /// Check whether <see cref="newPoint"/> should be added. /// </summary> @@ -243,31 +250,9 @@ namespace osu.Game.Beatmaps.ControlPoints return existing?.EquivalentTo(newPoint) == true; } - private void groupItemRemoved(ControlPoint obj) + private void groupItemAdded(ControlPoint controlPoint) { - switch (obj) - { - case TimingControlPoint typed: - timingPoints.Remove(typed); - break; - - case EffectControlPoint typed: - effectPoints.Remove(typed); - break; - - case SampleControlPoint typed: - samplePoints.Remove(typed); - break; - - case DifficultyControlPoint typed: - difficultyPoints.Remove(typed); - break; - } - } - - private void groupItemAdded(ControlPoint obj) - { - switch (obj) + switch (controlPoint) { case TimingControlPoint typed: timingPoints.Add(typed); @@ -287,29 +272,26 @@ namespace osu.Game.Beatmaps.ControlPoints } } - public void Clear() + private void groupItemRemoved(ControlPoint controlPoint) { - groups.Clear(); - timingPoints.Clear(); - difficultyPoints.Clear(); - samplePoints.Clear(); - effectPoints.Clear(); + switch (controlPoint) + { + case TimingControlPoint typed: + timingPoints.Remove(typed); + break; + + case EffectControlPoint typed: + effectPoints.Remove(typed); + break; + + case SampleControlPoint typed: + samplePoints.Remove(typed); + break; + + case DifficultyControlPoint typed: + difficultyPoints.Remove(typed); + break; + } } - - public ControlPointGroup CreateGroup(double time) - { - var newGroup = new ControlPointGroup(time); - - int i = groups.BinarySearch(newGroup); - - if (i > 0) - return groups[i]; - - groups.Insert(~i, newGroup); - - return newGroup; - } - - public void RemoveGroup(ControlPointGroup group) => groups.Remove(group); } } diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 2e9164c60f..6ae01e056f 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -140,7 +140,7 @@ namespace osu.Game.Screens.Edit.Timing private void addNew() { - selectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.CreateGroup(clock.CurrentTime); + selectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.GroupAt(clock.CurrentTime, true); } } } From cd4b7c04e9a18668680016c9b45ee1d4c3f1976d Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Mon, 28 Oct 2019 11:34:41 +0900 Subject: [PATCH 1946/2815] Add test coverage --- .../NonVisual/ControlPointInfoTest.cs | 227 ++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 osu.Game.Tests/NonVisual/ControlPointInfoTest.cs diff --git a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs new file mode 100644 index 0000000000..a51b90851c --- /dev/null +++ b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs @@ -0,0 +1,227 @@ +// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps.ControlPoints; + +namespace osu.Game.Tests.NonVisual +{ + [TestFixture] + public class ControlPointInfoTest + { + [Test] + public void TestAdd() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new TimingControlPoint()); + cpi.Add(1000, new TimingControlPoint { BeatLength = 500 }); + + Assert.That(cpi.Groups.Count, Is.EqualTo(2)); + Assert.That(cpi.TimingPoints.Count, Is.EqualTo(2)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2)); + } + + [Test] + public void TestAddRedundantTiming() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new TimingControlPoint()); // is *not* redundant, special exception for first timing point. + cpi.Add(1000, new TimingControlPoint()); // is redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + Assert.That(cpi.TimingPoints.Count, Is.EqualTo(1)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1)); + } + + [Test] + public void TestAddRedundantDifficulty() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new DifficultyControlPoint()); // is redundant + cpi.Add(1000, new DifficultyControlPoint()); // is redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(0)); + Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0)); + + cpi.Add(1000, new DifficultyControlPoint { SpeedMultiplier = 2 }); // is not redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1)); + } + + [Test] + public void TestAddRedundantSample() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new SampleControlPoint()); // is redundant + cpi.Add(1000, new SampleControlPoint()); // is redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(0)); + Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0)); + + cpi.Add(1000, new SampleControlPoint { SampleVolume = 50 }); // is not redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + Assert.That(cpi.SamplePoints.Count, Is.EqualTo(1)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1)); + } + + [Test] + public void TestAddRedundantEffect() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new EffectControlPoint()); // is redundant + cpi.Add(1000, new EffectControlPoint()); // is redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(0)); + Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0)); + + cpi.Add(1000, new EffectControlPoint { KiaiMode = true }); // is not redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + Assert.That(cpi.EffectPoints.Count, Is.EqualTo(1)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1)); + } + + [Test] + public void TestAddGroup() + { + var cpi = new ControlPointInfo(); + + var group = cpi.GroupAt(1000, true); + var group2 = cpi.GroupAt(1000, true); + + Assert.That(group, Is.EqualTo(group2)); + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + } + + [Test] + public void TestGroupAtLookupOnly() + { + var cpi = new ControlPointInfo(); + + var group = cpi.GroupAt(5000, true); + Assert.That(group, Is.Not.Null); + + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + Assert.That(cpi.GroupAt(1000), Is.Null); + Assert.That(cpi.GroupAt(5000), Is.Not.Null); + } + + [Test] + public void TestAddRemoveGroup() + { + var cpi = new ControlPointInfo(); + + var group = cpi.GroupAt(1000, true); + + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + + cpi.RemoveGroup(group); + + Assert.That(cpi.Groups.Count, Is.EqualTo(0)); + } + + [Test] + public void TestAddControlPointToGroup() + { + var cpi = new ControlPointInfo(); + + var group = cpi.GroupAt(1000, true); + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + + // usually redundant, but adding to group forces it to be added + group.Add(new DifficultyControlPoint()); + + Assert.That(group.ControlPoints.Count, Is.EqualTo(1)); + Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1)); + } + + [Test] + public void TestAddDuplicateControlPointToGroup() + { + var cpi = new ControlPointInfo(); + + var group = cpi.GroupAt(1000, true); + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + + group.Add(new DifficultyControlPoint()); + group.Add(new DifficultyControlPoint { SpeedMultiplier = 2 }); + + Assert.That(group.ControlPoints.Count, Is.EqualTo(1)); + Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1)); + Assert.That(cpi.DifficultyPoints.First().SpeedMultiplier, Is.EqualTo(2)); + } + + [Test] + public void TestRemoveControlPointFromGroup() + { + var cpi = new ControlPointInfo(); + + var group = cpi.GroupAt(1000, true); + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + + var difficultyPoint = new DifficultyControlPoint(); + + group.Add(difficultyPoint); + group.Remove(difficultyPoint); + + Assert.That(group.ControlPoints.Count, Is.EqualTo(0)); + Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0)); + Assert.That(cpi.AllControlPoints.Count, Is.EqualTo(0)); + } + + [Test] + public void TestOrdering() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new TimingControlPoint()); + cpi.Add(1000, new TimingControlPoint { BeatLength = 500 }); + cpi.Add(10000, new TimingControlPoint { BeatLength = 200 }); + cpi.Add(5000, new TimingControlPoint { BeatLength = 100 }); + cpi.Add(3000, new DifficultyControlPoint { SpeedMultiplier = 2 }); + cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SpeedMultiplier = 4 }); + cpi.GroupAt(1000).Add(new SampleControlPoint { SampleVolume = 0 }); + cpi.GroupAt(8000, true).Add(new EffectControlPoint { KiaiMode = true }); + + Assert.That(cpi.AllControlPoints.Count, Is.EqualTo(8)); + + Assert.That(cpi.Groups, Is.Ordered.Ascending.By(nameof(ControlPointGroup.Time))); + + Assert.That(cpi.AllControlPoints, Is.Ordered.Ascending.By(nameof(ControlPoint.Time))); + Assert.That(cpi.TimingPoints, Is.Ordered.Ascending.By(nameof(ControlPoint.Time))); + } + + [Test] + public void TestClear() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new TimingControlPoint()); + cpi.Add(1000, new TimingControlPoint { BeatLength = 500 }); + cpi.Add(10000, new TimingControlPoint { BeatLength = 200 }); + cpi.Add(5000, new TimingControlPoint { BeatLength = 100 }); + cpi.Add(3000, new DifficultyControlPoint { SpeedMultiplier = 2 }); + cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SpeedMultiplier = 4 }); + cpi.GroupAt(1000).Add(new SampleControlPoint { SampleVolume = 0 }); + cpi.GroupAt(8000, true).Add(new EffectControlPoint { KiaiMode = true }); + + cpi.Clear(); + + Assert.That(cpi.Groups.Count, Is.EqualTo(0)); + Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0)); + Assert.That(cpi.AllControlPoints.Count, Is.EqualTo(0)); + } + } +} From 10033239c7c38781455d25d9189c80abc28123fd Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Mon, 28 Oct 2019 11:39:17 +0900 Subject: [PATCH 1947/2815] Allow binding to ControlPointGroup's ControlPoints --- osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs index d57baf25be..cb73ce884e 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs @@ -2,8 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; +using osu.Framework.Bindables; namespace osu.Game.Beatmaps.ControlPoints { @@ -17,9 +17,9 @@ namespace osu.Game.Beatmaps.ControlPoints /// </summary> public double Time { get; } - public IReadOnlyList<ControlPoint> ControlPoints => controlPoints; + public IBindableList<ControlPoint> ControlPoints => controlPoints; - private readonly List<ControlPoint> controlPoints = new List<ControlPoint>(); + private readonly BindableList<ControlPoint> controlPoints = new BindableList<ControlPoint>(); public ControlPointGroup(double time) { From 59d983b66ebb540b45ebe0c07f9fe7ccc8213da8 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Mon, 28 Oct 2019 11:39:53 +0900 Subject: [PATCH 1948/2815] Allow binding to ControlPointInfo's Groups --- .../ControlPoints/ControlPointInfo.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 3927f46530..4218f9f66f 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; +using osu.Framework.Bindables; using osu.Framework.Lists; namespace osu.Game.Beatmaps.ControlPoints @@ -16,9 +17,9 @@ namespace osu.Game.Beatmaps.ControlPoints /// Control point groups. /// </summary> [JsonProperty] - public IReadOnlyList<ControlPointGroup> Groups => groups; + public IBindableList<ControlPointGroup> Groups => groups; - private readonly SortedList<ControlPointGroup> groups = new SortedList<ControlPointGroup>(Comparer<ControlPointGroup>.Default); + private readonly BindableList<ControlPointGroup> groups = new BindableList<ControlPointGroup>(); /// <summary> /// All timing points. @@ -272,5 +273,19 @@ namespace osu.Game.Beatmaps.ControlPoints samplePoints.Clear(); effectPoints.Clear(); } + + public ControlPointGroup CreateGroup(double time) + { + var newGroup = new ControlPointGroup(time); + + int i = groups.BinarySearch(newGroup); + if (i < 0) i = ~i; + + groups.Insert(i, newGroup); + + return newGroup; + } + + public void RemoveGroup(ControlPointGroup group) => groups.Remove(group); } } From 2a6b3fd67c2905540de016ae7f18ecf52bc3cc07 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Sun, 27 Oct 2019 16:31:23 +0900 Subject: [PATCH 1949/2815] Disallow inserting a group if one already exists with the current time value --- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 4218f9f66f..d9e92f1264 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -279,9 +279,11 @@ namespace osu.Game.Beatmaps.ControlPoints var newGroup = new ControlPointGroup(time); int i = groups.BinarySearch(newGroup); - if (i < 0) i = ~i; - groups.Insert(i, newGroup); + if (i > 0) + return groups[i]; + + groups.Insert(~i, newGroup); return newGroup; } From 45da22afe903a5d625f7b581354d751109a70f81 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Mon, 28 Oct 2019 11:40:33 +0900 Subject: [PATCH 1950/2815] Add xmldoc and combine GroupAt / CreateGroup --- .../ControlPoints/ControlPointInfo.cs | 152 +++++++++--------- 1 file changed, 78 insertions(+), 74 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index d9e92f1264..c3e2b469ae 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -14,7 +14,7 @@ namespace osu.Game.Beatmaps.ControlPoints public class ControlPointInfo { /// <summary> - /// Control point groups. + /// All control points grouped by time. /// </summary> [JsonProperty] public IBindableList<ControlPointGroup> Groups => groups; @@ -107,6 +107,62 @@ namespace osu.Game.Beatmaps.ControlPoints public double BPMMode => 60000 / (TimingPoints.GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).FirstOrDefault()?.FirstOrDefault() ?? new TimingControlPoint()).BeatLength; + /// <summary> + /// Remove all <see cref="ControlPointGroup"/>s and return to a pristine state. + /// </summary> + public void Clear() + { + groups.Clear(); + timingPoints.Clear(); + difficultyPoints.Clear(); + samplePoints.Clear(); + effectPoints.Clear(); + } + + /// <summary> + /// Add a new <see cref="ControlPoint"/>. Note that the provided control point may not be added if the correct state is already present at the provided time. + /// </summary> + /// <param name="time">The time at which the control point should be added.</param> + /// <param name="controlPoint">The control point to add.</param> + /// <returns>Whether the control point was added.</returns> + public bool Add(double time, ControlPoint controlPoint) + { + if (checkAlreadyExisting(time, controlPoint)) + return false; + + GroupAt(time, true).Add(controlPoint); + return true; + } + + public ControlPointGroup GroupAt(double time, bool addIfNotExisting = false) + { + var newGroup = new ControlPointGroup(time); + + int i = groups.BinarySearch(newGroup); + + if (i >= 0) + return groups[i]; + + if (addIfNotExisting) + { + newGroup.ItemAdded += groupItemAdded; + newGroup.ItemRemoved += groupItemRemoved; + + groups.Insert(~i, newGroup); + return newGroup; + } + + return null; + } + + public void RemoveGroup(ControlPointGroup group) + { + group.ItemAdded -= groupItemAdded; + group.ItemRemoved -= groupItemRemoved; + + groups.Remove(group); + } + /// <summary> /// Binary searches one of the control point lists to find the active control point at <paramref name="time"/>. /// Includes logic for returning a specific point when no matching point is found. @@ -161,33 +217,6 @@ namespace osu.Game.Beatmaps.ControlPoints return list[l - 1]; } - public void Add(double time, ControlPoint newPoint, bool force = false) - { - if (!force && checkAlreadyExisting(time, newPoint)) - return; - - GroupAt(time, true).Add(newPoint); - } - - public ControlPointGroup GroupAt(double time, bool createIfNotExisting = false) - { - var existing = Groups.FirstOrDefault(g => g.Time == time); - - if (existing != null) - return existing; - - if (createIfNotExisting) - { - var newGroup = new ControlPointGroup(time); - newGroup.ItemAdded += groupItemAdded; - newGroup.ItemRemoved += groupItemRemoved; - groups.Add(newGroup); - return newGroup; - } - - return null; - } - /// <summary> /// Check whether <see cref="newPoint"/> should be added. /// </summary> @@ -221,31 +250,9 @@ namespace osu.Game.Beatmaps.ControlPoints return existing?.EquivalentTo(newPoint) == true; } - private void groupItemRemoved(ControlPoint obj) + private void groupItemAdded(ControlPoint controlPoint) { - switch (obj) - { - case TimingControlPoint typed: - timingPoints.Remove(typed); - break; - - case EffectControlPoint typed: - effectPoints.Remove(typed); - break; - - case SampleControlPoint typed: - samplePoints.Remove(typed); - break; - - case DifficultyControlPoint typed: - difficultyPoints.Remove(typed); - break; - } - } - - private void groupItemAdded(ControlPoint obj) - { - switch (obj) + switch (controlPoint) { case TimingControlPoint typed: timingPoints.Add(typed); @@ -265,29 +272,26 @@ namespace osu.Game.Beatmaps.ControlPoints } } - public void Clear() + private void groupItemRemoved(ControlPoint controlPoint) { - groups.Clear(); - timingPoints.Clear(); - difficultyPoints.Clear(); - samplePoints.Clear(); - effectPoints.Clear(); + switch (controlPoint) + { + case TimingControlPoint typed: + timingPoints.Remove(typed); + break; + + case EffectControlPoint typed: + effectPoints.Remove(typed); + break; + + case SampleControlPoint typed: + samplePoints.Remove(typed); + break; + + case DifficultyControlPoint typed: + difficultyPoints.Remove(typed); + break; + } } - - public ControlPointGroup CreateGroup(double time) - { - var newGroup = new ControlPointGroup(time); - - int i = groups.BinarySearch(newGroup); - - if (i > 0) - return groups[i]; - - groups.Insert(~i, newGroup); - - return newGroup; - } - - public void RemoveGroup(ControlPointGroup group) => groups.Remove(group); } } From 4e80eda6daf5b0294c20768f7071e83d1c73a62f Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Mon, 28 Oct 2019 11:34:41 +0900 Subject: [PATCH 1951/2815] Add test coverage --- .../NonVisual/ControlPointInfoTest.cs | 227 ++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 osu.Game.Tests/NonVisual/ControlPointInfoTest.cs diff --git a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs new file mode 100644 index 0000000000..a51b90851c --- /dev/null +++ b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs @@ -0,0 +1,227 @@ +// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps.ControlPoints; + +namespace osu.Game.Tests.NonVisual +{ + [TestFixture] + public class ControlPointInfoTest + { + [Test] + public void TestAdd() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new TimingControlPoint()); + cpi.Add(1000, new TimingControlPoint { BeatLength = 500 }); + + Assert.That(cpi.Groups.Count, Is.EqualTo(2)); + Assert.That(cpi.TimingPoints.Count, Is.EqualTo(2)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2)); + } + + [Test] + public void TestAddRedundantTiming() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new TimingControlPoint()); // is *not* redundant, special exception for first timing point. + cpi.Add(1000, new TimingControlPoint()); // is redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + Assert.That(cpi.TimingPoints.Count, Is.EqualTo(1)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1)); + } + + [Test] + public void TestAddRedundantDifficulty() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new DifficultyControlPoint()); // is redundant + cpi.Add(1000, new DifficultyControlPoint()); // is redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(0)); + Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0)); + + cpi.Add(1000, new DifficultyControlPoint { SpeedMultiplier = 2 }); // is not redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1)); + } + + [Test] + public void TestAddRedundantSample() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new SampleControlPoint()); // is redundant + cpi.Add(1000, new SampleControlPoint()); // is redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(0)); + Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0)); + + cpi.Add(1000, new SampleControlPoint { SampleVolume = 50 }); // is not redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + Assert.That(cpi.SamplePoints.Count, Is.EqualTo(1)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1)); + } + + [Test] + public void TestAddRedundantEffect() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new EffectControlPoint()); // is redundant + cpi.Add(1000, new EffectControlPoint()); // is redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(0)); + Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0)); + + cpi.Add(1000, new EffectControlPoint { KiaiMode = true }); // is not redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + Assert.That(cpi.EffectPoints.Count, Is.EqualTo(1)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1)); + } + + [Test] + public void TestAddGroup() + { + var cpi = new ControlPointInfo(); + + var group = cpi.GroupAt(1000, true); + var group2 = cpi.GroupAt(1000, true); + + Assert.That(group, Is.EqualTo(group2)); + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + } + + [Test] + public void TestGroupAtLookupOnly() + { + var cpi = new ControlPointInfo(); + + var group = cpi.GroupAt(5000, true); + Assert.That(group, Is.Not.Null); + + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + Assert.That(cpi.GroupAt(1000), Is.Null); + Assert.That(cpi.GroupAt(5000), Is.Not.Null); + } + + [Test] + public void TestAddRemoveGroup() + { + var cpi = new ControlPointInfo(); + + var group = cpi.GroupAt(1000, true); + + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + + cpi.RemoveGroup(group); + + Assert.That(cpi.Groups.Count, Is.EqualTo(0)); + } + + [Test] + public void TestAddControlPointToGroup() + { + var cpi = new ControlPointInfo(); + + var group = cpi.GroupAt(1000, true); + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + + // usually redundant, but adding to group forces it to be added + group.Add(new DifficultyControlPoint()); + + Assert.That(group.ControlPoints.Count, Is.EqualTo(1)); + Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1)); + } + + [Test] + public void TestAddDuplicateControlPointToGroup() + { + var cpi = new ControlPointInfo(); + + var group = cpi.GroupAt(1000, true); + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + + group.Add(new DifficultyControlPoint()); + group.Add(new DifficultyControlPoint { SpeedMultiplier = 2 }); + + Assert.That(group.ControlPoints.Count, Is.EqualTo(1)); + Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1)); + Assert.That(cpi.DifficultyPoints.First().SpeedMultiplier, Is.EqualTo(2)); + } + + [Test] + public void TestRemoveControlPointFromGroup() + { + var cpi = new ControlPointInfo(); + + var group = cpi.GroupAt(1000, true); + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + + var difficultyPoint = new DifficultyControlPoint(); + + group.Add(difficultyPoint); + group.Remove(difficultyPoint); + + Assert.That(group.ControlPoints.Count, Is.EqualTo(0)); + Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0)); + Assert.That(cpi.AllControlPoints.Count, Is.EqualTo(0)); + } + + [Test] + public void TestOrdering() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new TimingControlPoint()); + cpi.Add(1000, new TimingControlPoint { BeatLength = 500 }); + cpi.Add(10000, new TimingControlPoint { BeatLength = 200 }); + cpi.Add(5000, new TimingControlPoint { BeatLength = 100 }); + cpi.Add(3000, new DifficultyControlPoint { SpeedMultiplier = 2 }); + cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SpeedMultiplier = 4 }); + cpi.GroupAt(1000).Add(new SampleControlPoint { SampleVolume = 0 }); + cpi.GroupAt(8000, true).Add(new EffectControlPoint { KiaiMode = true }); + + Assert.That(cpi.AllControlPoints.Count, Is.EqualTo(8)); + + Assert.That(cpi.Groups, Is.Ordered.Ascending.By(nameof(ControlPointGroup.Time))); + + Assert.That(cpi.AllControlPoints, Is.Ordered.Ascending.By(nameof(ControlPoint.Time))); + Assert.That(cpi.TimingPoints, Is.Ordered.Ascending.By(nameof(ControlPoint.Time))); + } + + [Test] + public void TestClear() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new TimingControlPoint()); + cpi.Add(1000, new TimingControlPoint { BeatLength = 500 }); + cpi.Add(10000, new TimingControlPoint { BeatLength = 200 }); + cpi.Add(5000, new TimingControlPoint { BeatLength = 100 }); + cpi.Add(3000, new DifficultyControlPoint { SpeedMultiplier = 2 }); + cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SpeedMultiplier = 4 }); + cpi.GroupAt(1000).Add(new SampleControlPoint { SampleVolume = 0 }); + cpi.GroupAt(8000, true).Add(new EffectControlPoint { KiaiMode = true }); + + cpi.Clear(); + + Assert.That(cpi.Groups.Count, Is.EqualTo(0)); + Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0)); + Assert.That(cpi.AllControlPoints.Count, Is.EqualTo(0)); + } + } +} From 66b00044483f1aa1429401f59c768ce28ab1839d Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Mon, 28 Oct 2019 12:02:58 +0900 Subject: [PATCH 1952/2815] Remove unused logger provider class --- osu.Game/Database/OsuDbContext.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index ea3318598f..2ae07b3cf8 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -166,19 +166,6 @@ namespace osu.Game.Database // no-op. called by tooling. } - private class OsuDbLoggerProvider : ILoggerProvider - { - #region Disposal - - public void Dispose() - { - } - - #endregion - - public ILogger CreateLogger(string categoryName) => new OsuDbLogger(); - } - private class OsuDbLogger : ILogger { public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) From 43ad4a3a3c978b4305a48576ec3769f91d7f929e Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Mon, 28 Oct 2019 12:31:38 +0900 Subject: [PATCH 1953/2815] Tidy up string output --- osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs | 5 +++++ osu.Game/Screens/Edit/Timing/DifficultySection.cs | 2 +- osu.Game/Screens/Edit/Timing/EffectSection.cs | 4 ++-- osu.Game/Screens/Edit/Timing/SampleSection.cs | 4 ++-- osu.Game/Screens/Edit/Timing/TimingSection.cs | 2 +- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index f8c84c79dd..e3451892bf 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -24,6 +24,11 @@ namespace osu.Game.Beatmaps.ControlPoints set => beatLength = MathHelper.Clamp(value, 6, 60000); } + /// <summary> + /// The BPM at this control point. + /// </summary> + public double BPM => 60000 / BeatLength; + private double beatLength = DEFAULT_BEAT_LENGTH; public override bool EquivalentTo(ControlPoint other) => diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs index 150c11f5ee..df61300451 100644 --- a/osu.Game/Screens/Edit/Timing/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Timing/DifficultySection.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier}"; }); + ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier::0.##}x"; }); } protected override DifficultyControlPoint CreatePoint() diff --git a/osu.Game/Screens/Edit/Timing/EffectSection.cs b/osu.Game/Screens/Edit/Timing/EffectSection.cs index ff8817147a..57bee44177 100644 --- a/osu.Game/Screens/Edit/Timing/EffectSection.cs +++ b/osu.Game/Screens/Edit/Timing/EffectSection.cs @@ -25,8 +25,8 @@ namespace osu.Game.Screens.Edit.Timing ControlPoint.BindValueChanged(point => { - kiai.Text = $"Kiai: {point.NewValue?.KiaiMode}"; - omitBarLine.Text = $"Skip Bar Line: {point.NewValue?.OmitFirstBarLine}"; + kiai.Text = $"Kiai: {(point.NewValue?.KiaiMode == true ? "on" : "off")}"; + omitBarLine.Text = $"Skip Bar Line: {(point.NewValue?.OmitFirstBarLine == true ? "on" : "off")}"; }); } diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs index 0d6bc74057..d623ad08ae 100644 --- a/osu.Game/Screens/Edit/Timing/SampleSection.cs +++ b/osu.Game/Screens/Edit/Timing/SampleSection.cs @@ -37,8 +37,8 @@ namespace osu.Game.Screens.Edit.Timing ControlPoint.BindValueChanged(point => { bank.Text = $"Bank: {point.NewValue?.SampleBank}"; - volume.Text = $"Volume: {point.NewValue?.SampleVolume}"; + volume.Text = $"Volume: {point.NewValue?.SampleVolume}%"; }); } } -} \ No newline at end of file +} diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index dcbb6f8bbf..4f2cf82a65 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Edit.Timing ControlPoint.BindValueChanged(point => { - bpm.Text = $"BPM: {point.NewValue?.BeatLength}"; + bpm.Text = $"BPM: {point.NewValue?.BPM:0.##}"; timeSignature.Text = $"Signature: {point.NewValue?.TimeSignature}"; }); } From ee5591d7d502244c61ca8cb2ee515eccc9403086 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Mon, 28 Oct 2019 12:42:17 +0900 Subject: [PATCH 1954/2815] Add missing license headers --- osu.Game/Screens/Edit/Timing/DifficultySection.cs | 3 +++ osu.Game/Screens/Edit/Timing/EffectSection.cs | 3 +++ osu.Game/Screens/Edit/Timing/SampleSection.cs | 3 +++ osu.Game/Screens/Edit/Timing/TimingSection.cs | 3 +++ 4 files changed, 12 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs index df61300451..bd363c3158 100644 --- a/osu.Game/Screens/Edit/Timing/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Timing/DifficultySection.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Framework.Allocation; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; diff --git a/osu.Game/Screens/Edit/Timing/EffectSection.cs b/osu.Game/Screens/Edit/Timing/EffectSection.cs index 57bee44177..bafdfd0b0d 100644 --- a/osu.Game/Screens/Edit/Timing/EffectSection.cs +++ b/osu.Game/Screens/Edit/Timing/EffectSection.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Framework.Allocation; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs index d623ad08ae..0477ad4e78 100644 --- a/osu.Game/Screens/Edit/Timing/SampleSection.cs +++ b/osu.Game/Screens/Edit/Timing/SampleSection.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Framework.Allocation; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 4f2cf82a65..8609da4c4d 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Framework.Allocation; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; From 9acdcc912924ecc83df4bddd80ff59347cdfef94 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Mon, 28 Oct 2019 14:44:45 +0900 Subject: [PATCH 1955/2815] Make all control point attributes bindable Properties are left intact for compatibility reasons. --- .../ControlPoints/DifficultyControlPoint.cs | 19 +++++++---- .../ControlPoints/EffectControlPoint.cs | 26 +++++++++++++-- .../ControlPoints/SampleControlPoint.cs | 27 ++++++++++++++-- .../ControlPoints/TimingControlPoint.cs | 32 ++++++++++++++----- ...egacyDifficultyCalculatorBeatmapDecoder.cs | 6 +++- 5 files changed, 90 insertions(+), 20 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index 42651fd0ca..7726eb0245 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -1,24 +1,31 @@ // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osuTK; +using osu.Framework.Bindables; namespace osu.Game.Beatmaps.ControlPoints { public class DifficultyControlPoint : ControlPoint { + /// <summary> + /// The speed multiplier at this control point. + /// </summary> + public readonly BindableDouble SpeedMultiplierBindable = new BindableDouble(1) + { + MinValue = 0.1, + MaxValue = 10 + }; + /// <summary> /// The speed multiplier at this control point. /// </summary> public double SpeedMultiplier { - get => speedMultiplier; - set => speedMultiplier = MathHelper.Clamp(value, 0.1, 10); + get => SpeedMultiplierBindable.Value; + set => SpeedMultiplierBindable.Value = value; } - private double speedMultiplier = 1; - public override bool EquivalentTo(ControlPoint other) => - other is DifficultyControlPoint otherTyped && otherTyped.SpeedMultiplier.Equals(speedMultiplier); + other is DifficultyControlPoint otherTyped && otherTyped.SpeedMultiplier.Equals(SpeedMultiplier); } } diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs index 928f2a51ad..369b93ff3d 100644 --- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs @@ -1,19 +1,39 @@ // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; + namespace osu.Game.Beatmaps.ControlPoints { public class EffectControlPoint : ControlPoint { /// <summary> - /// Whether this control point enables Kiai mode. + /// Whether the first bar line of this control point is ignored. /// </summary> - public bool KiaiMode; + public readonly BindableBool OmitFirstBarLineBindable = new BindableBool(); /// <summary> /// Whether the first bar line of this control point is ignored. /// </summary> - public bool OmitFirstBarLine; + public bool OmitFirstBarLine + { + get => OmitFirstBarLineBindable.Value; + set => OmitFirstBarLineBindable.Value = value; + } + + /// <summary> + /// Whether this control point enables Kiai mode. + /// </summary> + public readonly BindableBool KiaiModeBindable = new BindableBool(); + + /// <summary> + /// Whether this control point enables Kiai mode. + /// </summary> + public bool KiaiMode + { + get => KiaiModeBindable.Value; + set => KiaiModeBindable.Value = value; + } public override bool EquivalentTo(ControlPoint other) => other is EffectControlPoint otherTyped && diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index 07f5aa6c90..37607c716e 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; using osu.Game.Audio; namespace osu.Game.Beatmaps.ControlPoints @@ -12,12 +13,34 @@ namespace osu.Game.Beatmaps.ControlPoints /// <summary> /// The default sample bank at this control point. /// </summary> - public string SampleBank = DEFAULT_BANK; + public readonly Bindable<string> SampleBankBindable = new Bindable<string>(DEFAULT_BANK); + + /// <summary> + /// The speed multiplier at this control point. + /// </summary> + public string SampleBank + { + get => SampleBankBindable.Value; + set => SampleBankBindable.Value = value; + } + + /// <summary> + /// The default sample bank at this control point. + /// </summary> + public readonly BindableInt SampleVolumeBindable = new BindableInt(100) + { + MinValue = 0, + MaxValue = 100 + }; /// <summary> /// The default sample volume at this control point. /// </summary> - public int SampleVolume = 100; + public int SampleVolume + { + get => SampleVolumeBindable.Value; + set => SampleVolumeBindable.Value = value; + } /// <summary> /// Create a SampleInfo based on the sample settings in this control point. diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index f8c84c79dd..d25d08c5bd 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osuTK; +using osu.Framework.Bindables; using osu.Game.Beatmaps.Timing; namespace osu.Game.Beatmaps.ControlPoints @@ -11,23 +11,39 @@ namespace osu.Game.Beatmaps.ControlPoints /// <summary> /// The time signature at this control point. /// </summary> - public TimeSignatures TimeSignature = TimeSignatures.SimpleQuadruple; + public readonly Bindable<TimeSignatures> TimeSignatureBindable = new Bindable<TimeSignatures>(TimeSignatures.SimpleQuadruple); + + /// <summary> + /// The time signature at this control point. + /// </summary> + public TimeSignatures TimeSignature + { + get => TimeSignatureBindable.Value; + set => TimeSignatureBindable.Value = value; + } public const double DEFAULT_BEAT_LENGTH = 1000; /// <summary> /// The beat length at this control point. /// </summary> - public virtual double BeatLength + public readonly BindableDouble BeatLengthBindable = new BindableDouble(DEFAULT_BEAT_LENGTH) { - get => beatLength; - set => beatLength = MathHelper.Clamp(value, 6, 60000); - } + MinValue = 6, + MaxValue = 60000 + }; - private double beatLength = DEFAULT_BEAT_LENGTH; + /// <summary> + /// The beat length at this control point. + /// </summary> + public double BeatLength + { + get => BeatLengthBindable.Value; + set => BeatLengthBindable.Value = value; + } public override bool EquivalentTo(ControlPoint other) => other is TimingControlPoint otherTyped - && TimeSignature == otherTyped.TimeSignature && beatLength.Equals(otherTyped.beatLength); + && TimeSignature == otherTyped.TimeSignature && BeatLength.Equals(otherTyped.BeatLength); } } diff --git a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs index 238187bf8f..a16d391b99 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs @@ -32,7 +32,11 @@ namespace osu.Game.Beatmaps.Formats private class LegacyDifficultyCalculatorControlPoint : TimingControlPoint { - public override double BeatLength { get; set; } = DEFAULT_BEAT_LENGTH; + public LegacyDifficultyCalculatorControlPoint() + { + BeatLengthBindable.MinValue = double.MinValue; + BeatLengthBindable.MaxValue = double.MaxValue; + } } } } From d33b31f0c5100850be2ae873ca46ab4fdb71fffd Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Mon, 28 Oct 2019 15:33:08 +0900 Subject: [PATCH 1956/2815] Expose Current bindable in LabelledComponents Adds a `LabelledDrawable` class for usages where bindables are not present. --- .../TestSceneLabelledComponent.cs | 14 +- .../UserInterface/TestSceneLabelledTextBox.cs | 3 +- osu.Game.Tournament/Screens/SetupScreen.cs | 2 +- .../UserInterfaceV2/LabelledComponent.cs | 124 ++-------------- .../UserInterfaceV2/LabelledDrawable.cs | 132 ++++++++++++++++++ .../UserInterfaceV2/LabelledSwitchButton.cs | 2 +- .../UserInterfaceV2/LabelledTextBox.cs | 2 +- 7 files changed, 151 insertions(+), 128 deletions(-) create mode 100644 osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs index 700adad9cb..8179f92ffc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs @@ -11,7 +11,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.UserInterface { - public class TestSceneLabelledComponent : OsuTestScene + public class TestSceneLabelledDrawable : OsuTestScene { [TestCase(false)] [TestCase(true)] @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("create component", () => { - LabelledComponent<Drawable> component; + LabelledDrawable<Drawable> component; Child = new Container { @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.UserInterface Origin = Anchor.Centre, Width = 500, AutoSizeAxes = Axes.Y, - Child = component = padded ? (LabelledComponent<Drawable>)new PaddedLabelledComponent() : new NonPaddedLabelledComponent(), + Child = component = padded ? (LabelledDrawable<Drawable>)new PaddedLabelledDrawable() : new NonPaddedLabelledDrawable(), }; component.Label = "a sample component"; @@ -41,9 +41,9 @@ namespace osu.Game.Tests.Visual.UserInterface }); } - private class PaddedLabelledComponent : LabelledComponent<Drawable> + private class PaddedLabelledDrawable : LabelledDrawable<Drawable> { - public PaddedLabelledComponent() + public PaddedLabelledDrawable() : base(true) { } @@ -57,9 +57,9 @@ namespace osu.Game.Tests.Visual.UserInterface }; } - private class NonPaddedLabelledComponent : LabelledComponent<Drawable> + private class NonPaddedLabelledDrawable : LabelledDrawable<Drawable> { - public NonPaddedLabelledComponent() + public NonPaddedLabelledDrawable() : base(false) { } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs index 53a2bfabbc..8208b55952 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs @@ -7,7 +7,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; namespace osu.Game.Tests.Visual.UserInterface @@ -28,7 +27,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("create component", () => { - LabelledComponent<OsuTextBox> component; + LabelledTextBox component; Child = new Container { diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 091a837745..a67daa2756 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -89,7 +89,7 @@ namespace osu.Game.Tournament.Screens }; } - private class ActionableInfo : LabelledComponent<Drawable> + private class ActionableInfo : LabelledDrawable<Drawable> { private OsuButton button; diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs index 2e659825b7..1819b36667 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs @@ -1,132 +1,24 @@ // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.Game.Graphics.Containers; -using osuTK; +using osu.Framework.Graphics.UserInterface; namespace osu.Game.Graphics.UserInterfaceV2 { - public abstract class LabelledComponent<T> : CompositeDrawable - where T : Drawable + public abstract class LabelledComponent<T, U> : LabelledDrawable<T>, IHasCurrentValue<U> + where T : Drawable, IHasCurrentValue<U> { - protected const float CONTENT_PADDING_VERTICAL = 10; - protected const float CONTENT_PADDING_HORIZONTAL = 15; - protected const float CORNER_RADIUS = 15; - - /// <summary> - /// The component that is being displayed. - /// </summary> - protected readonly T Component; - - private readonly OsuTextFlowContainer labelText; - private readonly OsuTextFlowContainer descriptionText; - - /// <summary> - /// Creates a new <see cref="LabelledComponent{T}"/>. - /// </summary> - /// <param name="padded">Whether the component should be padded or should be expanded to the bounds of this <see cref="LabelledComponent{T}"/>.</param> protected LabelledComponent(bool padded) + : base(padded) { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - CornerRadius = CORNER_RADIUS; - Masking = true; - - InternalChildren = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex("1c2125"), - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Padding = padded - ? new MarginPadding { Horizontal = CONTENT_PADDING_HORIZONTAL, Vertical = CONTENT_PADDING_VERTICAL } - : new MarginPadding { Left = CONTENT_PADDING_HORIZONTAL }, - Spacing = new Vector2(0, 12), - Children = new Drawable[] - { - new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Content = new[] - { - new Drawable[] - { - labelText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(weight: FontWeight.Bold)) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = 20 } - }, - new Container - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Child = Component = CreateComponent().With(d => - { - d.Anchor = Anchor.CentreRight; - d.Origin = Anchor.CentreRight; - }) - } - }, - }, - RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, - ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } - }, - descriptionText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold, italics: true)) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Bottom = padded ? 0 : CONTENT_PADDING_VERTICAL }, - Alpha = 0, - } - } - } - }; } - [BackgroundDependencyLoader] - private void load(OsuColour osuColour) + public Bindable<U> Current { - descriptionText.Colour = osuColour.Yellow; + get => Component.Current; + set => Component.Current = value; } - - public string Label - { - set => labelText.Text = value; - } - - public string Description - { - set - { - descriptionText.Text = value; - - if (!string.IsNullOrEmpty(value)) - descriptionText.Show(); - else - descriptionText.Hide(); - } - } - - /// <summary> - /// Creates the component that should be displayed. - /// </summary> - /// <returns>The component.</returns> - protected abstract T CreateComponent(); } } diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs new file mode 100644 index 0000000000..f44bd72aee --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs @@ -0,0 +1,132 @@ +// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.Game.Graphics.Containers; +using osuTK; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public abstract class LabelledDrawable<T> : CompositeDrawable + where T : Drawable + { + protected const float CONTENT_PADDING_VERTICAL = 10; + protected const float CONTENT_PADDING_HORIZONTAL = 15; + protected const float CORNER_RADIUS = 15; + + /// <summary> + /// The component that is being displayed. + /// </summary> + protected readonly T Component; + + private readonly OsuTextFlowContainer labelText; + private readonly OsuTextFlowContainer descriptionText; + + /// <summary> + /// Creates a new <see cref="LabelledComponent{T, U}"/>. + /// </summary> + /// <param name="padded">Whether the component should be padded or should be expanded to the bounds of this <see cref="LabelledComponent{T, U}"/>.</param> + protected LabelledDrawable(bool padded) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + CornerRadius = CORNER_RADIUS; + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex("1c2125"), + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = padded + ? new MarginPadding { Horizontal = CONTENT_PADDING_HORIZONTAL, Vertical = CONTENT_PADDING_VERTICAL } + : new MarginPadding { Left = CONTENT_PADDING_HORIZONTAL }, + Spacing = new Vector2(0, 12), + Children = new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] + { + new Drawable[] + { + labelText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(weight: FontWeight.Bold)) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = 20 } + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = Component = CreateComponent().With(d => + { + d.Anchor = Anchor.CentreRight; + d.Origin = Anchor.CentreRight; + }) + } + }, + }, + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, + ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } + }, + descriptionText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold, italics: true)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Bottom = padded ? 0 : CONTENT_PADDING_VERTICAL }, + Alpha = 0, + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour osuColour) + { + descriptionText.Colour = osuColour.Yellow; + } + + public string Label + { + set => labelText.Text = value; + } + + public string Description + { + set + { + descriptionText.Text = value; + + if (!string.IsNullOrEmpty(value)) + descriptionText.Show(); + else + descriptionText.Hide(); + } + } + + /// <summary> + /// Creates the component that should be displayed. + /// </summary> + /// <returns>The component.</returns> + protected abstract T CreateComponent(); + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledSwitchButton.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledSwitchButton.cs index c973f1d13e..c374d80830 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledSwitchButton.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledSwitchButton.cs @@ -3,7 +3,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 { - public class LabelledSwitchButton : LabelledComponent<SwitchButton> + public class LabelledSwitchButton : LabelledComponent<SwitchButton, bool> { public LabelledSwitchButton() : base(true) diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs index 50d2a14482..2cbe095d0b 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs @@ -8,7 +8,7 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Graphics.UserInterfaceV2 { - public class LabelledTextBox : LabelledComponent<OsuTextBox> + public class LabelledTextBox : LabelledComponent<OsuTextBox, string> { public event TextBox.OnCommitHandler OnCommit; From 0a11cbf656aef28930ad01e1306ed907958d1832 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Mon, 28 Oct 2019 16:20:33 +0900 Subject: [PATCH 1957/2815] Make OsuButton correctly block hover events --- osu.Game/Graphics/UserInterface/OsuButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index c1810800a0..4124d2ad58 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -59,7 +59,7 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnHover(HoverEvent e) { hover.FadeIn(200); - return base.OnHover(e); + return true; } protected override void OnHoverLost(HoverLostEvent e) From 9c3e54909ca56882d230d4b4ebbd39d36fda96bb Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Mon, 28 Oct 2019 16:20:54 +0900 Subject: [PATCH 1958/2815] Ensure tooltips of RowAttributes are up-to-date --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 8 ++++---- osu.Game/Screens/Edit/Timing/RowAttribute.cs | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 729e631d87..96e3ab48f2 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -128,17 +128,17 @@ namespace osu.Game.Screens.Edit.Timing switch (controlPoint) { case TimingControlPoint timing: - return new RowAttribute("timing", $"{60000 / timing.BeatLength:n1}bpm {timing.TimeSignature}"); + return new RowAttribute("timing", () => $"{60000 / timing.BeatLength:n1}bpm {timing.TimeSignature}"); case DifficultyControlPoint difficulty: - return new RowAttribute("difficulty", $"{difficulty.SpeedMultiplier:n2}x"); + return new RowAttribute("difficulty", () => $"{difficulty.SpeedMultiplier:n2}x"); case EffectControlPoint effect: - return new RowAttribute("effect", $"{(effect.KiaiMode ? "Kiai " : "")}{(effect.OmitFirstBarLine ? "NoBarLine " : "")}"); + return new RowAttribute("effect", () => $"{(effect.KiaiMode ? "Kiai " : "")}{(effect.OmitFirstBarLine ? "NoBarLine " : "")}"); case SampleControlPoint sample: - return new RowAttribute("sample", $"{sample.SampleBank} {sample.SampleVolume}%"); + return new RowAttribute("sample", () => $"{sample.SampleBank} {sample.SampleVolume}%"); } return null; diff --git a/osu.Game/Screens/Edit/Timing/RowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttribute.cs index 8716382142..be8f693683 100644 --- a/osu.Game/Screens/Edit/Timing/RowAttribute.cs +++ b/osu.Game/Screens/Edit/Timing/RowAttribute.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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; @@ -14,9 +15,9 @@ namespace osu.Game.Screens.Edit.Timing public class RowAttribute : CompositeDrawable, IHasTooltip { private readonly string header; - private readonly string content; + private readonly Func<string> content; - public RowAttribute(string header, string content) + public RowAttribute(string header, Func<string> content) { this.header = header; this.content = content; @@ -54,6 +55,6 @@ namespace osu.Game.Screens.Edit.Timing }; } - public string TooltipText => content; + public string TooltipText => content(); } } From f761eddec754ef130297d12b4e383e9988d55de2 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Mon, 28 Oct 2019 16:21:14 +0900 Subject: [PATCH 1959/2815] Add default bindable values --- osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs | 2 ++ osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs | 5 +++-- osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index 7726eb0245..8b21098a51 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -12,6 +12,8 @@ namespace osu.Game.Beatmaps.ControlPoints /// </summary> public readonly BindableDouble SpeedMultiplierBindable = new BindableDouble(1) { + Precision = 0.1, + Default = 1, MinValue = 0.1, MaxValue = 10 }; diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index 37607c716e..42865c686c 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -13,7 +13,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// <summary> /// The default sample bank at this control point. /// </summary> - public readonly Bindable<string> SampleBankBindable = new Bindable<string>(DEFAULT_BANK); + public readonly Bindable<string> SampleBankBindable = new Bindable<string>(DEFAULT_BANK) { Default = DEFAULT_BANK }; /// <summary> /// The speed multiplier at this control point. @@ -30,7 +30,8 @@ namespace osu.Game.Beatmaps.ControlPoints public readonly BindableInt SampleVolumeBindable = new BindableInt(100) { MinValue = 0, - MaxValue = 100 + MaxValue = 100, + Default = 100 }; /// <summary> diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index fb548e60aa..51b3377394 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -11,7 +11,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// <summary> /// The time signature at this control point. /// </summary> - public readonly Bindable<TimeSignatures> TimeSignatureBindable = new Bindable<TimeSignatures>(TimeSignatures.SimpleQuadruple); + public readonly Bindable<TimeSignatures> TimeSignatureBindable = new Bindable<TimeSignatures>(TimeSignatures.SimpleQuadruple) { Default = TimeSignatures.SimpleQuadruple }; /// <summary> /// The time signature at this control point. @@ -29,6 +29,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// </summary> public readonly BindableDouble BeatLengthBindable = new BindableDouble(DEFAULT_BEAT_LENGTH) { + Default = DEFAULT_BEAT_LENGTH, MinValue = 6, MaxValue = 60000 }; From 522572eacefefa13ae0f4524402984ce141932f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Mon, 28 Oct 2019 16:21:31 +0900 Subject: [PATCH 1960/2815] Add ability to adjust all control point attributes --- .../Screens/Edit/Timing/DifficultySection.cs | 20 +++++-- osu.Game/Screens/Edit/Timing/EffectSection.cs | 17 +++--- osu.Game/Screens/Edit/Timing/SampleSection.cs | 28 ++++++--- osu.Game/Screens/Edit/Timing/TimingSection.cs | 59 ++++++++++++++++--- 4 files changed, 97 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs index bd363c3158..d3e1b2a84f 100644 --- a/osu.Game/Screens/Edit/Timing/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Timing/DifficultySection.cs @@ -2,21 +2,27 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Sprites; +using osu.Game.Overlays.Settings; namespace osu.Game.Screens.Edit.Timing { internal class DifficultySection : Section<DifficultyControlPoint> { - private OsuSpriteText multiplier; + private SettingsSlider<double> multiplier; [BackgroundDependencyLoader] private void load() { Flow.AddRange(new[] { - multiplier = new OsuSpriteText(), + multiplier = new SettingsSlider<double> + { + LabelText = "Speed Multiplier", + Bindable = new DifficultyControlPoint().SpeedMultiplierBindable, + RelativeSizeAxes = Axes.X, + } }); } @@ -24,7 +30,13 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier::0.##}x"; }); + ControlPoint.BindValueChanged(point => + { + if (point.NewValue != null) + { + multiplier.Bindable = point.NewValue.SpeedMultiplierBindable; + } + }, true); } protected override DifficultyControlPoint CreatePoint() diff --git a/osu.Game/Screens/Edit/Timing/EffectSection.cs b/osu.Game/Screens/Edit/Timing/EffectSection.cs index bafdfd0b0d..fcd733cf00 100644 --- a/osu.Game/Screens/Edit/Timing/EffectSection.cs +++ b/osu.Game/Screens/Edit/Timing/EffectSection.cs @@ -3,22 +3,22 @@ using osu.Framework.Allocation; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterfaceV2; namespace osu.Game.Screens.Edit.Timing { internal class EffectSection : Section<EffectControlPoint> { - private OsuSpriteText kiai; - private OsuSpriteText omitBarLine; + private LabelledSwitchButton kiai; + private LabelledSwitchButton omitBarLine; [BackgroundDependencyLoader] private void load() { Flow.AddRange(new[] { - kiai = new OsuSpriteText(), - omitBarLine = new OsuSpriteText(), + kiai = new LabelledSwitchButton { Label = "Kiai Time" }, + omitBarLine = new LabelledSwitchButton { Label = "Skip Bar Line" }, }); } @@ -28,8 +28,11 @@ namespace osu.Game.Screens.Edit.Timing ControlPoint.BindValueChanged(point => { - kiai.Text = $"Kiai: {(point.NewValue?.KiaiMode == true ? "on" : "off")}"; - omitBarLine.Text = $"Skip Bar Line: {(point.NewValue?.OmitFirstBarLine == true ? "on" : "off")}"; + if (point.NewValue != null) + { + kiai.Current = point.NewValue.KiaiModeBindable; + omitBarLine.Current = point.NewValue.OmitFirstBarLineBindable; + } }); } diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs index 0477ad4e78..816f90d19e 100644 --- a/osu.Game/Screens/Edit/Timing/SampleSection.cs +++ b/osu.Game/Screens/Edit/Timing/SampleSection.cs @@ -2,23 +2,32 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays.Settings; namespace osu.Game.Screens.Edit.Timing { internal class SampleSection : Section<SampleControlPoint> { - private OsuSpriteText bank; - private OsuSpriteText volume; + private LabelledTextBox bank; + private SettingsSlider<int> volume; [BackgroundDependencyLoader] private void load() { - Flow.AddRange(new[] + Flow.AddRange(new Drawable[] { - bank = new OsuSpriteText(), - volume = new OsuSpriteText(), + bank = new LabelledTextBox + { + Label = "Bank Name", + }, + volume = new SettingsSlider<int> + { + Bindable = new SampleControlPoint().SampleVolumeBindable, + LabelText = "Volume", + } }); } @@ -39,8 +48,11 @@ namespace osu.Game.Screens.Edit.Timing ControlPoint.BindValueChanged(point => { - bank.Text = $"Bank: {point.NewValue?.SampleBank}"; - volume.Text = $"Volume: {point.NewValue?.SampleVolume}%"; + if (point.NewValue != null) + { + bank.Current = point.NewValue.SampleBankBindable; + volume.Bindable = point.NewValue.SampleVolumeBindable; + } }); } } diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 8609da4c4d..9aecbe9160 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -2,23 +2,33 @@ // 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.Beatmaps.ControlPoints; -using osu.Game.Graphics.Sprites; +using osu.Game.Beatmaps.Timing; +using osu.Game.Overlays.Settings; namespace osu.Game.Screens.Edit.Timing { internal class TimingSection : Section<TimingControlPoint> { - private OsuSpriteText bpm; - private OsuSpriteText timeSignature; + private SettingsSlider<double> bpm; + private SettingsEnumDropdown<TimeSignatures> timeSignature; [BackgroundDependencyLoader] private void load() { - Flow.AddRange(new[] + Flow.AddRange(new Drawable[] { - bpm = new OsuSpriteText(), - timeSignature = new OsuSpriteText(), + bpm = new BPMSlider + { + Bindable = new TimingControlPoint().BeatLengthBindable, + LabelText = "BPM", + }, + timeSignature = new SettingsEnumDropdown<TimeSignatures> + { + LabelText = "Time Signature" + }, }); } @@ -28,8 +38,11 @@ namespace osu.Game.Screens.Edit.Timing ControlPoint.BindValueChanged(point => { - bpm.Text = $"BPM: {point.NewValue?.BPM:0.##}"; - timeSignature.Text = $"Signature: {point.NewValue?.TimeSignature}"; + if (point.NewValue != null) + { + bpm.Bindable = point.NewValue.BeatLengthBindable; + timeSignature.Bindable = point.NewValue.TimeSignatureBindable; + } }); } @@ -43,5 +56,35 @@ namespace osu.Game.Screens.Edit.Timing TimeSignature = reference.TimeSignature }; } + + private class BPMSlider : SettingsSlider<double> + { + private readonly BindableDouble beatLengthBindable = new BindableDouble(); + + private BindableDouble bpmBindable; + + public override Bindable<double> Bindable + { + get => base.Bindable; + set + { + // incoming will be beatlength + + beatLengthBindable.UnbindBindings(); + beatLengthBindable.BindTo(value); + + base.Bindable = bpmBindable = new BindableDouble(beatLengthToBpm(beatLengthBindable.Value)) + { + MinValue = beatLengthToBpm(beatLengthBindable.MaxValue), + MaxValue = beatLengthToBpm(beatLengthBindable.MinValue), + Default = beatLengthToBpm(beatLengthBindable.Default), + }; + + bpmBindable.BindValueChanged(bpm => beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue)); + } + } + + private double beatLengthToBpm(double beatLength) => 60000 / beatLength; + } } } From 8f87957c7089fb731aa28063486b356ee031cb75 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Mon, 28 Oct 2019 14:44:45 +0900 Subject: [PATCH 1961/2815] Make all control point attributes bindable Properties are left intact for compatibility reasons. --- .../ControlPoints/DifficultyControlPoint.cs | 19 +++++++---- .../ControlPoints/EffectControlPoint.cs | 26 +++++++++++++-- .../ControlPoints/SampleControlPoint.cs | 27 ++++++++++++++-- .../ControlPoints/TimingControlPoint.cs | 32 ++++++++++++++----- ...egacyDifficultyCalculatorBeatmapDecoder.cs | 6 +++- 5 files changed, 90 insertions(+), 20 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index 42651fd0ca..7726eb0245 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -1,24 +1,31 @@ // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osuTK; +using osu.Framework.Bindables; namespace osu.Game.Beatmaps.ControlPoints { public class DifficultyControlPoint : ControlPoint { + /// <summary> + /// The speed multiplier at this control point. + /// </summary> + public readonly BindableDouble SpeedMultiplierBindable = new BindableDouble(1) + { + MinValue = 0.1, + MaxValue = 10 + }; + /// <summary> /// The speed multiplier at this control point. /// </summary> public double SpeedMultiplier { - get => speedMultiplier; - set => speedMultiplier = MathHelper.Clamp(value, 0.1, 10); + get => SpeedMultiplierBindable.Value; + set => SpeedMultiplierBindable.Value = value; } - private double speedMultiplier = 1; - public override bool EquivalentTo(ControlPoint other) => - other is DifficultyControlPoint otherTyped && otherTyped.SpeedMultiplier.Equals(speedMultiplier); + other is DifficultyControlPoint otherTyped && otherTyped.SpeedMultiplier.Equals(SpeedMultiplier); } } diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs index 928f2a51ad..369b93ff3d 100644 --- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs @@ -1,19 +1,39 @@ // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; + namespace osu.Game.Beatmaps.ControlPoints { public class EffectControlPoint : ControlPoint { /// <summary> - /// Whether this control point enables Kiai mode. + /// Whether the first bar line of this control point is ignored. /// </summary> - public bool KiaiMode; + public readonly BindableBool OmitFirstBarLineBindable = new BindableBool(); /// <summary> /// Whether the first bar line of this control point is ignored. /// </summary> - public bool OmitFirstBarLine; + public bool OmitFirstBarLine + { + get => OmitFirstBarLineBindable.Value; + set => OmitFirstBarLineBindable.Value = value; + } + + /// <summary> + /// Whether this control point enables Kiai mode. + /// </summary> + public readonly BindableBool KiaiModeBindable = new BindableBool(); + + /// <summary> + /// Whether this control point enables Kiai mode. + /// </summary> + public bool KiaiMode + { + get => KiaiModeBindable.Value; + set => KiaiModeBindable.Value = value; + } public override bool EquivalentTo(ControlPoint other) => other is EffectControlPoint otherTyped && diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index 07f5aa6c90..37607c716e 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; using osu.Game.Audio; namespace osu.Game.Beatmaps.ControlPoints @@ -12,12 +13,34 @@ namespace osu.Game.Beatmaps.ControlPoints /// <summary> /// The default sample bank at this control point. /// </summary> - public string SampleBank = DEFAULT_BANK; + public readonly Bindable<string> SampleBankBindable = new Bindable<string>(DEFAULT_BANK); + + /// <summary> + /// The speed multiplier at this control point. + /// </summary> + public string SampleBank + { + get => SampleBankBindable.Value; + set => SampleBankBindable.Value = value; + } + + /// <summary> + /// The default sample bank at this control point. + /// </summary> + public readonly BindableInt SampleVolumeBindable = new BindableInt(100) + { + MinValue = 0, + MaxValue = 100 + }; /// <summary> /// The default sample volume at this control point. /// </summary> - public int SampleVolume = 100; + public int SampleVolume + { + get => SampleVolumeBindable.Value; + set => SampleVolumeBindable.Value = value; + } /// <summary> /// Create a SampleInfo based on the sample settings in this control point. diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index f8c84c79dd..d25d08c5bd 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osuTK; +using osu.Framework.Bindables; using osu.Game.Beatmaps.Timing; namespace osu.Game.Beatmaps.ControlPoints @@ -11,23 +11,39 @@ namespace osu.Game.Beatmaps.ControlPoints /// <summary> /// The time signature at this control point. /// </summary> - public TimeSignatures TimeSignature = TimeSignatures.SimpleQuadruple; + public readonly Bindable<TimeSignatures> TimeSignatureBindable = new Bindable<TimeSignatures>(TimeSignatures.SimpleQuadruple); + + /// <summary> + /// The time signature at this control point. + /// </summary> + public TimeSignatures TimeSignature + { + get => TimeSignatureBindable.Value; + set => TimeSignatureBindable.Value = value; + } public const double DEFAULT_BEAT_LENGTH = 1000; /// <summary> /// The beat length at this control point. /// </summary> - public virtual double BeatLength + public readonly BindableDouble BeatLengthBindable = new BindableDouble(DEFAULT_BEAT_LENGTH) { - get => beatLength; - set => beatLength = MathHelper.Clamp(value, 6, 60000); - } + MinValue = 6, + MaxValue = 60000 + }; - private double beatLength = DEFAULT_BEAT_LENGTH; + /// <summary> + /// The beat length at this control point. + /// </summary> + public double BeatLength + { + get => BeatLengthBindable.Value; + set => BeatLengthBindable.Value = value; + } public override bool EquivalentTo(ControlPoint other) => other is TimingControlPoint otherTyped - && TimeSignature == otherTyped.TimeSignature && beatLength.Equals(otherTyped.beatLength); + && TimeSignature == otherTyped.TimeSignature && BeatLength.Equals(otherTyped.BeatLength); } } diff --git a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs index 238187bf8f..a16d391b99 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs @@ -32,7 +32,11 @@ namespace osu.Game.Beatmaps.Formats private class LegacyDifficultyCalculatorControlPoint : TimingControlPoint { - public override double BeatLength { get; set; } = DEFAULT_BEAT_LENGTH; + public LegacyDifficultyCalculatorControlPoint() + { + BeatLengthBindable.MinValue = double.MinValue; + BeatLengthBindable.MaxValue = double.MaxValue; + } } } } From 090881cf6f742637e7b2546fab04853bd3df048b Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Mon, 28 Oct 2019 16:21:14 +0900 Subject: [PATCH 1962/2815] Add default bindable values --- osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs | 2 ++ osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs | 5 +++-- osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index 7726eb0245..8b21098a51 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -12,6 +12,8 @@ namespace osu.Game.Beatmaps.ControlPoints /// </summary> public readonly BindableDouble SpeedMultiplierBindable = new BindableDouble(1) { + Precision = 0.1, + Default = 1, MinValue = 0.1, MaxValue = 10 }; diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index 37607c716e..42865c686c 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -13,7 +13,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// <summary> /// The default sample bank at this control point. /// </summary> - public readonly Bindable<string> SampleBankBindable = new Bindable<string>(DEFAULT_BANK); + public readonly Bindable<string> SampleBankBindable = new Bindable<string>(DEFAULT_BANK) { Default = DEFAULT_BANK }; /// <summary> /// The speed multiplier at this control point. @@ -30,7 +30,8 @@ namespace osu.Game.Beatmaps.ControlPoints public readonly BindableInt SampleVolumeBindable = new BindableInt(100) { MinValue = 0, - MaxValue = 100 + MaxValue = 100, + Default = 100 }; /// <summary> diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index d25d08c5bd..c00b04b660 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -11,7 +11,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// <summary> /// The time signature at this control point. /// </summary> - public readonly Bindable<TimeSignatures> TimeSignatureBindable = new Bindable<TimeSignatures>(TimeSignatures.SimpleQuadruple); + public readonly Bindable<TimeSignatures> TimeSignatureBindable = new Bindable<TimeSignatures>(TimeSignatures.SimpleQuadruple) { Default = TimeSignatures.SimpleQuadruple }; /// <summary> /// The time signature at this control point. @@ -29,6 +29,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// </summary> public readonly BindableDouble BeatLengthBindable = new BindableDouble(DEFAULT_BEAT_LENGTH) { + Default = DEFAULT_BEAT_LENGTH, MinValue = 6, MaxValue = 60000 }; From 6980f488dcab3baed2acf980471a512e6e07c448 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Mon, 28 Oct 2019 16:20:33 +0900 Subject: [PATCH 1963/2815] Make OsuButton correctly block hover events --- osu.Game/Graphics/UserInterface/OsuButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index c1810800a0..4124d2ad58 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -59,7 +59,7 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnHover(HoverEvent e) { hover.FadeIn(200); - return base.OnHover(e); + return true; } protected override void OnHoverLost(HoverLostEvent e) From 08040adfad386e5e0f7a916f86f0d35ec971a0ae Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Mon, 28 Oct 2019 15:33:08 +0900 Subject: [PATCH 1964/2815] Expose Current bindable in LabelledComponents Adds a `LabelledDrawable` class for usages where bindables are not present. --- .../TestSceneLabelledComponent.cs | 14 +- .../UserInterface/TestSceneLabelledTextBox.cs | 3 +- osu.Game.Tournament/Screens/SetupScreen.cs | 2 +- .../UserInterfaceV2/LabelledComponent.cs | 124 ++-------------- .../UserInterfaceV2/LabelledDrawable.cs | 132 ++++++++++++++++++ .../UserInterfaceV2/LabelledSwitchButton.cs | 2 +- .../UserInterfaceV2/LabelledTextBox.cs | 2 +- 7 files changed, 151 insertions(+), 128 deletions(-) create mode 100644 osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs index 700adad9cb..8179f92ffc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs @@ -11,7 +11,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.UserInterface { - public class TestSceneLabelledComponent : OsuTestScene + public class TestSceneLabelledDrawable : OsuTestScene { [TestCase(false)] [TestCase(true)] @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("create component", () => { - LabelledComponent<Drawable> component; + LabelledDrawable<Drawable> component; Child = new Container { @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.UserInterface Origin = Anchor.Centre, Width = 500, AutoSizeAxes = Axes.Y, - Child = component = padded ? (LabelledComponent<Drawable>)new PaddedLabelledComponent() : new NonPaddedLabelledComponent(), + Child = component = padded ? (LabelledDrawable<Drawable>)new PaddedLabelledDrawable() : new NonPaddedLabelledDrawable(), }; component.Label = "a sample component"; @@ -41,9 +41,9 @@ namespace osu.Game.Tests.Visual.UserInterface }); } - private class PaddedLabelledComponent : LabelledComponent<Drawable> + private class PaddedLabelledDrawable : LabelledDrawable<Drawable> { - public PaddedLabelledComponent() + public PaddedLabelledDrawable() : base(true) { } @@ -57,9 +57,9 @@ namespace osu.Game.Tests.Visual.UserInterface }; } - private class NonPaddedLabelledComponent : LabelledComponent<Drawable> + private class NonPaddedLabelledDrawable : LabelledDrawable<Drawable> { - public NonPaddedLabelledComponent() + public NonPaddedLabelledDrawable() : base(false) { } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs index 53a2bfabbc..8208b55952 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs @@ -7,7 +7,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; namespace osu.Game.Tests.Visual.UserInterface @@ -28,7 +27,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("create component", () => { - LabelledComponent<OsuTextBox> component; + LabelledTextBox component; Child = new Container { diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 091a837745..a67daa2756 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -89,7 +89,7 @@ namespace osu.Game.Tournament.Screens }; } - private class ActionableInfo : LabelledComponent<Drawable> + private class ActionableInfo : LabelledDrawable<Drawable> { private OsuButton button; diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs index 2e659825b7..1819b36667 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs @@ -1,132 +1,24 @@ // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.Game.Graphics.Containers; -using osuTK; +using osu.Framework.Graphics.UserInterface; namespace osu.Game.Graphics.UserInterfaceV2 { - public abstract class LabelledComponent<T> : CompositeDrawable - where T : Drawable + public abstract class LabelledComponent<T, U> : LabelledDrawable<T>, IHasCurrentValue<U> + where T : Drawable, IHasCurrentValue<U> { - protected const float CONTENT_PADDING_VERTICAL = 10; - protected const float CONTENT_PADDING_HORIZONTAL = 15; - protected const float CORNER_RADIUS = 15; - - /// <summary> - /// The component that is being displayed. - /// </summary> - protected readonly T Component; - - private readonly OsuTextFlowContainer labelText; - private readonly OsuTextFlowContainer descriptionText; - - /// <summary> - /// Creates a new <see cref="LabelledComponent{T}"/>. - /// </summary> - /// <param name="padded">Whether the component should be padded or should be expanded to the bounds of this <see cref="LabelledComponent{T}"/>.</param> protected LabelledComponent(bool padded) + : base(padded) { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - CornerRadius = CORNER_RADIUS; - Masking = true; - - InternalChildren = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex("1c2125"), - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Padding = padded - ? new MarginPadding { Horizontal = CONTENT_PADDING_HORIZONTAL, Vertical = CONTENT_PADDING_VERTICAL } - : new MarginPadding { Left = CONTENT_PADDING_HORIZONTAL }, - Spacing = new Vector2(0, 12), - Children = new Drawable[] - { - new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Content = new[] - { - new Drawable[] - { - labelText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(weight: FontWeight.Bold)) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = 20 } - }, - new Container - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Child = Component = CreateComponent().With(d => - { - d.Anchor = Anchor.CentreRight; - d.Origin = Anchor.CentreRight; - }) - } - }, - }, - RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, - ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } - }, - descriptionText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold, italics: true)) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Bottom = padded ? 0 : CONTENT_PADDING_VERTICAL }, - Alpha = 0, - } - } - } - }; } - [BackgroundDependencyLoader] - private void load(OsuColour osuColour) + public Bindable<U> Current { - descriptionText.Colour = osuColour.Yellow; + get => Component.Current; + set => Component.Current = value; } - - public string Label - { - set => labelText.Text = value; - } - - public string Description - { - set - { - descriptionText.Text = value; - - if (!string.IsNullOrEmpty(value)) - descriptionText.Show(); - else - descriptionText.Hide(); - } - } - - /// <summary> - /// Creates the component that should be displayed. - /// </summary> - /// <returns>The component.</returns> - protected abstract T CreateComponent(); } } diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs new file mode 100644 index 0000000000..f44bd72aee --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs @@ -0,0 +1,132 @@ +// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.Game.Graphics.Containers; +using osuTK; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public abstract class LabelledDrawable<T> : CompositeDrawable + where T : Drawable + { + protected const float CONTENT_PADDING_VERTICAL = 10; + protected const float CONTENT_PADDING_HORIZONTAL = 15; + protected const float CORNER_RADIUS = 15; + + /// <summary> + /// The component that is being displayed. + /// </summary> + protected readonly T Component; + + private readonly OsuTextFlowContainer labelText; + private readonly OsuTextFlowContainer descriptionText; + + /// <summary> + /// Creates a new <see cref="LabelledComponent{T, U}"/>. + /// </summary> + /// <param name="padded">Whether the component should be padded or should be expanded to the bounds of this <see cref="LabelledComponent{T, U}"/>.</param> + protected LabelledDrawable(bool padded) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + CornerRadius = CORNER_RADIUS; + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex("1c2125"), + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = padded + ? new MarginPadding { Horizontal = CONTENT_PADDING_HORIZONTAL, Vertical = CONTENT_PADDING_VERTICAL } + : new MarginPadding { Left = CONTENT_PADDING_HORIZONTAL }, + Spacing = new Vector2(0, 12), + Children = new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] + { + new Drawable[] + { + labelText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(weight: FontWeight.Bold)) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = 20 } + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = Component = CreateComponent().With(d => + { + d.Anchor = Anchor.CentreRight; + d.Origin = Anchor.CentreRight; + }) + } + }, + }, + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, + ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } + }, + descriptionText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold, italics: true)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Bottom = padded ? 0 : CONTENT_PADDING_VERTICAL }, + Alpha = 0, + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour osuColour) + { + descriptionText.Colour = osuColour.Yellow; + } + + public string Label + { + set => labelText.Text = value; + } + + public string Description + { + set + { + descriptionText.Text = value; + + if (!string.IsNullOrEmpty(value)) + descriptionText.Show(); + else + descriptionText.Hide(); + } + } + + /// <summary> + /// Creates the component that should be displayed. + /// </summary> + /// <returns>The component.</returns> + protected abstract T CreateComponent(); + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledSwitchButton.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledSwitchButton.cs index c973f1d13e..c374d80830 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledSwitchButton.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledSwitchButton.cs @@ -3,7 +3,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 { - public class LabelledSwitchButton : LabelledComponent<SwitchButton> + public class LabelledSwitchButton : LabelledComponent<SwitchButton, bool> { public LabelledSwitchButton() : base(true) diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs index 50d2a14482..2cbe095d0b 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs @@ -8,7 +8,7 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Graphics.UserInterfaceV2 { - public class LabelledTextBox : LabelledComponent<OsuTextBox> + public class LabelledTextBox : LabelledComponent<OsuTextBox, string> { public event TextBox.OnCommitHandler OnCommit; From cf3ed42bfc9e299c599e4215ab72ea2df69f7940 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Mon, 28 Oct 2019 17:41:42 +0900 Subject: [PATCH 1965/2815] Fix download tracking components getting stuck on import failures --- osu.Game/Database/ArchiveModelManager.cs | 4 +++- osu.Game/Database/DownloadableArchiveModelManager.cs | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index b567f0c0e3..9fed8e03ac 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -108,7 +108,7 @@ namespace osu.Game.Database return Import(notification, paths); } - protected async Task Import(ProgressNotification notification, params string[] paths) + protected async Task<IEnumerable<TModel>> Import(ProgressNotification notification, params string[] paths) { notification.Progress = 0; notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is initialising..."; @@ -168,6 +168,8 @@ namespace osu.Game.Database notification.State = ProgressNotificationState.Completed; } + + return imported; } /// <summary> diff --git a/osu.Game/Database/DownloadableArchiveModelManager.cs b/osu.Game/Database/DownloadableArchiveModelManager.cs index 78c0837ce9..e3c6ad25e6 100644 --- a/osu.Game/Database/DownloadableArchiveModelManager.cs +++ b/osu.Game/Database/DownloadableArchiveModelManager.cs @@ -76,7 +76,12 @@ namespace osu.Game.Database Task.Factory.StartNew(async () => { // This gets scheduled back to the update thread, but we want the import to run in the background. - await Import(notification, filename); + var imported = await Import(notification, filename); + + // for now a failed import will be marked as a failed download for simplicity. + if (!imported.Any()) + DownloadFailed?.Invoke(request); + currentDownloads.Remove(request); }, TaskCreationOptions.LongRunning); }; From 16e33e8bc7d5661cf945bfc495780063ddd1b4ea Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Mon, 28 Oct 2019 18:34:58 +0900 Subject: [PATCH 1966/2815] Fix song progress not displaying correctly --- osu.Game/Screens/Play/SongProgress.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index 6642efdf8b..3df06ebfa8 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -106,6 +106,8 @@ namespace osu.Game.Screens.Play protected override void LoadComplete() { + base.LoadComplete(); + Show(); replayLoaded.ValueChanged += loaded => AllowSeeking = loaded.NewValue; From 46b44f4f99e08c0d5685ba3bddb239d4860bfb04 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Mon, 28 Oct 2019 18:37:58 +0900 Subject: [PATCH 1967/2815] Fix PlayerSettingsOverlay being shown by default --- .../Visual/Gameplay/TestSceneReplaySettingsOverlay.cs | 2 ++ osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplaySettingsOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplaySettingsOverlay.cs index 944480243d..cdfb3beb19 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplaySettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplaySettingsOverlay.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.PlayerSettings; @@ -20,6 +21,7 @@ namespace osu.Game.Tests.Visual.Gameplay { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, + State = { Value = Visibility.Visible } }); Add(container = new ExampleContainer()); diff --git a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs index b2c3952f38..d201b5d30e 100644 --- a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs +++ b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs @@ -45,8 +45,6 @@ namespace osu.Game.Screens.Play.HUD VisualSettings = new VisualSettings { Expanded = false } } }; - - Show(); } protected override void PopIn() => this.FadeIn(fade_duration); From 1a4817879e57800bf1ab7f829c5ca2c7d5b28623 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Mon, 28 Oct 2019 19:10:39 +0900 Subject: [PATCH 1968/2815] Fix precision changes in legacy control point types --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 8 ++++++++ .../Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs | 6 +++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 786b7611b5..c5b9ddc23b 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -376,7 +376,7 @@ namespace osu.Game.Beatmaps.Formats handleTimingControlPoint(controlPoint); } - handleDifficultyControlPoint(new DifficultyControlPoint + handleDifficultyControlPoint(new LegacyDifficultyControlPoint { Time = time, SpeedMultiplier = speedMultiplier, diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index a5a4380d4a..49457833f2 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -189,6 +189,14 @@ namespace osu.Game.Beatmaps.Formats Foreground = 3 } + internal class LegacyDifficultyControlPoint : DifficultyControlPoint + { + public LegacyDifficultyControlPoint() + { + SpeedMultiplierBindable.Precision = Double.Epsilon; + } + } + internal class LegacySampleControlPoint : SampleControlPoint { public int CustomSampleBank; diff --git a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs index a16d391b99..527f520172 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs @@ -28,11 +28,11 @@ namespace osu.Game.Beatmaps.Formats } protected override TimingControlPoint CreateTimingControlPoint() - => new LegacyDifficultyCalculatorControlPoint(); + => new LegacyDifficultyCalculatorTimingControlPoint(); - private class LegacyDifficultyCalculatorControlPoint : TimingControlPoint + private class LegacyDifficultyCalculatorTimingControlPoint : TimingControlPoint { - public LegacyDifficultyCalculatorControlPoint() + public LegacyDifficultyCalculatorTimingControlPoint() { BeatLengthBindable.MinValue = double.MinValue; BeatLengthBindable.MaxValue = double.MaxValue; From c181edaedfc59cd9f3cfc74f7f817059b8051dd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= <dach.bartlomiej@gmail.com> Date: Mon, 28 Oct 2019 15:07:36 +0100 Subject: [PATCH 1969/2815] Replace manual comparer implementation Replace manually-implemented CriteriaComparer with a call to Comparer<T>.Create() to decrease verbosity. --- .../Screens/Select/Carousel/CarouselGroup.cs | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index b32561eb88..aa48d1a04e 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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; @@ -85,7 +84,8 @@ namespace osu.Game.Screens.Select.Carousel InternalChildren.ForEach(c => c.Filter(criteria)); // IEnumerable<T>.OrderBy() is used instead of List<T>.Sort() to ensure sorting stability - InternalChildren = InternalChildren.OrderBy(c => c, new CriteriaComparer(criteria)).ToList(); + var criteriaComparer = Comparer<CarouselItem>.Create((x, y) => x.CompareTo(criteria, y)); + InternalChildren = InternalChildren.OrderBy(c => c, criteriaComparer).ToList(); } protected virtual void ChildItemStateChanged(CarouselItem item, CarouselItemState value) @@ -103,23 +103,5 @@ namespace osu.Game.Screens.Select.Carousel State.Value = CarouselItemState.Selected; } } - - private class CriteriaComparer : IComparer<CarouselItem> - { - private readonly FilterCriteria criteria; - - public CriteriaComparer(FilterCriteria criteria) - { - this.criteria = criteria; - } - - public int Compare(CarouselItem x, CarouselItem y) - { - if (x != null && y != null) - return x.CompareTo(criteria, y); - - throw new ArgumentNullException(); - } - } } } From a4a57eec544d7d66774364304f17cb629bfd4834 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Tue, 29 Oct 2019 01:09:49 +0900 Subject: [PATCH 1970/2815] Fix game-wide performance drop when triangles intro is used --- osu.Game/Screens/Menu/IntroSequence.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Menu/IntroSequence.cs b/osu.Game/Screens/Menu/IntroSequence.cs index 093d01f12d..e2dd14b18c 100644 --- a/osu.Game/Screens/Menu/IntroSequence.cs +++ b/osu.Game/Screens/Menu/IntroSequence.cs @@ -42,6 +42,7 @@ namespace osu.Game.Screens.Menu public IntroSequence() { RelativeSizeAxes = Axes.Both; + Alpha = 0; } [BackgroundDependencyLoader] From ecf14bc7b9721850744884822232af96db104038 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Tue, 29 Oct 2019 01:21:17 +0900 Subject: [PATCH 1971/2815] Rename class to match --- ...TestSceneLabelledComponent.cs => TestSceneLabelledDrawable.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game.Tests/Visual/UserInterface/{TestSceneLabelledComponent.cs => TestSceneLabelledDrawable.cs} (100%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDrawable.cs similarity index 100% rename from osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDrawable.cs From d1c6e3f62064305fbeb4d53bb627571bd8650ec6 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Tue, 29 Oct 2019 14:32:38 +0900 Subject: [PATCH 1972/2815] Add test for scroll to end when max history is exceeded --- .../Online/TestSceneStandAloneChatDisplay.cs | 37 +++++++++++++++++-- osu.Game/Online/Chat/Channel.cs | 8 ++-- osu.Game/Overlays/Chat/DrawableChannel.cs | 4 +- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 39c2fbfcc9..d973799405 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -7,6 +7,9 @@ using osu.Game.Online.Chat; using osu.Game.Users; using osuTK; using System; +using System.Linq; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays.Chat; namespace osu.Game.Tests.Visual.Online { @@ -42,14 +45,14 @@ namespace osu.Game.Tests.Visual.Online [Cached] private ChannelManager channelManager = new ChannelManager(); - private readonly StandAloneChatDisplay chatDisplay; - private readonly StandAloneChatDisplay chatDisplay2; + private readonly TestStandAloneChatDisplay chatDisplay; + private readonly TestStandAloneChatDisplay chatDisplay2; public TestSceneStandAloneChatDisplay() { Add(channelManager); - Add(chatDisplay = new StandAloneChatDisplay + Add(chatDisplay = new TestStandAloneChatDisplay { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -57,7 +60,7 @@ namespace osu.Game.Tests.Visual.Online Size = new Vector2(400, 80) }); - Add(chatDisplay2 = new StandAloneChatDisplay(true) + Add(chatDisplay2 = new TestStandAloneChatDisplay(true) { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, @@ -119,6 +122,32 @@ namespace osu.Game.Tests.Visual.Online Content = "Message from the future!", Timestamp = DateTimeOffset.Now })); + + AddUntilStep("ensure still scrolled to bottom", () => chatDisplay.ScrolledToBottom); + + const int messages_per_call = 10; + AddRepeatStep("add many messages", () => + { + for (int i = 0; i < messages_per_call; i++) + testChannel.AddNewMessages(new Message(sequence++) + { + Sender = longUsernameUser, + Content = "Many messages! " + Guid.NewGuid(), + Timestamp = DateTimeOffset.Now + }); + }, Channel.MAX_HISTORY / messages_per_call + 5); + + AddUntilStep("ensure still scrolled to bottom", () => chatDisplay.ScrolledToBottom); + } + + private class TestStandAloneChatDisplay : StandAloneChatDisplay + { + public TestStandAloneChatDisplay(bool textbox = false) + : base(textbox) + { + } + + public bool ScrolledToBottom => ((ScrollContainer<Drawable>)((Container)InternalChildren.OfType<DrawableChannel>().First().Child).Child).IsScrolledToEnd(1); } } } diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index 9ec39c5cb1..451174a73c 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -14,7 +14,7 @@ namespace osu.Game.Online.Chat { public class Channel { - public readonly int MaxHistory = 300; + public const int MAX_HISTORY = 300; /// <summary> /// Contains every joined user except the current logged in user. Currently only returned for PM channels. @@ -80,8 +80,6 @@ namespace osu.Game.Online.Chat /// </summary> public Bindable<bool> Joined = new Bindable<bool>(); - public const int MAX_HISTORY = 300; - [JsonConstructor] public Channel() { @@ -162,8 +160,8 @@ namespace osu.Game.Online.Chat { // never purge local echos int messageCount = Messages.Count - pendingMessages.Count; - if (messageCount > MaxHistory) - Messages.RemoveRange(0, messageCount - MaxHistory); + if (messageCount > MAX_HISTORY) + Messages.RemoveRange(0, messageCount - MAX_HISTORY); } } } diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 6cdbfabe0f..4ddaf4d5ae 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -107,7 +107,7 @@ namespace osu.Game.Overlays.Chat scrollToEnd(); var staleMessages = chatLines.Where(c => c.LifetimeEnd == double.MaxValue).ToArray(); - int count = staleMessages.Length - Channel.MaxHistory; + int count = staleMessages.Length - Channel.MAX_HISTORY; for (int i = 0; i < count; i++) { From 09a6d1184a42ce86f5c61d1cf2464425b1c0a7ad Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Tue, 29 Oct 2019 14:33:05 +0900 Subject: [PATCH 1973/2815] Tidy up order of scroll changes --- osu.Game/Overlays/Chat/DrawableChannel.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 4ddaf4d5ae..9301daa9ff 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -89,8 +89,10 @@ namespace osu.Game.Overlays.Chat private void newMessagesArrived(IEnumerable<Message> newMessages) { + bool shouldScrollToEnd = scroll.IsScrolledToEnd(10) || !chatLines.Any() || newMessages.Any(m => m is LocalMessage); + // Add up to last Channel.MAX_HISTORY messages - var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MaxHistory)); + var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MAX_HISTORY)); Message lastMessage = chatLines.LastOrDefault()?.Message; @@ -103,19 +105,18 @@ namespace osu.Game.Overlays.Chat lastMessage = message; } - if (scroll.IsScrolledToEnd(10) || !chatLines.Any() || newMessages.Any(m => m is LocalMessage)) - scrollToEnd(); - var staleMessages = chatLines.Where(c => c.LifetimeEnd == double.MaxValue).ToArray(); int count = staleMessages.Length - Channel.MAX_HISTORY; for (int i = 0; i < count; i++) { var d = staleMessages[i]; - if (!scroll.IsScrolledToEnd(10)) - scroll.OffsetScrollPosition(-d.DrawHeight); + scroll.OffsetScrollPosition(-d.DrawHeight); d.Expire(); } + + if (shouldScrollToEnd) + scrollToEnd(); } private void pendingMessageResolved(Message existing, Message updated) From b06e70e546ca0a2d82ecfbce85f977a2a200fb28 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Tue, 29 Oct 2019 15:27:08 +0900 Subject: [PATCH 1974/2815] Add failing test showing issue with day separator logic --- .../Online/TestSceneStandAloneChatDisplay.cs | 20 ++++++++++++++++++- osu.Game/Overlays/Chat/DrawableChannel.cs | 2 +- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index d973799405..01400bf1d9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -9,6 +9,7 @@ using osuTK; using System; using System.Linq; using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Containers; using osu.Game.Overlays.Chat; namespace osu.Game.Tests.Visual.Online @@ -137,6 +138,17 @@ namespace osu.Game.Tests.Visual.Online }); }, Channel.MAX_HISTORY / messages_per_call + 5); + AddAssert("Ensure no adjacent day separators", () => + { + var indices = chatDisplay.FillFlow.OfType<DrawableChannel.DaySeparator>().Select(ds => chatDisplay.FillFlow.IndexOf(ds)); + + foreach (var i in indices) + if (i < chatDisplay.FillFlow.Count && chatDisplay.FillFlow[i + 1] is DrawableChannel.DaySeparator) + return false; + + return true; + }); + AddUntilStep("ensure still scrolled to bottom", () => chatDisplay.ScrolledToBottom); } @@ -147,7 +159,13 @@ namespace osu.Game.Tests.Visual.Online { } - public bool ScrolledToBottom => ((ScrollContainer<Drawable>)((Container)InternalChildren.OfType<DrawableChannel>().First().Child).Child).IsScrolledToEnd(1); + protected DrawableChannel DrawableChannel => InternalChildren.OfType<DrawableChannel>().First(); + + protected OsuScrollContainer ScrollContainer => (OsuScrollContainer)((Container)DrawableChannel.Child).Child; + + public FillFlowContainer FillFlow => (FillFlowContainer)ScrollContainer.Child; + + public bool ScrolledToBottom => ScrollContainer.IsScrolledToEnd(1); } } } diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 9301daa9ff..636fafb5f2 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -142,7 +142,7 @@ namespace osu.Game.Overlays.Chat private void scrollToEnd() => ScheduleAfterChildren(() => scroll.ScrollToEnd()); - protected class DaySeparator : Container + public class DaySeparator : Container { public float TextSize { From 54befb6f8fdbdfe36e9f299b3df54782ff08cf7f Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Tue, 29 Oct 2019 15:45:41 +0900 Subject: [PATCH 1975/2815] Remove adjacent day separators --- osu.Game/Overlays/Chat/DrawableChannel.cs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 636fafb5f2..427bd8dcde 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -108,11 +108,25 @@ namespace osu.Game.Overlays.Chat var staleMessages = chatLines.Where(c => c.LifetimeEnd == double.MaxValue).ToArray(); int count = staleMessages.Length - Channel.MAX_HISTORY; - for (int i = 0; i < count; i++) + if (count > 0) { - var d = staleMessages[i]; - scroll.OffsetScrollPosition(-d.DrawHeight); - d.Expire(); + void expireAndAdjustScroll(Drawable d) + { + scroll.OffsetScrollPosition(-d.DrawHeight); + d.Expire(); + } + + for (int i = 0; i < count; i++) + expireAndAdjustScroll(staleMessages[i]); + + // remove all adjacent day separators after stale message removal + for (int i = 0; i < ChatLineFlow.Count - 1; i++) + { + if (!(ChatLineFlow[i] is DaySeparator)) break; + if (!(ChatLineFlow[i + 1] is DaySeparator)) break; + + expireAndAdjustScroll(ChatLineFlow[i]); + } } if (shouldScrollToEnd) From 8e1faf6ff1810b828bad316475ff5a9d2bed88dc Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Tue, 29 Oct 2019 17:03:52 +0900 Subject: [PATCH 1976/2815] Fix loader animation tests failing occasionally --- .../Visual/Menus/TestSceneLoaderAnimation.cs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoaderAnimation.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoaderAnimation.cs index 000832b784..61fed3013e 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneLoaderAnimation.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneLoaderAnimation.cs @@ -33,23 +33,15 @@ namespace osu.Game.Tests.Visual.Menus [Test] public void TestInstantLoad() { - bool logoVisible = false; + // visual only, very impossible to test this using asserts. - AddStep("begin loading", () => + AddStep("load immediately", () => { loader = new TestLoader(); loader.AllowLoad.Set(); LoadScreen(loader); }); - - AddUntilStep("loaded", () => - { - logoVisible = loader.Logo?.Alpha > 0; - return loader.Logo != null && loader.ScreenLoaded; - }); - - AddAssert("logo was not visible", () => !logoVisible); } [Test] @@ -58,7 +50,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("begin loading", () => LoadScreen(loader = new TestLoader())); AddUntilStep("wait for logo visible", () => loader.Logo?.Alpha > 0); AddStep("finish loading", () => loader.AllowLoad.Set()); - AddAssert("loaded", () => loader.Logo != null && loader.ScreenLoaded); + AddUntilStep("loaded", () => loader.Logo != null && loader.ScreenLoaded); AddUntilStep("logo gone", () => loader.Logo?.Alpha == 0); } From 7c6ccce3ba1b271309185d5587a76924f95d82b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Tue, 29 Oct 2019 18:02:30 +0900 Subject: [PATCH 1977/2815] Add tests covering precision case --- .../Formats/LegacyBeatmapDecoderTest.cs | 17 +++++++++++++++++ .../controlpoint-difficulty-multiplier.osu | 8 ++++++++ 2 files changed, 25 insertions(+) create mode 100644 osu.Game.Tests/Resources/controlpoint-difficulty-multiplier.osu diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index de516d3142..8545072199 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -362,6 +362,23 @@ namespace osu.Game.Tests.Beatmaps.Formats } } + [Test] + public void TestDecodeControlPointDifficultyChange() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var resStream = TestResources.OpenResource("controlpoint-difficulty-multiplier.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var controlPointInfo = decoder.Decode(stream).ControlPointInfo; + + Assert.That(controlPointInfo.DifficultyPointAt(5).SpeedMultiplier, Is.EqualTo(1)); + Assert.That(controlPointInfo.DifficultyPointAt(1000).SpeedMultiplier, Is.EqualTo(10)); + Assert.That(controlPointInfo.DifficultyPointAt(2000).SpeedMultiplier, Is.EqualTo(1.8518518518518519d)); + Assert.That(controlPointInfo.DifficultyPointAt(3000).SpeedMultiplier, Is.EqualTo(0.5)); + } + } + [Test] public void TestDecodeControlPointCustomSampleBank() { diff --git a/osu.Game.Tests/Resources/controlpoint-difficulty-multiplier.osu b/osu.Game.Tests/Resources/controlpoint-difficulty-multiplier.osu new file mode 100644 index 0000000000..5f06fc33c8 --- /dev/null +++ b/osu.Game.Tests/Resources/controlpoint-difficulty-multiplier.osu @@ -0,0 +1,8 @@ +osu file format v7 + +[TimingPoints] +0,100,4,2,0,100,1,0 +12,500,4,2,0,100,1,0 +1000,-10,4,2,0,100,0,0 +2000,-54,4,2,0,100,0,0 +3000,-200,4,2,0,100,0,0 From 97c1a6e86bfdfe65e18d0d52f53fb6e585f9edda Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Tue, 29 Oct 2019 18:05:32 +0900 Subject: [PATCH 1978/2815] 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 43c1302e54..8b31be3f12 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -62,6 +62,6 @@ </ItemGroup> <ItemGroup> <PackageReference Include="ppy.osu.Game.Resources" Version="2019.1010.0" /> - <PackageReference Include="ppy.osu.Framework.Android" Version="2019.1023.0" /> + <PackageReference Include="ppy.osu.Framework.Android" Version="2019.1029.0" /> </ItemGroup> </Project> diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e898a001de..0cb09d9b14 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.2" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2019.1010.0" /> - <PackageReference Include="ppy.osu.Framework" Version="2019.1023.0" /> + <PackageReference Include="ppy.osu.Framework" Version="2019.1029.0" /> <PackageReference Include="SharpCompress" Version="0.24.0" /> <PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" /> diff --git a/osu.iOS.props b/osu.iOS.props index 656c60543e..719aced705 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,8 +118,8 @@ <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.1" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.1" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2019.1010.0" /> - <PackageReference Include="ppy.osu.Framework" Version="2019.1023.0" /> - <PackageReference Include="ppy.osu.Framework.iOS" Version="2019.1023.0" /> + <PackageReference Include="ppy.osu.Framework" Version="2019.1029.0" /> + <PackageReference Include="ppy.osu.Framework.iOS" Version="2019.1029.0" /> <PackageReference Include="SharpCompress" Version="0.24.0" /> <PackageReference Include="NUnit" Version="3.11.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" /> From e9cb3337b31e39372f3905405366d45d3372bded Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Tue, 29 Oct 2019 22:31:27 +0900 Subject: [PATCH 1979/2815] Fix 1x1 white pixel appearing in the centre of hitcircles on default skin --- .../Objects/Drawables/Pieces/NumberPiece.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs index 62c4ba5ee3..7c94568835 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Game.Graphics.Sprites; using osuTK.Graphics; -using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Skinning; @@ -30,17 +29,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Children = new Drawable[] { - new CircularContainer + new Container { Masking = true, - Origin = Anchor.Centre, EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, Radius = 60, Colour = Color4.White.Opacity(0.5f), }, - Child = new Box() }, number = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText { From 5c2917d3030e872a656deda7a4f44e8a218ec6d7 Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya <gsculerlor@hotmail.com> Date: Wed, 30 Oct 2019 00:50:04 +0700 Subject: [PATCH 1980/2815] Place sign in button inside ShakeContainer --- .../Sections/General/LoginSettings.cs | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index b02b1a5489..a8bbccb168 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -200,6 +200,7 @@ namespace osu.Game.Overlays.Settings.Sections.General { private TextBox username; private TextBox password; + private ShakeContainer shakeSignIn; private IAPIProvider api; public Action RequestHide; @@ -208,6 +209,8 @@ namespace osu.Game.Overlays.Settings.Sections.General { if (!string.IsNullOrEmpty(username.Text) && !string.IsNullOrEmpty(password.Text)) api.Login(username.Text, password.Text); + else + shakeSignIn.Shake(); } [BackgroundDependencyLoader(permitNulls: true)] @@ -244,10 +247,23 @@ namespace osu.Game.Overlays.Settings.Sections.General LabelText = "Stay signed in", Bindable = config.GetBindable<bool>(OsuSetting.SavePassword), }, - new SettingsButton + new Container { - Text = "Sign in", - Action = performLogin + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + shakeSignIn = new ShakeContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = new SettingsButton + { + Text = "Sign in", + Action = performLogin + }, + } + } }, new SettingsButton { From e287dae64b59aa1cf6f67f2485f7541b98a8cc44 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski <megaman9919@gmail.com> Date: Tue, 29 Oct 2019 21:25:48 +0300 Subject: [PATCH 1981/2815] Fix VotePill can be activated by the comment sender --- osu.Game/Overlays/Comments/VotePill.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index e8d9013fd9..b2c8c52bf3 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -43,6 +43,7 @@ namespace osu.Game.Overlays.Comments private readonly BindableBool isVoted = new BindableBool(); private readonly BindableInt votesCount = new BindableInt(); + private bool disabled; public VotePill(Comment comment) { @@ -69,6 +70,8 @@ namespace osu.Game.Overlays.Comments votesCount.Value = comment.VotesCount; isVoted.BindValueChanged(voted => background.Colour = voted.NewValue ? AccentColour : OsuColour.Gray(0.05f), true); votesCount.BindValueChanged(count => votesCounter.Text = $"+{count.NewValue}", true); + + api.LocalUser.BindValueChanged(user => disabled = user.NewValue?.Id == comment.UserId ? true : false, true); } private void onAction() @@ -145,16 +148,30 @@ namespace osu.Game.Overlays.Comments protected override bool OnHover(HoverEvent e) { + if (disabled) + return false; + onHoverAction(); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { + if (disabled) + return; + updateDisplay(); base.OnHoverLost(e); } + protected override bool OnClick(ClickEvent e) + { + if (disabled) + return false; + + return base.OnClick(e); + } + private void updateDisplay() { if (isVoted.Value) From 30d9b21b86264bbc8cfae70f7ac65ae390ea250e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski <megaman9919@gmail.com> Date: Tue, 29 Oct 2019 21:57:20 +0300 Subject: [PATCH 1982/2815] Condition simplification --- osu.Game/Overlays/Comments/VotePill.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index b2c8c52bf3..32fbe55fb5 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -71,7 +71,7 @@ namespace osu.Game.Overlays.Comments isVoted.BindValueChanged(voted => background.Colour = voted.NewValue ? AccentColour : OsuColour.Gray(0.05f), true); votesCount.BindValueChanged(count => votesCounter.Text = $"+{count.NewValue}", true); - api.LocalUser.BindValueChanged(user => disabled = user.NewValue?.Id == comment.UserId ? true : false, true); + api.LocalUser.BindValueChanged(user => disabled = user.NewValue?.Id == comment.UserId, true); } private void onAction() From 092d16bb6465a2555654f991d492098a0355d7c6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski <megaman9919@gmail.com> Date: Tue, 29 Oct 2019 23:43:16 +0300 Subject: [PATCH 1983/2815] Dont use binding to set disabled value --- osu.Game/Overlays/Comments/VotePill.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index 32fbe55fb5..a895ad2bcf 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -61,6 +61,8 @@ namespace osu.Game.Overlays.Comments { AccentColour = borderContainer.BorderColour = sideNumber.Colour = colours.GreenLight; hoverLayer.Colour = Color4.Black.Opacity(0.5f); + + disabled = api.LocalUser.Value.Id == comment.UserId; } protected override void LoadComplete() @@ -70,8 +72,6 @@ namespace osu.Game.Overlays.Comments votesCount.Value = comment.VotesCount; isVoted.BindValueChanged(voted => background.Colour = voted.NewValue ? AccentColour : OsuColour.Gray(0.05f), true); votesCount.BindValueChanged(count => votesCounter.Text = $"+{count.NewValue}", true); - - api.LocalUser.BindValueChanged(user => disabled = user.NewValue?.Id == comment.UserId, true); } private void onAction() From 1502a6c631920debf7bcdbda293326bb9be300e1 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski <megaman9919@gmail.com> Date: Wed, 30 Oct 2019 03:09:14 +0300 Subject: [PATCH 1984/2815] Cleanups --- osu.Game/Overlays/Comments/VotePill.cs | 37 +++++++++----------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index a895ad2bcf..5d0431314b 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -43,14 +43,11 @@ namespace osu.Game.Overlays.Comments private readonly BindableBool isVoted = new BindableBool(); private readonly BindableInt votesCount = new BindableInt(); - private bool disabled; public VotePill(Comment comment) { this.comment = comment; - Action = onAction; - AutoSizeAxes = Axes.X; Height = 20; LoadingAnimationSize = new Vector2(10); @@ -62,7 +59,8 @@ namespace osu.Game.Overlays.Comments AccentColour = borderContainer.BorderColour = sideNumber.Colour = colours.GreenLight; hoverLayer.Colour = Color4.Black.Opacity(0.5f); - disabled = api.LocalUser.Value.Id == comment.UserId; + if (api.LocalUser.Value.Id != comment.UserId) + Action = onAction; } protected override void LoadComplete() @@ -148,41 +146,30 @@ namespace osu.Game.Overlays.Comments protected override bool OnHover(HoverEvent e) { - if (disabled) - return false; - onHoverAction(); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - if (disabled) - return; - updateDisplay(); base.OnHoverLost(e); } - protected override bool OnClick(ClickEvent e) - { - if (disabled) - return false; - - return base.OnClick(e); - } - private void updateDisplay() { - if (isVoted.Value) + if (Action != null) { - hoverLayer.FadeTo(IsHovered ? 1 : 0); - sideNumber.Hide(); - } - else - sideNumber.FadeTo(IsHovered ? 1 : 0); + if (isVoted.Value) + { + hoverLayer.FadeTo(IsHovered ? 1 : 0); + sideNumber.Hide(); + } + else + sideNumber.FadeTo(IsHovered ? 1 : 0); - borderContainer.BorderThickness = IsHovered ? 3 : 0; + borderContainer.BorderThickness = IsHovered ? 3 : 0; + } } private void onHoverAction() From 2c31492bbe57f2ae9cca0b77f6a6f7cc0e967971 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski <megaman9919@gmail.com> Date: Wed, 30 Oct 2019 03:27:06 +0300 Subject: [PATCH 1985/2815] Fix comment can be voted if user is null --- osu.Game/Overlays/Comments/VotePill.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index 5d0431314b..9a10e4a2c1 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.Comments AccentColour = borderContainer.BorderColour = sideNumber.Colour = colours.GreenLight; hoverLayer.Colour = Color4.Black.Opacity(0.5f); - if (api.LocalUser.Value.Id != comment.UserId) + if ((api.LocalUser.Value?.Id ?? comment.UserId) != comment.UserId) Action = onAction; } From f1a2643c401b83ee8ec952b76dacf896910990d1 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski <megaman9919@gmail.com> Date: Wed, 30 Oct 2019 03:45:05 +0300 Subject: [PATCH 1986/2815] Add test scene --- .../Visual/Online/TestSceneVotePill.cs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneVotePill.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs new file mode 100644 index 0000000000..8ddb16d8ca --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs @@ -0,0 +1,59 @@ +// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.Graphics; +using osu.Game.Overlays.Comments; +using osu.Framework.Allocation; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Tests.Visual.Online +{ + [TestFixture] + public class TestSceneVotePill : OsuTestScene + { + public override IReadOnlyList<Type> RequiredTypes => new[] + { + typeof(VotePill) + }; + + private VotePill votePill; + + [BackgroundDependencyLoader] + private void load() + { + var userComment = new Comment + { + IsVoted = false, + UserId = API.LocalUser.Value?.Id, + VotesCount = 10, + }; + + var randomComment = new Comment + { + IsVoted = false, + UserId = 455454, + VotesCount = 2, + }; + + AddStep("Random comment", () => addVotePill(randomComment)); + AddStep("Click", () => votePill.Click()); + AddAssert("Loading", () => votePill.IsLoading == true); + AddStep("User comment", () => addVotePill(userComment)); + AddStep("Click", () => votePill.Click()); + AddAssert("Not loading", () => votePill.IsLoading == false); + } + + private void addVotePill(Comment comment) + { + Clear(); + Add(votePill = new VotePill(comment) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + } + } +} From 7f755fe726085b1e42afa8430f9f07429aa02545 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski <megaman9919@gmail.com> Date: Wed, 30 Oct 2019 03:47:17 +0300 Subject: [PATCH 1987/2815] Add more tests --- osu.Game.Tests/Visual/Online/TestSceneVotePill.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs index 8ddb16d8ca..76bf825541 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs @@ -44,6 +44,9 @@ namespace osu.Game.Tests.Visual.Online AddStep("User comment", () => addVotePill(userComment)); AddStep("Click", () => votePill.Click()); AddAssert("Not loading", () => votePill.IsLoading == false); + AddStep("Log out", API.Logout); + AddStep("Click", () => votePill.Click()); + AddAssert("Not loading", () => votePill.IsLoading == false); } private void addVotePill(Comment comment) From 8ad5ccda68e679acae583b636cd125ac9b74674e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski <megaman9919@gmail.com> Date: Wed, 30 Oct 2019 04:16:14 +0300 Subject: [PATCH 1988/2815] Test steps rearrangement and condition fix --- osu.Game.Tests/Visual/Online/TestSceneVotePill.cs | 13 ++++++++----- osu.Game/Overlays/Comments/VotePill.cs | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs index 76bf825541..47cb806fb8 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs @@ -27,24 +27,27 @@ namespace osu.Game.Tests.Visual.Online var userComment = new Comment { IsVoted = false, - UserId = API.LocalUser.Value?.Id, + UserId = API.LocalUser.Value.Id, VotesCount = 10, }; var randomComment = new Comment { IsVoted = false, - UserId = 455454, + UserId = 4444, VotesCount = 2, }; + AddStep("User comment", () => addVotePill(userComment)); + AddStep("Click", () => votePill.Click()); + AddAssert("Not loading", () => votePill.IsLoading == false); + AddStep("Random comment", () => addVotePill(randomComment)); AddStep("Click", () => votePill.Click()); AddAssert("Loading", () => votePill.IsLoading == true); - AddStep("User comment", () => addVotePill(userComment)); - AddStep("Click", () => votePill.Click()); - AddAssert("Not loading", () => votePill.IsLoading == false); + AddStep("Log out", API.Logout); + AddStep("Random comment", () => addVotePill(randomComment)); AddStep("Click", () => votePill.Click()); AddAssert("Not loading", () => votePill.IsLoading == false); } diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index 9a10e4a2c1..54eba63095 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.Comments AccentColour = borderContainer.BorderColour = sideNumber.Colour = colours.GreenLight; hoverLayer.Colour = Color4.Black.Opacity(0.5f); - if ((api.LocalUser.Value?.Id ?? comment.UserId) != comment.UserId) + if (api.LocalUser.Value.Id != comment.UserId && api.LocalUser.Value.Id != 1) Action = onAction; } From 759395bcb5802f76dfea0db38be65f27561da27f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski <megaman9919@gmail.com> Date: Wed, 30 Oct 2019 04:31:09 +0300 Subject: [PATCH 1989/2815] CI fix --- osu.Game.Tests/Visual/Online/TestSceneVotePill.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs index 47cb806fb8..7ccb025b47 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs @@ -40,16 +40,16 @@ namespace osu.Game.Tests.Visual.Online AddStep("User comment", () => addVotePill(userComment)); AddStep("Click", () => votePill.Click()); - AddAssert("Not loading", () => votePill.IsLoading == false); + AddAssert("Not loading", () => !votePill.IsLoading); AddStep("Random comment", () => addVotePill(randomComment)); AddStep("Click", () => votePill.Click()); - AddAssert("Loading", () => votePill.IsLoading == true); + AddAssert("Loading", () => votePill.IsLoading); AddStep("Log out", API.Logout); AddStep("Random comment", () => addVotePill(randomComment)); AddStep("Click", () => votePill.Click()); - AddAssert("Not loading", () => votePill.IsLoading == false); + AddAssert("Not loading", () => !votePill.IsLoading); } private void addVotePill(Comment comment) From 95ff48c123cc19c7388c1140f2dc93513898244d Mon Sep 17 00:00:00 2001 From: smoogipoo <smoogipoo@smgi.me> Date: Wed, 30 Oct 2019 14:38:06 +0900 Subject: [PATCH 1990/2815] Don't log cancelled join requests --- osu.Game/Screens/Multi/RoomManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs index 6f473aaafa..8ceff7edef 100644 --- a/osu.Game/Screens/Multi/RoomManager.cs +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -98,7 +98,8 @@ namespace osu.Game.Screens.Multi currentJoinRoomRequest.Failure += exception => { - Logger.Log($"Failed to join room: {exception}", level: LogLevel.Important); + if (!(exception is OperationCanceledException)) + Logger.Log($"Failed to join room: {exception}", level: LogLevel.Important); onError?.Invoke(exception.ToString()); }; From b6457f0ce957342b9d76c2f36b37f85bccf640ea Mon Sep 17 00:00:00 2001 From: smoogipoo <smoogipoo@smgi.me> Date: Wed, 30 Oct 2019 14:41:54 +0900 Subject: [PATCH 1991/2815] Cancel room joins on part --- osu.Game/Screens/Multi/RoomManager.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs index 8ceff7edef..cdaba85b9e 100644 --- a/osu.Game/Screens/Multi/RoomManager.cs +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -87,9 +87,8 @@ namespace osu.Game.Screens.Multi public void JoinRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null) { currentJoinRoomRequest?.Cancel(); - currentJoinRoomRequest = null; - currentJoinRoomRequest = new JoinRoomRequest(room, api.LocalUser.Value); + currentJoinRoomRequest.Success += () => { joinedRoom = room; @@ -108,6 +107,8 @@ namespace osu.Game.Screens.Multi public void PartRoom() { + currentJoinRoomRequest?.Cancel(); + if (joinedRoom == null) return; From f56d9fe50cf2933a1fd94afc90df7082ebaff331 Mon Sep 17 00:00:00 2001 From: smoogipoo <smoogipoo@smgi.me> Date: Wed, 30 Oct 2019 14:42:14 +0900 Subject: [PATCH 1992/2815] Forcefully part room when multiplayer exits --- osu.Game/Screens/Multi/Multiplayer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 90806bab6e..5945e9de13 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -212,6 +212,8 @@ namespace osu.Game.Screens.Multi public override bool OnExiting(IScreen next) { + roomManager.PartRoom(); + if (screenStack.CurrentScreen != null && !(screenStack.CurrentScreen is LoungeSubScreen)) { screenStack.Exit(); From ced6042b3e977dcfcd18ca1e77872e061e0a8811 Mon Sep 17 00:00:00 2001 From: Dan Balasescu <smoogipoo@smgi.me> Date: Wed, 30 Oct 2019 15:51:09 +0900 Subject: [PATCH 1993/2815] Use internal type name --- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 49457833f2..2b914669cb 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -193,7 +193,7 @@ namespace osu.Game.Beatmaps.Formats { public LegacyDifficultyControlPoint() { - SpeedMultiplierBindable.Precision = Double.Epsilon; + SpeedMultiplierBindable.Precision = double.Epsilon; } } From 45af7969434d6216f71dc8da59759c3e33626986 Mon Sep 17 00:00:00 2001 From: smoogipoo <smoogipoo@smgi.me> Date: Wed, 30 Oct 2019 17:05:15 +0900 Subject: [PATCH 1994/2815] Remove usages of EF internals --- .../UserInterface/TestSceneBeatSyncedContainer.cs | 4 ++-- osu.Game/Screens/Edit/EditorClock.cs | 13 +++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index b6df559686..ed44d82bce 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; -using Microsoft.EntityFrameworkCore.Internal; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Track; @@ -153,7 +153,7 @@ namespace osu.Game.Tests.Visual.UserInterface }; } - private IReadOnlyList<TimingControlPoint> timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints; + private List<TimingControlPoint> timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ToList(); private TimingControlPoint getNextTimingPoint(TimingControlPoint current) { diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 1cfb123f25..f674a62a3a 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -3,7 +3,6 @@ using System; using System.Linq; -using Microsoft.EntityFrameworkCore.Internal; using osu.Framework.MathUtils; using osu.Framework.Timing; using osu.Game.Beatmaps; @@ -88,7 +87,17 @@ namespace osu.Game.Screens.Edit if (direction < 0 && timingPoint.Time == CurrentTime) { // When going backwards and we're at the boundary of two timing points, we compute the seek distance with the timing point which we are seeking into - int activeIndex = ControlPointInfo.TimingPoints.IndexOf(timingPoint); + int activeIndex = -1; + + for (int i = 0; i < ControlPointInfo.TimingPoints.Count; i++) + { + if (ControlPointInfo.TimingPoints[i] == timingPoint) + { + activeIndex = i; + break; + } + } + while (activeIndex > 0 && CurrentTime == timingPoint.Time) timingPoint = ControlPointInfo.TimingPoints[--activeIndex]; } From cf2d885099a0b92257adabf8bee9d130d8450d7d Mon Sep 17 00:00:00 2001 From: smoogipoo <smoogipoo@smgi.me> Date: Wed, 30 Oct 2019 18:02:18 +0900 Subject: [PATCH 1995/2815] Fix control points being flushed too late --- .../Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 15 +++++++++++++++ .../timingpoint-speedmultiplier-reset.osu | 5 +++++ osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 6 +++--- 3 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 osu.Game.Tests/Resources/timingpoint-speedmultiplier-reset.osu diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index a6b0dedf56..2ecc516919 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -262,6 +262,21 @@ namespace osu.Game.Tests.Beatmaps.Formats } } + [Test] + public void TestTimingPointResetsSpeedMultiplier() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var resStream = TestResources.OpenResource("timingpoint-speedmultiplier-reset.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var controlPoints = decoder.Decode(stream).ControlPointInfo; + + Assert.That(controlPoints.DifficultyPointAt(0).SpeedMultiplier, Is.EqualTo(0.5).Within(0.1)); + Assert.That(controlPoints.DifficultyPointAt(2000).SpeedMultiplier, Is.EqualTo(1).Within(0.1)); + } + } + [Test] public void TestDecodeBeatmapColours() { diff --git a/osu.Game.Tests/Resources/timingpoint-speedmultiplier-reset.osu b/osu.Game.Tests/Resources/timingpoint-speedmultiplier-reset.osu new file mode 100644 index 0000000000..4512903c68 --- /dev/null +++ b/osu.Game.Tests/Resources/timingpoint-speedmultiplier-reset.osu @@ -0,0 +1,5 @@ +osu file format v14 + +[TimingPoints] +0,-200,4,1,0,100,0,0 +2000,100,1,1,0,100,1,0 diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index a6b60bd7a0..aeb5df46f8 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -411,15 +411,15 @@ namespace osu.Game.Beatmaps.Formats private void addControlPoint(double time, ControlPoint point, bool timingChange) { + if (time != pendingControlPointsTime) + flushPendingPoints(); + if (timingChange) { beatmap.ControlPointInfo.Add(time, point); return; } - if (time != pendingControlPointsTime) - flushPendingPoints(); - pendingControlPoints.Add(point); pendingControlPointsTime = time; } From 5899bbd8a63d8a4035c180f0c507fc7b3676cda3 Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Wed, 30 Oct 2019 18:44:07 +0900 Subject: [PATCH 1996/2815] Fix merge regressions --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 6 +++--- osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index cf084e838e..2ecc516919 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -167,9 +167,9 @@ namespace osu.Game.Tests.Beatmaps.Formats var controlPoints = beatmap.ControlPointInfo; Assert.AreEqual(4, controlPoints.TimingPoints.Count); - Assert.AreEqual(6, controlPoints.DifficultyPoints.Count); + Assert.AreEqual(5, controlPoints.DifficultyPoints.Count); Assert.AreEqual(34, controlPoints.SamplePoints.Count); - Assert.AreEqual(9, controlPoints.EffectPoints.Count); + Assert.AreEqual(8, controlPoints.EffectPoints.Count); var timingPoint = controlPoints.TimingPointAt(0); Assert.AreEqual(956, timingPoint.Time); @@ -191,7 +191,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); difficultyPoint = controlPoints.DifficultyPointAt(48428); - Assert.AreEqual(956, difficultyPoint.Time); + Assert.AreEqual(0, difficultyPoint.Time); Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); difficultyPoint = controlPoints.DifficultyPointAt(116999); diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index d9328d1636..51b3377394 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -34,6 +34,7 @@ namespace osu.Game.Beatmaps.ControlPoints MaxValue = 60000 }; + /// <summary> /// The beat length at this control point. /// </summary> public double BeatLength From 7f2916454d934e57622c28c71ccaa57d538aeebf Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Wed, 30 Oct 2019 19:09:46 +0900 Subject: [PATCH 1997/2815] Simplify EditorClock.Seek method --- osu.Game/Screens/Edit/EditorClock.cs | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index f674a62a3a..bd2db4ae2b 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -85,22 +85,8 @@ namespace osu.Game.Screens.Edit var timingPoint = ControlPointInfo.TimingPointAt(CurrentTime); if (direction < 0 && timingPoint.Time == CurrentTime) - { // When going backwards and we're at the boundary of two timing points, we compute the seek distance with the timing point which we are seeking into - int activeIndex = -1; - - for (int i = 0; i < ControlPointInfo.TimingPoints.Count; i++) - { - if (ControlPointInfo.TimingPoints[i] == timingPoint) - { - activeIndex = i; - break; - } - } - - while (activeIndex > 0 && CurrentTime == timingPoint.Time) - timingPoint = ControlPointInfo.TimingPoints[--activeIndex]; - } + timingPoint = ControlPointInfo.TimingPointAt(CurrentTime - 1); double seekAmount = timingPoint.BeatLength / beatDivisor.Value * amount; double seekTime = CurrentTime + seekAmount * direction; From 35be8f9dfbab66ba527631ba2b100f1c9c48f35f Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Wed, 30 Oct 2019 19:33:54 +0900 Subject: [PATCH 1998/2815] Share framework file-exclusion function --- osu.Game/IO/Archives/ZipArchiveReader.cs | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/osu.Game/IO/Archives/ZipArchiveReader.cs b/osu.Game/IO/Archives/ZipArchiveReader.cs index 9033e7529d..35f38ea7e8 100644 --- a/osu.Game/IO/Archives/ZipArchiveReader.cs +++ b/osu.Game/IO/Archives/ZipArchiveReader.cs @@ -1,30 +1,16 @@ // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.IO; using System.Linq; +using osu.Framework.IO.Stores; using SharpCompress.Archives.Zip; -using SharpCompress.Common; namespace osu.Game.IO.Archives { public sealed class ZipArchiveReader : ArchiveReader { - /// <summary> - /// List of substrings that indicate a file should be ignored during the import process - /// (usually due to representing no useful data and being autogenerated by the OS). - /// </summary> - private static readonly string[] filename_ignore_list = - { - // Mac-specific - "__MACOSX", - ".DS_Store", - // Windows-specific - "Thumbs.db" - }; - private readonly Stream archiveStream; private readonly ZipArchive archive; @@ -58,9 +44,7 @@ namespace osu.Game.IO.Archives archiveStream.Dispose(); } - private static bool canBeIgnored(IEntry entry) => filename_ignore_list.Any(ignoredName => entry.Key.IndexOf(ignoredName, StringComparison.OrdinalIgnoreCase) >= 0); - - public override IEnumerable<string> Filenames => archive.Entries.Where(e => !canBeIgnored(e)).Select(e => e.Key).ToArray(); + public override IEnumerable<string> Filenames => archive.Entries.Select(e => e.Key).ExcludeSystemFileNames(); public override Stream GetUnderlyingStream() => archiveStream; } From 8fbe76b574471e3f2decbcf49bde704fd202c5eb Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan <huoyaoyuan@hotmail.com> Date: Wed, 30 Oct 2019 21:06:55 +0800 Subject: [PATCH 1999/2815] Use slnf for filtering platform. --- osu.Android.sln | 126 ------ osu.Android.sln.DotSettings | 834 ----------------------------------- osu.Android.slnf | 19 + osu.Desktop.slnf | 20 + osu.iOS.sln | 187 -------- osu.iOS.sln.DotSettings | 836 ------------------------------------ osu.iOS.slnf | 18 + osu.sln | 318 +++++++++++++- 8 files changed, 371 insertions(+), 1987 deletions(-) delete mode 100644 osu.Android.sln delete mode 100644 osu.Android.sln.DotSettings create mode 100644 osu.Android.slnf create mode 100644 osu.Desktop.slnf delete mode 100644 osu.iOS.sln delete mode 100644 osu.iOS.sln.DotSettings create mode 100644 osu.iOS.slnf diff --git a/osu.Android.sln b/osu.Android.sln deleted file mode 100644 index ebf2c55cb4..0000000000 --- a/osu.Android.sln +++ /dev/null @@ -1,126 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.28516.95 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game", "osu.Game\osu.Game.csproj", "{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Osu", "osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj", "{C92A607B-1FDD-4954-9F92-03FF547D9080}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Catch", "osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj", "{58F6C80C-1253-4A0E-A465-B8C85EBEADF3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Taiko", "osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj", "{F167E17A-7DE6-4AF5-B920-A5112296C695}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Mania", "osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj", "{48F4582B-7687-4621-9CBE-5C24197CB536}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Android", "osu.Android\osu.Android.csproj", "{D1D5F9A8-B40B-40E6-B02F-482D03346D3D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Catch.Tests.Android", "osu.Game.Rulesets.Catch.Tests.Android\osu.Game.Rulesets.Catch.Tests.Android.csproj", "{C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Mania.Tests.Android", "osu.Game.Rulesets.Mania.Tests.Android\osu.Game.Rulesets.Mania.Tests.Android.csproj", "{531F1092-DB27-445D-AA33-2A77C7187C99}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Osu.Tests.Android", "osu.Game.Rulesets.Osu.Tests.Android\osu.Game.Rulesets.Osu.Tests.Android.csproj", "{90CAB706-39CB-4B93-9629-3218A6FF8E9B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Taiko.Tests.Android", "osu.Game.Rulesets.Taiko.Tests.Android\osu.Game.Rulesets.Taiko.Tests.Android.csproj", "{3701A0A1-8476-42C6-B5C4-D24129B4A484}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Tests.Android", "osu.Game.Tests.Android\osu.Game.Tests.Android.csproj", "{5CC222DC-5716-4499-B897-DCBDDA4A5CF9}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|Any CPU.Build.0 = Release|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|Any CPU.Build.0 = Release|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|Any CPU.Build.0 = Release|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|Any CPU.Build.0 = Release|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|Any CPU.Build.0 = Debug|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|Any CPU.ActiveCfg = Release|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|Any CPU.Build.0 = Release|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|Any CPU.Build.0 = Release|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|Any CPU.Deploy.0 = Release|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|Any CPU.Build.0 = Release|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|Any CPU.Deploy.0 = Release|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|Any CPU.Build.0 = Debug|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|Any CPU.ActiveCfg = Release|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|Any CPU.Build.0 = Release|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|Any CPU.Deploy.0 = Release|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|Any CPU.Build.0 = Release|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|Any CPU.Deploy.0 = Release|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|Any CPU.Build.0 = Release|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|Any CPU.Deploy.0 = Release|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|Any CPU.Build.0 = Release|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|Any CPU.Deploy.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {671B0BEC-2403-45B0-9357-2C97CC517668} - EndGlobalSection - GlobalSection(MonoDevelopProperties) = preSolution - Policies = $0 - $0.TextStylePolicy = $1 - $1.EolMarker = Windows - $1.inheritsSet = VisualStudio - $1.inheritsScope = text/plain - $1.scope = text/x-csharp - $0.CSharpFormattingPolicy = $2 - $2.IndentSwitchSection = True - $2.NewLinesForBracesInProperties = True - $2.NewLinesForBracesInAccessors = True - $2.NewLinesForBracesInAnonymousMethods = True - $2.NewLinesForBracesInControlBlocks = True - $2.NewLinesForBracesInAnonymousTypes = True - $2.NewLinesForBracesInObjectCollectionArrayInitializers = True - $2.NewLinesForBracesInLambdaExpressionBody = True - $2.NewLineForElse = True - $2.NewLineForCatch = True - $2.NewLineForFinally = True - $2.NewLineForMembersInObjectInit = True - $2.NewLineForMembersInAnonymousTypes = True - $2.NewLineForClausesInQuery = True - $2.SpacingAfterMethodDeclarationName = False - $2.SpaceAfterMethodCallName = False - $2.SpaceBeforeOpenSquareBracket = False - $2.inheritsSet = Mono - $2.inheritsScope = text/x-csharp - $2.scope = text/x-csharp - EndGlobalSection -EndGlobal diff --git a/osu.Android.sln.DotSettings b/osu.Android.sln.DotSettings deleted file mode 100644 index 5a97fc7518..0000000000 --- a/osu.Android.sln.DotSettings +++ /dev/null @@ -1,834 +0,0 @@ -<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> - <s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Efnt/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Emp3/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Epng/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ewav/@EntryIndexedValue">True</s:Boolean> - <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=2A66DD92_002DADB1_002D4994_002D89E2_002DC94E04ACDA0D_002Fd_003AMigrations/@EntryIndexedValue">ExplicitlyExcluded</s:String> - <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=D9A367C9_002D4C1A_002D489F_002D9B05_002DA0CEA2B53B58/@EntryIndexedValue">ExplicitlyExcluded</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/AnalysisEnabled/@EntryValue">SOLUTION</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeAccessorOwnerBody/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeModifiersOrder/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeRedundantParentheses/@EntryIndexedValue"></s:String> - <s:Boolean x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeRedundantParentheses/@EntryIndexRemoved">True</s:Boolean> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeTypeMemberModifiers/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeTypeModifiers/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=AssignedValueIsNeverUsed/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=AssignNullToNotNullAttribute/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=AutoPropertyCanBeMadeGetOnly_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=AutoPropertyCanBeMadeGetOnly_002ELocal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CanBeReplacedWithTryCastAndCheckForNull/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CheckForReferenceEqualityInstead_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CheckForReferenceEqualityInstead_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ClassNeverInstantiated_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ClassNeverInstantiated_002ELocal/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ClassWithVirtualMembersNeverInherited_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CollectionNeverQueried_002EGlobal/@EntryIndexedValue">SUGGESTION</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CollectionNeverQueried_002ELocal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CommentTypo/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CompareOfFloatsByEqualityOperator/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertClosureToMethodGroup/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfDoToWhile/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfStatementToConditionalTernaryExpression/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfStatementToNullCoalescingExpression/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfToOrExpression/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertNullableToShortForm/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertPropertyToExpressionBody/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToAutoProperty/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToConstant_002ELocal/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToLambdaExpression/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToLocalFunction/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToStaticClass/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=DoubleNegationOperator/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=EmptyGeneralCatchClause/@EntryIndexedValue">DO_NOT_SHOW</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=EventNeverSubscribedTo_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=EventNeverSubscribedTo_002ELocal/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=FieldCanBeMadeReadOnly_002EGlobal/@EntryIndexedValue">DO_NOT_SHOW</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=FieldCanBeMadeReadOnly_002ELocal/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ForCanBeConvertedToForeach/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=IdentifierTypo/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ImpureMethodCallOnReadonlyValueField/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=InconsistentNaming/@EntryIndexedValue">ERROR</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=InheritdocConsiderUsage/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=InlineOutVariableDeclaration/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=InvertIf/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=InvokeAsExtensionMethod/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=JoinDeclarationAndInitializer/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=JoinNullCheckWithUsage/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MemberCanBeMadeStatic_002ELocal/@EntryIndexedValue">DO_NOT_SHOW</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MemberCanBePrivate_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MemberCanBePrivate_002ELocal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MemberCanBeProtected_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MergeCastWithTypeCheck/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MergeConditionalExpression/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MergeSequentialChecks/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MethodSupportsCancellation/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MoreSpecificForeachVariableTypeAvailable/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=NestedStringInterpolation/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=NotAccessedField_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ParameterHidesMember/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ParameterOnlyUsedForPreconditionCheck_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ParameterOnlyUsedForPreconditionCheck_002ELocal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PossibleMultipleEnumeration/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PrivateVariableCanBeMadeReadonly/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PublicConstructorInAbstractClass/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantArrayCreationExpression/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantAttributeParentheses/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantAttributeUsageProperty/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantCaseLabel/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantCommaInAttributeList/@EntryIndexedValue">DO_NOT_SHOW</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantCommaInEnumDeclaration/@EntryIndexedValue">DO_NOT_SHOW</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantCommaInInitializer/@EntryIndexedValue">DO_NOT_SHOW</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantEmptyObjectCreationArgumentList/@EntryIndexedValue">WARNING</s:String> - - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantExplicitParamsArrayCreation/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantImmediateDelegateInvocation/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantLambdaSignatureParentheses/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantUsingDirective/@EntryIndexedValue">ERROR</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantStringInterpolation/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantVerbatimPrefix/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantVerbatimStringPrefix/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RemoveRedundantOrStatement_002EFalse/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RemoveRedundantOrStatement_002ETrue/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RemoveToList_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RemoveToList_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithFirstOrDefault_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithFirstOrDefault_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithFirstOrDefault_002E3/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithFirstOrDefault_002E4/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithLastOrDefault_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithLastOrDefault_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithLastOrDefault_002E3/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithLastOrDefault_002E4/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002E3/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002EAny_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002EAny_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002ECount_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002ECount_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002EFirst_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002EFirst_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002EFirstOrDefault_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002EFirstOrDefault_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002ELast_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002ELast_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002ELastOrDefault_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002ELastOrDefault_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002ELongCount/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002ESingle_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002ESingle_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002ESingleOrDefault_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002ESingleOrDefault_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002EWhere/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSimpleAssignment_002EFalse/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSimpleAssignment_002ETrue/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleAssignment_002EFalse/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleAssignment_002ETrue/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleCallToAny/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleCallToCount/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleCallToFirst/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleCallToFirstOrDefault/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleCallToLast/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleCallToLastOrDefault/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleCallToSingle/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleCallToSingleOrDefault/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleOrDefault_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleOrDefault_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleOrDefault_002E3/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleOrDefault_002E4/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=StringLiteralTypo/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SuggestVarOrType_005FBuiltInTypes/@EntryIndexedValue">DO_NOT_SHOW</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SuggestVarOrType_005FSimpleTypes/@EntryIndexedValue">DO_NOT_SHOW</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SwitchStatementMissingSomeCases/@EntryIndexedValue">DO_NOT_SHOW</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=TooWideLocalVariableScope/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=TryCastAlwaysSucceeds/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnassignedField_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnnecessaryWhitespace/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedAutoPropertyAccessor_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedMemberHierarchy_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedMemberInSuper_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedMember_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedMember_002ELocal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedMethodReturnValue_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedMethodReturnValue_002ELocal/@EntryIndexedValue">HINT</s:String> - - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedParameter_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseCollectionCountProperty/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseFormatSpecifierInFormatString/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseFormatSpecifierInInterpolation/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseNameofExpression/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseNullPropagation/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseObjectOrCollectionInitializer/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UsePatternMatching/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseStringInterpolation/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=VariableCanBeMadeConst/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=VirtualMemberCallInConstructor/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=VirtualMemberNeverOverridden_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=VirtualMemberNeverOverridden_002ELocal/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Code_0020Cleanup_0020_0028peppy_0029/@EntryIndexedValue"><?xml version="1.0" encoding="utf-16"?><Profile name="Code Cleanup (peppy)"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseVar><BehavourStyle>CAN_CHANGE_TO_EXPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_EXPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_EXPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" /><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers></Profile></s:String> - <s:String x:Key="/Default/CodeStyle/CodeCleanup/RecentlyUsedProfile/@EntryValue">Code Cleanup (peppy)</s:String> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOR/@EntryValue">Required</s:String> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOREACH/@EntryValue">Required</s:String> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_WHILE/@EntryValue">Required</s:String> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/DEFAULT_INTERNAL_MODIFIER/@EntryValue">Explicit</s:String> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/LOCAL_FUNCTION_BODY/@EntryValue">ExpressionBody</s:String> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/METHOD_OR_OPERATOR_BODY/@EntryValue">ExpressionBody</s:String> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/USE_HEURISTICS_FOR_BODY_STYLE/@EntryValue">True</s:Boolean> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ACCESSOR_DECLARATION_BRACES/@EntryValue">NEXT_LINE</s:String> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_LINQ_QUERY/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_CALLS_CHAIN/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_EXTENDS_LIST/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_FOR_STMT/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_PARAMETER/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTIPLE_DECLARATION/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTLINE_TYPE_PARAMETER_CONSTRAINS/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTLINE_TYPE_PARAMETER_LIST/@EntryValue">True</s:Boolean> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue">NEXT_LINE</s:String> - <s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_BEFORE_BLOCK_STATEMENTS/@EntryValue">1</s:Int64> - <s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_BEFORE_CASE/@EntryValue">1</s:Int64> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/CASE_BLOCK_BRACES/@EntryValue">NEXT_LINE</s:String> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/EMPTY_BLOCK_STYLE/@EntryValue">MULTILINE</s:String> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INITIALIZER_BRACES/@EntryValue">NEXT_LINE</s:String> - <s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_CODE/@EntryValue">1</s:Int64> - <s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_DECLARATIONS/@EntryValue">1</s:Int64> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/LINE_FEED_AT_FILE_END/@EntryValue">True</s:Boolean> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/OTHER_BRACES/@EntryValue">NEXT_LINE</s:String> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_CATCH_ON_NEW_LINE/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_CONSTRUCTOR_INITIALIZER_ON_SAME_LINE/@EntryValue">False</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ELSE_ON_NEW_LINE/@EntryValue">True</s:Boolean> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_WHILE_ON_NEW_LINE/@EntryValue">False</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AFTER_TYPECAST_PARENTHESES/@EntryValue">False</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AROUND_MULTIPLICATIVE_OP/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_SIZEOF_PARENTHESES/@EntryValue">False</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_TYPEOF_PARENTHESES/@EntryValue">False</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_SINGLE_LINE_ARRAY_INITIALIZER_BRACES/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHING_EMPTY_BRACES/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/STICK_COMMENT/@EntryValue">False</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_AFTER_DECLARATION_LPAR/@EntryValue">False</s:Boolean> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARRAY_INITIALIZER_STYLE/@EntryValue">CHOP_IF_LONG</s:String> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_BEFORE_BINARY_OPSIGN/@EntryValue">True</s:Boolean> - <s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LIMIT/@EntryValue">200</s:Int64> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_OBJECT_AND_COLLECTION_INITIALIZER_STYLE/@EntryValue">CHOP_IF_LONG</s:String> - <s:Boolean x:Key="/Default/CodeStyle/EncapsulateField/MakeFieldPrivate/@EntryValue">False</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/EncapsulateField/UseAutoProperty/@EntryValue">False</s:Boolean> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AABB/@EntryIndexedValue">AABB</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=API/@EntryIndexedValue">API</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BPM/@EntryIndexedValue">BPM</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GC/@EntryIndexedValue">GC</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GL/@EntryIndexedValue">GL</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GLSL/@EntryIndexedValue">GLSL</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HID/@EntryIndexedValue">HID</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HUD/@EntryIndexedValue">HUD</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ID/@EntryIndexedValue">ID</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IP/@EntryIndexedValue">IP</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IPC/@EntryIndexedValue">IPC</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LTRB/@EntryIndexedValue">LTRB</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MD/@EntryIndexedValue">MD5</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=NS/@EntryIndexedValue">NS</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OS/@EntryIndexedValue">OS</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RGB/@EntryIndexedValue">RGB</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RNG/@EntryIndexedValue">RNG</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SHA/@EntryIndexedValue">SHA</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SRGB/@EntryIndexedValue">SRGB</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=TK/@EntryIndexedValue">TK</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SS/@EntryIndexedValue">SS</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PP/@EntryIndexedValue">PP</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GMT/@EntryIndexedValue">GMT</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=QAT/@EntryIndexedValue">QAT</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BNG/@EntryIndexedValue">BNG</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UI/@EntryIndexedValue">UI</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=EnumMember/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeStyle/CSharpFileLayoutPatterns/Pattern/@EntryValue"><?xml version="1.0" encoding="utf-16"?> -<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> - <TypePattern DisplayName="COM interfaces or structs"> - <TypePattern.Match> - <Or> - <And> - <Kind Is="Interface" /> - <Or> - <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> - <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> - </Or> - </And> - <Kind Is="Struct" /> - </Or> - </TypePattern.Match> - </TypePattern> - <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> - <TypePattern.Match> - <And> - <Kind Is="Class" /> - <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> - </And> - </TypePattern.Match> - <Entry DisplayName="Setup/Teardown Methods"> - <Entry.Match> - <And> - <Kind Is="Method" /> - <Or> - <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="All other members" /> - <Entry Priority="100" DisplayName="Test Methods"> - <Entry.Match> - <And> - <Kind Is="Method" /> - <HasAttribute Name="NUnit.Framework.TestAttribute" /> - </And> - </Entry.Match> - <Entry.SortBy> - <Name /> - </Entry.SortBy> - </Entry> - </TypePattern> - <TypePattern DisplayName="Default Pattern"> - <Group DisplayName="Fields/Properties"> - <Group DisplayName="Public Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Public Properties"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - <Group DisplayName="Internal Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Internal Properties"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - <Group DisplayName="Protected Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Protected Properties"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - <Group DisplayName="Private Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Private Properties"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Constructor/Destructor"> - <Entry DisplayName="Ctor"> - <Entry.Match> - <Kind Is="Constructor" /> - </Entry.Match> - </Entry> - <Region Name="Disposal"> - <Entry DisplayName="Dtor"> - <Entry.Match> - <Kind Is="Destructor" /> - </Entry.Match> - </Entry> - <Entry DisplayName="Dispose()"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Kind Is="Method" /> - <Name Is="Dispose" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Dispose(true)"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Or> - <Virtual /> - <Override /> - </Or> - <Kind Is="Method" /> - <Name Is="Dispose" /> - </And> - </Entry.Match> - </Entry> - </Region> - </Group> - <Group DisplayName="Methods"> - <Group DisplayName="Public"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Internal"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Protected"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Private"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - </Group> - </TypePattern> -</Patterns></s:String> - <s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. -See the LICENCE file in the repository root for full licence text. -</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Constants/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=EnumMember/@EntryIndexedValue"><Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=LocalConstants/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=LocalFunctions/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateConstants/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=StaticReadonly/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypeParameters/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=9d1af99b_002Dbefe_002D48a4_002D9eb3_002D661384e29869/@EntryIndexedValue"><Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private methods"><ElementKinds><Kind Name="ASYNC_METHOD" /><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=9ffbe43b_002Dc610_002D411b_002D9839_002D1416a146d9b0/@EntryIndexedValue"><Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public methods"><ElementKinds><Kind Name="ASYNC_METHOD" /><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=a4c2df6c_002Db202_002D48d5_002Db077_002De678cb548c25/@EntryIndexedValue"><Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private properties"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=fd562728_002Dc23d_002D417f_002Da19f_002D9d854247fbea/@EntryIndexedValue"><Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public properties"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FCONSTANT/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FFUNCTION/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FVARIABLE/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FCLASS/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FCONSTRUCTOR/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FFUNCTION/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FGLOBAL_005FVARIABLE/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FLABEL/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FLOCAL_005FCONSTRUCTOR/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FLOCAL_005FVARIABLE/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FOBJECT_005FPROPERTY_005FOF_005FFUNCTION/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FPARAMETER/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FCLASS/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FENUM/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FENUM_005FMEMBER/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FINTERFACE/@EntryIndexedValue"><Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FMIXED_005FENUM/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FMODULE/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FMODULE_005FEXPORTED/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FMODULE_005FLOCAL/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPRIVATE_005FMEMBER_005FACCESSOR/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPRIVATE_005FSTATIC_005FTYPE_005FFIELD/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPRIVATE_005FTYPE_005FFIELD/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPRIVATE_005FTYPE_005FMETHOD/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPROTECTED_005FMEMBER_005FACCESSOR/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPROTECTED_005FSTATIC_005FTYPE_005FFIELD/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPROTECTED_005FTYPE_005FFIELD/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPROTECTED_005FTYPE_005FMETHOD/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPUBLIC_005FMEMBER_005FACCESSOR/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPUBLIC_005FSTATIC_005FTYPE_005FFIELD/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPUBLIC_005FTYPE_005FFIELD/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPUBLIC_005FTYPE_005FMETHOD/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FTYPE_005FALIAS/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FTYPE_005FPARAMETER/@EntryIndexedValue"><Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/WebNaming/UserRules/=ASP_005FFIELD/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/WebNaming/UserRules/=ASP_005FHTML_005FCONTROL/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/WebNaming/UserRules/=ASP_005FTAG_005FNAME/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/WebNaming/UserRules/=ASP_005FTAG_005FPREFIX/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=NAMESPACE_005FALIAS/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FFIELD/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FRESOURCE/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpRenamePlacementToArrangementMigration/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/@KeyIndexDefined">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Applicability/=Live/@EntryIndexedValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Description/@EntryValue">o!f – Object Initializer: Anchor&Origin</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Field/=anchor/@KeyIndexDefined">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Field/=anchor/Expression/@EntryValue">constant("Centre")</s:String> - <s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Field/=anchor/Order/@EntryValue">0</s:Int64> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Reformat/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Shortcut/@EntryValue">ofao</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/ShortenQualifiedReferences/@EntryValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Text/@EntryValue">Anchor = Anchor.$anchor$, -Origin = Anchor.$anchor$,</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=2A3ECBA387AF6D468F6ABDA35DED325A/@KeyIndexDefined">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=2A3ECBA387AF6D468F6ABDA35DED325A/Applicability/=Live/@EntryIndexedValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=2A3ECBA387AF6D468F6ABDA35DED325A/Description/@EntryValue">o!f – InternalChildren = []</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=2A3ECBA387AF6D468F6ABDA35DED325A/Reformat/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=2A3ECBA387AF6D468F6ABDA35DED325A/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=2A3ECBA387AF6D468F6ABDA35DED325A/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=2A3ECBA387AF6D468F6ABDA35DED325A/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=2A3ECBA387AF6D468F6ABDA35DED325A/Shortcut/@EntryValue">ofic</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=2A3ECBA387AF6D468F6ABDA35DED325A/ShortenQualifiedReferences/@EntryValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=2A3ECBA387AF6D468F6ABDA35DED325A/Text/@EntryValue">InternalChildren = new Drawable[] -{ - $END$ -};</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=62B70E4DCA5E284A9E383E16C13789C1/@KeyIndexDefined">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=62B70E4DCA5E284A9E383E16C13789C1/Applicability/=Live/@EntryIndexedValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=62B70E4DCA5E284A9E383E16C13789C1/Description/@EntryValue">o!f – new GridContainer { .. }</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=62B70E4DCA5E284A9E383E16C13789C1/Reformat/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=62B70E4DCA5E284A9E383E16C13789C1/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=62B70E4DCA5E284A9E383E16C13789C1/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=62B70E4DCA5E284A9E383E16C13789C1/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=62B70E4DCA5E284A9E383E16C13789C1/Shortcut/@EntryValue">ofgc</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=62B70E4DCA5E284A9E383E16C13789C1/ShortenQualifiedReferences/@EntryValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=62B70E4DCA5E284A9E383E16C13789C1/Text/@EntryValue">new GridContainer -{ - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] { $END$ }, - new Drawable[] { } - } -};</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=72BD3C3DCA42C84DA1E71F1D05A903C4/@KeyIndexDefined">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=72BD3C3DCA42C84DA1E71F1D05A903C4/Applicability/=Live/@EntryIndexedValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=72BD3C3DCA42C84DA1E71F1D05A903C4/Description/@EntryValue">o!f – new FillFlowContainer { .. }</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=72BD3C3DCA42C84DA1E71F1D05A903C4/Reformat/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=72BD3C3DCA42C84DA1E71F1D05A903C4/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=72BD3C3DCA42C84DA1E71F1D05A903C4/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=72BD3C3DCA42C84DA1E71F1D05A903C4/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=72BD3C3DCA42C84DA1E71F1D05A903C4/Shortcut/@EntryValue">offf</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=72BD3C3DCA42C84DA1E71F1D05A903C4/ShortenQualifiedReferences/@EntryValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=72BD3C3DCA42C84DA1E71F1D05A903C4/Text/@EntryValue">new FillFlowContainer -{ - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - $END$ - } -},</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=750A3C67E083484FAEEA0ED2382181CC/@KeyIndexDefined">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=750A3C67E083484FAEEA0ED2382181CC/Applicability/=Live/@EntryIndexedValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=750A3C67E083484FAEEA0ED2382181CC/Description/@EntryValue">o!f – new Container { .. }</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=750A3C67E083484FAEEA0ED2382181CC/Reformat/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=750A3C67E083484FAEEA0ED2382181CC/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=750A3C67E083484FAEEA0ED2382181CC/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=750A3C67E083484FAEEA0ED2382181CC/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=750A3C67E083484FAEEA0ED2382181CC/Shortcut/@EntryValue">ofcont</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=750A3C67E083484FAEEA0ED2382181CC/ShortenQualifiedReferences/@EntryValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=750A3C67E083484FAEEA0ED2382181CC/Text/@EntryValue">new Container -{ - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - $END$ - } -},</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=78D9C2B1742FD449BD69CD18437E0C07/@KeyIndexDefined">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=78D9C2B1742FD449BD69CD18437E0C07/Applicability/=Live/@EntryIndexedValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=78D9C2B1742FD449BD69CD18437E0C07/Description/@EntryValue">o!f – BackgroundDependencyLoader load()</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=78D9C2B1742FD449BD69CD18437E0C07/Reformat/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=78D9C2B1742FD449BD69CD18437E0C07/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=78D9C2B1742FD449BD69CD18437E0C07/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=78D9C2B1742FD449BD69CD18437E0C07/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=78D9C2B1742FD449BD69CD18437E0C07/Shortcut/@EntryValue">ofbdl</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=78D9C2B1742FD449BD69CD18437E0C07/ShortenQualifiedReferences/@EntryValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=78D9C2B1742FD449BD69CD18437E0C07/Text/@EntryValue">[BackgroundDependencyLoader] -private void load() -{ - $END$ -}</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CC879477D8841A4CBD724C2DCD249435/@KeyIndexDefined">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CC879477D8841A4CBD724C2DCD249435/Applicability/=Live/@EntryIndexedValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CC879477D8841A4CBD724C2DCD249435/Description/@EntryValue">o!f – new Box { .. }</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CC879477D8841A4CBD724C2DCD249435/Reformat/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CC879477D8841A4CBD724C2DCD249435/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CC879477D8841A4CBD724C2DCD249435/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CC879477D8841A4CBD724C2DCD249435/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CC879477D8841A4CBD724C2DCD249435/Shortcut/@EntryValue">ofbox</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CC879477D8841A4CBD724C2DCD249435/ShortenQualifiedReferences/@EntryValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CC879477D8841A4CBD724C2DCD249435/Text/@EntryValue">new Box -{ - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, -},</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F5B3CB743153774F99FB9FCA0FC744EE/@KeyIndexDefined">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F5B3CB743153774F99FB9FCA0FC744EE/Applicability/=Live/@EntryIndexedValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F5B3CB743153774F99FB9FCA0FC744EE/Description/@EntryValue">o!f – Children = []</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F5B3CB743153774F99FB9FCA0FC744EE/Reformat/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F5B3CB743153774F99FB9FCA0FC744EE/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F5B3CB743153774F99FB9FCA0FC744EE/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F5B3CB743153774F99FB9FCA0FC744EE/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F5B3CB743153774F99FB9FCA0FC744EE/Shortcut/@EntryValue">ofc</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F5B3CB743153774F99FB9FCA0FC744EE/ShortenQualifiedReferences/@EntryValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F5B3CB743153774F99FB9FCA0FC744EE/Text/@EntryValue">Children = new Drawable[] -{ - $END$ -};</s:String> - <s:Boolean x:Key="/Default/UserDictionary/Words/=Beatmap/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=beatmaps/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=beatmap_0027s/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=bindable/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=Catmull/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=Drawables/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=gameplay/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=hitobjects/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=keymods/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=Kiai/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=Leaderboard/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=Leaderboards/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=Playfield/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=resampler/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=ruleset/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=rulesets/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=Taiko/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=Unranked/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> diff --git a/osu.Android.slnf b/osu.Android.slnf new file mode 100644 index 0000000000..7d90f97eb9 --- /dev/null +++ b/osu.Android.slnf @@ -0,0 +1,19 @@ +{ + "solution": { + "path": "osu.sln", + "projects": [ + "osu.Android\\osu.Android.csproj", + "osu.Game.Rulesets.Catch.Tests.Android\\osu.Game.Rulesets.Catch.Tests.Android.csproj", + "osu.Game.Rulesets.Catch\\osu.Game.Rulesets.Catch.csproj", + "osu.Game.Rulesets.Mania.Tests.Android\\osu.Game.Rulesets.Mania.Tests.Android.csproj", + "osu.Game.Rulesets.Mania\\osu.Game.Rulesets.Mania.csproj", + "osu.Game.Rulesets.Osu.Tests.Android\\osu.Game.Rulesets.Osu.Tests.Android.csproj", + "osu.Game.Rulesets.Osu\\osu.Game.Rulesets.Osu.csproj", + "osu.Game.Rulesets.Taiko.Tests.Android\\osu.Game.Rulesets.Taiko.Tests.Android.csproj", + "osu.Game.Rulesets.Taiko\\osu.Game.Rulesets.Taiko.csproj", + "osu.Game.Tests.Android\\osu.Game.Tests.Android.csproj", + "osu.Game.Tests\\osu.Game.Tests.csproj", + "osu.Game\\osu.Game.csproj" + ] + } +} \ No newline at end of file diff --git a/osu.Desktop.slnf b/osu.Desktop.slnf new file mode 100644 index 0000000000..e6b6446f72 --- /dev/null +++ b/osu.Desktop.slnf @@ -0,0 +1,20 @@ +{ + "solution": { + "path": "osu.sln", + "projects": [ + "osu.Desktop\\osu.Desktop.csproj", + "osu.Game.Rulesets.Catch.Tests\\osu.Game.Rulesets.Catch.Tests.csproj", + "osu.Game.Rulesets.Catch\\osu.Game.Rulesets.Catch.csproj", + "osu.Game.Rulesets.Mania.Tests\\osu.Game.Rulesets.Mania.Tests.csproj", + "osu.Game.Rulesets.Mania\\osu.Game.Rulesets.Mania.csproj", + "osu.Game.Rulesets.Osu.Tests\\osu.Game.Rulesets.Osu.Tests.csproj", + "osu.Game.Rulesets.Osu\\osu.Game.Rulesets.Osu.csproj", + "osu.Game.Rulesets.Taiko.Tests\\osu.Game.Rulesets.Taiko.Tests.csproj", + "osu.Game.Rulesets.Taiko\\osu.Game.Rulesets.Taiko.csproj", + "osu.Game.Tests\\osu.Game.Tests.csproj", + "osu.Game.Tournament.Tests\\osu.Game.Tournament.Tests.csproj", + "osu.Game.Tournament\\osu.Game.Tournament.csproj", + "osu.Game\\osu.Game.csproj" + ] + } +} \ No newline at end of file diff --git a/osu.iOS.sln b/osu.iOS.sln deleted file mode 100644 index 21d02d33ab..0000000000 --- a/osu.iOS.sln +++ /dev/null @@ -1,187 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27004.2006 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game", "osu.Game\osu.Game.csproj", "{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Osu", "osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj", "{C92A607B-1FDD-4954-9F92-03FF547D9080}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Catch", "osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj", "{58F6C80C-1253-4A0E-A465-B8C85EBEADF3}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Taiko", "osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj", "{F167E17A-7DE6-4AF5-B920-A5112296C695}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Mania", "osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj", "{48F4582B-7687-4621-9CBE-5C24197CB536}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.iOS", "osu.iOS\osu.iOS.csproj", "{3F082D0B-A964-43D7-BDF7-C256D76A50D0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Tests.iOS", "osu.Game.Tests.iOS\osu.Game.Tests.iOS.csproj", "{65FF8E19-6934-469B-B690-23C6D6E56A17}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Taiko.Tests.iOS", "osu.Game.Rulesets.Taiko.Tests.iOS\osu.Game.Rulesets.Taiko.Tests.iOS.csproj", "{7E408809-66AC-49D1-AF4D-98834F9B979A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Osu.Tests.iOS", "osu.Game.Rulesets.Osu.Tests.iOS\osu.Game.Rulesets.Osu.Tests.iOS.csproj", "{6653CA6F-DB06-4604-A3FD-762E25C2AF96}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Mania.Tests.iOS", "osu.Game.Rulesets.Mania.Tests.iOS\osu.Game.Rulesets.Mania.Tests.iOS.csproj", "{39FD990E-B6CE-4B2A-999F-BC008CF2C64C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Catch.Tests.iOS", "osu.Game.Rulesets.Catch.Tests.iOS\osu.Game.Rulesets.Catch.Tests.iOS.csproj", "{4004C7B7-1A62-43F1-9DF2-52450FA67E70}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - Debug|iPhoneSimulator = Debug|iPhoneSimulator - Release|iPhone = Release|iPhone - Release|iPhoneSimulator = Release|iPhoneSimulator - Debug|iPhone = Debug|iPhone - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|Any CPU.Build.0 = Release|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhone.ActiveCfg = Release|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhone.Build.0 = Release|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhone.Build.0 = Debug|Any CPU - {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Release|Any CPU.Build.0 = Release|Any CPU - {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Release|iPhone.ActiveCfg = Release|Any CPU - {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Release|iPhone.Build.0 = Release|Any CPU - {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Debug|iPhone.Build.0 = Debug|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|Any CPU.Build.0 = Release|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|iPhone.ActiveCfg = Release|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|iPhone.Build.0 = Release|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|iPhone.Build.0 = Debug|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|Any CPU.Build.0 = Release|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|iPhone.ActiveCfg = Release|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|iPhone.Build.0 = Release|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|iPhone.Build.0 = Debug|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|Any CPU.Build.0 = Release|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|iPhone.ActiveCfg = Release|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|iPhone.Build.0 = Release|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|iPhone.Build.0 = Debug|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|Any CPU.Build.0 = Debug|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|Any CPU.ActiveCfg = Release|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|Any CPU.Build.0 = Release|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|iPhone.ActiveCfg = Release|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|iPhone.Build.0 = Release|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|iPhone.Build.0 = Debug|Any CPU - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|Any CPU.ActiveCfg = Release|iPhone - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|iPhone.ActiveCfg = Release|iPhone - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|iPhone.Build.0 = Release|iPhone - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|iPhone.ActiveCfg = Debug|iPhone - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|iPhone.Build.0 = Debug|iPhone - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|Any CPU.ActiveCfg = Release|iPhone - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|iPhone.ActiveCfg = Release|iPhone - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|iPhone.Build.0 = Release|iPhone - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|iPhone.ActiveCfg = Debug|iPhone - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|iPhone.Build.0 = Debug|iPhone - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|Any CPU.ActiveCfg = Release|iPhone - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|iPhone.ActiveCfg = Release|iPhone - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|iPhone.Build.0 = Release|iPhone - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|iPhone.ActiveCfg = Debug|iPhone - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|iPhone.Build.0 = Debug|iPhone - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|Any CPU.ActiveCfg = Release|iPhone - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|iPhone.ActiveCfg = Release|iPhone - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|iPhone.Build.0 = Release|iPhone - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|iPhone.ActiveCfg = Debug|iPhone - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|iPhone.Build.0 = Debug|iPhone - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|Any CPU.ActiveCfg = Release|iPhone - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|iPhone.ActiveCfg = Release|iPhone - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|iPhone.Build.0 = Release|iPhone - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|iPhone.ActiveCfg = Debug|iPhone - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|iPhone.Build.0 = Debug|iPhone - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|Any CPU.ActiveCfg = Release|iPhone - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|iPhone.ActiveCfg = Release|iPhone - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|iPhone.Build.0 = Release|iPhone - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|iPhone.ActiveCfg = Debug|iPhone - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|iPhone.Build.0 = Debug|iPhone - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {671B0BEC-2403-45B0-9357-2C97CC517668} - EndGlobalSection - GlobalSection(MonoDevelopProperties) = preSolution - Policies = $0 - $0.TextStylePolicy = $1 - $1.EolMarker = Windows - $1.scope = text/x-csharp - $1.FileWidth = 80 - $1.TabsToSpaces = True - $0.CSharpFormattingPolicy = $2 - $2.scope = text/x-csharp - EndGlobalSection -EndGlobal diff --git a/osu.iOS.sln.DotSettings b/osu.iOS.sln.DotSettings deleted file mode 100644 index 752b817910..0000000000 --- a/osu.iOS.sln.DotSettings +++ /dev/null @@ -1,836 +0,0 @@ -<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> - <s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Efnt/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Emp3/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Epng/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ewav/@EntryIndexedValue">True</s:Boolean> - <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=2A66DD92_002DADB1_002D4994_002D89E2_002DC94E04ACDA0D_002Fd_003AMigrations/@EntryIndexedValue">ExplicitlyExcluded</s:String> - <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=D9A367C9_002D4C1A_002D489F_002D9B05_002DA0CEA2B53B58/@EntryIndexedValue">ExplicitlyExcluded</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/AnalysisEnabled/@EntryValue">SOLUTION</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeAccessorOwnerBody/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeModifiersOrder/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeRedundantParentheses/@EntryIndexedValue"></s:String> - <s:Boolean x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeRedundantParentheses/@EntryIndexRemoved">True</s:Boolean> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeTypeMemberModifiers/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeTypeModifiers/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=AssignedValueIsNeverUsed/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=AssignNullToNotNullAttribute/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=AutoPropertyCanBeMadeGetOnly_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=AutoPropertyCanBeMadeGetOnly_002ELocal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CanBeReplacedWithTryCastAndCheckForNull/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CheckForReferenceEqualityInstead_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CheckForReferenceEqualityInstead_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ClassNeverInstantiated_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ClassNeverInstantiated_002ELocal/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ClassWithVirtualMembersNeverInherited_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CollectionNeverQueried_002EGlobal/@EntryIndexedValue">SUGGESTION</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CollectionNeverQueried_002ELocal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CommentTypo/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CompareOfFloatsByEqualityOperator/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertClosureToMethodGroup/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfDoToWhile/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfStatementToConditionalTernaryExpression/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfStatementToNullCoalescingExpression/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfToOrExpression/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertNullableToShortForm/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertPropertyToExpressionBody/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToAutoProperty/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToConstant_002ELocal/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToLambdaExpression/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToLocalFunction/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToStaticClass/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=DoubleNegationOperator/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=EmptyGeneralCatchClause/@EntryIndexedValue">DO_NOT_SHOW</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=EventNeverSubscribedTo_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=EventNeverSubscribedTo_002ELocal/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=FieldCanBeMadeReadOnly_002EGlobal/@EntryIndexedValue">DO_NOT_SHOW</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=FieldCanBeMadeReadOnly_002ELocal/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ForCanBeConvertedToForeach/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=IdentifierTypo/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ImpureMethodCallOnReadonlyValueField/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=InconsistentNaming/@EntryIndexedValue">ERROR</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=InheritdocConsiderUsage/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=InlineOutVariableDeclaration/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=InvertIf/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=InvokeAsExtensionMethod/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=JoinDeclarationAndInitializer/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=JoinNullCheckWithUsage/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MemberCanBeMadeStatic_002ELocal/@EntryIndexedValue">DO_NOT_SHOW</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MemberCanBePrivate_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MemberCanBePrivate_002ELocal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MemberCanBeProtected_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MergeCastWithTypeCheck/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MergeConditionalExpression/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MergeSequentialChecks/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MethodSupportsCancellation/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MoreSpecificForeachVariableTypeAvailable/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=NestedStringInterpolation/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=NotAccessedField_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ParameterHidesMember/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ParameterOnlyUsedForPreconditionCheck_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ParameterOnlyUsedForPreconditionCheck_002ELocal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PossibleMultipleEnumeration/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PrivateVariableCanBeMadeReadonly/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PublicConstructorInAbstractClass/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantArrayCreationExpression/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantAttributeParentheses/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantAttributeUsageProperty/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantCaseLabel/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantCommaInAttributeList/@EntryIndexedValue">DO_NOT_SHOW</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantCommaInEnumDeclaration/@EntryIndexedValue">DO_NOT_SHOW</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantCommaInInitializer/@EntryIndexedValue">DO_NOT_SHOW</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantEmptyObjectCreationArgumentList/@EntryIndexedValue">WARNING</s:String> - - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantExplicitParamsArrayCreation/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantImmediateDelegateInvocation/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantLambdaSignatureParentheses/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantUsingDirective/@EntryIndexedValue">ERROR</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantStringInterpolation/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantVerbatimPrefix/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantVerbatimStringPrefix/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RemoveRedundantOrStatement_002EFalse/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RemoveRedundantOrStatement_002ETrue/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RemoveToList_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RemoveToList_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithFirstOrDefault_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithFirstOrDefault_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithFirstOrDefault_002E3/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithFirstOrDefault_002E4/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithLastOrDefault_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithLastOrDefault_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithLastOrDefault_002E3/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithLastOrDefault_002E4/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002E3/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002EAny_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002EAny_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002ECount_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002ECount_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002EFirst_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002EFirst_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002EFirstOrDefault_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002EFirstOrDefault_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002ELast_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002ELast_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002ELastOrDefault_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002ELastOrDefault_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002ELongCount/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002ESingle_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002ESingle_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002ESingleOrDefault_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002ESingleOrDefault_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithOfType_002EWhere/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSimpleAssignment_002EFalse/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSimpleAssignment_002ETrue/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleAssignment_002EFalse/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleAssignment_002ETrue/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleCallToAny/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleCallToCount/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleCallToFirst/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleCallToFirstOrDefault/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleCallToLast/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleCallToLastOrDefault/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleCallToSingle/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleCallToSingleOrDefault/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleOrDefault_002E1/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleOrDefault_002E2/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleOrDefault_002E3/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceWithSingleOrDefault_002E4/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=StringLiteralTypo/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SuggestVarOrType_005FBuiltInTypes/@EntryIndexedValue">DO_NOT_SHOW</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SuggestVarOrType_005FSimpleTypes/@EntryIndexedValue">DO_NOT_SHOW</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SwitchStatementMissingSomeCases/@EntryIndexedValue">DO_NOT_SHOW</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=TooWideLocalVariableScope/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=TryCastAlwaysSucceeds/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnassignedField_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnnecessaryWhitespace/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedAutoPropertyAccessor_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedMemberHierarchy_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedMemberInSuper_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedMember_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedMember_002ELocal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedMethodReturnValue_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedMethodReturnValue_002ELocal/@EntryIndexedValue">HINT</s:String> - - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedParameter_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseCollectionCountProperty/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseFormatSpecifierInFormatString/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseFormatSpecifierInInterpolation/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseNameofExpression/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseNullPropagation/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseObjectOrCollectionInitializer/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UsePatternMatching/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseStringInterpolation/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=VariableCanBeMadeConst/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=VirtualMemberCallInConstructor/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=VirtualMemberNeverOverridden_002EGlobal/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=VirtualMemberNeverOverridden_002ELocal/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=WrongIndentSize/@EntryIndexedValue">WARNING</s:String> - <s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Code_0020Cleanup_0020_0028peppy_0029/@EntryIndexedValue"><?xml version="1.0" encoding="utf-16"?><Profile name="Code Cleanup (peppy)"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseVar><BehavourStyle>CAN_CHANGE_TO_EXPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_EXPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_EXPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" /><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers></Profile></s:String> - <s:String x:Key="/Default/CodeStyle/CodeCleanup/RecentlyUsedProfile/@EntryValue">Code Cleanup (peppy)</s:String> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOR/@EntryValue">Required</s:String> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOREACH/@EntryValue">Required</s:String> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_WHILE/@EntryValue">Required</s:String> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/DEFAULT_INTERNAL_MODIFIER/@EntryValue">Explicit</s:String> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/LOCAL_FUNCTION_BODY/@EntryValue">ExpressionBody</s:String> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/METHOD_OR_OPERATOR_BODY/@EntryValue">ExpressionBody</s:String> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/USE_HEURISTICS_FOR_BODY_STYLE/@EntryValue">True</s:Boolean> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ACCESSOR_DECLARATION_BRACES/@EntryValue">NEXT_LINE</s:String> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_LINQ_QUERY/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_CALLS_CHAIN/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_EXTENDS_LIST/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_FOR_STMT/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_PARAMETER/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTIPLE_DECLARATION/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTLINE_TYPE_PARAMETER_CONSTRAINS/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTLINE_TYPE_PARAMETER_LIST/@EntryValue">True</s:Boolean> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue">NEXT_LINE</s:String> - <s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_BEFORE_BLOCK_STATEMENTS/@EntryValue">1</s:Int64> - <s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_BEFORE_CASE/@EntryValue">1</s:Int64> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/CASE_BLOCK_BRACES/@EntryValue">NEXT_LINE</s:String> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/EMPTY_BLOCK_STYLE/@EntryValue">MULTILINE</s:String> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INITIALIZER_BRACES/@EntryValue">NEXT_LINE</s:String> - <s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_CODE/@EntryValue">1</s:Int64> - <s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_DECLARATIONS/@EntryValue">1</s:Int64> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/LINE_FEED_AT_FILE_END/@EntryValue">True</s:Boolean> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/OTHER_BRACES/@EntryValue">NEXT_LINE</s:String> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_CATCH_ON_NEW_LINE/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_CONSTRUCTOR_INITIALIZER_ON_SAME_LINE/@EntryValue">False</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ELSE_ON_NEW_LINE/@EntryValue">True</s:Boolean> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_WHILE_ON_NEW_LINE/@EntryValue">False</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AFTER_TYPECAST_PARENTHESES/@EntryValue">False</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AROUND_MULTIPLICATIVE_OP/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_SIZEOF_PARENTHESES/@EntryValue">False</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_TYPEOF_PARENTHESES/@EntryValue">False</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_SINGLE_LINE_ARRAY_INITIALIZER_BRACES/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHING_EMPTY_BRACES/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/STICK_COMMENT/@EntryValue">False</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_AFTER_DECLARATION_LPAR/@EntryValue">False</s:Boolean> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARRAY_INITIALIZER_STYLE/@EntryValue">CHOP_IF_LONG</s:String> - <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_BEFORE_BINARY_OPSIGN/@EntryValue">True</s:Boolean> - <s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LIMIT/@EntryValue">200</s:Int64> - <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_OBJECT_AND_COLLECTION_INITIALIZER_STYLE/@EntryValue">CHOP_IF_LONG</s:String> - <s:Boolean x:Key="/Default/CodeStyle/EncapsulateField/MakeFieldPrivate/@EntryValue">False</s:Boolean> - <s:Boolean x:Key="/Default/CodeStyle/EncapsulateField/UseAutoProperty/@EntryValue">False</s:Boolean> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AABB/@EntryIndexedValue">AABB</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=API/@EntryIndexedValue">API</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BPM/@EntryIndexedValue">BPM</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GC/@EntryIndexedValue">GC</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GL/@EntryIndexedValue">GL</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GLSL/@EntryIndexedValue">GLSL</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HID/@EntryIndexedValue">HID</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HUD/@EntryIndexedValue">HUD</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ID/@EntryIndexedValue">ID</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IP/@EntryIndexedValue">IP</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IPC/@EntryIndexedValue">IPC</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LTRB/@EntryIndexedValue">LTRB</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MD/@EntryIndexedValue">MD5</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=NS/@EntryIndexedValue">NS</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OS/@EntryIndexedValue">OS</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RGB/@EntryIndexedValue">RGB</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RNG/@EntryIndexedValue">RNG</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SHA/@EntryIndexedValue">SHA</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SRGB/@EntryIndexedValue">SRGB</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=TK/@EntryIndexedValue">TK</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SS/@EntryIndexedValue">SS</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PP/@EntryIndexedValue">PP</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GMT/@EntryIndexedValue">GMT</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=QAT/@EntryIndexedValue">QAT</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BNG/@EntryIndexedValue">BNG</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UI/@EntryIndexedValue">UI</s:String> - <s:Boolean x:Key="/Default/CodeStyle/Naming/CSharpNaming/ApplyAutoDetectedRules/@EntryValue">False</s:Boolean> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=EnumMember/@EntryIndexedValue">HINT</s:String> - <s:String x:Key="/Default/CodeStyle/CSharpFileLayoutPatterns/Pattern/@EntryValue"><?xml version="1.0" encoding="utf-16"?> -<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> - <TypePattern DisplayName="COM interfaces or structs"> - <TypePattern.Match> - <Or> - <And> - <Kind Is="Interface" /> - <Or> - <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> - <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> - </Or> - </And> - <Kind Is="Struct" /> - </Or> - </TypePattern.Match> - </TypePattern> - <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> - <TypePattern.Match> - <And> - <Kind Is="Class" /> - <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> - </And> - </TypePattern.Match> - <Entry DisplayName="Setup/Teardown Methods"> - <Entry.Match> - <And> - <Kind Is="Method" /> - <Or> - <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="All other members" /> - <Entry Priority="100" DisplayName="Test Methods"> - <Entry.Match> - <And> - <Kind Is="Method" /> - <HasAttribute Name="NUnit.Framework.TestAttribute" /> - </And> - </Entry.Match> - <Entry.SortBy> - <Name /> - </Entry.SortBy> - </Entry> - </TypePattern> - <TypePattern DisplayName="Default Pattern"> - <Group DisplayName="Fields/Properties"> - <Group DisplayName="Public Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Public Properties"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - <Group DisplayName="Internal Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Internal Properties"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - <Group DisplayName="Protected Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Protected Properties"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - <Group DisplayName="Private Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Private Properties"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Constructor/Destructor"> - <Entry DisplayName="Ctor"> - <Entry.Match> - <Kind Is="Constructor" /> - </Entry.Match> - </Entry> - <Region Name="Disposal"> - <Entry DisplayName="Dtor"> - <Entry.Match> - <Kind Is="Destructor" /> - </Entry.Match> - </Entry> - <Entry DisplayName="Dispose()"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Kind Is="Method" /> - <Name Is="Dispose" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Dispose(true)"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Or> - <Virtual /> - <Override /> - </Or> - <Kind Is="Method" /> - <Name Is="Dispose" /> - </And> - </Entry.Match> - </Entry> - </Region> - </Group> - <Group DisplayName="Methods"> - <Group DisplayName="Public"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Internal"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Protected"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Private"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - </Group> - </TypePattern> -</Patterns></s:String> - <s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. -See the LICENCE file in the repository root for full licence text. -</s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Constants/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=EnumMember/@EntryIndexedValue"><Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=LocalConstants/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=LocalFunctions/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateConstants/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=StaticReadonly/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypeParameters/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=9d1af99b_002Dbefe_002D48a4_002D9eb3_002D661384e29869/@EntryIndexedValue"><Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private methods"><ElementKinds><Kind Name="ASYNC_METHOD" /><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=9ffbe43b_002Dc610_002D411b_002D9839_002D1416a146d9b0/@EntryIndexedValue"><Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public methods"><ElementKinds><Kind Name="ASYNC_METHOD" /><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=a4c2df6c_002Db202_002D48d5_002Db077_002De678cb548c25/@EntryIndexedValue"><Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private properties"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=fd562728_002Dc23d_002D417f_002Da19f_002D9d854247fbea/@EntryIndexedValue"><Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public properties"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FCONSTANT/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FFUNCTION/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FVARIABLE/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FCLASS/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FCONSTRUCTOR/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FFUNCTION/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FGLOBAL_005FVARIABLE/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FLABEL/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FLOCAL_005FCONSTRUCTOR/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FLOCAL_005FVARIABLE/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FOBJECT_005FPROPERTY_005FOF_005FFUNCTION/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FPARAMETER/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FCLASS/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FENUM/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FENUM_005FMEMBER/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FINTERFACE/@EntryIndexedValue"><Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FMIXED_005FENUM/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FMODULE/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FMODULE_005FEXPORTED/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FMODULE_005FLOCAL/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPRIVATE_005FMEMBER_005FACCESSOR/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPRIVATE_005FSTATIC_005FTYPE_005FFIELD/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPRIVATE_005FTYPE_005FFIELD/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPRIVATE_005FTYPE_005FMETHOD/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPROTECTED_005FMEMBER_005FACCESSOR/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPROTECTED_005FSTATIC_005FTYPE_005FFIELD/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPROTECTED_005FTYPE_005FFIELD/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPROTECTED_005FTYPE_005FMETHOD/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPUBLIC_005FMEMBER_005FACCESSOR/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPUBLIC_005FSTATIC_005FTYPE_005FFIELD/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPUBLIC_005FTYPE_005FFIELD/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPUBLIC_005FTYPE_005FMETHOD/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FTYPE_005FALIAS/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FTYPE_005FPARAMETER/@EntryIndexedValue"><Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/WebNaming/UserRules/=ASP_005FFIELD/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/WebNaming/UserRules/=ASP_005FHTML_005FCONTROL/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/WebNaming/UserRules/=ASP_005FTAG_005FNAME/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/WebNaming/UserRules/=ASP_005FTAG_005FPREFIX/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=NAMESPACE_005FALIAS/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FFIELD/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FRESOURCE/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> - <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpRenamePlacementToArrangementMigration/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/@KeyIndexDefined">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Applicability/=Live/@EntryIndexedValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Description/@EntryValue">o!f – Object Initializer: Anchor&Origin</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Field/=anchor/@KeyIndexDefined">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Field/=anchor/Expression/@EntryValue">constant("Centre")</s:String> - <s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Field/=anchor/Order/@EntryValue">0</s:Int64> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Reformat/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Shortcut/@EntryValue">ofao</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/ShortenQualifiedReferences/@EntryValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Text/@EntryValue">Anchor = Anchor.$anchor$, -Origin = Anchor.$anchor$,</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=2A3ECBA387AF6D468F6ABDA35DED325A/@KeyIndexDefined">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=2A3ECBA387AF6D468F6ABDA35DED325A/Applicability/=Live/@EntryIndexedValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=2A3ECBA387AF6D468F6ABDA35DED325A/Description/@EntryValue">o!f – InternalChildren = []</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=2A3ECBA387AF6D468F6ABDA35DED325A/Reformat/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=2A3ECBA387AF6D468F6ABDA35DED325A/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=2A3ECBA387AF6D468F6ABDA35DED325A/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=2A3ECBA387AF6D468F6ABDA35DED325A/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=2A3ECBA387AF6D468F6ABDA35DED325A/Shortcut/@EntryValue">ofic</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=2A3ECBA387AF6D468F6ABDA35DED325A/ShortenQualifiedReferences/@EntryValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=2A3ECBA387AF6D468F6ABDA35DED325A/Text/@EntryValue">InternalChildren = new Drawable[] -{ - $END$ -};</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=62B70E4DCA5E284A9E383E16C13789C1/@KeyIndexDefined">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=62B70E4DCA5E284A9E383E16C13789C1/Applicability/=Live/@EntryIndexedValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=62B70E4DCA5E284A9E383E16C13789C1/Description/@EntryValue">o!f – new GridContainer { .. }</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=62B70E4DCA5E284A9E383E16C13789C1/Reformat/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=62B70E4DCA5E284A9E383E16C13789C1/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=62B70E4DCA5E284A9E383E16C13789C1/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=62B70E4DCA5E284A9E383E16C13789C1/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=62B70E4DCA5E284A9E383E16C13789C1/Shortcut/@EntryValue">ofgc</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=62B70E4DCA5E284A9E383E16C13789C1/ShortenQualifiedReferences/@EntryValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=62B70E4DCA5E284A9E383E16C13789C1/Text/@EntryValue">new GridContainer -{ - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] { $END$ }, - new Drawable[] { } - } -};</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=72BD3C3DCA42C84DA1E71F1D05A903C4/@KeyIndexDefined">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=72BD3C3DCA42C84DA1E71F1D05A903C4/Applicability/=Live/@EntryIndexedValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=72BD3C3DCA42C84DA1E71F1D05A903C4/Description/@EntryValue">o!f – new FillFlowContainer { .. }</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=72BD3C3DCA42C84DA1E71F1D05A903C4/Reformat/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=72BD3C3DCA42C84DA1E71F1D05A903C4/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=72BD3C3DCA42C84DA1E71F1D05A903C4/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=72BD3C3DCA42C84DA1E71F1D05A903C4/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=72BD3C3DCA42C84DA1E71F1D05A903C4/Shortcut/@EntryValue">offf</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=72BD3C3DCA42C84DA1E71F1D05A903C4/ShortenQualifiedReferences/@EntryValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=72BD3C3DCA42C84DA1E71F1D05A903C4/Text/@EntryValue">new FillFlowContainer -{ - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - $END$ - } -},</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=750A3C67E083484FAEEA0ED2382181CC/@KeyIndexDefined">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=750A3C67E083484FAEEA0ED2382181CC/Applicability/=Live/@EntryIndexedValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=750A3C67E083484FAEEA0ED2382181CC/Description/@EntryValue">o!f – new Container { .. }</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=750A3C67E083484FAEEA0ED2382181CC/Reformat/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=750A3C67E083484FAEEA0ED2382181CC/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=750A3C67E083484FAEEA0ED2382181CC/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=750A3C67E083484FAEEA0ED2382181CC/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=750A3C67E083484FAEEA0ED2382181CC/Shortcut/@EntryValue">ofcont</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=750A3C67E083484FAEEA0ED2382181CC/ShortenQualifiedReferences/@EntryValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=750A3C67E083484FAEEA0ED2382181CC/Text/@EntryValue">new Container -{ - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - $END$ - } -},</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=78D9C2B1742FD449BD69CD18437E0C07/@KeyIndexDefined">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=78D9C2B1742FD449BD69CD18437E0C07/Applicability/=Live/@EntryIndexedValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=78D9C2B1742FD449BD69CD18437E0C07/Description/@EntryValue">o!f – BackgroundDependencyLoader load()</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=78D9C2B1742FD449BD69CD18437E0C07/Reformat/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=78D9C2B1742FD449BD69CD18437E0C07/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=78D9C2B1742FD449BD69CD18437E0C07/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=78D9C2B1742FD449BD69CD18437E0C07/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=78D9C2B1742FD449BD69CD18437E0C07/Shortcut/@EntryValue">ofbdl</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=78D9C2B1742FD449BD69CD18437E0C07/ShortenQualifiedReferences/@EntryValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=78D9C2B1742FD449BD69CD18437E0C07/Text/@EntryValue">[BackgroundDependencyLoader] -private void load() -{ - $END$ -}</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CC879477D8841A4CBD724C2DCD249435/@KeyIndexDefined">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CC879477D8841A4CBD724C2DCD249435/Applicability/=Live/@EntryIndexedValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CC879477D8841A4CBD724C2DCD249435/Description/@EntryValue">o!f – new Box { .. }</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CC879477D8841A4CBD724C2DCD249435/Reformat/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CC879477D8841A4CBD724C2DCD249435/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CC879477D8841A4CBD724C2DCD249435/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CC879477D8841A4CBD724C2DCD249435/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CC879477D8841A4CBD724C2DCD249435/Shortcut/@EntryValue">ofbox</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CC879477D8841A4CBD724C2DCD249435/ShortenQualifiedReferences/@EntryValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CC879477D8841A4CBD724C2DCD249435/Text/@EntryValue">new Box -{ - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, -},</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F5B3CB743153774F99FB9FCA0FC744EE/@KeyIndexDefined">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F5B3CB743153774F99FB9FCA0FC744EE/Applicability/=Live/@EntryIndexedValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F5B3CB743153774F99FB9FCA0FC744EE/Description/@EntryValue">o!f – Children = []</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F5B3CB743153774F99FB9FCA0FC744EE/Reformat/@EntryValue">True</s:Boolean> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F5B3CB743153774F99FB9FCA0FC744EE/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F5B3CB743153774F99FB9FCA0FC744EE/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F5B3CB743153774F99FB9FCA0FC744EE/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F5B3CB743153774F99FB9FCA0FC744EE/Shortcut/@EntryValue">ofc</s:String> - <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F5B3CB743153774F99FB9FCA0FC744EE/ShortenQualifiedReferences/@EntryValue">True</s:Boolean> - <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F5B3CB743153774F99FB9FCA0FC744EE/Text/@EntryValue">Children = new Drawable[] -{ - $END$ -};</s:String> - <s:Boolean x:Key="/Default/UserDictionary/Words/=Beatmap/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=beatmaps/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=beatmap_0027s/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=bindable/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=Catmull/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=Drawables/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=gameplay/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=hitobjects/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=keymods/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=Kiai/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=Leaderboard/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=Leaderboards/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=Playfield/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=resampler/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=ruleset/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=rulesets/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=Taiko/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/UserDictionary/Words/=Unranked/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> diff --git a/osu.iOS.slnf b/osu.iOS.slnf new file mode 100644 index 0000000000..33a1497a54 --- /dev/null +++ b/osu.iOS.slnf @@ -0,0 +1,18 @@ +{ + "solution": { + "path": "osu.sln", + "projects": [ + "osu.Game.Rulesets.Catch.Tests.iOS\\osu.Game.Rulesets.Catch.Tests.iOS.csproj", + "osu.Game.Rulesets.Catch\\osu.Game.Rulesets.Catch.csproj", + "osu.Game.Rulesets.Mania.Tests.iOS\\osu.Game.Rulesets.Mania.Tests.iOS.csproj", + "osu.Game.Rulesets.Mania\\osu.Game.Rulesets.Mania.csproj", + "osu.Game.Rulesets.Osu.Tests.iOS\\osu.Game.Rulesets.Osu.Tests.iOS.csproj", + "osu.Game.Rulesets.Osu\\osu.Game.Rulesets.Osu.csproj", + "osu.Game.Rulesets.Taiko.Tests.iOS\\osu.Game.Rulesets.Taiko.Tests.iOS.csproj", + "osu.Game.Rulesets.Taiko\\osu.Game.Rulesets.Taiko.csproj", + "osu.Game.Tests.iOS\\osu.Game.Tests.iOS.csproj", + "osu.Game.Tests\\osu.Game.Tests.csproj", + "osu.Game\\osu.Game.csproj" + ] + } +} \ No newline at end of file diff --git a/osu.sln b/osu.sln index 688339fab5..12ea89aaad 100644 --- a/osu.sln +++ b/osu.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27004.2006 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29424.173 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game", "osu.Game\osu.Game.csproj", "{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}" EndProject @@ -25,68 +25,378 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Taiko.Tes EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Osu.Tests", "osu.Game.Rulesets.Osu.Tests\osu.Game.Rulesets.Osu.Tests.csproj", "{6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Tournament", "osu.Game.Tournament\osu.Game.Tournament.csproj", "{5672CA4D-1B37-425B-A118-A8DA26E78938}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Tournament", "osu.Game.Tournament\osu.Game.Tournament.csproj", "{5672CA4D-1B37-425B-A118-A8DA26E78938}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Tournament.Tests", "osu.Game.Tournament.Tests\osu.Game.Tournament.Tests.csproj", "{5789E78D-38F9-4072-AB7B-978F34B2C17F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Tournament.Tests", "osu.Game.Tournament.Tests\osu.Game.Tournament.Tests.csproj", "{5789E78D-38F9-4072-AB7B-978F34B2C17F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.iOS", "osu.iOS\osu.iOS.csproj", "{3F082D0B-A964-43D7-BDF7-C256D76A50D0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Tests.iOS", "osu.Game.Tests.iOS\osu.Game.Tests.iOS.csproj", "{65FF8E19-6934-469B-B690-23C6D6E56A17}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Taiko.Tests.iOS", "osu.Game.Rulesets.Taiko.Tests.iOS\osu.Game.Rulesets.Taiko.Tests.iOS.csproj", "{7E408809-66AC-49D1-AF4D-98834F9B979A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Osu.Tests.iOS", "osu.Game.Rulesets.Osu.Tests.iOS\osu.Game.Rulesets.Osu.Tests.iOS.csproj", "{6653CA6F-DB06-4604-A3FD-762E25C2AF96}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Mania.Tests.iOS", "osu.Game.Rulesets.Mania.Tests.iOS\osu.Game.Rulesets.Mania.Tests.iOS.csproj", "{39FD990E-B6CE-4B2A-999F-BC008CF2C64C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Catch.Tests.iOS", "osu.Game.Rulesets.Catch.Tests.iOS\osu.Game.Rulesets.Catch.Tests.iOS.csproj", "{4004C7B7-1A62-43F1-9DF2-52450FA67E70}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Android", "osu.Android\osu.Android.csproj", "{D1D5F9A8-B40B-40E6-B02F-482D03346D3D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Catch.Tests.Android", "osu.Game.Rulesets.Catch.Tests.Android\osu.Game.Rulesets.Catch.Tests.Android.csproj", "{C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Mania.Tests.Android", "osu.Game.Rulesets.Mania.Tests.Android\osu.Game.Rulesets.Mania.Tests.Android.csproj", "{531F1092-DB27-445D-AA33-2A77C7187C99}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Osu.Tests.Android", "osu.Game.Rulesets.Osu.Tests.Android\osu.Game.Rulesets.Osu.Tests.Android.csproj", "{90CAB706-39CB-4B93-9629-3218A6FF8E9B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Taiko.Tests.Android", "osu.Game.Rulesets.Taiko.Tests.Android\osu.Game.Rulesets.Taiko.Tests.Android.csproj", "{3701A0A1-8476-42C6-B5C4-D24129B4A484}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Tests.Android", "osu.Game.Tests.Android\osu.Game.Tests.Android.csproj", "{5CC222DC-5716-4499-B897-DCBDDA4A5CF9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{10DF8F12-50FD-45D8-8A38-17BA764BF54D}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + osu.Android.props = osu.Android.props + osu.Game.props = osu.Game.props + osu.iOS.props = osu.iOS.props + osu.sln.DotSettings = osu.sln.DotSettings + osu.TestProject.props = osu.TestProject.props + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|iPhone = Debug|iPhone + Debug|iPhoneSimulator = Debug|iPhoneSimulator Release|Any CPU = Release|Any CPU + Release|iPhone = Release|iPhone + Release|iPhoneSimulator = Release|iPhoneSimulator EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhone.Build.0 = Debug|Any CPU + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|Any CPU.ActiveCfg = Release|Any CPU {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|Any CPU.Build.0 = Release|Any CPU + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhone.ActiveCfg = Release|Any CPU + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhone.Build.0 = Release|Any CPU + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|iPhone.Build.0 = Debug|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|Any CPU.ActiveCfg = Release|Any CPU {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|Any CPU.Build.0 = Release|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|iPhone.ActiveCfg = Release|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|iPhone.Build.0 = Release|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|iPhone.Build.0 = Debug|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|Any CPU.ActiveCfg = Release|Any CPU {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|Any CPU.Build.0 = Release|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|iPhone.ActiveCfg = Release|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|iPhone.Build.0 = Release|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|iPhone.Build.0 = Debug|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|Any CPU.ActiveCfg = Release|Any CPU {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|Any CPU.Build.0 = Release|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|iPhone.ActiveCfg = Release|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|iPhone.Build.0 = Release|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|iPhone.Build.0 = Debug|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|Any CPU.ActiveCfg = Release|Any CPU {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|Any CPU.Build.0 = Release|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|iPhone.ActiveCfg = Release|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|iPhone.Build.0 = Release|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {54377672-20B1-40AF-8087-5CF73BF3953A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {54377672-20B1-40AF-8087-5CF73BF3953A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {54377672-20B1-40AF-8087-5CF73BF3953A}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {54377672-20B1-40AF-8087-5CF73BF3953A}.Debug|iPhone.Build.0 = Debug|Any CPU + {54377672-20B1-40AF-8087-5CF73BF3953A}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {54377672-20B1-40AF-8087-5CF73BF3953A}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {54377672-20B1-40AF-8087-5CF73BF3953A}.Release|Any CPU.ActiveCfg = Release|Any CPU {54377672-20B1-40AF-8087-5CF73BF3953A}.Release|Any CPU.Build.0 = Release|Any CPU + {54377672-20B1-40AF-8087-5CF73BF3953A}.Release|iPhone.ActiveCfg = Release|Any CPU + {54377672-20B1-40AF-8087-5CF73BF3953A}.Release|iPhone.Build.0 = Release|Any CPU + {54377672-20B1-40AF-8087-5CF73BF3953A}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {54377672-20B1-40AF-8087-5CF73BF3953A}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {419659FD-72EA-4678-9EB8-B22A746CED70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {419659FD-72EA-4678-9EB8-B22A746CED70}.Debug|Any CPU.Build.0 = Debug|Any CPU + {419659FD-72EA-4678-9EB8-B22A746CED70}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {419659FD-72EA-4678-9EB8-B22A746CED70}.Debug|iPhone.Build.0 = Debug|Any CPU + {419659FD-72EA-4678-9EB8-B22A746CED70}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {419659FD-72EA-4678-9EB8-B22A746CED70}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {419659FD-72EA-4678-9EB8-B22A746CED70}.Release|Any CPU.ActiveCfg = Release|Any CPU {419659FD-72EA-4678-9EB8-B22A746CED70}.Release|Any CPU.Build.0 = Release|Any CPU + {419659FD-72EA-4678-9EB8-B22A746CED70}.Release|iPhone.ActiveCfg = Release|Any CPU + {419659FD-72EA-4678-9EB8-B22A746CED70}.Release|iPhone.Build.0 = Release|Any CPU + {419659FD-72EA-4678-9EB8-B22A746CED70}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {419659FD-72EA-4678-9EB8-B22A746CED70}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Debug|iPhone.Build.0 = Debug|Any CPU + {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Release|Any CPU.ActiveCfg = Release|Any CPU {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Release|Any CPU.Build.0 = Release|Any CPU + {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Release|iPhone.ActiveCfg = Release|Any CPU + {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Release|iPhone.Build.0 = Release|Any CPU + {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {7E9E9C34-B204-406B-82E2-E01E900699CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7E9E9C34-B204-406B-82E2-E01E900699CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7E9E9C34-B204-406B-82E2-E01E900699CD}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {7E9E9C34-B204-406B-82E2-E01E900699CD}.Debug|iPhone.Build.0 = Debug|Any CPU + {7E9E9C34-B204-406B-82E2-E01E900699CD}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {7E9E9C34-B204-406B-82E2-E01E900699CD}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {7E9E9C34-B204-406B-82E2-E01E900699CD}.Release|Any CPU.ActiveCfg = Release|Any CPU {7E9E9C34-B204-406B-82E2-E01E900699CD}.Release|Any CPU.Build.0 = Release|Any CPU + {7E9E9C34-B204-406B-82E2-E01E900699CD}.Release|iPhone.ActiveCfg = Release|Any CPU + {7E9E9C34-B204-406B-82E2-E01E900699CD}.Release|iPhone.Build.0 = Release|Any CPU + {7E9E9C34-B204-406B-82E2-E01E900699CD}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {7E9E9C34-B204-406B-82E2-E01E900699CD}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Debug|iPhone.Build.0 = Debug|Any CPU + {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Release|Any CPU.ActiveCfg = Release|Any CPU {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Release|Any CPU.Build.0 = Release|Any CPU + {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Release|iPhone.ActiveCfg = Release|Any CPU + {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Release|iPhone.Build.0 = Release|Any CPU + {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Debug|iPhone.Build.0 = Debug|Any CPU + {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Release|Any CPU.ActiveCfg = Release|Any CPU {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Release|Any CPU.Build.0 = Release|Any CPU + {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Release|iPhone.ActiveCfg = Release|Any CPU + {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Release|iPhone.Build.0 = Release|Any CPU + {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {5672CA4D-1B37-425B-A118-A8DA26E78938}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5672CA4D-1B37-425B-A118-A8DA26E78938}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5672CA4D-1B37-425B-A118-A8DA26E78938}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {5672CA4D-1B37-425B-A118-A8DA26E78938}.Debug|iPhone.Build.0 = Debug|Any CPU + {5672CA4D-1B37-425B-A118-A8DA26E78938}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {5672CA4D-1B37-425B-A118-A8DA26E78938}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {5672CA4D-1B37-425B-A118-A8DA26E78938}.Release|Any CPU.ActiveCfg = Release|Any CPU {5672CA4D-1B37-425B-A118-A8DA26E78938}.Release|Any CPU.Build.0 = Release|Any CPU + {5672CA4D-1B37-425B-A118-A8DA26E78938}.Release|iPhone.ActiveCfg = Release|Any CPU + {5672CA4D-1B37-425B-A118-A8DA26E78938}.Release|iPhone.Build.0 = Release|Any CPU + {5672CA4D-1B37-425B-A118-A8DA26E78938}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {5672CA4D-1B37-425B-A118-A8DA26E78938}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Debug|iPhone.Build.0 = Debug|Any CPU + {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Release|Any CPU.ActiveCfg = Release|Any CPU {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Release|Any CPU.Build.0 = Release|Any CPU + {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Release|iPhone.ActiveCfg = Release|Any CPU + {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Release|iPhone.Build.0 = Release|Any CPU + {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|iPhone.ActiveCfg = Debug|iPhone + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|iPhone.Build.0 = Debug|iPhone + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|Any CPU.ActiveCfg = Release|iPhone + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|iPhone.ActiveCfg = Release|iPhone + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|iPhone.Build.0 = Release|iPhone + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|iPhone.ActiveCfg = Debug|iPhone + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|iPhone.Build.0 = Debug|iPhone + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|Any CPU.ActiveCfg = Release|iPhone + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|iPhone.ActiveCfg = Release|iPhone + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|iPhone.Build.0 = Release|iPhone + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|iPhone.ActiveCfg = Debug|iPhone + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|iPhone.Build.0 = Debug|iPhone + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|Any CPU.ActiveCfg = Release|iPhone + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|iPhone.ActiveCfg = Release|iPhone + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|iPhone.Build.0 = Release|iPhone + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|iPhone.ActiveCfg = Debug|iPhone + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|iPhone.Build.0 = Debug|iPhone + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|Any CPU.ActiveCfg = Release|iPhone + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|iPhone.ActiveCfg = Release|iPhone + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|iPhone.Build.0 = Release|iPhone + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|iPhone.ActiveCfg = Debug|iPhone + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|iPhone.Build.0 = Debug|iPhone + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|Any CPU.ActiveCfg = Release|iPhone + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|iPhone.ActiveCfg = Release|iPhone + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|iPhone.Build.0 = Release|iPhone + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|iPhone.ActiveCfg = Debug|iPhone + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|iPhone.Build.0 = Debug|iPhone + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|Any CPU.ActiveCfg = Release|iPhone + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|iPhone.ActiveCfg = Release|iPhone + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|iPhone.Build.0 = Release|iPhone + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|iPhone.Build.0 = Debug|Any CPU + {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|iPhone.Deploy.0 = Debug|Any CPU + {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU + {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|Any CPU.Build.0 = Release|Any CPU + {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|Any CPU.Deploy.0 = Release|Any CPU + {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|iPhone.ActiveCfg = Release|Any CPU + {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|iPhone.Build.0 = Release|Any CPU + {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|iPhone.Deploy.0 = Release|Any CPU + {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU + {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|iPhone.Build.0 = Debug|Any CPU + {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|iPhone.Deploy.0 = Debug|Any CPU + {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU + {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|Any CPU.Build.0 = Release|Any CPU + {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|Any CPU.Deploy.0 = Release|Any CPU + {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|iPhone.ActiveCfg = Release|Any CPU + {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|iPhone.Build.0 = Release|Any CPU + {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|iPhone.Deploy.0 = Release|Any CPU + {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU + {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|Any CPU.Build.0 = Debug|Any CPU + {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|iPhone.Build.0 = Debug|Any CPU + {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|iPhone.Deploy.0 = Debug|Any CPU + {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU + {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|Any CPU.ActiveCfg = Release|Any CPU + {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|Any CPU.Build.0 = Release|Any CPU + {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|Any CPU.Deploy.0 = Release|Any CPU + {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|iPhone.ActiveCfg = Release|Any CPU + {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|iPhone.Build.0 = Release|Any CPU + {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|iPhone.Deploy.0 = Release|Any CPU + {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU + {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|iPhone.Build.0 = Debug|Any CPU + {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|iPhone.Deploy.0 = Debug|Any CPU + {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU + {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|Any CPU.Build.0 = Release|Any CPU + {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|Any CPU.Deploy.0 = Release|Any CPU + {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|iPhone.ActiveCfg = Release|Any CPU + {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|iPhone.Build.0 = Release|Any CPU + {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|iPhone.Deploy.0 = Release|Any CPU + {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU + {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|iPhone.Build.0 = Debug|Any CPU + {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|iPhone.Deploy.0 = Debug|Any CPU + {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU + {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|Any CPU.Build.0 = Release|Any CPU + {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|Any CPU.Deploy.0 = Release|Any CPU + {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|iPhone.ActiveCfg = Release|Any CPU + {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|iPhone.Build.0 = Release|Any CPU + {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|iPhone.Deploy.0 = Release|Any CPU + {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU + {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|iPhone.Build.0 = Debug|Any CPU + {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|iPhone.Deploy.0 = Debug|Any CPU + {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU + {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|Any CPU.Build.0 = Release|Any CPU + {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|Any CPU.Deploy.0 = Release|Any CPU + {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|iPhone.ActiveCfg = Release|Any CPU + {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|iPhone.Build.0 = Release|Any CPU + {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|iPhone.Deploy.0 = Release|Any CPU + {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 93fae7ad4d257e91c53ecbd9d0bb8a106111fcd4 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan <huoyaoyuan@hotmail.com> Date: Wed, 30 Oct 2019 21:46:30 +0800 Subject: [PATCH 2000/2815] Remove meanless properties. --- .../osu.Game.Rulesets.Catch.Tests.iOS.csproj | 2 -- .../osu.Game.Rulesets.Mania.Tests.iOS.csproj | 2 -- .../osu.Game.Rulesets.Osu.Tests.iOS.csproj | 2 -- .../osu.Game.Rulesets.Taiko.Tests.iOS.csproj | 2 -- osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj | 2 -- osu.Game/osu.Game.csproj | 3 --- osu.TestProject.props | 3 --- osu.iOS/osu.iOS.csproj | 2 -- 8 files changed, 18 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj index 7990c35e09..708779986c 100644 --- a/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj +++ b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> -<Import Project="..\packages\NUnit.3.11.0\build\NUnit.props" Condition="Exists('..\packages\NUnit.3.11.0\build\NUnit.props')" /> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">iPhoneSimulator</Platform> @@ -33,5 +32,4 @@ </ProjectReference> </ItemGroup> <Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" /> - <Import Project="..\packages\NETStandard.Library.2.0.0\build\netstandard2.0\NETStandard.Library.targets" Condition="Exists('..\packages\NETStandard.Library.2.0.0\build\netstandard2.0\NETStandard.Library.targets')" /> </Project> \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj b/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj index 58c2e2aa5a..52e558931b 100644 --- a/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj +++ b/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> -<Import Project="..\packages\NUnit.3.11.0\build\NUnit.props" Condition="Exists('..\packages\NUnit.3.11.0\build\NUnit.props')" /> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">iPhoneSimulator</Platform> @@ -33,5 +32,4 @@ </ProjectReference> </ItemGroup> <Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" /> - <Import Project="..\packages\NETStandard.Library.2.0.0\build\netstandard2.0\NETStandard.Library.targets" Condition="Exists('..\packages\NETStandard.Library.2.0.0\build\netstandard2.0\NETStandard.Library.targets')" /> </Project> \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj index c7787bd162..cacd035078 100644 --- a/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj +++ b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> -<Import Project="..\packages\NUnit.3.11.0\build\NUnit.props" Condition="Exists('..\packages\NUnit.3.11.0\build\NUnit.props')" /> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">iPhoneSimulator</Platform> @@ -33,5 +32,4 @@ </ProjectReference> </ItemGroup> <Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" /> - <Import Project="..\packages\NETStandard.Library.2.0.0\build\netstandard2.0\NETStandard.Library.targets" Condition="Exists('..\packages\NETStandard.Library.2.0.0\build\netstandard2.0\NETStandard.Library.targets')" /> </Project> \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj b/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj index 3e46bb89af..c20bd4fe02 100644 --- a/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> -<Import Project="..\packages\NUnit.3.11.0\build\NUnit.props" Condition="Exists('..\packages\NUnit.3.11.0\build\NUnit.props')" /> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">iPhoneSimulator</Platform> @@ -33,5 +32,4 @@ </ProjectReference> </ItemGroup> <Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" /> - <Import Project="..\packages\NETStandard.Library.2.0.0\build\netstandard2.0\NETStandard.Library.targets" Condition="Exists('..\packages\NETStandard.Library.2.0.0\build\netstandard2.0\NETStandard.Library.targets')" /> </Project> \ No newline at end of file diff --git a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj index 5c0713b895..583d188295 100644 --- a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj +++ b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <Import Project="..\packages\NUnit.3.11.0\build\NUnit.props" Condition="Exists('..\packages\NUnit.3.11.0\build\NUnit.props')" /> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">iPhoneSimulator</Platform> @@ -48,5 +47,4 @@ <PackageReference Include="DeepEqual" Version="2.0.0" /> </ItemGroup> <Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" /> - <Import Project="..\packages\NETStandard.Library.2.0.0\build\netstandard2.0\NETStandard.Library.targets" Condition="Exists('..\packages\NETStandard.Library.2.0.0\build\netstandard2.0\NETStandard.Library.targets')" /> </Project> \ No newline at end of file diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 0cb09d9b14..53678b96c7 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -17,9 +17,6 @@ <copyright>Copyright (c) 2019 ppy Pty Ltd</copyright> <PackageTags>osu game</PackageTags> </PropertyGroup> - <ItemGroup Label="Service"> - <Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" /> - </ItemGroup> <ItemGroup Label="Package References"> <PackageReference Include="Humanizer" Version="2.7.9" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> diff --git a/osu.TestProject.props b/osu.TestProject.props index a5c70f4edc..79f1cf49d1 100644 --- a/osu.TestProject.props +++ b/osu.TestProject.props @@ -3,9 +3,6 @@ <PropertyGroup> <StartupObject>osu.Game.Tests.VisualTestRunner</StartupObject> </PropertyGroup> - <ItemGroup Label="Service"> - <Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" /> - </ItemGroup> <ItemGroup Label="Project References"> <ProjectReference Include="..\osu.Game\osu.Game.csproj" /> </ItemGroup> diff --git a/osu.iOS/osu.iOS.csproj b/osu.iOS/osu.iOS.csproj index 19d1acf014..49d45e9031 100644 --- a/osu.iOS/osu.iOS.csproj +++ b/osu.iOS/osu.iOS.csproj @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <Import Project="..\packages\NUnit.3.11.0\build\NUnit.props" Condition="Exists('..\packages\NUnit.3.11.0\build\NUnit.props')" /> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">iPhoneSimulator</Platform> @@ -77,5 +76,4 @@ <ImageAsset Include="Assets.xcassets\AppIcon.appiconset\iPhoneSpotlight3x.png" /> </ItemGroup> <Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" /> - <Import Project="..\packages\NETStandard.Library.2.0.0\build\netstandard2.0\NETStandard.Library.targets" Condition="Exists('..\packages\NETStandard.Library.2.0.0\build\netstandard2.0\NETStandard.Library.targets')" /> </Project> \ No newline at end of file From 31595159beed1e0476c48ccf5b33ed3e222915ab Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan <huoyaoyuan@hotmail.com> Date: Wed, 30 Oct 2019 21:54:14 +0800 Subject: [PATCH 2001/2815] Normalize .props --- osu.Game.props => Directory.Build.props | 6 +++--- osu.Desktop/osu.Desktop.csproj | 1 - osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj | 1 - osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj | 1 - osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj | 1 - osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj | 1 - osu.Game.Tournament/osu.Game.Tournament.csproj | 1 - osu.Game/osu.Game.csproj | 1 - osu.TestProject.props | 5 ++--- 9 files changed, 5 insertions(+), 13 deletions(-) rename osu.Game.props => Directory.Build.props (78%) diff --git a/osu.Game.props b/Directory.Build.props similarity index 78% rename from osu.Game.props rename to Directory.Build.props index 1a3c0aec3e..4f7ad880b5 100644 --- a/osu.Game.props +++ b/Directory.Build.props @@ -1,13 +1,13 @@ <!-- Contains required properties for osu!framework projects. --> <Project> <PropertyGroup Label="C#"> - <LangVersion>7.2</LangVersion> + <LangVersion>7.3</LangVersion> </PropertyGroup> <PropertyGroup> - <ApplicationManifest>..\app.manifest</ApplicationManifest> + <ApplicationManifest>$(MSBuildThisFileDirectory)app.manifest</ApplicationManifest> </PropertyGroup> <ItemGroup Label="License"> - <None Include="..\osu.licenseheader"> + <None Include="$(MSBuildThisFileDirectory)osu.licenseheader"> <Link>osu.licenseheader</Link> </None> </ItemGroup> diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 2d1282634f..84881ce1d3 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -1,5 +1,4 @@ <Project Sdk="Microsoft.NET.Sdk"> - <Import Project="..\osu.Game.props" /> <PropertyGroup Label="Project"> <TargetFramework>netcoreapp3.0</TargetFramework> <OutputType>WinExe</OutputType> diff --git a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj index 883cac67d1..53d8651476 100644 --- a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj +++ b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj @@ -1,5 +1,4 @@ <Project Sdk="Microsoft.NET.Sdk"> - <Import Project="..\osu.Game.props" /> <PropertyGroup Label="Project"> <TargetFramework>netstandard2.0</TargetFramework> <OutputType>Library</OutputType> diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj index a086da0565..061cced227 100644 --- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj +++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj @@ -1,5 +1,4 @@ <Project Sdk="Microsoft.NET.Sdk"> - <Import Project="..\osu.Game.props" /> <PropertyGroup Label="Project"> <TargetFramework>netstandard2.0</TargetFramework> <OutputType>Library</OutputType> diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index b0ca314551..afd5e1f753 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -1,5 +1,4 @@ <Project Sdk="Microsoft.NET.Sdk"> - <Import Project="..\osu.Game.props" /> <PropertyGroup Label="Project"> <TargetFramework>netstandard2.0</TargetFramework> <OutputType>Library</OutputType> diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj index 656ebcc7c2..482f459564 100644 --- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj +++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj @@ -1,5 +1,4 @@ <Project Sdk="Microsoft.NET.Sdk"> - <Import Project="..\osu.Game.props" /> <PropertyGroup Label="Project"> <TargetFramework>netstandard2.0</TargetFramework> <OutputType>Library</OutputType> diff --git a/osu.Game.Tournament/osu.Game.Tournament.csproj b/osu.Game.Tournament/osu.Game.Tournament.csproj index bddaff0a80..7e1f0eb2b3 100644 --- a/osu.Game.Tournament/osu.Game.Tournament.csproj +++ b/osu.Game.Tournament/osu.Game.Tournament.csproj @@ -1,5 +1,4 @@ <Project Sdk="Microsoft.NET.Sdk"> - <Import Project="..\osu.Game.props" /> <PropertyGroup Label="Project"> <TargetFramework>netstandard2.0</TargetFramework> <OutputType>Library</OutputType> diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 53678b96c7..717e3e7de8 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -1,5 +1,4 @@ <Project Sdk="Microsoft.NET.Sdk"> - <Import Project="..\osu.Game.props" /> <PropertyGroup Label="Project"> <TargetFramework>netstandard2.0</TargetFramework> <OutputType>Library</OutputType> diff --git a/osu.TestProject.props b/osu.TestProject.props index 79f1cf49d1..7e87bc5414 100644 --- a/osu.TestProject.props +++ b/osu.TestProject.props @@ -1,13 +1,12 @@ <Project> - <Import Project="osu.Game.props" /> <PropertyGroup> <StartupObject>osu.Game.Tests.VisualTestRunner</StartupObject> </PropertyGroup> <ItemGroup Label="Project References"> - <ProjectReference Include="..\osu.Game\osu.Game.csproj" /> + <ProjectReference Include="$(MSBuildThisFileDirectory)osu.Game\osu.Game.csproj" /> </ItemGroup> <ItemGroup> - <Compile Include="..\osu.Game\Tests\VisualTestRunner.cs"> + <Compile Include="$(MSBuildThisFileDirectory)osu.Game\Tests\VisualTestRunner.cs"> <Link>VisualTestRunner.cs</Link> </Compile> </ItemGroup> From 990227827731a203e5091844f7993a077b91bd6e Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan <huoyaoyuan@hotmail.com> Date: Wed, 30 Oct 2019 22:28:10 +0800 Subject: [PATCH 2002/2815] Merge props orthogonally. --- osu.Android.props | 27 ++++-------- osu.iOS.props | 105 ++++++++++++++-------------------------------- 2 files changed, 41 insertions(+), 91 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 8b31be3f12..26a80660ca 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -1,13 +1,10 @@ <Project> - <PropertyGroup> - <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> - <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> +<PropertyGroup> <OutputPath>bin\$(Configuration)</OutputPath> <WarningLevel>4</WarningLevel> <SchemaVersion>2.0</SchemaVersion> <BundleAssemblies>false</BundleAssemblies> <AotAssemblies>false</AotAssemblies> - <LangVersion>default</LangVersion> <OutputType>Library</OutputType> <FileAlignment>512</FileAlignment> <GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies> @@ -15,37 +12,31 @@ <AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType> <TargetFrameworkVersion>v9.0</TargetFrameworkVersion> <AndroidUseLatestPlatformSdk>false</AndroidUseLatestPlatformSdk> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + <AndroidSupportedAbis>armeabi-v7a;x86;arm64-v8a</AndroidSupportedAbis> + <AndroidEnableSGenConcurrent>true</AndroidEnableSGenConcurrent> + <MandroidI18n>cjk,mideast,other,rare,west</MandroidI18n> + <AndroidLinkMode>SdkOnly</AndroidLinkMode> + <ErrorReport>prompt</ErrorReport> </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <PropertyGroup Condition="'$(Configuration)' == 'Debug'"> <DebugSymbols>True</DebugSymbols> <DebugType>portable</DebugType> <Optimize>False</Optimize> <DefineConstants>DEBUG;TRACE</DefineConstants> - <ErrorReport>prompt</ErrorReport> <EnableLLVM>false</EnableLLVM> <AndroidManagedSymbols>false</AndroidManagedSymbols> - <AndroidLinkMode>SdkOnly</AndroidLinkMode> <AndroidUseSharedRuntime>true</AndroidUseSharedRuntime> <EmbedAssembliesIntoApk>false</EmbedAssembliesIntoApk> - <MandroidI18n>cjk,mideast,other,rare,west</MandroidI18n> - <AndroidEnableSGenConcurrent>true</AndroidEnableSGenConcurrent> - <AndroidSupportedAbis>armeabi-v7a;x86;arm64-v8a</AndroidSupportedAbis> - <AllowUnsafeBlocks>true</AllowUnsafeBlocks> </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <PropertyGroup Condition="'$(Configuration)' == 'Release'"> <DebugSymbols>false</DebugSymbols> <DebugType>None</DebugType> <Optimize>True</Optimize> - <ErrorReport>prompt</ErrorReport> <EnableLLVM>true</EnableLLVM> <AndroidManagedSymbols>false</AndroidManagedSymbols> - <AndroidLinkMode>SdkOnly</AndroidLinkMode> <AndroidUseSharedRuntime>False</AndroidUseSharedRuntime> <EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk> - <MandroidI18n>cjk,mideast,other,rare,west</MandroidI18n> - <AndroidEnableSGenConcurrent>true</AndroidEnableSGenConcurrent> - <AndroidSupportedAbis>armeabi-v7a;x86;arm64-v8a</AndroidSupportedAbis> - <AllowUnsafeBlocks>true</AllowUnsafeBlocks> </PropertyGroup> <ItemGroup> <None Include="$(MSBuildThisFileDirectory)\osu.licenseheader"> diff --git a/osu.iOS.props b/osu.iOS.props index 719aced705..8079022ca0 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -5,91 +5,50 @@ <RestoreProjectStyle>PackageReference</RestoreProjectStyle> <DefaultMtouchExtraArgs>--nolinkaway</DefaultMtouchExtraArgs> <DefaultMtouchGccFlags>-lstdc++ -lbz2 -framework AudioToolbox -framework AVFoundation -framework CoreMedia -framework VideoToolbox -framework SystemConfiguration -framework CFNetwork -framework Accelerate</DefaultMtouchGccFlags> + <OutputPath>bin\$(Platform)\$(Configuration)</OutputPath> + <MtouchI18n>cjk,mideast,other,rare,west</MtouchI18n> + <OptimizePNGs>false</OptimizePNGs> + <MtouchExtraArgs>$(DefaultMtouchExtraArgs) -gcc_flags "$(DefaultMtouchGccFlags)"</MtouchExtraArgs> + <MtouchHttpClientHandler>NSUrlSessionHandler</MtouchHttpClientHandler> + <MtouchVerbosity></MtouchVerbosity> + <CodesignKey>iPhone Developer</CodesignKey> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)' == 'Debug'"> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <DefineConstants>DEBUG;ENABLE_TEST_CLOUD;</DefineConstants> + <MtouchDebug>true</MtouchDebug> + <MtouchNoSymbolStrip>true</MtouchNoSymbolStrip> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)' == 'Release'"> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + </PropertyGroup> + <PropertyGroup Condition="'$(Platform)' == 'iPhoneSimulator'"> + <MtouchArch>x86_64</MtouchArch> + <MtouchLink>None</MtouchLink> + </PropertyGroup> + <PropertyGroup Condition="'$(Platform)' == 'iPhone'"> + <MtouchFloat32>true</MtouchFloat32> + <MtouchLink>SdkOnly</MtouchLink> + <MtouchArch>ARM64</MtouchArch> + <CodesignEntitlements>Entitlements.plist</CodesignEntitlements> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhoneSimulator' "> - <DebugSymbols>true</DebugSymbols> - <DebugType>full</DebugType> - <Optimize>false</Optimize> - <OutputPath>bin\iPhoneSimulator\Debug</OutputPath> - <DefineConstants>DEBUG;ENABLE_TEST_CLOUD;</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - <CodesignKey>iPhone Developer</CodesignKey> - <MtouchDebug>true</MtouchDebug> - <MtouchNoSymbolStrip>true</MtouchNoSymbolStrip> <MtouchFastDev>true</MtouchFastDev> <IOSDebuggerPort>25823</IOSDebuggerPort> - <MtouchLink>None</MtouchLink> - <MtouchArch>x86_64</MtouchArch> - <MtouchHttpClientHandler>NSUrlSessionHandler</MtouchHttpClientHandler> <DeviceSpecificBuild>false</DeviceSpecificBuild> - <MtouchVerbosity></MtouchVerbosity> - <LangVersion>Default</LangVersion> - <MtouchExtraArgs>$(DefaultMtouchExtraArgs) -gcc_flags "$(DefaultMtouchGccFlags)"</MtouchExtraArgs> - <OptimizePNGs>false</OptimizePNGs> - <MtouchI18n>cjk,mideast,other,rare,west</MtouchI18n> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhone' "> - <DebugType>pdbonly</DebugType> - <Optimize>true</Optimize> - <OutputPath>bin\iPhone\Release</OutputPath> - <DefineConstants></DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - <CodesignKey>iPhone Distribution</CodesignKey> - <MtouchUseLlvm>true</MtouchUseLlvm> - <MtouchFloat32>true</MtouchFloat32> - <CodesignEntitlements>Entitlements.plist</CodesignEntitlements> - <MtouchLink>SdkOnly</MtouchLink> - <MtouchArch>ARM64</MtouchArch> - <MtouchHttpClientHandler>NSUrlSessionHandler</MtouchHttpClientHandler> - <MtouchVerbosity></MtouchVerbosity> - <LangVersion>Default</LangVersion> - <MtouchExtraArgs>$(DefaultMtouchExtraArgs) -gcc_flags "$(DefaultMtouchGccFlags)"</MtouchExtraArgs> - <OptimizePNGs>false</OptimizePNGs> - <MtouchI18n>cjk,mideast,other,rare,west</MtouchI18n> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhoneSimulator' "> - <DebugType>pdbonly</DebugType> - <Optimize>true</Optimize> - <OutputPath>bin\iPhoneSimulator\Release</OutputPath> - <DefineConstants></DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - <CodesignKey>iPhone Developer</CodesignKey> <MtouchNoSymbolStrip>true</MtouchNoSymbolStrip> - <MtouchLink>None</MtouchLink> - <MtouchArch>x86_64</MtouchArch> - <MtouchHttpClientHandler>NSUrlSessionHandler</MtouchHttpClientHandler> - <MtouchVerbosity></MtouchVerbosity> - <LangVersion>Default</LangVersion> - <MtouchExtraArgs>$(DefaultMtouchExtraArgs) -gcc_flags "$(DefaultMtouchGccFlags)"</MtouchExtraArgs> - <OptimizePNGs>false</OptimizePNGs> - <MtouchI18n>cjk,mideast,other,rare,west</MtouchI18n> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhone' "> - <DebugSymbols>true</DebugSymbols> - <DebugType>full</DebugType> - <Optimize>false</Optimize> - <OutputPath>bin\iPhone\Debug</OutputPath> - <DefineConstants>DEBUG;ENABLE_TEST_CLOUD;</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - <CodesignKey>iPhone Developer</CodesignKey> <DeviceSpecificBuild>true</DeviceSpecificBuild> - <MtouchDebug>true</MtouchDebug> - <MtouchNoSymbolStrip>true</MtouchNoSymbolStrip> - <MtouchFloat32>true</MtouchFloat32> - <CodesignEntitlements>Entitlements.plist</CodesignEntitlements> <IOSDebuggerPort>28126</IOSDebuggerPort> - <MtouchLink>SdkOnly</MtouchLink> - <MtouchArch>ARM64</MtouchArch> - <MtouchHttpClientHandler>NSUrlSessionHandler</MtouchHttpClientHandler> - <MtouchVerbosity></MtouchVerbosity> - <LangVersion>Default</LangVersion> - <MtouchExtraArgs>$(DefaultMtouchExtraArgs) -gcc_flags "$(DefaultMtouchGccFlags)"</MtouchExtraArgs> - <OptimizePNGs>false</OptimizePNGs> - <MtouchI18n>cjk,mideast,other,rare,west</MtouchI18n> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhone' "> + <MtouchUseLlvm>true</MtouchUseLlvm> </PropertyGroup> <ItemGroup> <NativeReference Include="$(OutputPath)\libbass.a;$(OutputPath)\libbass_fx.a"> From 47e3498b7149920e3b32367289cd96424cca2806 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan <huoyaoyuan@hotmail.com> Date: Wed, 30 Oct 2019 22:42:25 +0800 Subject: [PATCH 2003/2815] Use cake as local tool and builds for slnf. --- .config/dotnet-tools.json | 12 ++++++++++++ build.ps1 | 2 +- build.sh | 2 +- build/Desktop.proj | 17 +++++++++++++++++ build/build.cake | 7 ++++--- global.json | 5 +++++ osu.sln | 1 + 7 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 .config/dotnet-tools.json create mode 100644 build/Desktop.proj create mode 100644 global.json diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000000..1b70c949eb --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "cake.tool": { + "version": "0.35.0", + "commands": [ + "dotnet-cake" + ] + } + } +} \ No newline at end of file diff --git a/build.ps1 b/build.ps1 index 2dbd10a150..4b3b1f717a 100755 --- a/build.ps1 +++ b/build.ps1 @@ -21,7 +21,7 @@ if ($DryRun) { $cakeArguments += "-dryrun" } if ($Experimental) { $cakeArguments += "-experimental" } $cakeArguments += $ScriptArgs -dotnet tool install Cake.Tool --global --version 0.35.0 +dotnet tool restore dotnet cake ./build/build.cake --bootstrap dotnet cake ./build/build.cake $cakeArguments exit $LASTEXITCODE \ No newline at end of file diff --git a/build.sh b/build.sh index ac6bd877a6..2c22f08574 100755 --- a/build.sh +++ b/build.sh @@ -1,5 +1,5 @@ echo "Installing Cake.Tool..." -dotnet tool install Cake.Tool --global --version 0.35.0 +dotnet tool restore # Parse arguments. CAKE_ARGUMENTS=() diff --git a/build/Desktop.proj b/build/Desktop.proj new file mode 100644 index 0000000000..b1c6b065e8 --- /dev/null +++ b/build/Desktop.proj @@ -0,0 +1,17 @@ +<Project Sdk="Microsoft.Build.Traversal"> + <ItemGroup> + <ProjectReference Include="..\osu.Desktop\osu.Desktop.csproj" /> + <ProjectReference Include="..\osu.Game.Rulesets.Catch.Tests\osu.Game.Rulesets.Catch.Tests.csproj" /> + <ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" /> + <ProjectReference Include="..\osu.Game.Rulesets.Mania.Tests\osu.Game.Rulesets.Mania.Tests.csproj" /> + <ProjectReference Include="..\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" /> + <ProjectReference Include="..\osu.Game.Rulesets.Osu.Tests\osu.Game.Rulesets.Osu.Tests.csproj" /> + <ProjectReference Include="..\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj" /> + <ProjectReference Include="..\osu.Game.Rulesets.Taiko.Tests\osu.Game.Rulesets.Taiko.Tests.csproj" /> + <ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" /> + <ProjectReference Include="..\osu.Game.Tests\osu.Game.Tests.csproj" /> + <ProjectReference Include="..\osu.Game.Tournament.Tests\osu.Game.Tournament.Tests.csproj" /> + <ProjectReference Include="..\osu.Game.Tournament\osu.Game.Tournament.csproj" /> + <ProjectReference Include="..\osu.Game\osu.Game.csproj" /> + </ItemGroup> +</Project> \ No newline at end of file diff --git a/build/build.cake b/build/build.cake index cfdfebee61..389ff4829d 100644 --- a/build/build.cake +++ b/build/build.cake @@ -11,7 +11,8 @@ var target = Argument("target", "Build"); var configuration = Argument("configuration", "Release"); var rootDirectory = new DirectoryPath(".."); -var solution = rootDirectory.CombineWithFilePath("osu.sln"); +var desktopBuilds = rootDirectory.CombineWithFilePath("build/Desktop.proj"); +var desktopSlnf = rootDirectory.CombineWithFilePath("osu.Desktop.slnf"); /////////////////////////////////////////////////////////////////////////////// // TASKS @@ -19,7 +20,7 @@ var solution = rootDirectory.CombineWithFilePath("osu.sln"); Task("Compile") .Does(() => { - DotNetCoreBuild(solution.FullPath, new DotNetCoreBuildSettings { + DotNetCoreBuild(desktopBuilds.FullPath, new DotNetCoreBuildSettings { Configuration = configuration, }); }); @@ -41,7 +42,7 @@ Task("InspectCode") .WithCriteria(IsRunningOnWindows()) .IsDependentOn("Compile") .Does(() => { - InspectCode(solution, new InspectCodeSettings { + InspectCode(desktopSlnf, new InspectCodeSettings { CachesHome = "inspectcode", OutputFile = "inspectcodereport.xml", }); diff --git a/global.json b/global.json new file mode 100644 index 0000000000..d8b8d14c36 --- /dev/null +++ b/global.json @@ -0,0 +1,5 @@ +{ + "msbuild-sdks": { + "Microsoft.Build.Traversal": "2.0.19" + } +} \ No newline at end of file diff --git a/osu.sln b/osu.sln index 12ea89aaad..1baf62da15 100644 --- a/osu.sln +++ b/osu.sln @@ -56,6 +56,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{10DF8F12-50FD-45D8-8A38-17BA764BF54D}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig + global.json = global.json osu.Android.props = osu.Android.props osu.Game.props = osu.Game.props osu.iOS.props = osu.iOS.props From bcf8e3a9d473856d20d7c25141da7c4754d18a1d Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan <huoyaoyuan@hotmail.com> Date: Wed, 30 Oct 2019 22:56:33 +0800 Subject: [PATCH 2004/2815] Remove redundant properties from desktop projects. While OutputType=Library maybe doubtful, PlatformTarget=AnyCPU should be clearly the default. --- Directory.Build.props | 12 ++++++++++-- osu.Desktop/osu.Desktop.csproj | 3 +-- .../osu.Game.Rulesets.Catch.csproj | 1 - .../osu.Game.Rulesets.Mania.csproj | 1 - osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj | 1 - .../osu.Game.Rulesets.Taiko.csproj | 1 - osu.Game.Tournament/osu.Game.Tournament.csproj | 1 - osu.Game/osu.Game.csproj | 8 -------- osu.sln | 2 +- 9 files changed, 12 insertions(+), 18 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 4f7ad880b5..76f1ccc0ca 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -15,10 +15,18 @@ <EmbeddedResource Include="Resources\**\*.*" /> </ItemGroup> <PropertyGroup Label="Project"> - <Company>ppy Pty Ltd</Company> - <Copyright>Copyright (c) 2019 ppy Pty Ltd</Copyright> <!-- DeepEqual is not netstandard-compatible. This is fine since we run tests with .NET Framework anyway. This is required due to https://github.com/NuGet/Home/issues/5740 --> <NoWarn>NU1701</NoWarn> </PropertyGroup> + <PropertyGroup Label="Nuget"> + <Authors>ppy Pty Ltd</Authors> + <PackageLicenseUrl>https://github.com/ppy/osu/blob/master/LICENCE.md</PackageLicenseUrl> + <PackageProjectUrl>https://github.com/ppy/osu</PackageProjectUrl> + <RepositoryUrl>https://github.com/ppy/osu</RepositoryUrl> + <PackageReleaseNotes>Automated release.</PackageReleaseNotes> + <Company>ppy Pty Ltd</Company> + <Copyright>Copyright (c) 2019 ppy Pty Ltd</Copyright> + <PackageTags>osu game</PackageTags> + </PropertyGroup> </Project> \ No newline at end of file diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 84881ce1d3..453cf6f94d 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -2,7 +2,6 @@ <PropertyGroup Label="Project"> <TargetFramework>netcoreapp3.0</TargetFramework> <OutputType>WinExe</OutputType> - <PlatformTarget>AnyCPU</PlatformTarget> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> <Description>click the circles. to the beat.</Description> <AssemblyName>osu!</AssemblyName> @@ -22,13 +21,13 @@ <ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" /> <ProjectReference Include="..\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" /> <ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" /> - <PackageReference Include="Microsoft.Win32.Registry" Version="4.6.0" /> </ItemGroup> <ItemGroup Label="Package References"> <PackageReference Include="System.IO.Packaging" Version="4.6.0" /> <PackageReference Include="ppy.squirrel.windows" Version="1.9.0.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" /> + <PackageReference Include="Microsoft.Win32.Registry" Version="4.6.0" /> </ItemGroup> <ItemGroup Label="Resources"> <EmbeddedResource Include="lazer.ico" /> diff --git a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj index 53d8651476..f24cf1def9 100644 --- a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj +++ b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj @@ -2,7 +2,6 @@ <PropertyGroup Label="Project"> <TargetFramework>netstandard2.0</TargetFramework> <OutputType>Library</OutputType> - <PlatformTarget>AnyCPU</PlatformTarget> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> <Description>catch the fruit. to the beat.</Description> </PropertyGroup> diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj index 061cced227..0af200d19b 100644 --- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj +++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj @@ -2,7 +2,6 @@ <PropertyGroup Label="Project"> <TargetFramework>netstandard2.0</TargetFramework> <OutputType>Library</OutputType> - <PlatformTarget>AnyCPU</PlatformTarget> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> <Description>smash the keys. to the beat.</Description> </PropertyGroup> diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index afd5e1f753..fb3fe8808d 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -2,7 +2,6 @@ <PropertyGroup Label="Project"> <TargetFramework>netstandard2.0</TargetFramework> <OutputType>Library</OutputType> - <PlatformTarget>AnyCPU</PlatformTarget> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> <Description>click the circles. to the beat.</Description> </PropertyGroup> diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj index 482f459564..0a2b189c3a 100644 --- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj +++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj @@ -2,7 +2,6 @@ <PropertyGroup Label="Project"> <TargetFramework>netstandard2.0</TargetFramework> <OutputType>Library</OutputType> - <PlatformTarget>AnyCPU</PlatformTarget> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> <Description>bash the drum. to the beat.</Description> </PropertyGroup> diff --git a/osu.Game.Tournament/osu.Game.Tournament.csproj b/osu.Game.Tournament/osu.Game.Tournament.csproj index 7e1f0eb2b3..f5306facaf 100644 --- a/osu.Game.Tournament/osu.Game.Tournament.csproj +++ b/osu.Game.Tournament/osu.Game.Tournament.csproj @@ -2,7 +2,6 @@ <PropertyGroup Label="Project"> <TargetFramework>netstandard2.0</TargetFramework> <OutputType>Library</OutputType> - <PlatformTarget>AnyCPU</PlatformTarget> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> <Description>tools for tournaments.</Description> </PropertyGroup> diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 717e3e7de8..fce50c43d2 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -2,19 +2,11 @@ <PropertyGroup Label="Project"> <TargetFramework>netstandard2.0</TargetFramework> <OutputType>Library</OutputType> - <PlatformTarget>AnyCPU</PlatformTarget> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> </PropertyGroup> <PropertyGroup Label="Nuget"> <Title>osu! ppy.osu.Game - ppy Pty Ltd - https://github.com/ppy/osu/blob/master/LICENCE.md - https://github.com/ppy/osu - https://github.com/ppy/osu - Automated release. - Copyright (c) 2019 ppy Pty Ltd - osu game diff --git a/osu.sln b/osu.sln index 1baf62da15..1f4faae6b9 100644 --- a/osu.sln +++ b/osu.sln @@ -56,9 +56,9 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{10DF8F12-50FD-45D8-8A38-17BA764BF54D}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig + Directory.Build.props = Directory.Build.props global.json = global.json osu.Android.props = osu.Android.props - osu.Game.props = osu.Game.props osu.iOS.props = osu.iOS.props osu.sln.DotSettings = osu.sln.DotSettings osu.TestProject.props = osu.TestProject.props From 847cf8639fa87e505f47154134c0f8f7b1751cfa Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 30 Oct 2019 22:58:23 +0800 Subject: [PATCH 2005/2815] Move to VS2019 stable for CI. --- appveyor.yml | 2 +- appveyor_deploy.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index f59c0b162d..f911d67c6e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ clone_depth: 1 version: '{branch}-{build}' -image: Visual Studio 2019 Preview +image: Visual Studio 2019 test: off build_script: - cmd: PowerShell -Version 2.0 .\build.ps1 diff --git a/appveyor_deploy.yml b/appveyor_deploy.yml index 13635b943c..fb7825b31d 100644 --- a/appveyor_deploy.yml +++ b/appveyor_deploy.yml @@ -1,6 +1,6 @@ clone_depth: 1 version: '{build}' -image: Visual Studio 2019 Preview +image: Visual Studio 2019 test: off skip_non_tags: true build_script: From c95052b10fc6f4707bebf3587e23ee938d10fb60 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 30 Oct 2019 23:03:02 +0800 Subject: [PATCH 2006/2815] Resolve package downgrade and VS claims for iOS. --- osu.iOS.props | 12 +------ osu.iOS/osu.iOS.csproj | 80 +++++++++++++++++++++++++++++++----------- 2 files changed, 61 insertions(+), 31 deletions(-) diff --git a/osu.iOS.props b/osu.iOS.props index 8079022ca0..55f1317a58 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -12,6 +12,7 @@ NSUrlSessionHandler iPhone Developer + true true @@ -68,21 +69,10 @@ - - - - - - - - - - - diff --git a/osu.iOS/osu.iOS.csproj b/osu.iOS/osu.iOS.csproj index 49d45e9031..378ac231c2 100644 --- a/osu.iOS/osu.iOS.csproj +++ b/osu.iOS/osu.iOS.csproj @@ -54,26 +54,66 @@ - - - - - - - - - - - - - - - - - - - - + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + \ No newline at end of file From e57e9a3817094fb8788f1f5a29af0fb6b483e8d3 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 30 Oct 2019 23:07:22 +0800 Subject: [PATCH 2007/2815] Update VSCode restore instructions. Use slnf when dotnet.exe knows it. --- .vscode/tasks.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 70e620bca2..04ff7c1bea 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -100,7 +100,7 @@ "command": "dotnet", "args": [ "restore", - "osu.sln" + "build/Desktop.proj" ], "problemMatcher": [] } From 3ad0369d7b77b6890ecfc69e32aef219aff8d138 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 30 Oct 2019 23:09:08 +0800 Subject: [PATCH 2008/2815] Resolve new diagnostics in C# 7.3 --- osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs | 2 +- osu.Game/Updater/SimpleUpdateManager.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index 3a14b6d9c2..fbf0435953 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -195,7 +195,7 @@ namespace osu.Game.Tournament.Screens.Drawings } } - writeOp = writeOp?.ContinueWith(t => { writeAction(); }) ?? Task.Run((Action)writeAction); + writeOp = writeOp?.ContinueWith(t => { writeAction(); }) ?? Task.Run(writeAction); } private void reloadTeams() diff --git a/osu.Game/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs index 4789ac94d2..f76cba7f41 100644 --- a/osu.Game/Updater/SimpleUpdateManager.cs +++ b/osu.Game/Updater/SimpleUpdateManager.cs @@ -29,7 +29,7 @@ namespace osu.Game.Updater version = game.Version; if (game.IsDeployedBuild) - Schedule(() => Task.Run(() => checkForUpdateAsync())); + Schedule(() => Task.Run(checkForUpdateAsync)); } private async void checkForUpdateAsync() From b0414c105f966f255fe25d4cbd18116b050b1ef6 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 30 Oct 2019 23:16:57 +0800 Subject: [PATCH 2009/2815] Copy parts of DotSettings from framework. --- osu.sln.DotSettings | 830 ++++++++++++++++++++++---------------------- 1 file changed, 421 insertions(+), 409 deletions(-) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index ed162eed6e..0a65fad9df 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -125,6 +125,7 @@ WARNING WARNING WARNING + WARNING WARNING WARNING ERROR @@ -201,6 +202,7 @@ HINT HINT + HINT WARNING WARNING WARNING @@ -271,10 +273,13 @@ GC GL GLSL + 2D + 3D HID HUD ID IL + IOS IP IPC JIT @@ -286,399 +291,400 @@ RGB RNG SHA + RGB SRGB TK - SS - PP - GMT - QAT - BNG + SS + PP + GMT + QAT + BNG UI False HINT <?xml version="1.0" encoding="utf-16"?> <Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> - <TypePattern DisplayName="COM interfaces or structs"> - <TypePattern.Match> - <Or> - <And> - <Kind Is="Interface" /> - <Or> - <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> - <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> - </Or> - </And> - <Kind Is="Struct" /> - </Or> - </TypePattern.Match> - </TypePattern> - <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> - <TypePattern.Match> - <And> - <Kind Is="Class" /> - <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> - </And> - </TypePattern.Match> - <Entry DisplayName="Setup/Teardown Methods"> - <Entry.Match> - <And> - <Kind Is="Method" /> - <Or> - <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="All other members" /> - <Entry Priority="100" DisplayName="Test Methods"> - <Entry.Match> - <And> - <Kind Is="Method" /> - <HasAttribute Name="NUnit.Framework.TestAttribute" /> - </And> - </Entry.Match> - <Entry.SortBy> - <Name /> - </Entry.SortBy> - </Entry> - </TypePattern> - <TypePattern DisplayName="Default Pattern"> - <Group DisplayName="Fields/Properties"> - <Group DisplayName="Public Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Public Properties"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - <Group DisplayName="Internal Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Internal Properties"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - <Group DisplayName="Protected Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Protected Properties"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - <Group DisplayName="Private Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Private Properties"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Constructor/Destructor"> - <Entry DisplayName="Ctor"> - <Entry.Match> - <Kind Is="Constructor" /> - </Entry.Match> - </Entry> - <Region Name="Disposal"> - <Entry DisplayName="Dtor"> - <Entry.Match> - <Kind Is="Destructor" /> - </Entry.Match> - </Entry> - <Entry DisplayName="Dispose()"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Kind Is="Method" /> - <Name Is="Dispose" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Dispose(true)"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Or> - <Virtual /> - <Override /> - </Or> - <Kind Is="Method" /> - <Name Is="Dispose" /> - </And> - </Entry.Match> - </Entry> - </Region> - </Group> - <Group DisplayName="Methods"> - <Group DisplayName="Public"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Internal"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Protected"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Private"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - </Group> - </TypePattern> + <TypePattern DisplayName="COM interfaces or structs"> + <TypePattern.Match> + <Or> + <And> + <Kind Is="Interface" /> + <Or> + <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> + <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> + </Or> + </And> + <Kind Is="Struct" /> + </Or> + </TypePattern.Match> + </TypePattern> + <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> + <TypePattern.Match> + <And> + <Kind Is="Class" /> + <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> + </And> + </TypePattern.Match> + <Entry DisplayName="Setup/Teardown Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <Or> + <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="All other members" /> + <Entry Priority="100" DisplayName="Test Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <HasAttribute Name="NUnit.Framework.TestAttribute" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + </TypePattern> + <TypePattern DisplayName="Default Pattern"> + <Group DisplayName="Fields/Properties"> + <Group DisplayName="Public Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Public Properties"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Internal Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Internal Properties"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Protected Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Protected Properties"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Private Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Private Properties"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Constructor/Destructor"> + <Entry DisplayName="Ctor"> + <Entry.Match> + <Kind Is="Constructor" /> + </Entry.Match> + </Entry> + <Region Name="Disposal"> + <Entry DisplayName="Dtor"> + <Entry.Match> + <Kind Is="Destructor" /> + </Entry.Match> + </Entry> + <Entry DisplayName="Dispose()"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Method" /> + <Name Is="Dispose" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Dispose(true)"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Or> + <Virtual /> + <Override /> + </Or> + <Kind Is="Method" /> + <Name Is="Dispose" /> + </And> + </Entry.Match> + </Entry> + </Region> + </Group> + <Group DisplayName="Methods"> + <Group DisplayName="Public"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Internal"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Protected"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Private"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + </Group> + </TypePattern> </Patterns> Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. See the LICENCE file in the repository root for full licence text. @@ -738,6 +744,7 @@ See the LICENCE file in the repository root for full licence text. <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True True True @@ -773,7 +780,7 @@ Origin = Anchor.$anchor$, True InternalChildren = new Drawable[] { - $END$ + $END$ }; True True @@ -786,12 +793,12 @@ Origin = Anchor.$anchor$, True new GridContainer { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] { $END$ }, - new Drawable[] { } - } + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { $END$ }, + new Drawable[] { } + } }; True True @@ -804,12 +811,12 @@ Origin = Anchor.$anchor$, True new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - $END$ - } + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + $END$ + } }, True True @@ -822,11 +829,11 @@ Origin = Anchor.$anchor$, True new Container { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - $END$ - } + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + $END$ + } }, True True @@ -840,7 +847,7 @@ Origin = Anchor.$anchor$, [BackgroundDependencyLoader] private void load() { - $END$ + $END$ } True True @@ -853,8 +860,8 @@ private void load() True new Box { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, }, True True @@ -867,8 +874,9 @@ private void load() True Children = new Drawable[] { - $END$ + $END$ }; + True True True True @@ -886,4 +894,8 @@ private void load() True True True - True + True + True + True + True + True From 9d4f80c2a236ecb35f1b0e02fed21494bddddb0a Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 30 Oct 2019 23:37:58 +0800 Subject: [PATCH 2010/2815] Add icon and use licence expression for NuGet. Unlike framework, if other components are packed, they are likely to use different icons. --- Directory.Build.props | 2 +- assets/lazer-nuget.png | Bin 0 -> 12471 bytes osu.Game/osu.Game.csproj | 7 +++++++ 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 assets/lazer-nuget.png diff --git a/Directory.Build.props b/Directory.Build.props index 76f1ccc0ca..b4baa2833e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -21,7 +21,7 @@ ppy Pty Ltd - https://github.com/ppy/osu/blob/master/LICENCE.md + MIT https://github.com/ppy/osu https://github.com/ppy/osu Automated release. diff --git a/assets/lazer-nuget.png b/assets/lazer-nuget.png new file mode 100644 index 0000000000000000000000000000000000000000..c2a587fdc26734c1f263023d7eefa8fd644e978b GIT binary patch literal 12471 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 BjmH20 literal 0 HcmV?d00001 diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index fce50c43d2..6e7a07e192 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -7,7 +7,14 @@ osu! ppy.osu.Game + icon.png + + + True + icon.png + + From 8d290a324291dcceccbbc587b40f483106cf0436 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 30 Oct 2019 23:43:13 +0800 Subject: [PATCH 2011/2815] Resolve CS0067. --- osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs | 6 +++++- .../Visual/Gameplay/TestSceneSkinnableDrawable.cs | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs index 685a51d208..46769f65fe 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs @@ -101,7 +101,11 @@ namespace osu.Game.Rulesets.Osu.Tests public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); - public event Action SourceChanged; + public event Action SourceChanged + { + add { } + remove { } + } } private class MovingCursorInputManager : ManualInputManager diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index b3d4820737..8beb107269 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -326,7 +326,11 @@ namespace osu.Game.Tests.Visual.Gameplay public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); - public event Action SourceChanged; + public event Action SourceChanged + { + add { } + remove { } + } } private class TestSkinComponent : ISkinComponent From ff3db9f3b26eb9182116d53916a030eba4946dcb Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 31 Oct 2019 02:58:58 +0800 Subject: [PATCH 2012/2815] Update README to require VS2019 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0460e9cbcf..1e2d539697 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh - A desktop platform with the [.NET Core SDK 3.0](https://www.microsoft.com/net/learn/get-started) or higher installed. - When running on linux, please have a system-wide ffmpeg installation available to support video decoding. - When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs. -- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio 2017+](https://visualstudio.microsoft.com/vs/), [Jetbrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/). +- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [Jetbrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/). ## Running osu! From 473045308374118211ae5338e06d9b96a2f949c6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 31 Oct 2019 02:10:00 +0300 Subject: [PATCH 2013/2815] Apply suggestions --- .../Visual/Online/TestSceneVotePill.cs | 22 ++++++++++++------- osu.Game/Overlays/Comments/VotePill.cs | 20 ++++++++--------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs index 7ccb025b47..22e11aa464 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs @@ -8,6 +8,8 @@ using osu.Framework.Graphics; using osu.Game.Overlays.Comments; using osu.Framework.Allocation; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Users; +using osu.Framework.MathUtils; namespace osu.Game.Tests.Visual.Online { @@ -24,13 +26,6 @@ namespace osu.Game.Tests.Visual.Online [BackgroundDependencyLoader] private void load() { - var userComment = new Comment - { - IsVoted = false, - UserId = API.LocalUser.Value.Id, - VotesCount = 10, - }; - var randomComment = new Comment { IsVoted = false, @@ -38,7 +33,11 @@ namespace osu.Game.Tests.Visual.Online VotesCount = 2, }; - AddStep("User comment", () => addVotePill(userComment)); + AddStep("Log in", () => API.LocalUser.Value = new User + { + Id = RNG.Next(2, 100000) + }); + AddStep("User comment", () => addVotePill(getUserComment())); AddStep("Click", () => votePill.Click()); AddAssert("Not loading", () => !votePill.IsLoading); @@ -52,6 +51,13 @@ namespace osu.Game.Tests.Visual.Online AddAssert("Not loading", () => !votePill.IsLoading); } + private Comment getUserComment() => new Comment + { + IsVoted = false, + UserId = API.LocalUser.Value.Id, + VotesCount = 10, + }; + private void addVotePill(Comment comment) { Clear(); diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index 54eba63095..ad17264229 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -158,18 +158,18 @@ namespace osu.Game.Overlays.Comments private void updateDisplay() { - if (Action != null) - { - if (isVoted.Value) - { - hoverLayer.FadeTo(IsHovered ? 1 : 0); - sideNumber.Hide(); - } - else - sideNumber.FadeTo(IsHovered ? 1 : 0); + if (Action == null) + return; - borderContainer.BorderThickness = IsHovered ? 3 : 0; + if (isVoted.Value) + { + hoverLayer.FadeTo(IsHovered ? 1 : 0); + sideNumber.Hide(); } + else + sideNumber.FadeTo(IsHovered ? 1 : 0); + + borderContainer.BorderThickness = IsHovered ? 3 : 0; } private void onHoverAction() From 32dabf80a6f896cba970220d0e2435051a2c5982 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Oct 2019 13:42:11 +0900 Subject: [PATCH 2014/2815] Ensure forceful exit completely exits from mutliplayer Previously it may have gotten blocked by being in a sub screen. --- osu.Game/Screens/Multi/Multiplayer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 5945e9de13..941d7a2478 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -174,7 +174,10 @@ namespace osu.Game.Screens.Multi { // This is temporary since we don't currently have a way to force screens to be exited if (this.IsCurrentScreen()) - this.Exit(); + { + while (this.IsCurrentScreen()) + this.Exit(); + } else { this.MakeCurrent(); From 5b405abc5253480414eb421028d48c1cb019ef0b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Oct 2019 13:43:25 +0900 Subject: [PATCH 2015/2815] Schedule forcefullyExit call for safety Screen state may have changed at an inopportune moment. Run on local scheduler, not API scheduler to avoid any weirdness. --- osu.Game/Screens/Multi/Multiplayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 941d7a2478..86d52ff791 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -167,7 +167,7 @@ namespace osu.Game.Screens.Multi public void APIStateChanged(IAPIProvider api, APIState state) { if (state != APIState.Online) - forcefullyExit(); + Schedule(forcefullyExit); } private void forcefullyExit() From 0cd912fcd3ad364b8ade2a7d5e98685f3e3c68d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Oct 2019 15:04:13 +0900 Subject: [PATCH 2016/2815] Cover all non-APIAccess APIRequest calls with exception handling --- osu.Game/Beatmaps/BeatmapManager.cs | 11 ++++-- .../DownloadableArchiveModelManager.cs | 35 ++++++++++++------- .../Changelog/ChangelogSingleBuild.cs | 12 ++++++- osu.Game/Overlays/ChangelogOverlay.cs | 19 ++++++++-- 4 files changed, 60 insertions(+), 17 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index dd2044b4bc..6e485f642a 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -392,8 +392,15 @@ namespace osu.Game.Beatmaps req.Failure += e => { LogForModel(set, $"Online retrieval failed for {beatmap} ({e.Message})"); }; - // intentionally blocking to limit web request concurrency - req.Perform(api); + try + { + // intentionally blocking to limit web request concurrency + req.Perform(api); + } + catch (Exception e) + { + LogForModel(set, $"Online retrieval failed for {beatmap} ({e.Message})"); + } } } } diff --git a/osu.Game/Database/DownloadableArchiveModelManager.cs b/osu.Game/Database/DownloadableArchiveModelManager.cs index e3c6ad25e6..a81dff3475 100644 --- a/osu.Game/Database/DownloadableArchiveModelManager.cs +++ b/osu.Game/Database/DownloadableArchiveModelManager.cs @@ -86,16 +86,7 @@ namespace osu.Game.Database }, TaskCreationOptions.LongRunning); }; - request.Failure += error => - { - DownloadFailed?.Invoke(request); - - if (error is OperationCanceledException) return; - - notification.State = ProgressNotificationState.Cancelled; - Logger.Error(error, $"{HumanisedModelName.Titleize()} download failed!"); - currentDownloads.Remove(request); - }; + request.Failure += triggerFailure; notification.CancelRequested += () => { @@ -108,11 +99,31 @@ namespace osu.Game.Database currentDownloads.Add(request); PostNotification?.Invoke(notification); - Task.Factory.StartNew(() => request.Perform(api), TaskCreationOptions.LongRunning); + Task.Factory.StartNew(() => + { + try + { + request.Perform(api); + } + catch (Exception error) + { + triggerFailure(error); + } + }, TaskCreationOptions.LongRunning); DownloadBegan?.Invoke(request); - return true; + + void triggerFailure(Exception error) + { + DownloadFailed?.Invoke(request); + + if (error is OperationCanceledException) return; + + notification.State = ProgressNotificationState.Cancelled; + Logger.Error(error, $"{HumanisedModelName.Titleize()} download failed!"); + currentDownloads.Remove(request); + } } public bool IsAvailableLocally(TModel model) => CheckLocalAvailability(model, modelStore.ConsumableItems.Where(m => !m.DeletePending)); diff --git a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs index 9c3504f477..adcd33fb48 100644 --- a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs @@ -44,7 +44,17 @@ namespace osu.Game.Overlays.Changelog req.Failure += _ => complete = true; // This is done on a separate thread to support cancellation below - Task.Run(() => req.Perform(api)); + Task.Run(() => + { + try + { + req.Perform(api); + } + catch + { + complete = true; + } + }); while (!complete) { diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index dfe3669813..559989af5c 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -170,6 +170,7 @@ namespace osu.Game.Overlays var tcs = new TaskCompletionSource(); var req = new GetChangelogRequest(); + req.Success += res => Schedule(() => { // remap streams to builds to ensure model equality @@ -183,8 +184,22 @@ namespace osu.Game.Overlays tcs.SetResult(true); }); - req.Failure += _ => initialFetchTask = null; - req.Perform(API); + + req.Failure += _ => + { + initialFetchTask = null; + tcs.SetResult(false); + }; + + try + { + req.Perform(API); + } + catch + { + initialFetchTask = null; + tcs.SetResult(false); + } await tcs.Task; }); From 7a3ebcd0b1c77b7caaac8c88f0a722353acd4a25 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 31 Oct 2019 15:52:38 +0900 Subject: [PATCH 2017/2815] Fix path changes not updating tail circle --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 010bf072e8..f60b7e67b2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -37,6 +37,8 @@ namespace osu.Game.Rulesets.Osu.Objects { PathBindable.Value = value; endPositionCache.Invalidate(); + + updateNestedPositions(); } } @@ -48,14 +50,9 @@ namespace osu.Game.Rulesets.Osu.Objects set { base.Position = value; - endPositionCache.Invalidate(); - if (HeadCircle != null) - HeadCircle.Position = value; - - if (TailCircle != null) - TailCircle.Position = EndPosition; + updateNestedPositions(); } } @@ -197,6 +194,15 @@ namespace osu.Game.Rulesets.Osu.Objects } } + private void updateNestedPositions() + { + if (HeadCircle != null) + HeadCircle.Position = Position; + + if (TailCircle != null) + TailCircle.Position = EndPosition; + } + private List getNodeSamples(int nodeIndex) => nodeIndex < NodeSamples.Count ? NodeSamples[nodeIndex] : Samples; From f8187fa30125db2a93b455bd369fe123903bbb44 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Oct 2019 16:23:10 +0900 Subject: [PATCH 2018/2815] Don't rely on masking for bar display --- osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs index 5227bec92a..8c150e6ed1 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs @@ -33,7 +33,6 @@ namespace osu.Game.Overlays.BeatmapSet : base(value) { AutoSizeAxes = Axes.Both; - Masking = true; FillFlowContainer nameContainer; @@ -87,7 +86,6 @@ namespace osu.Game.Overlays.BeatmapSet Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.X, - Height = 4f, }, new HoverClickSounds(), }; @@ -95,9 +93,10 @@ namespace osu.Game.Overlays.BeatmapSet BeatmapSet.BindValueChanged(setInfo => { var beatmapsCount = setInfo.NewValue?.Beatmaps.Count(b => b.Ruleset.Equals(Value)) ?? 0; - count.Text = beatmapsCount.ToString(); + count.Text = beatmapsCount.ToString(); count.Alpha = beatmapsCount > 0 ? 1f : 0f; + Enabled.Value = beatmapsCount > 0; }, true); @@ -120,9 +119,10 @@ namespace osu.Game.Overlays.BeatmapSet private void updateState() { var isHoveredOrActive = IsHovered || Active.Value; - name.Colour = isHoveredOrActive ? colour.GrayE : colour.GrayC; - bar.MoveToY(isHoveredOrActive ? 0f : bar.Height, 120); + bar.ResizeHeightTo(isHoveredOrActive ? 4 : 1, 200, Easing.OutQuint); + + name.Colour = isHoveredOrActive ? colour.GrayE : colour.GrayC; name.Font = name.Font.With(weight: Active.Value ? FontWeight.Bold : FontWeight.Regular); } From e23a75c64a400705b202122570ca1f41d09c0735 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 31 Oct 2019 16:23:54 +0900 Subject: [PATCH 2019/2815] Implement control point selection --- .../Components/PathControlPointPiece.cs | 84 +++++++++++++++---- .../Components/PathControlPointVisualiser.cs | 21 +++++ 2 files changed, 87 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 7afb8fcf49..3b0b9bce1f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; @@ -11,18 +12,21 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Rulesets.Osu.Objects; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { public class PathControlPointPiece : BlueprintPiece { public Action ControlPointsChanged; + public readonly Bindable IsSelected = new Bindable(); + + public readonly int Index; private readonly Slider slider; - private readonly int index; - private readonly Path path; - private readonly CircularContainer marker; + private readonly Container marker; + private readonly Drawable markerRing; [Resolved] private OsuColour colours { get; set; } @@ -30,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public PathControlPointPiece(Slider slider, int index) { this.slider = slider; - this.index = index; + Index = index; Origin = Anchor.Centre; AutoSizeAxes = Axes.Both; @@ -42,13 +46,36 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components Anchor = Anchor.Centre, PathRadius = 1 }, - marker = new CircularContainer + marker = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(10), - Masking = true, - Child = new Box { RelativeSizeAxes = Axes.Both } + AutoSizeAxes = Axes.Both, + Children = new[] + { + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(10), + }, + markerRing = new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(14), + Masking = true, + BorderThickness = 2, + BorderColour = Color4.White, + Alpha = 0, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + } } }; } @@ -57,21 +84,42 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { base.Update(); - Position = slider.StackedPosition + slider.Path.ControlPoints[index]; + Position = slider.StackedPosition + slider.Path.ControlPoints[Index]; - marker.Colour = isSegmentSeparator ? colours.Red : colours.Yellow; + updateMarkerDisplay(); + updateConnectingPath(); + } + /// + /// Updates the state of the circular control point marker. + /// + private void updateMarkerDisplay() + { + markerRing.Alpha = IsSelected.Value ? 1 : 0; + + Color4 colour = isSegmentSeparator ? colours.Red : colours.Yellow; + if (IsHovered || IsSelected.Value) + colour = Color4.White; + marker.Colour = colour; + } + + /// + /// Updates the path connecting this control point to the previous one. + /// + private void updateConnectingPath() + { path.ClearVertices(); - if (index != slider.Path.ControlPoints.Length - 1) + if (Index != slider.Path.ControlPoints.Length - 1) { path.AddVertex(Vector2.Zero); - path.AddVertex(slider.Path.ControlPoints[index + 1] - slider.Path.ControlPoints[index]); + path.AddVertex(slider.Path.ControlPoints[Index + 1] - slider.Path.ControlPoints[Index]); } path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero); } + // The connecting path is excluded from positional input public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => marker.ReceivePositionalInputAt(screenSpacePos); protected override bool OnDragStart(DragStartEvent e) => true; @@ -80,7 +128,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { var newControlPoints = slider.Path.ControlPoints.ToArray(); - if (index == 0) + if (Index == 0) { // Special handling for the head - only the position of the slider changes slider.Position += e.Delta; @@ -90,13 +138,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components newControlPoints[i] -= e.Delta; } else - newControlPoints[index] += e.Delta; + newControlPoints[Index] += e.Delta; if (isSegmentSeparatorWithNext) - newControlPoints[index + 1] = newControlPoints[index]; + newControlPoints[Index + 1] = newControlPoints[Index]; if (isSegmentSeparatorWithPrevious) - newControlPoints[index - 1] = newControlPoints[index]; + newControlPoints[Index - 1] = newControlPoints[Index]; ControlPointsChanged?.Invoke(newControlPoints); @@ -107,8 +155,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private bool isSegmentSeparator => isSegmentSeparatorWithNext || isSegmentSeparatorWithPrevious; - private bool isSegmentSeparatorWithNext => index < slider.Path.ControlPoints.Length - 1 && slider.Path.ControlPoints[index + 1] == slider.Path.ControlPoints[index]; + private bool isSegmentSeparatorWithNext => Index < slider.Path.ControlPoints.Length - 1 && slider.Path.ControlPoints[Index + 1] == slider.Path.ControlPoints[Index]; - private bool isSegmentSeparatorWithPrevious => index > 0 && slider.Path.ControlPoints[index - 1] == slider.Path.ControlPoints[index]; + private bool isSegmentSeparatorWithPrevious => Index > 0 && slider.Path.ControlPoints[Index - 1] == slider.Path.ControlPoints[Index]; } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 0385824b27..b0e579907d 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Osu.Objects; using osuTK; @@ -21,6 +22,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { this.slider = slider; + RelativeSizeAxes = Axes.Both; + InternalChild = pieces = new Container { RelativeSizeAxes = Axes.Both }; } @@ -33,5 +36,23 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components while (slider.Path.ControlPoints.Length < pieces.Count) pieces.Remove(pieces[pieces.Count - 1]); } + + protected override bool OnMouseDown(MouseDownEvent e) + { + bool anySelected = false; + + foreach (var piece in pieces) + { + if (piece.IsHovered) + { + piece.IsSelected.Value = true; + anySelected = true; + } + else + piece.IsSelected.Value = false; + } + + return anySelected; + } } } From bf45fa630984d624828bbeaf34ee7498a93fc170 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Oct 2019 16:24:03 +0900 Subject: [PATCH 2020/2815] Use lambda function expression --- osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs index 8c150e6ed1..3fecfbdaaf 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs @@ -138,10 +138,7 @@ namespace osu.Game.Overlays.BeatmapSet return false; } - protected override void OnHoverLost(HoverLostEvent e) - { - updateState(); - } + protected override void OnHoverLost(HoverLostEvent e) => updateState(); #endregion } From cfdf7106764b904c733e001eeba71b1e6b005d97 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 31 Oct 2019 16:51:58 +0900 Subject: [PATCH 2021/2815] Add test --- .../TestSceneSliderSelectionBlueprint.cs | 33 +++++++++++++++++++ .../Components/PathControlPointVisualiser.cs | 16 ++++----- .../Sliders/SliderSelectionBlueprint.cs | 3 +- .../Visual/SelectionBlueprintTestScene.cs | 2 +- 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs index 5df0b70f12..0e7d8c9c08 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs @@ -16,6 +16,7 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Tests.Visual; using osuTK; +using osuTK.Input; namespace osu.Game.Rulesets.Osu.Tests { @@ -85,6 +86,25 @@ namespace osu.Game.Rulesets.Osu.Tests checkPositions(); } + [Test] + public void TestSingleControlPointSelection() + { + moveMouseToControlPoint(0); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + checkControlPointSelected(0, true); + checkControlPointSelected(1, false); + + moveMouseToControlPoint(1); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + checkControlPointSelected(0, false); + checkControlPointSelected(1, true); + + AddStep("move mouse outside control point", () => InputManager.MoveMouseTo(drawableObject)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + checkControlPointSelected(0, false); + checkControlPointSelected(1, false); + } + private void moveHitObject() { AddStep("move hitobject", () => @@ -104,11 +124,24 @@ namespace osu.Game.Rulesets.Osu.Tests () => Precision.AlmostEquals(blueprint.TailBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre)); } + private void moveMouseToControlPoint(int index) + { + AddStep($"move mouse to control point {index}", () => + { + Vector2 position = slider.Position + slider.Path.ControlPoints[index]; + InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position)); + }); + } + + private void checkControlPointSelected(int index, bool selected) + => AddAssert($"control point {index} {(selected ? "selected" : "not selected")}", () => blueprint.ControlPointVisualiser.Pieces[index].IsSelected.Value == selected); + private class TestSliderBlueprint : SliderSelectionBlueprint { public new SliderBodyPiece BodyPiece => base.BodyPiece; public new TestSliderCircleBlueprint HeadBlueprint => (TestSliderCircleBlueprint)base.HeadBlueprint; public new TestSliderCircleBlueprint TailBlueprint => (TestSliderCircleBlueprint)base.TailBlueprint; + public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; public TestSliderBlueprint(DrawableSlider slider) : base(slider) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index b0e579907d..6efa0ef550 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -14,9 +14,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { public Action ControlPointsChanged; - private readonly Slider slider; + internal Container Pieces { get; } - private readonly Container pieces; + private readonly Slider slider; public PathControlPointVisualiser(Slider slider) { @@ -24,24 +24,24 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components RelativeSizeAxes = Axes.Both; - InternalChild = pieces = new Container { RelativeSizeAxes = Axes.Both }; + InternalChild = Pieces = new Container { RelativeSizeAxes = Axes.Both }; } protected override void Update() { base.Update(); - while (slider.Path.ControlPoints.Length > pieces.Count) - pieces.Add(new PathControlPointPiece(slider, pieces.Count) { ControlPointsChanged = c => ControlPointsChanged?.Invoke(c) }); - while (slider.Path.ControlPoints.Length < pieces.Count) - pieces.Remove(pieces[pieces.Count - 1]); + while (slider.Path.ControlPoints.Length > Pieces.Count) + Pieces.Add(new PathControlPointPiece(slider, Pieces.Count) { ControlPointsChanged = c => ControlPointsChanged?.Invoke(c) }); + while (slider.Path.ControlPoints.Length < Pieces.Count) + Pieces.Remove(Pieces[Pieces.Count - 1]); } protected override bool OnMouseDown(MouseDownEvent e) { bool anySelected = false; - foreach (var piece in pieces) + foreach (var piece in Pieces) { if (piece.IsHovered) { diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index b09f598bcc..ba502d3a7c 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -18,6 +18,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected readonly SliderBodyPiece BodyPiece; protected readonly SliderCircleSelectionBlueprint HeadBlueprint; protected readonly SliderCircleSelectionBlueprint TailBlueprint; + protected readonly PathControlPointVisualiser ControlPointVisualiser; [Resolved(CanBeNull = true)] private HitObjectComposer composer { get; set; } @@ -32,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders BodyPiece = new SliderBodyPiece(), HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start), TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End), - new PathControlPointVisualiser(sliderObject) { ControlPointsChanged = onNewControlPoints }, + ControlPointVisualiser = new PathControlPointVisualiser(sliderObject) { ControlPointsChanged = onNewControlPoints }, }; } diff --git a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs index f53c12b047..3233ee160d 100644 --- a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs @@ -8,7 +8,7 @@ using osu.Game.Rulesets.Edit; namespace osu.Game.Tests.Visual { - public abstract class SelectionBlueprintTestScene : OsuTestScene + public abstract class SelectionBlueprintTestScene : ManualInputManagerTestScene { protected override Container Content => content ?? base.Content; private readonly Container content; From fe93df718675d16c931a67b903e4fbeee7fe6dcc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Oct 2019 16:52:56 +0900 Subject: [PATCH 2022/2815] Remove unnecessary using statement --- osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index 6c7a3e4108..286971bc90 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -3,7 +3,6 @@ using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet; From 4f04abf2825d374f3deefd4bc013606f50d2400f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Oct 2019 16:54:28 +0900 Subject: [PATCH 2023/2815] Fix tabs to match design (should not show pinhair line) --- osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs index 3fecfbdaaf..cdea49afe7 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs @@ -120,7 +120,7 @@ namespace osu.Game.Overlays.BeatmapSet { var isHoveredOrActive = IsHovered || Active.Value; - bar.ResizeHeightTo(isHoveredOrActive ? 4 : 1, 200, Easing.OutQuint); + bar.ResizeHeightTo(isHoveredOrActive ? 4 : 0, 200, Easing.OutQuint); name.Colour = isHoveredOrActive ? colour.GrayE : colour.GrayC; name.Font = name.Font.With(weight: Active.Value ? FontWeight.Bold : FontWeight.Regular); From 3e3ff812291f343d1847ecd037914e38ddf70d50 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Oct 2019 17:13:00 +0900 Subject: [PATCH 2024/2815] Reorder methods --- osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 55 +++++++++---------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index bffe779da1..bf2a92cd4f 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -45,38 +45,10 @@ namespace osu.Game.Overlays.BeatmapSet if (value == beatmapSet) return; beatmapSet = value; - updateDisplay(); } } - private void updateDisplay() - { - Difficulties.Clear(); - - if (BeatmapSet != null) - { - Difficulties.ChildrenEnumerable = BeatmapSet.Beatmaps.Where(b => b.Ruleset.Equals(ruleset.Value)).OrderBy(b => b.StarDifficulty).Select(b => new DifficultySelectorButton(b) - { - State = DifficultySelectorState.NotSelected, - OnHovered = beatmap => - { - showBeatmap(beatmap); - starRating.Text = beatmap.StarDifficulty.ToString("Star Difficulty 0.##"); - starRating.FadeIn(100); - }, - OnClicked = beatmap => { Beatmap.Value = beatmap; }, - }); - } - - starRating.FadeOut(100); - Beatmap.Value = Difficulties.FirstOrDefault()?.Beatmap; - plays.Value = BeatmapSet?.OnlineInfo.PlayCount ?? 0; - favourites.Value = BeatmapSet?.OnlineInfo.FavouriteCount ?? 0; - - updateDifficultyButtons(); - } - public BeatmapPicker() { RelativeSizeAxes = Axes.X; @@ -169,6 +141,33 @@ namespace osu.Game.Overlays.BeatmapSet Beatmap.TriggerChange(); } + private void updateDisplay() + { + Difficulties.Clear(); + + if (BeatmapSet != null) + { + Difficulties.ChildrenEnumerable = BeatmapSet.Beatmaps.Where(b => b.Ruleset.Equals(ruleset.Value)).OrderBy(b => b.StarDifficulty).Select(b => new DifficultySelectorButton(b) + { + State = DifficultySelectorState.NotSelected, + OnHovered = beatmap => + { + showBeatmap(beatmap); + starRating.Text = beatmap.StarDifficulty.ToString("Star Difficulty 0.##"); + starRating.FadeIn(100); + }, + OnClicked = beatmap => { Beatmap.Value = beatmap; }, + }); + } + + starRating.FadeOut(100); + Beatmap.Value = Difficulties.FirstOrDefault()?.Beatmap; + plays.Value = BeatmapSet?.OnlineInfo.PlayCount ?? 0; + favourites.Value = BeatmapSet?.OnlineInfo.FavouriteCount ?? 0; + + updateDifficultyButtons(); + } + private void showBeatmap(BeatmapInfo beatmap) { version.Text = beatmap?.Version; From 8d50b155e8022f43bca1a9c824ee8697aa4acf70 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 31 Oct 2019 17:13:10 +0900 Subject: [PATCH 2025/2815] Make selection happen on click only --- .../Components/PathControlPointPiece.cs | 25 ++++++++++++++-- .../Components/PathControlPointVisualiser.cs | 30 ++++++++++--------- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 3b0b9bce1f..bd6a905b38 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -18,9 +18,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { public class PathControlPointPiece : BlueprintPiece { + public Action RequestSelection; public Action ControlPointsChanged; - public readonly Bindable IsSelected = new Bindable(); + public readonly Bindable IsSelected = new Bindable(); public readonly int Index; private readonly Slider slider; @@ -28,6 +29,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private readonly Container marker; private readonly Drawable markerRing; + private bool isClicked; + [Resolved] private OsuColour colours { get; set; } @@ -98,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components markerRing.Alpha = IsSelected.Value ? 1 : 0; Color4 colour = isSegmentSeparator ? colours.Red : colours.Yellow; - if (IsHovered || IsSelected.Value) + if (IsHovered || isClicked || IsSelected.Value) colour = Color4.White; marker.Colour = colour; } @@ -122,6 +125,24 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components // The connecting path is excluded from positional input public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => marker.ReceivePositionalInputAt(screenSpacePos); + protected override bool OnMouseDown(MouseDownEvent e) + { + isClicked = true; + return true; + } + + protected override bool OnMouseUp(MouseUpEvent e) + { + isClicked = false; + return true; + } + + protected override bool OnClick(ClickEvent e) + { + RequestSelection?.Invoke(Index); + return true; + } + protected override bool OnDragStart(DragStartEvent e) => true; protected override bool OnDrag(DragEvent e) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 6efa0ef550..160168eb37 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -32,27 +32,29 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components base.Update(); while (slider.Path.ControlPoints.Length > Pieces.Count) - Pieces.Add(new PathControlPointPiece(slider, Pieces.Count) { ControlPointsChanged = c => ControlPointsChanged?.Invoke(c) }); + { + Pieces.Add(new PathControlPointPiece(slider, Pieces.Count) + { + ControlPointsChanged = c => ControlPointsChanged?.Invoke(c), + RequestSelection = selectPiece + }); + } + while (slider.Path.ControlPoints.Length < Pieces.Count) Pieces.Remove(Pieces[Pieces.Count - 1]); } - protected override bool OnMouseDown(MouseDownEvent e) + protected override bool OnClick(ClickEvent e) { - bool anySelected = false; - foreach (var piece in Pieces) - { - if (piece.IsHovered) - { - piece.IsSelected.Value = true; - anySelected = true; - } - else - piece.IsSelected.Value = false; - } + piece.IsSelected.Value = false; + return false; + } - return anySelected; + private void selectPiece(int index) + { + foreach (var piece in Pieces) + piece.IsSelected.Value = piece.Index == index; } } } From ce19b2ed36b04186014123222a12067e9301c927 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Oct 2019 17:16:26 +0900 Subject: [PATCH 2026/2815] Avoid using CreateChildDependencies with a *child* bindable Don't do this. --- osu.Game/Overlays/BeatmapSet/Header.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index 0e3d29c25b..7b42e7e459 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -50,6 +50,9 @@ namespace osu.Game.Overlays.BeatmapSet private readonly LoadingAnimation loading; + [Cached(typeof(IBindable))] + private readonly Bindable ruleset = new Bindable(); + public Header() { ExternalLinkButton externalLink; @@ -80,6 +83,7 @@ namespace osu.Game.Overlays.BeatmapSet }, RulesetSelector = new BeatmapRulesetSelector { + Current = ruleset, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, } @@ -222,13 +226,6 @@ namespace osu.Game.Overlays.BeatmapSet }; } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.CacheAs>(RulesetSelector.Current); - return dependencies; - } - [BackgroundDependencyLoader] private void load(OsuColour colours) { From 20aeb7aaffe4fe48bfcfa820a49f7765dc544bce Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 31 Oct 2019 17:25:30 +0900 Subject: [PATCH 2027/2815] Implement multiple selection --- .../TestSceneSliderSelectionBlueprint.cs | 68 +++++++++++++++++++ .../Components/PathControlPointVisualiser.cs | 22 ++++-- 2 files changed, 86 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs index 0e7d8c9c08..dde2aa53e0 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs @@ -93,11 +93,79 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("click", () => InputManager.Click(MouseButton.Left)); checkControlPointSelected(0, true); checkControlPointSelected(1, false); + } + + [Test] + public void TestSingleControlPointDeselectionViaOtherControlPoint() + { + moveMouseToControlPoint(0); + AddStep("click", () => InputManager.Click(MouseButton.Left)); moveMouseToControlPoint(1); AddStep("click", () => InputManager.Click(MouseButton.Left)); checkControlPointSelected(0, false); checkControlPointSelected(1, true); + } + + [Test] + public void TestSingleControlPointDeselectionViaClickOutside() + { + moveMouseToControlPoint(0); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + + AddStep("move mouse outside control point", () => InputManager.MoveMouseTo(drawableObject)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + checkControlPointSelected(0, false); + checkControlPointSelected(1, false); + } + + [Test] + public void TestMultipleControlPointSelection() + { + moveMouseToControlPoint(0); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + moveMouseToControlPoint(1); + AddStep("ctrl + click", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Click(MouseButton.Left); + InputManager.ReleaseKey(Key.ControlLeft); + }); + checkControlPointSelected(0, true); + checkControlPointSelected(1, true); + } + + [Test] + public void TestMultipleControlPointDeselectionViaOtherControlPoint() + { + moveMouseToControlPoint(0); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + moveMouseToControlPoint(1); + AddStep("ctrl + click", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Click(MouseButton.Left); + InputManager.ReleaseKey(Key.ControlLeft); + }); + + moveMouseToControlPoint(2); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + checkControlPointSelected(0, false); + checkControlPointSelected(1, false); + } + + [Test] + public void TestMultipleControlPointDeselectionViaClickOutside() + { + moveMouseToControlPoint(0); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + moveMouseToControlPoint(1); + AddStep("ctrl + click", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Click(MouseButton.Left); + InputManager.ReleaseKey(Key.ControlLeft); + }); AddStep("move mouse outside control point", () => InputManager.MoveMouseTo(drawableObject)); AddStep("click", () => InputManager.Click(MouseButton.Left)); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 160168eb37..bbe771d8b0 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Rulesets.Osu.Objects; using osuTK; @@ -14,10 +15,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { public Action ControlPointsChanged; - internal Container Pieces { get; } - + internal readonly Container Pieces; private readonly Slider slider; + private InputManager inputManager; + public PathControlPointVisualiser(Slider slider) { this.slider = slider; @@ -27,6 +29,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components InternalChild = Pieces = new Container { RelativeSizeAxes = Axes.Both }; } + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + } + protected override void Update() { base.Update(); @@ -53,8 +62,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private void selectPiece(int index) { - foreach (var piece in Pieces) - piece.IsSelected.Value = piece.Index == index; + if (inputManager.CurrentState.Keyboard.ControlPressed) + Pieces[index].IsSelected.Value = true; + else + { + foreach (var piece in Pieces) + piece.IsSelected.Value = piece.Index == index; + } } } } From 43b2cbb865f031eacb431a7d8ccb5fd4fbd04954 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 31 Oct 2019 16:25:26 +0900 Subject: [PATCH 2028/2815] Implement slider control point deletion --- .../Components/PathControlPointVisualiser.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index bbe771d8b0..b578c75474 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -2,12 +2,16 @@ // 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.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit.Compose; using osuTK; +using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { @@ -20,6 +24,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private InputManager inputManager; + [Resolved(CanBeNull = true)] + private IPlacementHandler placementHandler { get; set; } + public PathControlPointVisualiser(Slider slider) { this.slider = slider; @@ -70,5 +77,44 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components piece.IsSelected.Value = piece.Index == index; } } + + protected override bool OnKeyDown(KeyDownEvent e) + { + switch (e.Key) + { + case Key.Delete: + var newControlPoints = new List(); + + foreach (var piece in pieces) + { + if (!piece.IsSelected.Value) + newControlPoints.Add(slider.Path.ControlPoints[piece.Index]); + } + + // Ensure that there are any points to be deleted + if (newControlPoints.Count == slider.Path.ControlPoints.Length) + return false; + + // If there are 0 remaining control points, treat the slider as being deleted + if (newControlPoints.Count == 0) + { + placementHandler?.Delete(slider); + return true; + } + + // Make control points relative + Vector2 first = newControlPoints[0]; + for (int i = 0; i < newControlPoints.Count; i++) + newControlPoints[i] = newControlPoints[i] - first; + + // The slider's position defines the position of the first control point, and all further control points are relative to that point + slider.Position = slider.Position + first; + ControlPointsChanged?.Invoke(newControlPoints.ToArray()); + + return true; + } + + return false; + } } } From 83abb845b648def4b42b2cebace3f5ace3c78e01 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Oct 2019 17:50:06 +0900 Subject: [PATCH 2029/2815] Fix code quality --- osu.Game/Graphics/Containers/LinkFlowContainer.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 66911d9615..efd4aae23e 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -74,7 +74,13 @@ namespace osu.Game.Graphics.Containers { RelativeSizeAxes = Axes.Both, TooltipText = tooltipText ?? (url != text ? url : string.Empty), - Action = action ?? (() => game.HandleLink(url, linkType, linkArgument)), + Action = () => + { + if (action != null) + action(); + else + game.HandleLink(url, linkType, linkArgument); + }, }); return drawables; From 78194cfa6e193c53cef9a4417a8cbbbe7d7283f6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 31 Oct 2019 17:54:06 +0900 Subject: [PATCH 2030/2815] Implement slider control point deletions --- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index b578c75474..17eb236ae0 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components case Key.Delete: var newControlPoints = new List(); - foreach (var piece in pieces) + foreach (var piece in Pieces) { if (!piece.IsSelected.Value) newControlPoints.Add(slider.Path.ControlPoints[piece.Index]); From 18f374eec653612784227dfcbe7d6aaf914e5c5b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 31 Oct 2019 17:58:33 +0900 Subject: [PATCH 2031/2815] Deselect deleted control points --- .../Sliders/Components/PathControlPointVisualiser.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 17eb236ae0..a6ee928c90 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -109,6 +109,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components // The slider's position defines the position of the first control point, and all further control points are relative to that point slider.Position = slider.Position + first; + + // Since pieces are re-used, they will not point to the deleted control points while remaining selected + foreach (var piece in Pieces) + piece.IsSelected.Value = false; + ControlPointsChanged?.Invoke(newControlPoints.ToArray()); return true; From 9f28b1905b50b19511d10b698d1d97da0a2267f9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 31 Oct 2019 18:15:19 +0900 Subject: [PATCH 2032/2815] Expose composer method to update hitobject --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 23 +++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 5922bfba78..1c942e52ce 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Edit EditorBeatmap = new EditorBeatmap(playableBeatmap); EditorBeatmap.HitObjectAdded += addHitObject; EditorBeatmap.HitObjectRemoved += removeHitObject; - EditorBeatmap.StartTimeChanged += updateHitObject; + EditorBeatmap.StartTimeChanged += UpdateHitObject; var dependencies = new DependencyContainer(parent); dependencies.CacheAs(EditorBeatmap); @@ -225,11 +225,7 @@ namespace osu.Game.Rulesets.Edit private ScheduledDelegate scheduledUpdate; - private void addHitObject(HitObject hitObject) => updateHitObject(hitObject); - - private void removeHitObject(HitObject hitObject) => updateHitObject(null); - - private void updateHitObject([CanBeNull] HitObject hitObject) + public override void UpdateHitObject(HitObject hitObject) { scheduledUpdate?.Cancel(); scheduledUpdate = Schedule(() => @@ -240,6 +236,10 @@ namespace osu.Game.Rulesets.Edit }); } + private void addHitObject(HitObject hitObject) => UpdateHitObject(hitObject); + + private void removeHitObject(HitObject hitObject) => UpdateHitObject(null); + public override IEnumerable HitObjects => drawableRulesetWrapper.Playfield.AllHitObjects; public override bool CursorInPlacementArea => drawableRulesetWrapper.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); @@ -351,11 +351,22 @@ namespace osu.Game.Rulesets.Edit [CanBeNull] protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable selectedHitObjects) => null; + /// + /// Updates a , invoking and re-processing the beatmap. + /// + /// The to update. + public abstract void UpdateHitObject([CanBeNull] HitObject hitObject); + public abstract (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time); + public abstract float GetBeatSnapDistanceAt(double referenceTime); + public abstract float DurationToDistance(double referenceTime, double duration); + public abstract double DistanceToDuration(double referenceTime, float distance); + public abstract double GetSnappedDurationFromDistance(double referenceTime, float distance); + public abstract float GetSnappedDistanceFromDistance(double referenceTime, float distance); } } From 41ae66d517e7a005f5f17e3faf1e583eb26b2151 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 31 Oct 2019 18:24:38 +0900 Subject: [PATCH 2033/2815] Update slider when control points change --- .../Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 ++ osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index b09f598bcc..585a477aba 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -49,6 +49,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders var snappedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance; HitObject.Path = new SliderPath(unsnappedPath.Type, controlPoints, snappedDistance); + + UpdateHitObject(); } public override Vector2 SelectionPoint => HeadBlueprint.SelectionPoint; diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 44f38acfd4..0701513933 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -3,10 +3,12 @@ using System; using osu.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osuTK; @@ -36,6 +38,9 @@ namespace osu.Game.Rulesets.Edit public override bool HandlePositionalInput => ShouldBeAlive; public override bool RemoveWhenNotAlive => false; + [Resolved(CanBeNull = true)] + private HitObjectComposer composer { get; set; } + protected SelectionBlueprint(DrawableHitObject drawableObject) { DrawableObject = drawableObject; @@ -89,6 +94,11 @@ namespace osu.Game.Rulesets.Edit public bool IsSelected => State == SelectionState.Selected; + /// + /// Updates the , invoking and re-processing the beatmap. + /// + protected void UpdateHitObject() => composer?.UpdateHitObject(DrawableObject.HitObject); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos); /// From 11447023eb21ad6f251a95d1310d16bf7852c003 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Oct 2019 18:34:41 +0900 Subject: [PATCH 2034/2815] Improve splitting out of link handling --- .../Graphics/Containers/LinkFlowContainer.cs | 29 +++++++++--------- osu.Game/Online/Chat/MessageFormatter.cs | 19 ++++++------ osu.Game/OsuGame.cs | 30 +++++++------------ osu.Game/Overlays/Changelog/ChangelogBuild.cs | 4 +-- .../Screens/Multi/Components/BeatmapTitle.cs | 2 +- osu.iOS/AppDelegate.cs | 2 +- 6 files changed, 40 insertions(+), 46 deletions(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index efd4aae23e..b5ffd1c6ad 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -44,46 +44,47 @@ namespace osu.Game.Graphics.Containers foreach (var link in links) { AddText(text.Substring(previousLinkEnd, link.Index - previousLinkEnd)); - AddLink(text.Substring(link.Index, link.Length), link.Url, link.Action, link.Argument); + AddLink(text.Substring(link.Index, link.Length), link.Action, link.Argument ?? link.Url); previousLinkEnd = link.Index + link.Length; } AddText(text.Substring(previousLinkEnd)); } - public IEnumerable AddLink(string text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null, Action creationParameters = null) - => createLink(AddText(text, creationParameters), text, url, linkType, linkArgument, tooltipText); + public void AddLink(string text, string url, Action creationParameters = null) => + createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.External, url), text); - public IEnumerable AddLink(string text, Action action, string tooltipText = null, Action creationParameters = null) - => createLink(AddText(text, creationParameters), text, tooltipText: tooltipText, action: action); + public void AddLink(string text, Action action, string tooltipText = null, Action creationParameters = null) + => createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.Custom, null), tooltipText, action); - public IEnumerable AddLink(IEnumerable text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null) + public void AddLink(string text, LinkAction action, string argument, string tooltipText = null, Action creationParameters = null) + => createLink(AddText(text, creationParameters), new LinkDetails(action, argument), null); + + public void AddLink(IEnumerable text, LinkAction action = LinkAction.External, string linkArgument = null, string tooltipText = null) { foreach (var t in text) AddArbitraryDrawable(t); - return createLink(text, null, url, linkType, linkArgument, tooltipText); + createLink(text, new LinkDetails(action, linkArgument), tooltipText); } - public IEnumerable AddUserLink(User user, Action creationParameters = null) - => createLink(AddText(user.Username, creationParameters), user.Username, null, LinkAction.OpenUserProfile, user.Id.ToString(), "View profile"); + public void AddUserLink(User user, Action creationParameters = null) + => createLink(AddText(user.Username, creationParameters), new LinkDetails(LinkAction.OpenUserProfile, user.Id.ToString()), "View Profile"); - private IEnumerable createLink(IEnumerable drawables, string text, string url = null, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null, Action action = null) + private void createLink(IEnumerable drawables, LinkDetails link, string tooltipText, Action action = null) { AddInternal(new DrawableLinkCompiler(drawables.OfType().ToList()) { RelativeSizeAxes = Axes.Both, - TooltipText = tooltipText ?? (url != text ? url : string.Empty), + TooltipText = tooltipText, Action = () => { if (action != null) action(); else - game.HandleLink(url, linkType, linkArgument); + game.HandleLink(link); }, }); - - return drawables; } // We want the compilers to always be visible no matter where they are, so RelativeSizeAxes is used. diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 31a324d0ea..717de18c14 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -255,17 +255,17 @@ namespace osu.Game.Online.Chat OriginalText = Text = text; } } + } - public class LinkDetails + public class LinkDetails + { + public LinkAction Action; + public string Argument; + + public LinkDetails(LinkAction action, string argument) { - public LinkAction Action; - public string Argument; - - public LinkDetails(LinkAction action, string argument) - { - Action = action; - Argument = argument; - } + Action = action; + Argument = argument; } } @@ -279,6 +279,7 @@ namespace osu.Game.Online.Chat JoinMultiplayerMatch, Spectate, OpenUserProfile, + Custom } public class Link : IComparable diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index c028896cff..b55cc41454 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -43,7 +43,6 @@ using osu.Game.Scoring; using osu.Game.Screens.Select; using osu.Game.Utils; using LogLevel = osu.Framework.Logging.LogLevel; -using static osu.Game.Online.Chat.MessageFormatter; namespace osu.Game { @@ -216,7 +215,9 @@ namespace osu.Game private ExternalLinkOpener externalLinkOpener; - public void HandleLink(string url, LinkAction linkType, string linkArgument) + public void HandleLink(string url) => HandleLink(MessageFormatter.GetLinkDetails(url)); + + public void HandleLink(LinkDetails link) { Action showNotImplementedError = () => notifications?.Post(new SimpleNotification { @@ -224,27 +225,27 @@ namespace osu.Game Icon = FontAwesome.Solid.LifeRing, }); - switch (linkType) + switch (link.Action) { case LinkAction.OpenBeatmap: // TODO: proper query params handling - if (linkArgument != null && int.TryParse(linkArgument.Contains('?') ? linkArgument.Split('?')[0] : linkArgument, out int beatmapId)) + if (link.Argument != null && int.TryParse(link.Argument.Contains('?') ? link.Argument.Split('?')[0] : link.Argument, out int beatmapId)) ShowBeatmap(beatmapId); break; case LinkAction.OpenBeatmapSet: - if (int.TryParse(linkArgument, out int setId)) + if (int.TryParse(link.Argument, out int setId)) ShowBeatmapSet(setId); break; case LinkAction.OpenChannel: try { - channelManager.OpenChannel(linkArgument); + channelManager.OpenChannel(link.Argument); } catch (ChannelNotFoundException) { - Logger.Log($"The requested channel \"{linkArgument}\" does not exist"); + Logger.Log($"The requested channel \"{link.Argument}\" does not exist"); } break; @@ -256,28 +257,19 @@ namespace osu.Game break; case LinkAction.External: - OpenUrlExternally(url); + OpenUrlExternally(link.Argument); break; case LinkAction.OpenUserProfile: - if (long.TryParse(linkArgument, out long userId)) + if (long.TryParse(link.Argument, out long userId)) ShowUser(userId); break; default: - throw new NotImplementedException($"This {nameof(LinkAction)} ({linkType.ToString()}) is missing an associated action."); + throw new NotImplementedException($"This {nameof(LinkAction)} ({link.Action.ToString()}) is missing an associated action."); } } - public void HandleUrl(string url) - { - Logger.Log($"Request to handle url: {url}"); - - LinkDetails linkDetails = GetLinkDetails(url); - - Schedule(() => HandleLink(url, linkDetails.Action, linkDetails.Argument)); - } - public void OpenUrlExternally(string url) { if (url.StartsWith("/")) diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs index bce1be5941..d8488b21ab 100644 --- a/osu.Game/Overlays/Changelog/ChangelogBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs @@ -110,7 +110,7 @@ namespace osu.Game.Overlays.Changelog t.Font = fontLarge; t.Colour = entryColour; }); - title.AddLink($"{entry.Repository.Replace("ppy/", "")}#{entry.GithubPullRequestId}", entry.GithubUrl, Online.Chat.LinkAction.External, + title.AddLink($"{entry.Repository.Replace("ppy/", "")}#{entry.GithubPullRequestId}", entry.GithubUrl, creationParameters: t => { t.Font = fontLarge; @@ -140,7 +140,7 @@ namespace osu.Game.Overlays.Changelog t.Colour = entryColour; }); else if (entry.GithubUser.GithubUrl != null) - title.AddLink(entry.GithubUser.DisplayName, entry.GithubUser.GithubUrl, Online.Chat.LinkAction.External, null, null, t => + title.AddLink(entry.GithubUser.DisplayName, entry.GithubUser.GithubUrl, t => { t.Font = fontMedium; t.Colour = entryColour; diff --git a/osu.Game/Screens/Multi/Components/BeatmapTitle.cs b/osu.Game/Screens/Multi/Components/BeatmapTitle.cs index e096fb33da..f79cac7649 100644 --- a/osu.Game/Screens/Multi/Components/BeatmapTitle.cs +++ b/osu.Game/Screens/Multi/Components/BeatmapTitle.cs @@ -81,7 +81,7 @@ namespace osu.Game.Screens.Multi.Components Text = new LocalisedString((beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title)), Font = OsuFont.GetFont(size: TextSize), } - }, null, LinkAction.OpenBeatmap, beatmap.OnlineBeatmapID.ToString(), "Open beatmap"); + }, LinkAction.OpenBeatmap, beatmap.OnlineBeatmapID.ToString(), "Open beatmap"); } } } diff --git a/osu.iOS/AppDelegate.cs b/osu.iOS/AppDelegate.cs index 07e0245195..164a182ebe 100644 --- a/osu.iOS/AppDelegate.cs +++ b/osu.iOS/AppDelegate.cs @@ -21,7 +21,7 @@ namespace osu.iOS if (url.IsFileUrl) Task.Run(() => game.Import(url.Path)); else - Task.Run(() => game.HandleUrl(url.AbsoluteString)); + Task.Run(() => game.HandleLink(url.AbsoluteString)); return true; } } From f13dc93a4d867352797046d7a5b46a9c941eaf06 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Oct 2019 18:49:13 +0900 Subject: [PATCH 2035/2815] Fix external link tooltip regression --- osu.Game/Graphics/Containers/LinkFlowContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index b5ffd1c6ad..61391b7102 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -52,7 +52,7 @@ namespace osu.Game.Graphics.Containers } public void AddLink(string text, string url, Action creationParameters = null) => - createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.External, url), text); + createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.External, url), url); public void AddLink(string text, Action action, string tooltipText = null, Action creationParameters = null) => createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.Custom, null), tooltipText, action); From e3ca64bb8c6dad982b579d126fbaefa402629c04 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Oct 2019 19:18:54 +0900 Subject: [PATCH 2036/2815] Remove iOS "import from stable" for now --- osu.Game/Database/ArchiveModelManager.cs | 2 +- osu.iOS/Info.plist | 8 +++----- osu.iOS/OsuGameIOS.cs | 2 -- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 3c1e07b765..9fed8e03ac 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -67,7 +67,7 @@ namespace osu.Game.Database public virtual string[] HandledExtensions => new[] { ".zip" }; - public virtual bool SupportsImportFromStable => (RuntimeInfo.IsDesktop || RuntimeInfo.OS == RuntimeInfo.Platform.iOS); + public virtual bool SupportsImportFromStable => RuntimeInfo.IsDesktop; protected readonly FileStore Files; diff --git a/osu.iOS/Info.plist b/osu.iOS/Info.plist index 67f71b64b6..5ceccdf99f 100644 --- a/osu.iOS/Info.plist +++ b/osu.iOS/Info.plist @@ -34,9 +34,9 @@ UIStatusBarHidden NSCameraUsageDescription - We don't really use the camera. - NSMicrophoneUsageDescription - We don't really use the microphone. + We don't really use the camera. + NSMicrophoneUsageDescription + We don't really use the microphone. UISupportedInterfaceOrientations UIInterfaceOrientationPortrait @@ -111,8 +111,6 @@ - UIFileSharingEnabled - CFBundleURLTypes diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index ac66357fc9..6cf18df9a6 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -3,14 +3,12 @@ using System; using Foundation; -using osu.Framework.Platform; using osu.Game; namespace osu.iOS { public class OsuGameIOS : OsuGame { - public override Storage GetStorageForStableInstall() => Storage; public override Version AssemblyVersion => new Version(NSBundle.MainBundle.InfoDictionary["CFBundleVersion"].ToString()); } } From ba65d0e4ee712fcc1f87b3f07205db05c1406c26 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Thu, 31 Oct 2019 12:39:09 +0000 Subject: [PATCH 2037/2815] Force ordering of issue templates --- .github/ISSUE_TEMPLATE/{mobile-issues.md => 00-mobile-issues.md} | 0 .github/ISSUE_TEMPLATE/{bug-issues.md => 01-bug-issues.md} | 0 .github/ISSUE_TEMPLATE/{crash-issues.md => 02-crash-issues.md} | 0 .../{missing-for-live-issues.md => 03-missing-for-live.md} | 0 .../{feature-request-issues.md => 04-feature-request-issues.md} | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename .github/ISSUE_TEMPLATE/{mobile-issues.md => 00-mobile-issues.md} (100%) rename .github/ISSUE_TEMPLATE/{bug-issues.md => 01-bug-issues.md} (100%) rename .github/ISSUE_TEMPLATE/{crash-issues.md => 02-crash-issues.md} (100%) rename .github/ISSUE_TEMPLATE/{missing-for-live-issues.md => 03-missing-for-live.md} (100%) rename .github/ISSUE_TEMPLATE/{feature-request-issues.md => 04-feature-request-issues.md} (100%) diff --git a/.github/ISSUE_TEMPLATE/mobile-issues.md b/.github/ISSUE_TEMPLATE/00-mobile-issues.md similarity index 100% rename from .github/ISSUE_TEMPLATE/mobile-issues.md rename to .github/ISSUE_TEMPLATE/00-mobile-issues.md diff --git a/.github/ISSUE_TEMPLATE/bug-issues.md b/.github/ISSUE_TEMPLATE/01-bug-issues.md similarity index 100% rename from .github/ISSUE_TEMPLATE/bug-issues.md rename to .github/ISSUE_TEMPLATE/01-bug-issues.md diff --git a/.github/ISSUE_TEMPLATE/crash-issues.md b/.github/ISSUE_TEMPLATE/02-crash-issues.md similarity index 100% rename from .github/ISSUE_TEMPLATE/crash-issues.md rename to .github/ISSUE_TEMPLATE/02-crash-issues.md diff --git a/.github/ISSUE_TEMPLATE/missing-for-live-issues.md b/.github/ISSUE_TEMPLATE/03-missing-for-live.md similarity index 100% rename from .github/ISSUE_TEMPLATE/missing-for-live-issues.md rename to .github/ISSUE_TEMPLATE/03-missing-for-live.md diff --git a/.github/ISSUE_TEMPLATE/feature-request-issues.md b/.github/ISSUE_TEMPLATE/04-feature-request-issues.md similarity index 100% rename from .github/ISSUE_TEMPLATE/feature-request-issues.md rename to .github/ISSUE_TEMPLATE/04-feature-request-issues.md From 08b1631dbbfe496c0df15ad8aa2632dfa20b8e9f Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Thu, 31 Oct 2019 12:42:25 +0000 Subject: [PATCH 2038/2815] Add issue template config --- .github/ISSUE_TEMPLATE/config.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..69baeee60c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: osu!stable issues + url: https://github.com/ppy/osu-stable-issues + about: For issues regarding osu!stable (not osu!lazer), open them here. From 3ada1510d04f635ae36050060b8534f66c5e2a08 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Thu, 31 Oct 2019 13:09:36 +0000 Subject: [PATCH 2039/2815] Remove 'missing-for-live.md' --- ...ture-request-issues.md => 03-feature-request-issues.md} | 0 .github/ISSUE_TEMPLATE/03-missing-for-live.md | 7 ------- 2 files changed, 7 deletions(-) rename .github/ISSUE_TEMPLATE/{04-feature-request-issues.md => 03-feature-request-issues.md} (100%) delete mode 100644 .github/ISSUE_TEMPLATE/03-missing-for-live.md diff --git a/.github/ISSUE_TEMPLATE/04-feature-request-issues.md b/.github/ISSUE_TEMPLATE/03-feature-request-issues.md similarity index 100% rename from .github/ISSUE_TEMPLATE/04-feature-request-issues.md rename to .github/ISSUE_TEMPLATE/03-feature-request-issues.md diff --git a/.github/ISSUE_TEMPLATE/03-missing-for-live.md b/.github/ISSUE_TEMPLATE/03-missing-for-live.md deleted file mode 100644 index 5822da9c65..0000000000 --- a/.github/ISSUE_TEMPLATE/03-missing-for-live.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -name: Missing for Live -about: Features which are available in osu!stable but not yet in osu!lazer. ---- -**Describe the missing feature:** - -**Proposal designs of the feature:** From 944380906324d53936b67b04b30e9a01097bdb42 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 11:22:32 +0900 Subject: [PATCH 2040/2815] Protect against requests to show overlays before the target overlay is ready --- osu.Game/OsuGame.cs | 77 ++++++++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 22 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b55cc41454..fb9a7f7965 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -215,16 +215,20 @@ namespace osu.Game private ExternalLinkOpener externalLinkOpener; - public void HandleLink(string url) => HandleLink(MessageFormatter.GetLinkDetails(url)); + /// + /// Handle an arbitrary URL. Displays via in-game overlays where possible. + /// This can be called from a non-thread-safe non-game-loaded state. + /// + /// The URL to load. + public void HandleLink(string url) => Schedule(() => HandleLink(MessageFormatter.GetLinkDetails(url))); + /// + /// Handle a specific . + /// This can be called from a non-thread-safe non-game-loaded state. + /// + /// The link to load. public void HandleLink(LinkDetails link) { - Action showNotImplementedError = () => notifications?.Post(new SimpleNotification - { - Text = @"This link type is not yet supported!", - Icon = FontAwesome.Solid.LifeRing, - }); - switch (link.Action) { case LinkAction.OpenBeatmap: @@ -239,21 +243,17 @@ namespace osu.Game break; case LinkAction.OpenChannel: - try - { - channelManager.OpenChannel(link.Argument); - } - catch (ChannelNotFoundException) - { - Logger.Log($"The requested channel \"{link.Argument}\" does not exist"); - } - + ShowChannel(link.Argument); break; case LinkAction.OpenEditorTimestamp: case LinkAction.JoinMultiplayerMatch: case LinkAction.Spectate: - showNotImplementedError.Invoke(); + waitForReady(() => notifications, _ => notifications?.Post(new SimpleNotification + { + Text = @"This link type is not yet supported!", + Icon = FontAwesome.Solid.LifeRing, + })); break; case LinkAction.External: @@ -270,31 +270,47 @@ namespace osu.Game } } - public void OpenUrlExternally(string url) + public void OpenUrlExternally(string url) => waitForReady(() => externalLinkOpener, _ => { if (url.StartsWith("/")) url = $"{API.Endpoint}{url}"; externalLinkOpener.OpenUrlExternally(url); - } + }); + + /// + /// Open a specific channel in chat. + /// + /// The channel to display. + public void ShowChannel(string channel) => waitForReady(() => channelManager, _ => + { + try + { + channelManager.OpenChannel(channel); + } + catch (ChannelNotFoundException) + { + Logger.Log($"The requested channel \"{channel}\" does not exist"); + } + }); /// /// Show a beatmap set as an overlay. /// /// The set to display. - public void ShowBeatmapSet(int setId) => beatmapSetOverlay.FetchAndShowBeatmapSet(setId); + public void ShowBeatmapSet(int setId) => waitForReady(() => beatmapSetOverlay, _ => beatmapSetOverlay.FetchAndShowBeatmapSet(setId)); /// /// Show a user's profile as an overlay. /// /// The user to display. - public void ShowUser(long userId) => userProfile.ShowUser(userId); + public void ShowUser(long userId) => waitForReady(() => userProfile, _ => userProfile.ShowUser(userId)); /// /// Show a beatmap's set as an overlay, displaying the given beatmap. /// /// The beatmap to show. - public void ShowBeatmap(int beatmapId) => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId); + public void ShowBeatmap(int beatmapId) => waitForReady(() => beatmapSetOverlay, _ => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId)); /// /// Present a beatmap at song select immediately. @@ -452,6 +468,23 @@ namespace osu.Game performFromMainMenuTask = Schedule(() => performFromMainMenu(action, taskName)); } + /// + /// Wait for the game (and target component) to become loaded and then run an action. + /// + /// A function to retrieve a (potentially not-yet-constructed) target instance. + /// The action to perform on the instance when load is confirmed. + /// The type of the target instance. + private void waitForReady(Func retrieveInstance, Action action) + where T : Drawable + { + var instance = retrieveInstance(); + + if (ScreenStack == null || ScreenStack.CurrentScreen is StartupScreen || instance?.IsLoaded != true) + Schedule(() => waitForReady(retrieveInstance, action)); + else + action(instance); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 898520935ec5c26994ac209ccd8229eb362b880e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 11:40:51 +0900 Subject: [PATCH 2041/2815] Move link handling code to OsuGame This allows for future calls from arguments / associations --- .../Graphics/Containers/LinkFlowContainer.cs | 92 +++++-------------- osu.Game/Online/Chat/MessageFormatter.cs | 25 ++--- osu.Game/OsuGame.cs | 55 +++++++++++ osu.Game/Overlays/Changelog/ChangelogBuild.cs | 4 +- .../Screens/Multi/Components/BeatmapTitle.cs | 2 +- 5 files changed, 92 insertions(+), 86 deletions(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 15068d81c0..61391b7102 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -8,9 +8,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using System.Collections.Generic; using osu.Framework.Graphics; -using osu.Framework.Logging; -using osu.Game.Overlays; -using osu.Game.Overlays.Notifications; using osu.Game.Users; namespace osu.Game.Graphics.Containers @@ -23,21 +20,12 @@ namespace osu.Game.Graphics.Containers } private OsuGame game; - private ChannelManager channelManager; - private Action showNotImplementedError; [BackgroundDependencyLoader(true)] - private void load(OsuGame game, NotificationOverlay notifications, ChannelManager channelManager) + private void load(OsuGame game) { // will be null in tests this.game = game; - this.channelManager = channelManager; - - showNotImplementedError = () => notifications?.Post(new SimpleNotification - { - Text = @"This link type is not yet supported!", - Icon = FontAwesome.Solid.LifeRing, - }); } public void AddLinks(string text, List links) @@ -56,85 +44,47 @@ namespace osu.Game.Graphics.Containers foreach (var link in links) { AddText(text.Substring(previousLinkEnd, link.Index - previousLinkEnd)); - AddLink(text.Substring(link.Index, link.Length), link.Url, link.Action, link.Argument); + AddLink(text.Substring(link.Index, link.Length), link.Action, link.Argument ?? link.Url); previousLinkEnd = link.Index + link.Length; } AddText(text.Substring(previousLinkEnd)); } - public IEnumerable AddLink(string text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null, Action creationParameters = null) - => createLink(AddText(text, creationParameters), text, url, linkType, linkArgument, tooltipText); + public void AddLink(string text, string url, Action creationParameters = null) => + createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.External, url), url); - public IEnumerable AddLink(string text, Action action, string tooltipText = null, Action creationParameters = null) - => createLink(AddText(text, creationParameters), text, tooltipText: tooltipText, action: action); + public void AddLink(string text, Action action, string tooltipText = null, Action creationParameters = null) + => createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.Custom, null), tooltipText, action); - public IEnumerable AddLink(IEnumerable text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null) + public void AddLink(string text, LinkAction action, string argument, string tooltipText = null, Action creationParameters = null) + => createLink(AddText(text, creationParameters), new LinkDetails(action, argument), null); + + public void AddLink(IEnumerable text, LinkAction action = LinkAction.External, string linkArgument = null, string tooltipText = null) { foreach (var t in text) AddArbitraryDrawable(t); - return createLink(text, null, url, linkType, linkArgument, tooltipText); + createLink(text, new LinkDetails(action, linkArgument), tooltipText); } - public IEnumerable AddUserLink(User user, Action creationParameters = null) - => createLink(AddText(user.Username, creationParameters), user.Username, null, LinkAction.OpenUserProfile, user.Id.ToString(), "View profile"); + public void AddUserLink(User user, Action creationParameters = null) + => createLink(AddText(user.Username, creationParameters), new LinkDetails(LinkAction.OpenUserProfile, user.Id.ToString()), "View Profile"); - private IEnumerable createLink(IEnumerable drawables, string text, string url = null, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null, Action action = null) + private void createLink(IEnumerable drawables, LinkDetails link, string tooltipText, Action action = null) { AddInternal(new DrawableLinkCompiler(drawables.OfType().ToList()) { RelativeSizeAxes = Axes.Both, - TooltipText = tooltipText ?? (url != text ? url : string.Empty), - Action = action ?? (() => + TooltipText = tooltipText, + Action = () => { - switch (linkType) - { - case LinkAction.OpenBeatmap: - // TODO: proper query params handling - if (linkArgument != null && int.TryParse(linkArgument.Contains('?') ? linkArgument.Split('?')[0] : linkArgument, out int beatmapId)) - game?.ShowBeatmap(beatmapId); - break; - - case LinkAction.OpenBeatmapSet: - if (int.TryParse(linkArgument, out int setId)) - game?.ShowBeatmapSet(setId); - break; - - case LinkAction.OpenChannel: - try - { - channelManager?.OpenChannel(linkArgument); - } - catch (ChannelNotFoundException) - { - Logger.Log($"The requested channel \"{linkArgument}\" does not exist"); - } - - break; - - case LinkAction.OpenEditorTimestamp: - case LinkAction.JoinMultiplayerMatch: - case LinkAction.Spectate: - showNotImplementedError?.Invoke(); - break; - - case LinkAction.External: - game?.OpenUrlExternally(url); - break; - - case LinkAction.OpenUserProfile: - if (long.TryParse(linkArgument, out long userId)) - game?.ShowUser(userId); - break; - - default: - throw new NotImplementedException($"This {nameof(LinkAction)} ({linkType.ToString()}) is missing an associated action."); - } - }), + if (action != null) + action(); + else + game.HandleLink(link); + }, }); - - return drawables; } // We want the compilers to always be visible no matter where they are, so RelativeSizeAxes is used. diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 3ffff281f8..717de18c14 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -81,7 +81,7 @@ namespace osu.Game.Online.Chat //since we just changed the line display text, offset any already processed links. result.Links.ForEach(l => l.Index -= l.Index > index ? m.Length - displayText.Length : 0); - var details = getLinkDetails(linkText); + var details = GetLinkDetails(linkText); result.Links.Add(new Link(linkText, index, displayText.Length, linkActionOverride ?? details.Action, details.Argument)); //adjust the offset for processing the current matches group. @@ -98,7 +98,7 @@ namespace osu.Game.Online.Chat var linkText = m.Groups["link"].Value; var indexLength = linkText.Length; - var details = getLinkDetails(linkText); + var details = GetLinkDetails(linkText); var link = new Link(linkText, index, indexLength, details.Action, details.Argument); // sometimes an already-processed formatted link can reduce to a simple URL, too @@ -109,7 +109,7 @@ namespace osu.Game.Online.Chat } } - private static LinkDetails getLinkDetails(string url) + public static LinkDetails GetLinkDetails(string url) { var args = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); args[0] = args[0].TrimEnd(':'); @@ -255,17 +255,17 @@ namespace osu.Game.Online.Chat OriginalText = Text = text; } } + } - public class LinkDetails + public class LinkDetails + { + public LinkAction Action; + public string Argument; + + public LinkDetails(LinkAction action, string argument) { - public LinkAction Action; - public string Argument; - - public LinkDetails(LinkAction action, string argument) - { - Action = action; - Argument = argument; - } + Action = action; + Argument = argument; } } @@ -279,6 +279,7 @@ namespace osu.Game.Online.Chat JoinMultiplayerMatch, Spectate, OpenUserProfile, + Custom } public class Link : IComparable diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 4dcc181bea..b55cc41454 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -215,6 +215,61 @@ namespace osu.Game private ExternalLinkOpener externalLinkOpener; + public void HandleLink(string url) => HandleLink(MessageFormatter.GetLinkDetails(url)); + + public void HandleLink(LinkDetails link) + { + Action showNotImplementedError = () => notifications?.Post(new SimpleNotification + { + Text = @"This link type is not yet supported!", + Icon = FontAwesome.Solid.LifeRing, + }); + + switch (link.Action) + { + case LinkAction.OpenBeatmap: + // TODO: proper query params handling + if (link.Argument != null && int.TryParse(link.Argument.Contains('?') ? link.Argument.Split('?')[0] : link.Argument, out int beatmapId)) + ShowBeatmap(beatmapId); + break; + + case LinkAction.OpenBeatmapSet: + if (int.TryParse(link.Argument, out int setId)) + ShowBeatmapSet(setId); + break; + + case LinkAction.OpenChannel: + try + { + channelManager.OpenChannel(link.Argument); + } + catch (ChannelNotFoundException) + { + Logger.Log($"The requested channel \"{link.Argument}\" does not exist"); + } + + break; + + case LinkAction.OpenEditorTimestamp: + case LinkAction.JoinMultiplayerMatch: + case LinkAction.Spectate: + showNotImplementedError.Invoke(); + break; + + case LinkAction.External: + OpenUrlExternally(link.Argument); + break; + + case LinkAction.OpenUserProfile: + if (long.TryParse(link.Argument, out long userId)) + ShowUser(userId); + break; + + default: + throw new NotImplementedException($"This {nameof(LinkAction)} ({link.Action.ToString()}) is missing an associated action."); + } + } + public void OpenUrlExternally(string url) { if (url.StartsWith("/")) diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs index bce1be5941..d8488b21ab 100644 --- a/osu.Game/Overlays/Changelog/ChangelogBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs @@ -110,7 +110,7 @@ namespace osu.Game.Overlays.Changelog t.Font = fontLarge; t.Colour = entryColour; }); - title.AddLink($"{entry.Repository.Replace("ppy/", "")}#{entry.GithubPullRequestId}", entry.GithubUrl, Online.Chat.LinkAction.External, + title.AddLink($"{entry.Repository.Replace("ppy/", "")}#{entry.GithubPullRequestId}", entry.GithubUrl, creationParameters: t => { t.Font = fontLarge; @@ -140,7 +140,7 @@ namespace osu.Game.Overlays.Changelog t.Colour = entryColour; }); else if (entry.GithubUser.GithubUrl != null) - title.AddLink(entry.GithubUser.DisplayName, entry.GithubUser.GithubUrl, Online.Chat.LinkAction.External, null, null, t => + title.AddLink(entry.GithubUser.DisplayName, entry.GithubUser.GithubUrl, t => { t.Font = fontMedium; t.Colour = entryColour; diff --git a/osu.Game/Screens/Multi/Components/BeatmapTitle.cs b/osu.Game/Screens/Multi/Components/BeatmapTitle.cs index e096fb33da..f79cac7649 100644 --- a/osu.Game/Screens/Multi/Components/BeatmapTitle.cs +++ b/osu.Game/Screens/Multi/Components/BeatmapTitle.cs @@ -81,7 +81,7 @@ namespace osu.Game.Screens.Multi.Components Text = new LocalisedString((beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title)), Font = OsuFont.GetFont(size: TextSize), } - }, null, LinkAction.OpenBeatmap, beatmap.OnlineBeatmapID.ToString(), "Open beatmap"); + }, LinkAction.OpenBeatmap, beatmap.OnlineBeatmapID.ToString(), "Open beatmap"); } } } From f038c579f01b89327c57f0ff307c0917957c9667 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 11:22:32 +0900 Subject: [PATCH 2042/2815] Protect against requests to show overlays before the target overlay is ready --- osu.Game/OsuGame.cs | 77 ++++++++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 22 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b55cc41454..fb9a7f7965 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -215,16 +215,20 @@ namespace osu.Game private ExternalLinkOpener externalLinkOpener; - public void HandleLink(string url) => HandleLink(MessageFormatter.GetLinkDetails(url)); + /// + /// Handle an arbitrary URL. Displays via in-game overlays where possible. + /// This can be called from a non-thread-safe non-game-loaded state. + /// + /// The URL to load. + public void HandleLink(string url) => Schedule(() => HandleLink(MessageFormatter.GetLinkDetails(url))); + /// + /// Handle a specific . + /// This can be called from a non-thread-safe non-game-loaded state. + /// + /// The link to load. public void HandleLink(LinkDetails link) { - Action showNotImplementedError = () => notifications?.Post(new SimpleNotification - { - Text = @"This link type is not yet supported!", - Icon = FontAwesome.Solid.LifeRing, - }); - switch (link.Action) { case LinkAction.OpenBeatmap: @@ -239,21 +243,17 @@ namespace osu.Game break; case LinkAction.OpenChannel: - try - { - channelManager.OpenChannel(link.Argument); - } - catch (ChannelNotFoundException) - { - Logger.Log($"The requested channel \"{link.Argument}\" does not exist"); - } - + ShowChannel(link.Argument); break; case LinkAction.OpenEditorTimestamp: case LinkAction.JoinMultiplayerMatch: case LinkAction.Spectate: - showNotImplementedError.Invoke(); + waitForReady(() => notifications, _ => notifications?.Post(new SimpleNotification + { + Text = @"This link type is not yet supported!", + Icon = FontAwesome.Solid.LifeRing, + })); break; case LinkAction.External: @@ -270,31 +270,47 @@ namespace osu.Game } } - public void OpenUrlExternally(string url) + public void OpenUrlExternally(string url) => waitForReady(() => externalLinkOpener, _ => { if (url.StartsWith("/")) url = $"{API.Endpoint}{url}"; externalLinkOpener.OpenUrlExternally(url); - } + }); + + /// + /// Open a specific channel in chat. + /// + /// The channel to display. + public void ShowChannel(string channel) => waitForReady(() => channelManager, _ => + { + try + { + channelManager.OpenChannel(channel); + } + catch (ChannelNotFoundException) + { + Logger.Log($"The requested channel \"{channel}\" does not exist"); + } + }); /// /// Show a beatmap set as an overlay. /// /// The set to display. - public void ShowBeatmapSet(int setId) => beatmapSetOverlay.FetchAndShowBeatmapSet(setId); + public void ShowBeatmapSet(int setId) => waitForReady(() => beatmapSetOverlay, _ => beatmapSetOverlay.FetchAndShowBeatmapSet(setId)); /// /// Show a user's profile as an overlay. /// /// The user to display. - public void ShowUser(long userId) => userProfile.ShowUser(userId); + public void ShowUser(long userId) => waitForReady(() => userProfile, _ => userProfile.ShowUser(userId)); /// /// Show a beatmap's set as an overlay, displaying the given beatmap. /// /// The beatmap to show. - public void ShowBeatmap(int beatmapId) => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId); + public void ShowBeatmap(int beatmapId) => waitForReady(() => beatmapSetOverlay, _ => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId)); /// /// Present a beatmap at song select immediately. @@ -452,6 +468,23 @@ namespace osu.Game performFromMainMenuTask = Schedule(() => performFromMainMenu(action, taskName)); } + /// + /// Wait for the game (and target component) to become loaded and then run an action. + /// + /// A function to retrieve a (potentially not-yet-constructed) target instance. + /// The action to perform on the instance when load is confirmed. + /// The type of the target instance. + private void waitForReady(Func retrieveInstance, Action action) + where T : Drawable + { + var instance = retrieveInstance(); + + if (ScreenStack == null || ScreenStack.CurrentScreen is StartupScreen || instance?.IsLoaded != true) + Schedule(() => waitForReady(retrieveInstance, action)); + else + action(instance); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 0171b2ae7c0882e2d0ad7e499d4e4369b580af6b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 12:10:03 +0900 Subject: [PATCH 2043/2815] Fix scrolling hitobjects expiring too soon --- .../UI/Scrolling/ScrollingHitObjectContainer.cs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index e00597dd56..857929ff9e 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -13,12 +13,6 @@ namespace osu.Game.Rulesets.UI.Scrolling { public class ScrollingHitObjectContainer : HitObjectContainer { - /// - /// A multiplier applied to the length of the scrolling area to determine a safe default lifetime end for hitobjects. - /// This is only used to limit the lifetime end within reason, as proper lifetime management should be implemented on hitobjects themselves. - /// - private const float safe_lifetime_end_multiplier = 2; - private readonly IBindable timeRange = new BindableDouble(); private readonly IBindable direction = new Bindable(); @@ -123,28 +117,22 @@ namespace osu.Game.Rulesets.UI.Scrolling if (cached.IsValid) return; - double endTime = hitObject.HitObject.StartTime; - if (hitObject.HitObject is IHasEndTime e) { - endTime = e.EndTime; - switch (direction.Value) { case ScrollingDirection.Up: case ScrollingDirection.Down: - hitObject.Height = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, endTime, timeRange.Value, scrollLength); + hitObject.Height = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, e.EndTime, timeRange.Value, scrollLength); break; case ScrollingDirection.Left: case ScrollingDirection.Right: - hitObject.Width = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, endTime, timeRange.Value, scrollLength); + hitObject.Width = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, e.EndTime, timeRange.Value, scrollLength); break; } } - hitObject.LifetimeEnd = scrollingInfo.Algorithm.TimeAt(scrollLength * safe_lifetime_end_multiplier, endTime, timeRange.Value, scrollLength); - foreach (var obj in hitObject.NestedHitObjects) { computeInitialStateRecursive(obj); From ac02bb005d982603c55002d8212af592453a584d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 14:11:18 +0900 Subject: [PATCH 2044/2815] Fix GameplayClockContainer operating on beatmap's track after scren exited --- osu.Game/Screens/Play/GameplayClockContainer.cs | 15 ++++++++++++++- osu.Game/Screens/Play/Player.cs | 4 ++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 6a03271b86..f2efbe6073 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -29,7 +30,7 @@ namespace osu.Game.Screens.Play /// /// The original source (usually a 's track). /// - private readonly IAdjustableClock sourceClock; + private IAdjustableClock sourceClock; public readonly BindableBool IsPaused = new BindableBool(); @@ -153,6 +154,18 @@ namespace osu.Game.Screens.Play IsPaused.Value = true; } + /// + /// Changes the backing clock to avoid using the originally provided beatmap's track. + /// + public void StopUsingBeatmapClock() + { + if (sourceClock != beatmap.Track) + return; + + sourceClock = new TrackVirtual(beatmap.Track.Length); + adjustableClock.ChangeSource(sourceClock); + } + public void ResetLocalAdjustments() { // In the case of replays, we may have changed the playback rate. diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 0b363eac4d..4820c62da3 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -527,6 +527,10 @@ namespace osu.Game.Screens.Play GameplayClockContainer.ResetLocalAdjustments(); + // GameplayClockContainer performs seeks / start / stop operations on the beatmap's track. + // as we are no longer the current screen, we cannot guarantee the track is still usable. + GameplayClockContainer.StopUsingBeatmapClock(); + fadeOut(); return base.OnExiting(next); } From dcc8f6e82799b3fe7055068b446780ad5ed76113 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 14:43:52 +0900 Subject: [PATCH 2045/2815] Better group cancel conditional --- 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 7eccde4555..280ebca651 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -446,9 +446,9 @@ namespace osu.Game.Screens.Play if (IsResuming) { DrawableRuleset.CancelResume(); + IsResuming = false; } - IsResuming = false; GameplayClockContainer.Stop(); PauseOverlay.Show(); lastPauseActionTime = GameplayClockContainer.GameplayClock.CurrentTime; From 05002ea3e8cd308e46e8147f909920ebd56300b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 14:50:38 +0900 Subject: [PATCH 2046/2815] Move Show/Hide code to PopIn/PopOut --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 8347d255fa..aecfac3b70 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -38,9 +38,10 @@ namespace osu.Game.Rulesets.Osu.UI }); } - public override void Show() + protected override void PopIn() { - base.Show(); + base.PopIn(); + GameplayCursor.ActiveCursor.Hide(); cursorScaleContainer.MoveTo(GameplayCursor.ActiveCursor.Position); clickToResumeCursor.Appear(); @@ -55,13 +56,13 @@ namespace osu.Game.Rulesets.Osu.UI } } - public override void Hide() + protected override void PopOut() { + base.PopOut(); + localCursorContainer?.Expire(); localCursorContainer = null; - GameplayCursor.ActiveCursor.Show(); - - base.Hide(); + GameplayCursor?.ActiveCursor.Show(); } protected override bool OnHover(HoverEvent e) => true; From 14fec6f1f35e4cf9e9a0e65a535e407c68ba9ed5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 15:06:36 +0900 Subject: [PATCH 2047/2815] Move ReplayDownloadButton to correct namespace --- .../Visual/Gameplay/TestSceneReplayDownloadButton.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs | 1 + .../{Play => Ranking/Pages}/ReplayDownloadButton.cs | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) rename osu.Game/Screens/{Play => Ranking/Pages}/ReplayDownloadButton.cs (98%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 0dfcda122f..4c870e04f5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -7,11 +7,11 @@ using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; -using osu.Game.Screens.Play; using osu.Game.Users; using osuTK; using System; using System.Collections.Generic; +using osu.Game.Screens.Ranking.Pages; namespace osu.Game.Tests.Visual.Gameplay { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs index f3c8f89db7..c4b42f6356 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs @@ -26,6 +26,7 @@ namespace osu.Game.Tests.Visual.Gameplay typeof(Results), typeof(ResultsPage), typeof(ScoreResultsPage), + typeof(ReplayDownloadButton), typeof(LocalLeaderboardPage) }; diff --git a/osu.Game/Screens/Play/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/Pages/ReplayDownloadButton.cs similarity index 98% rename from osu.Game/Screens/Play/ReplayDownloadButton.cs rename to osu.Game/Screens/Ranking/Pages/ReplayDownloadButton.cs index 290e00f287..c8f4ecd2d5 100644 --- a/osu.Game/Screens/Play/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/Pages/ReplayDownloadButton.cs @@ -4,12 +4,12 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Online; -using osu.Game.Scoring; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Graphics.UserInterface; +using osu.Game.Online; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Scoring; -namespace osu.Game.Screens.Play +namespace osu.Game.Screens.Ranking.Pages { public class ReplayDownloadButton : DownloadTrackingComposite { From e23ea94383e293c26764125637ef4627232f436f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 15:33:38 +0900 Subject: [PATCH 2048/2815] Add one more level of null check --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index aecfac3b70..3b18e41f30 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.UI localCursorContainer?.Expire(); localCursorContainer = null; - GameplayCursor?.ActiveCursor.Show(); + GameplayCursor?.ActiveCursor?.Show(); } protected override bool OnHover(HoverEvent e) => true; From 84d17f3702bdf25f37a1bfffa34a216a8ab031f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 15:32:06 +0900 Subject: [PATCH 2049/2815] Add retry button --- .../Gameplay/TestSceneReplayDownloadButton.cs | 2 - .../Visual/Gameplay/TestSceneResults.cs | 2 +- osu.Game/Screens/Play/Player.cs | 8 +-- .../Ranking/Pages/ReplayDownloadButton.cs | 2 + osu.Game/Screens/Ranking/Pages/RetryButton.cs | 59 +++++++++++++++++++ .../Screens/Ranking/Pages/ScoreResultsPage.cs | 11 +++- 6 files changed, 75 insertions(+), 9 deletions(-) create mode 100644 osu.Game/Screens/Ranking/Pages/RetryButton.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 4c870e04f5..7b22fedbd5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -8,7 +8,6 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Users; -using osuTK; using System; using System.Collections.Generic; using osu.Game.Screens.Ranking.Pages; @@ -42,7 +41,6 @@ namespace osu.Game.Tests.Visual.Gameplay { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(80, 40), }; }); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs index c4b42f6356..bf26892539 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs @@ -22,10 +22,10 @@ namespace osu.Game.Tests.Visual.Gameplay public override IReadOnlyList RequiredTypes => new[] { - typeof(ScoreInfo), typeof(Results), typeof(ResultsPage), typeof(ScoreResultsPage), + typeof(RetryButton), typeof(ReplayDownloadButton), typeof(LocalLeaderboardPage) }; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 0b363eac4d..38068ec751 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -30,6 +30,7 @@ using osu.Game.Users; namespace osu.Game.Screens.Play { + [Cached] public class Player : ScreenWithBeatmapBackground { public override bool AllowBackButton => false; // handled by HoldForMenuButton @@ -313,12 +314,11 @@ namespace osu.Game.Screens.Play public void Restart() { - if (!this.IsCurrentScreen()) return; - sampleRestart?.Play(); - RestartRequested?.Invoke(); - performImmediateExit(); + + if (this.IsCurrentScreen()) + performImmediateExit(); } private ScheduledDelegate completionProgressDelegate; diff --git a/osu.Game/Screens/Ranking/Pages/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/Pages/ReplayDownloadButton.cs index c8f4ecd2d5..73c647d6fa 100644 --- a/osu.Game/Screens/Ranking/Pages/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/Pages/ReplayDownloadButton.cs @@ -8,6 +8,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; using osu.Game.Scoring; +using osuTK; namespace osu.Game.Screens.Ranking.Pages { @@ -33,6 +34,7 @@ namespace osu.Game.Screens.Ranking.Pages public ReplayDownloadButton(ScoreInfo score) : base(score) { + Size = new Vector2(50, 30); } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Screens/Ranking/Pages/RetryButton.cs b/osu.Game/Screens/Ranking/Pages/RetryButton.cs new file mode 100644 index 0000000000..c4f63b265d --- /dev/null +++ b/osu.Game/Screens/Ranking/Pages/RetryButton.cs @@ -0,0 +1,59 @@ +// 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.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Screens; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Play; +using osuTK; + +namespace osu.Game.Screens.Ranking.Pages +{ + public class RetryButton : OsuAnimatedButton + { + private readonly Box background; + + [Resolved(canBeNull: true)] + private Player player { get; set; } + + public RetryButton() + { + Size = new Vector2(50, 30); + + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Depth = float.MaxValue + }, + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(13), + Icon = FontAwesome.Solid.ArrowCircleLeft, + }, + }; + + TooltipText = "Retry"; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Green; + + if (player != null) + Action = () => + { + player.Restart(); + player.MakeCurrent(); + }; + } + } +} diff --git a/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs index 56ae069a26..27cea99f1c 100644 --- a/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs +++ b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs @@ -169,12 +169,19 @@ namespace osu.Game.Screens.Ranking.Pages }, }, }, - new ReplayDownloadButton(score) + new FillFlowContainer { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Margin = new MarginPadding { Bottom = 10 }, - Size = new Vector2(50, 30), + Spacing = new Vector2(5), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new ReplayDownloadButton(score), + new RetryButton() + } }, }; From c532f7765737b6dfd9e7609eba0dde8fddc324ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 15:49:36 +0900 Subject: [PATCH 2050/2815] Add hold-to-retry support to results --- osu.Game/Screens/Ranking/Results.cs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index cac26b3dbf..a7ff1fe6a0 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -19,6 +19,7 @@ using osu.Game.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Sprites; using osu.Game.Scoring; +using osu.Game.Screens.Play; namespace osu.Game.Screens.Ranking { @@ -34,6 +35,9 @@ namespace osu.Game.Screens.Ranking private ResultModeTabControl modeChangeButtons; + [Resolved(canBeNull: true)] + private Player player { get; set; } + public override bool DisallowExternalBeatmapRulesetChanges => true; protected readonly ScoreInfo Score; @@ -100,10 +104,7 @@ namespace osu.Game.Screens.Ranking public override bool OnExiting(IScreen next) { - allCircles.ForEach(c => - { - c.ScaleTo(0, transition_time, Easing.OutSine); - }); + allCircles.ForEach(c => { c.ScaleTo(0, transition_time, Easing.OutSine); }); Background.ScaleTo(1f, transition_time / 4, Easing.OutQuint); @@ -253,7 +254,17 @@ namespace osu.Game.Screens.Ranking } } } - } + }, + new HotkeyRetryOverlay + { + Action = () => + { + if (!this.IsCurrentScreen()) return; + + player.Restart(); + player.MakeCurrent(); + }, + }, }; var pages = CreateResultPages(); From 14453da1d2b254af738c322f9fd9ba25bb078b0e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 15:51:10 +0900 Subject: [PATCH 2051/2815] Centralise MakeCurrent call --- osu.Game/Screens/Play/Player.cs | 2 ++ osu.Game/Screens/Ranking/Pages/RetryButton.cs | 7 +------ osu.Game/Screens/Ranking/Results.cs | 1 - 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 38068ec751..769688e829 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -319,6 +319,8 @@ namespace osu.Game.Screens.Play if (this.IsCurrentScreen()) performImmediateExit(); + else + this.MakeCurrent(); } private ScheduledDelegate completionProgressDelegate; diff --git a/osu.Game/Screens/Ranking/Pages/RetryButton.cs b/osu.Game/Screens/Ranking/Pages/RetryButton.cs index c4f63b265d..2a281224c1 100644 --- a/osu.Game/Screens/Ranking/Pages/RetryButton.cs +++ b/osu.Game/Screens/Ranking/Pages/RetryButton.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Play; @@ -49,11 +48,7 @@ namespace osu.Game.Screens.Ranking.Pages background.Colour = colours.Green; if (player != null) - Action = () => - { - player.Restart(); - player.MakeCurrent(); - }; + Action = () => player.Restart(); } } } diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index a7ff1fe6a0..ca2ee5adae 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -262,7 +262,6 @@ namespace osu.Game.Screens.Ranking if (!this.IsCurrentScreen()) return; player.Restart(); - player.MakeCurrent(); }, }, }; From daa0ebe2b57916f5408cdd5b3dbc1b0b6591e9f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 15:51:45 +0900 Subject: [PATCH 2052/2815] Add xmldoc --- osu.Game/Screens/Play/Player.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 769688e829..f97114f929 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -312,6 +312,10 @@ namespace osu.Game.Screens.Play this.Exit(); } + /// + /// Restart gameplay via a parent . + /// This can be called from a child screen in order to trigger the restart process. + /// public void Restart() { sampleRestart?.Play(); From 156d0ae9b91c1bd6ea03a7b753807b0bcb4260a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 16:08:44 +0900 Subject: [PATCH 2053/2815] Remove braces Co-Authored-By: Salman Ahmed --- osu.Game/Screens/Ranking/Results.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index ca2ee5adae..d89932e105 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -104,7 +104,7 @@ namespace osu.Game.Screens.Ranking public override bool OnExiting(IScreen next) { - allCircles.ForEach(c => { c.ScaleTo(0, transition_time, Easing.OutSine); }); + allCircles.ForEach(c => c.ScaleTo(0, transition_time, Easing.OutSine)); Background.ScaleTo(1f, transition_time / 4, Easing.OutQuint); From ddd58ea3da88aedcb7123cd7921f656f4b5c9498 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 19:40:45 +0900 Subject: [PATCH 2054/2815] Use LongRunningLoad on network load components --- osu.Game/Audio/PreviewTrack.cs | 1 + osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs | 1 + osu.Game/Users/Drawables/DrawableAvatar.cs | 1 + 3 files changed, 3 insertions(+) diff --git a/osu.Game/Audio/PreviewTrack.cs b/osu.Game/Audio/PreviewTrack.cs index 22ce7d4711..ff667249a7 100644 --- a/osu.Game/Audio/PreviewTrack.cs +++ b/osu.Game/Audio/PreviewTrack.cs @@ -9,6 +9,7 @@ using osu.Framework.Threading; namespace osu.Game.Audio { + [LongRunningLoad] public abstract class PreviewTrack : Component { /// diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs index d0db7765c2..5245bc319d 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Textures; namespace osu.Game.Beatmaps.Drawables { + [LongRunningLoad] public class BeatmapSetCover : Sprite { private readonly BeatmapSetInfo set; diff --git a/osu.Game/Users/Drawables/DrawableAvatar.cs b/osu.Game/Users/Drawables/DrawableAvatar.cs index ee3cf6331b..ee9af15863 100644 --- a/osu.Game/Users/Drawables/DrawableAvatar.cs +++ b/osu.Game/Users/Drawables/DrawableAvatar.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics.Containers; namespace osu.Game.Users.Drawables { + [LongRunningLoad] public class DrawableAvatar : Container { /// From d9a91100fb6448d33ec26896da0fd946d4b566dd Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Fri, 1 Nov 2019 22:46:13 +0700 Subject: [PATCH 2055/2815] Add tint to user's score container background --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 9387482f14..e5a8f565aa 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -21,6 +21,7 @@ using osu.Game.Users.Drawables; using osuTK; using osuTK.Graphics; using Humanizer; +using osu.Game.Online.API; namespace osu.Game.Online.Leaderboards { @@ -59,7 +60,7 @@ namespace osu.Game.Online.Leaderboards } [BackgroundDependencyLoader] - private void load() + private void load(IAPIProvider api, OsuColour colour) { var user = score.User; @@ -100,7 +101,7 @@ namespace osu.Game.Online.Leaderboards background = new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, + Colour = user.Id == api.LocalUser.Value.Id ? colour.YellowLight : Color4.Black, Alpha = background_alpha, }, }, From ce3b34a76878ee47c9c7ed93098e00c285c420e8 Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Fri, 1 Nov 2019 23:00:55 +0700 Subject: [PATCH 2056/2815] Fix UserTopScoreContainer is also tinted --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 6 ++++-- .../Screens/Select/Leaderboards/UserTopScoreContainer.cs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index e5a8f565aa..f8d7c0b697 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -38,6 +38,7 @@ namespace osu.Game.Online.Leaderboards private readonly ScoreInfo score; private readonly int rank; + private readonly bool canHighlighted; private Box background; private Container content; @@ -50,10 +51,11 @@ namespace osu.Game.Online.Leaderboards private List statisticsLabels; - public LeaderboardScore(ScoreInfo score, int rank) + public LeaderboardScore(ScoreInfo score, int rank, bool canHighlighted = true) { this.score = score; this.rank = rank; + this.canHighlighted = canHighlighted; RelativeSizeAxes = Axes.X; Height = HEIGHT; @@ -101,7 +103,7 @@ namespace osu.Game.Online.Leaderboards background = new Box { RelativeSizeAxes = Axes.Both, - Colour = user.Id == api.LocalUser.Value.Id ? colour.YellowLight : Color4.Black, + Colour = user.Id == api.LocalUser.Value.Id && canHighlighted ? colour.YellowLight : Color4.Black, Alpha = background_alpha, }, }, diff --git a/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs b/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs index da8f676cd0..a787eb5629 100644 --- a/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs +++ b/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (newScore == null) return; - LoadComponentAsync(new LeaderboardScore(newScore.Score, newScore.Position) + LoadComponentAsync(new LeaderboardScore(newScore.Score, newScore.Position, false) { Action = () => ScoreSelected?.Invoke(newScore.Score) }, drawableScore => From 8da15f68974258cf63c27a96ed92ed19195ae28c Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Fri, 1 Nov 2019 23:19:15 +0700 Subject: [PATCH 2057/2815] Fix all score are highlighted on local scope --- osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 337d46ecdd..3ef1fe5bc5 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -179,7 +179,7 @@ namespace osu.Game.Screens.Select.Leaderboards return req; } - protected override LeaderboardScore CreateDrawableScore(ScoreInfo model, int index) => new LeaderboardScore(model, index) + protected override LeaderboardScore CreateDrawableScore(ScoreInfo model, int index) => new LeaderboardScore(model, index, IsOnlineScope) { Action = () => ScoreSelected?.Invoke(model) }; From 2f703090ef1996c34eb4db392a894f015cdb0130 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 1 Nov 2019 21:49:50 +0300 Subject: [PATCH 2058/2815] Update API with latest web changes --- .../Visual/Online/TestSceneRankingsTables.cs | 6 +++--- .../Online/API/Requests/GetCountriesResponse.cs | 15 +++++++++++++++ .../API/Requests/GetCountryRankingsRequest.cs | 3 +-- .../Online/API/Requests/GetRankingsRequest.cs | 3 +-- .../Online/API/Requests/GetUserRankingsRequest.cs | 3 +-- osu.Game/Online/API/Requests/GetUsersResponse.cs | 2 +- 6 files changed, 22 insertions(+), 10 deletions(-) create mode 100644 osu.Game/Online/API/Requests/GetCountriesResponse.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs index 08a6da734f..49656900cc 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Online { var table = new CountriesTable(page) { - Rankings = rankings, + Rankings = rankings.Countries, }; LoadComponentAsync(table, t => @@ -104,7 +104,7 @@ namespace osu.Game.Tests.Visual.Online { var table = new PerformanceTable(page) { - Rankings = rankings, + Rankings = rankings.Users, }; LoadComponentAsync(table, t => @@ -131,7 +131,7 @@ namespace osu.Game.Tests.Visual.Online { var table = new ScoresTable(page) { - Rankings = rankings, + Rankings = rankings.Users, }; LoadComponentAsync(table, t => diff --git a/osu.Game/Online/API/Requests/GetCountriesResponse.cs b/osu.Game/Online/API/Requests/GetCountriesResponse.cs new file mode 100644 index 0000000000..7b65c6fadd --- /dev/null +++ b/osu.Game/Online/API/Requests/GetCountriesResponse.cs @@ -0,0 +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.Collections.Generic; +using Newtonsoft.Json; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Online.API.Requests +{ + public class GetCountriesResponse : ResponseWithCursor + { + [JsonProperty("ranking")] + public List Countries; + } +} diff --git a/osu.Game/Online/API/Requests/GetCountryRankingsRequest.cs b/osu.Game/Online/API/Requests/GetCountryRankingsRequest.cs index 3bd772732f..d8a1198627 100644 --- a/osu.Game/Online/API/Requests/GetCountryRankingsRequest.cs +++ b/osu.Game/Online/API/Requests/GetCountryRankingsRequest.cs @@ -1,12 +1,11 @@ // 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.Online.API.Requests.Responses; using osu.Game.Rulesets; namespace osu.Game.Online.API.Requests { - public class GetCountryRankingsRequest : GetRankingsRequest + public class GetCountryRankingsRequest : GetRankingsRequest { public GetCountryRankingsRequest(RulesetInfo ruleset, int page = 1) : base(ruleset, page) diff --git a/osu.Game/Online/API/Requests/GetRankingsRequest.cs b/osu.Game/Online/API/Requests/GetRankingsRequest.cs index efaaa2cb40..941691c4c1 100644 --- a/osu.Game/Online/API/Requests/GetRankingsRequest.cs +++ b/osu.Game/Online/API/Requests/GetRankingsRequest.cs @@ -3,11 +3,10 @@ using osu.Framework.IO.Network; using osu.Game.Rulesets; -using System.Collections.Generic; namespace osu.Game.Online.API.Requests { - public abstract class GetRankingsRequest : APIRequest> + public abstract class GetRankingsRequest : APIRequest { private readonly RulesetInfo ruleset; private readonly int page; diff --git a/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs b/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs index bbba6a210d..9c3eba9fdc 100644 --- a/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs @@ -2,12 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.IO.Network; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; namespace osu.Game.Online.API.Requests { - public class GetUserRankingsRequest : GetRankingsRequest + public class GetUserRankingsRequest : GetRankingsRequest { private readonly string country; private readonly UserRankingsType type; diff --git a/osu.Game/Online/API/Requests/GetUsersResponse.cs b/osu.Game/Online/API/Requests/GetUsersResponse.cs index 860785875a..c60bc71096 100644 --- a/osu.Game/Online/API/Requests/GetUsersResponse.cs +++ b/osu.Game/Online/API/Requests/GetUsersResponse.cs @@ -10,6 +10,6 @@ namespace osu.Game.Online.API.Requests public class GetUsersResponse : ResponseWithCursor { [JsonProperty("ranking")] - public List Users; + public List Users; } } From 5be7d439ae2f149772372bdce9356c1d88163764 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 2 Nov 2019 10:32:23 +0900 Subject: [PATCH 2059/2815] Add null check for nullable dependency --- osu.Game/Screens/Ranking/Results.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index d89932e105..3640197dad 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -261,7 +261,7 @@ namespace osu.Game.Screens.Ranking { if (!this.IsCurrentScreen()) return; - player.Restart(); + player?.Restart(); }, }, }; From fe3583b6eec4575a9bde01ef3454452071bdd161 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 3 Nov 2019 13:16:54 +0900 Subject: [PATCH 2060/2815] Move schedule call --- osu.Game/OsuGame.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index fb9a7f7965..1f823e6eba 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -220,14 +220,14 @@ namespace osu.Game /// This can be called from a non-thread-safe non-game-loaded state. /// /// The URL to load. - public void HandleLink(string url) => Schedule(() => HandleLink(MessageFormatter.GetLinkDetails(url))); + public void HandleLink(string url) => HandleLink(MessageFormatter.GetLinkDetails(url)); /// /// Handle a specific . /// This can be called from a non-thread-safe non-game-loaded state. /// /// The link to load. - public void HandleLink(LinkDetails link) + public void HandleLink(LinkDetails link) => Schedule(() => { switch (link.Action) { @@ -268,7 +268,7 @@ namespace osu.Game default: throw new NotImplementedException($"This {nameof(LinkAction)} ({link.Action.ToString()}) is missing an associated action."); } - } + }); public void OpenUrlExternally(string url) => waitForReady(() => externalLinkOpener, _ => { From 4dbdfcdd3c601a547887b9b563c06692feb6c53f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 3 Nov 2019 18:41:29 +0900 Subject: [PATCH 2061/2815] Change control to toggle rather than always select --- .../Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs | 2 +- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index bd6a905b38..e2ea6a12d7 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public Action RequestSelection; public Action ControlPointsChanged; - public readonly Bindable IsSelected = new Bindable(); + public readonly BindableBool IsSelected = new BindableBool(); public readonly int Index; private readonly Slider slider; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index bbe771d8b0..b70c11427a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private void selectPiece(int index) { if (inputManager.CurrentState.Keyboard.ControlPressed) - Pieces[index].IsSelected.Value = true; + Pieces[index].IsSelected.Toggle(); else { foreach (var piece in Pieces) From bcf8a6d514d6c44df4f3bcd4fca97a51464c7297 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 3 Nov 2019 19:59:37 +0900 Subject: [PATCH 2062/2815] Fix slider creation regressing with path selection changes --- .../Sliders/Components/PathControlPointPiece.cs | 13 +++++++++---- .../Components/PathControlPointVisualiser.cs | 14 ++++++++++---- .../Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- .../Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 +- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index e2ea6a12d7..cfcd266c7f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -128,19 +128,24 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components protected override bool OnMouseDown(MouseDownEvent e) { isClicked = true; - return true; + return false; } protected override bool OnMouseUp(MouseUpEvent e) { isClicked = false; - return true; + return false; } protected override bool OnClick(ClickEvent e) { - RequestSelection?.Invoke(Index); - return true; + if (RequestSelection != null) + { + RequestSelection.Invoke(Index); + return true; + } + + return false; } protected override bool OnDragStart(DragStartEvent e) => true; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index b70c11427a..c0fc5ccb0a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -17,12 +17,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components internal readonly Container Pieces; private readonly Slider slider; + private readonly bool allowSelection; private InputManager inputManager; - public PathControlPointVisualiser(Slider slider) + public PathControlPointVisualiser(Slider slider, bool allowSelection) { this.slider = slider; + this.allowSelection = allowSelection; RelativeSizeAxes = Axes.Both; @@ -42,11 +44,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components while (slider.Path.ControlPoints.Length > Pieces.Count) { - Pieces.Add(new PathControlPointPiece(slider, Pieces.Count) + var piece = new PathControlPointPiece(slider, Pieces.Count) { ControlPointsChanged = c => ControlPointsChanged?.Invoke(c), - RequestSelection = selectPiece - }); + }; + + if (allowSelection) + piece.RequestSelection = selectPiece; + + Pieces.Add(piece); } while (slider.Path.ControlPoints.Length < Pieces.Count) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 6f5309c2c2..9c0afada29 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders bodyPiece = new SliderBodyPiece(), headCirclePiece = new HitCirclePiece(), tailCirclePiece = new HitCirclePiece(), - new PathControlPointVisualiser(HitObject) { ControlPointsChanged = _ => updateSlider() }, + new PathControlPointVisualiser(HitObject, false) { ControlPointsChanged = _ => updateSlider() }, }; setState(PlacementState.Initial); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index f612ba9dfc..25362820a3 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders BodyPiece = new SliderBodyPiece(), HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start), TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End), - ControlPointVisualiser = new PathControlPointVisualiser(sliderObject) { ControlPointsChanged = onNewControlPoints }, + ControlPointVisualiser = new PathControlPointVisualiser(sliderObject, true) { ControlPointsChanged = onNewControlPoints }, }; } From 48385dbdfe2dfbe685fbd03a8ca09206a794c4e1 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 3 Nov 2019 14:31:23 +0300 Subject: [PATCH 2063/2815] Layout adjustments --- .../Overlays/Rankings/Tables/CountriesTable.cs | 12 ++++++------ .../Overlays/Rankings/Tables/PerformanceTable.cs | 12 ++++++------ osu.Game/Overlays/Rankings/Tables/RankingsTable.cs | 1 + osu.Game/Overlays/Rankings/Tables/ScoresTable.cs | 14 +++++++------- 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs index 8e0e55595d..386225a606 100644 --- a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs @@ -23,12 +23,12 @@ namespace osu.Game.Overlays.Rankings.Tables { new TableColumn("", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 50)), // place new TableColumn("", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed)), // flag and country name - new TableColumn("Active Users", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 100)), - new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 100)), - new TableColumn("Ranked Score", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 100)), - new TableColumn("Avg. Score", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 100)), - new TableColumn("Performance", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 80)), - new TableColumn("Avg. Perf.", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 50)), + new TableColumn("Active Users", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("Ranked Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("Avg. Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("Performance", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("Avg. Perf.", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), }; protected override Drawable[] CreateContent(int index, APICountryRankings item) => new Drawable[] diff --git a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs index 2d582f4321..11357a82a1 100644 --- a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs @@ -24,12 +24,12 @@ namespace osu.Game.Overlays.Rankings.Tables { new TableColumn("", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 50)), // place new TableColumn("", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed)), // flag and username - new TableColumn("Accuracy", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 80)), - new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 80)), - new TableColumn("Performance", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 80)), - new TableColumn("SS", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 50)), - new TableColumn("S", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 50)), - new TableColumn("A", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 50)), + new TableColumn("Accuracy", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("Performance", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("SS", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("S", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("A", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), }; protected override Drawable[] CreateContent(int index, APIUserRankings item) diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index a04e8be9d0..16dca06c44 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -91,6 +91,7 @@ namespace osu.Game.Overlays.Rankings.Tables public RowText() { Font = OsuFont.GetFont(size: TEXT_SIZE); + Margin = new MarginPadding { Horizontal = 10 }; } } diff --git a/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs b/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs index 5d572a6af0..0a8ec50e30 100644 --- a/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs @@ -24,13 +24,13 @@ namespace osu.Game.Overlays.Rankings.Tables { new TableColumn("", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 50)), // place new TableColumn("", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed)), // flag and username - new TableColumn("Accuracy", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 80)), - new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 80)), - new TableColumn("Total Score", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 100)), - new TableColumn("Ranked Score", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 100)), - new TableColumn("SS", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 70)), - new TableColumn("S", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 70)), - new TableColumn("A", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 70)), + new TableColumn("Accuracy", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("Total Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("Ranked Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("SS", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("S", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("A", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), }; protected override Drawable[] CreateContent(int index, APIUserRankings item) From 5761cc673f09c2826f909164e9bf7767664bab50 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 10 May 2019 19:44:22 +0200 Subject: [PATCH 2064/2815] implement CatchModRelax --- osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs index 0454bc969d..47e32b1292 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs @@ -1,12 +1,48 @@ // 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.Containers; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; +using osuTK; +using System; -namespace osu.Game.Rulesets.Catch.Mods -{ - public class CatchModRelax : ModRelax - { +namespace osu.Game.Rulesets.Catch.Mods { + public class CatchModRelax : ModRelax, IApplicableToDrawableRuleset { public override string Description => @"Use the mouse to control the catcher."; + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) => + (drawableRuleset.Playfield.Parent as Container).Add(new CatchModRelaxHelper(drawableRuleset.Playfield as CatchPlayfield)); + + private class CatchModRelaxHelper : Drawable, IKeyBindingHandler, IRequireHighFrequencyMousePosition { + private CatcherArea.Catcher catcher; + + public CatchModRelaxHelper(CatchPlayfield catchPlayfield) { + catcher = catchPlayfield.CatcherArea.MovableCatcher; + RelativeSizeAxes = Axes.Both; + } + + //disable keyboard controls + public bool OnPressed(CatchAction action) => true; + public bool OnReleased(CatchAction action) => true; + + protected override bool OnMouseMove(MouseMoveEvent e) { + //lock catcher to mouse position horizontally + catcher.X = e.MousePosition.X / DrawSize.X; + + //make Yuzu face the direction he's moving + var direction = Math.Sign(e.Delta.X); + if (direction != 0) + catcher.Scale = new Vector2(Math.Abs(catcher.Scale.X) * direction, catcher.Scale.Y); + + return base.OnMouseMove(e); + } + } } } From a842d727c7905af23bb411121ec513292fe240ae Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 11 May 2019 10:03:59 +0200 Subject: [PATCH 2065/2815] formatting --- osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs index 47e32b1292..5fa22f8bdb 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs @@ -13,17 +13,21 @@ using osu.Game.Rulesets.UI; using osuTK; using System; -namespace osu.Game.Rulesets.Catch.Mods { - public class CatchModRelax : ModRelax, IApplicableToDrawableRuleset { +namespace osu.Game.Rulesets.Catch.Mods +{ + public class CatchModRelax : ModRelax, IApplicableToDrawableRuleset + { public override string Description => @"Use the mouse to control the catcher."; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) => (drawableRuleset.Playfield.Parent as Container).Add(new CatchModRelaxHelper(drawableRuleset.Playfield as CatchPlayfield)); - private class CatchModRelaxHelper : Drawable, IKeyBindingHandler, IRequireHighFrequencyMousePosition { + private class CatchModRelaxHelper : Drawable, IKeyBindingHandler, IRequireHighFrequencyMousePosition + { private CatcherArea.Catcher catcher; - public CatchModRelaxHelper(CatchPlayfield catchPlayfield) { + public CatchModRelaxHelper(CatchPlayfield catchPlayfield) + { catcher = catchPlayfield.CatcherArea.MovableCatcher; RelativeSizeAxes = Axes.Both; } @@ -32,7 +36,8 @@ namespace osu.Game.Rulesets.Catch.Mods { public bool OnPressed(CatchAction action) => true; public bool OnReleased(CatchAction action) => true; - protected override bool OnMouseMove(MouseMoveEvent e) { + protected override bool OnMouseMove(MouseMoveEvent e) + { //lock catcher to mouse position horizontally catcher.X = e.MousePosition.X / DrawSize.X; From a8fab4cba1deacc0d154546f2d6fd7bdc3457056 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 11 May 2019 10:26:24 +0200 Subject: [PATCH 2066/2815] make catcher field readonly --- osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs index 5fa22f8bdb..9852e753c5 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Mods private class CatchModRelaxHelper : Drawable, IKeyBindingHandler, IRequireHighFrequencyMousePosition { - private CatcherArea.Catcher catcher; + private readonly CatcherArea.Catcher catcher; public CatchModRelaxHelper(CatchPlayfield catchPlayfield) { From 750e3c4aafa1d65e8aed975505fef3ded1c2eeee Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 11 May 2019 11:25:29 +0200 Subject: [PATCH 2067/2815] replace 'as' with direct cast to avoid possible nullref --- osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs index 9852e753c5..062c0f8b26 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Catch.Mods public override string Description => @"Use the mouse to control the catcher."; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) => - (drawableRuleset.Playfield.Parent as Container).Add(new CatchModRelaxHelper(drawableRuleset.Playfield as CatchPlayfield)); + ((Container)drawableRuleset.Playfield.Parent).Add(new CatchModRelaxHelper(drawableRuleset.Playfield as CatchPlayfield)); private class CatchModRelaxHelper : Drawable, IKeyBindingHandler, IRequireHighFrequencyMousePosition { From 8e248d5cfa6f0bd1560f67f2ceeb94a60734800c Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 12 May 2019 20:20:11 +0200 Subject: [PATCH 2068/2815] add invisible cursor for ctb --- osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs | 14 ++++++++++++++ osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 3 +++ 2 files changed, 17 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs diff --git a/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs new file mode 100644 index 0000000000..073f2e05b2 --- /dev/null +++ b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs @@ -0,0 +1,14 @@ +using osu.Framework.Graphics; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Catch.UI { + class CatchCursorContainer : GameplayCursorContainer + { + protected override Drawable CreateCursor() => new InvisibleCursor(); + + private class InvisibleCursor : Drawable + { + + } + } +} diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index b6d8cf9cbe..7741096da2 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -9,6 +9,7 @@ using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Catch.UI @@ -19,6 +20,8 @@ namespace osu.Game.Rulesets.Catch.UI internal readonly CatcherArea CatcherArea; + protected override GameplayCursorContainer CreateCursor() => new CatchCursorContainer(); + public CatchPlayfield(BeatmapDifficulty difficulty, Func> createDrawableRepresentation) { Container explodingFruitContainer; From bd5cb86b1546e9c9c7fe6d64f373f5b9766e0500 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 12 May 2019 21:01:51 +0200 Subject: [PATCH 2069/2815] add license header --- osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs index 073f2e05b2..072c5d4fdf 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs @@ -1,4 +1,7 @@ -using osu.Framework.Graphics; +// 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.Rulesets.UI; namespace osu.Game.Rulesets.Catch.UI { From 883d5ed58edfb1922df3f00e42f5821efdeff15b Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 12 May 2019 21:14:27 +0200 Subject: [PATCH 2070/2815] formatting --- osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs index 072c5d4fdf..642e0bb9de 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs @@ -4,7 +4,8 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.UI; -namespace osu.Game.Rulesets.Catch.UI { +namespace osu.Game.Rulesets.Catch.UI +{ class CatchCursorContainer : GameplayCursorContainer { protected override Drawable CreateCursor() => new InvisibleCursor(); From 2a0db8aeac74ba0dc41d7ee53c3cf96c10be8829 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 12 May 2019 21:17:06 +0200 Subject: [PATCH 2071/2815] add missing access modifier --- osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs index 642e0bb9de..d4ac4b2f2e 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs @@ -6,7 +6,7 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Catch.UI { - class CatchCursorContainer : GameplayCursorContainer + public class CatchCursorContainer : GameplayCursorContainer { protected override Drawable CreateCursor() => new InvisibleCursor(); From 9570f7c04bc8a4653ee30f82d1abbcb11c0ca661 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 12 May 2019 21:23:59 +0200 Subject: [PATCH 2072/2815] more formatting --- osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs index d4ac4b2f2e..03bfad7e8c 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs @@ -10,9 +10,6 @@ namespace osu.Game.Rulesets.Catch.UI { protected override Drawable CreateCursor() => new InvisibleCursor(); - private class InvisibleCursor : Drawable - { - - } + private class InvisibleCursor : Drawable { } } } From c180a71afe4e788a6d66043df3b9267ad2dec2ab Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 3 Nov 2019 15:57:52 +0100 Subject: [PATCH 2073/2815] even more formatting --- osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs index 03bfad7e8c..c596217196 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs @@ -10,6 +10,7 @@ namespace osu.Game.Rulesets.Catch.UI { protected override Drawable CreateCursor() => new InvisibleCursor(); - private class InvisibleCursor : Drawable { } + private class InvisibleCursor : Drawable { + } } } From 131809838f7cab4413713cb7f2c99a2af9f590aa Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 3 Nov 2019 16:22:07 +0100 Subject: [PATCH 2074/2815] pleasing CI --- osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs index c596217196..163718f202 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs @@ -10,7 +10,8 @@ namespace osu.Game.Rulesets.Catch.UI { protected override Drawable CreateCursor() => new InvisibleCursor(); - private class InvisibleCursor : Drawable { + private class InvisibleCursor : Drawable + { } } } From fe23b9a262d90ad9926d754e9d6bc4df5dd3ca42 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 3 Nov 2019 07:32:47 -0800 Subject: [PATCH 2075/2815] Fix mod section overflowing mod select overlay at higher ui scale --- osu.Game/Overlays/Mods/ModSection.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index dedd397fa5..35a94eb43f 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -158,7 +158,8 @@ namespace osu.Game.Overlays.Mods }, ButtonsContainer = new FillFlowContainer { - AutoSizeAxes = Axes.Both, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, Spacing = new Vector2(50f, 0f), From 1a7b803d4db52d824eb7899ca0e479e2cebed1a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Nov 2019 08:39:51 +0900 Subject: [PATCH 2076/2815] Change colour to green to match web implementation --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index f8d7c0b697..623db07938 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -38,7 +38,7 @@ namespace osu.Game.Online.Leaderboards private readonly ScoreInfo score; private readonly int rank; - private readonly bool canHighlighted; + private readonly bool allowHighlight; private Box background; private Container content; @@ -51,11 +51,11 @@ namespace osu.Game.Online.Leaderboards private List statisticsLabels; - public LeaderboardScore(ScoreInfo score, int rank, bool canHighlighted = true) + public LeaderboardScore(ScoreInfo score, int rank, bool allowHighlight = true) { this.score = score; this.rank = rank; - this.canHighlighted = canHighlighted; + this.allowHighlight = allowHighlight; RelativeSizeAxes = Axes.X; Height = HEIGHT; @@ -103,7 +103,7 @@ namespace osu.Game.Online.Leaderboards background = new Box { RelativeSizeAxes = Axes.Both, - Colour = user.Id == api.LocalUser.Value.Id && canHighlighted ? colour.YellowLight : Color4.Black, + Colour = user.Id == api.LocalUser.Value.Id && allowHighlight ? colour.Green : Color4.Black, Alpha = background_alpha, }, }, From 3ec9580ba8b6724a2ae418933690e5a305444b4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Nov 2019 01:52:26 +0100 Subject: [PATCH 2077/2815] Add test for retry overlay presence Add visual tests checking presence (or lack thereof) of the hold-to-retry overlay in the results screen. --- .../Visual/Gameplay/TestSceneResults.cs | 98 +++++++++++++++---- 1 file changed, 80 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs index bf26892539..7790126db5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs @@ -3,11 +3,16 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Screens; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking.Pages; @@ -27,7 +32,8 @@ namespace osu.Game.Tests.Visual.Gameplay typeof(ScoreResultsPage), typeof(RetryButton), typeof(ReplayDownloadButton), - typeof(LocalLeaderboardPage) + typeof(LocalLeaderboardPage), + typeof(TestPlayer) }; [BackgroundDependencyLoader] @@ -43,26 +49,82 @@ namespace osu.Game.Tests.Visual.Gameplay var beatmapInfo = beatmaps.QueryBeatmap(b => b.RulesetID == 0); if (beatmapInfo != null) Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); + } - LoadScreen(new SoloResults(new ScoreInfo + private TestSoloResults createResultsScreen() => new TestSoloResults(new ScoreInfo + { + TotalScore = 2845370, + Accuracy = 0.98, + MaxCombo = 123, + Rank = ScoreRank.A, + Date = DateTimeOffset.Now, + Statistics = new Dictionary { - TotalScore = 2845370, - Accuracy = 0.98, - MaxCombo = 123, - Rank = ScoreRank.A, - Date = DateTimeOffset.Now, - Statistics = new Dictionary + { HitResult.Great, 50 }, + { HitResult.Good, 20 }, + { HitResult.Meh, 50 }, + { HitResult.Miss, 1 } + }, + User = new User + { + Username = "peppy", + } + }); + + [Test] + public void ResultsWithoutPlayer() + { + TestSoloResults screen = null; + + AddStep("load results", () => Child = new OsuScreenStack(screen = createResultsScreen()) + { + RelativeSizeAxes = Axes.Both + }); + AddUntilStep("wait for loaded", () => screen.IsLoaded); + AddAssert("retry overlay not present", () => screen.RetryOverlay == null); + } + + [Test] + public void ResultsWithPlayer() + { + TestSoloResults screen = null; + + AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen())); + AddUntilStep("wait for loaded", () => screen.IsLoaded); + AddAssert("retry overlay present", () => screen.RetryOverlay != null); + } + + private class TestResultsContainer : Container + { + [Cached(typeof(Player))] + private readonly Player player = new TestPlayer(); + + public TestResultsContainer(IScreen screen) + { + RelativeSizeAxes = Axes.Both; + + InternalChild = new OsuScreenStack(screen) { - { HitResult.Great, 50 }, - { HitResult.Good, 20 }, - { HitResult.Meh, 50 }, - { HitResult.Miss, 1 } - }, - User = new User - { - Username = "peppy", - } - })); + RelativeSizeAxes = Axes.Both, + }; + } + } + + private class TestSoloResults : SoloResults + { + public HotkeyRetryOverlay RetryOverlay; + + public TestSoloResults(ScoreInfo score) + : base(score) + { + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + RetryOverlay = InternalChildren.OfType().SingleOrDefault(); + } } } } From 539f8ad6dd952dd40943db2eea26d1c400fc74f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Nov 2019 01:57:31 +0100 Subject: [PATCH 2078/2815] Remove overlay when viewing leaderboard scores Do not add the hold-to-retry hotkey overlay if the user has navigated to the results screen from the leaderboard and not from gameplay. --- osu.Game/Screens/Ranking/Results.cs | 269 ++++++++++++++-------------- 1 file changed, 135 insertions(+), 134 deletions(-) diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index 3640197dad..d063988b3f 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -116,146 +116,147 @@ namespace osu.Game.Screens.Ranking [BackgroundDependencyLoader] private void load(OsuColour colours) { - InternalChildren = new Drawable[] + InternalChild = new AspectContainer { - new AspectContainer + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Height = overscan, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Y, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Height = overscan, - Children = new Drawable[] + circleOuterBackground = new CircularContainer { - circleOuterBackground = new CircularContainer + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Masking = true, - Children = new Drawable[] + new Box { - new Box - { - Alpha = 0.2f, - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - } - } - }, - circleOuter = new CircularContainer - { - Size = new Vector2(circle_outer_scale), - EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.Black.Opacity(0.4f), - Type = EdgeEffectType.Shadow, - Radius = 15, - }, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - }, - backgroundParallax = new ParallaxContainer - { - RelativeSizeAxes = Axes.Both, - ParallaxAmount = 0.01f, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Children = new Drawable[] - { - new Sprite - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.2f, - Texture = Beatmap.Value.Background, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - FillMode = FillMode.Fill - } - } - }, - modeChangeButtons = new ResultModeTabControl - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.X, - Height = 50, - Margin = new MarginPadding { Bottom = 110 }, - }, - new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.BottomCentre, - Text = $"{Score.MaxCombo}x", - RelativePositionAxes = Axes.X, - Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 40), - X = 0.1f, - Colour = colours.BlueDarker, - }, - new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.TopCentre, - Text = "max combo", - Font = OsuFont.GetFont(size: 20), - RelativePositionAxes = Axes.X, - X = 0.1f, - Colour = colours.Gray6, - }, - new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.BottomCentre, - Text = $"{Score.Accuracy:P2}", - Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 40), - RelativePositionAxes = Axes.X, - X = 0.9f, - Colour = colours.BlueDarker, - }, - new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.TopCentre, - Text = "accuracy", - Font = OsuFont.GetFont(size: 20), - RelativePositionAxes = Axes.X, - X = 0.9f, - Colour = colours.Gray6, - }, - } - }, - circleInner = new CircularContainer - { - Size = new Vector2(0.6f), - EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.Black.Opacity(0.4f), - Type = EdgeEffectType.Shadow, - Radius = 15, - }, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - }, + Alpha = 0.2f, + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, } } + }, + circleOuter = new CircularContainer + { + Size = new Vector2(circle_outer_scale), + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.4f), + Type = EdgeEffectType.Shadow, + Radius = 15, + }, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }, + backgroundParallax = new ParallaxContainer + { + RelativeSizeAxes = Axes.Both, + ParallaxAmount = 0.01f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new Sprite + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.2f, + Texture = Beatmap.Value.Background, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fill + } + } + }, + modeChangeButtons = new ResultModeTabControl + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.X, + Height = 50, + Margin = new MarginPadding { Bottom = 110 }, + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.BottomCentre, + Text = $"{Score.MaxCombo}x", + RelativePositionAxes = Axes.X, + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 40), + X = 0.1f, + Colour = colours.BlueDarker, + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.TopCentre, + Text = "max combo", + Font = OsuFont.GetFont(size: 20), + RelativePositionAxes = Axes.X, + X = 0.1f, + Colour = colours.Gray6, + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.BottomCentre, + Text = $"{Score.Accuracy:P2}", + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 40), + RelativePositionAxes = Axes.X, + X = 0.9f, + Colour = colours.BlueDarker, + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.TopCentre, + Text = "accuracy", + Font = OsuFont.GetFont(size: 20), + RelativePositionAxes = Axes.X, + X = 0.9f, + Colour = colours.Gray6, + }, + } + }, + circleInner = new CircularContainer + { + Size = new Vector2(0.6f), + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.4f), + Type = EdgeEffectType.Shadow, + Radius = 15, + }, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }, + } } - }, - new HotkeyRetryOverlay + } + }; + + if (player != null) + { + AddInternal(new HotkeyRetryOverlay { Action = () => { @@ -263,8 +264,8 @@ namespace osu.Game.Screens.Ranking player?.Restart(); }, - }, - }; + }); + } var pages = CreateResultPages(); From 2bc6932567548187d6696f4900ae98cfd1eaddc9 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 5 Nov 2019 00:55:55 +0800 Subject: [PATCH 2079/2815] make interface mod applicable --- osu.Game/Rulesets/Mods/IModHasSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/IModHasSettings.cs b/osu.Game/Rulesets/Mods/IModHasSettings.cs index 004279f71d..b5058de82b 100644 --- a/osu.Game/Rulesets/Mods/IModHasSettings.cs +++ b/osu.Game/Rulesets/Mods/IModHasSettings.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Mods /// /// An interface for mods that allows user control over it's properties. /// - public interface IModHasSettings + public interface IModHasSettings : IApplicableMod { Drawable[] CreateControls(); } From a92b32f6dc94a164e5a2502e5f070240673f555e Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 5 Nov 2019 00:56:09 +0800 Subject: [PATCH 2080/2815] add basic tests --- .../TestSceneModCustomizationSettings.cs | 108 ++++++++++++++++++ osu.Game/Overlays/Mods/ModSelectOverlay.cs | 8 +- 2 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs new file mode 100644 index 0000000000..ec5b3c1e16 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs @@ -0,0 +1,108 @@ +// 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.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Mods; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneModCustomizationSettings : OsuTestScene + { + private TestModSelectOverlay modSelect; + + [BackgroundDependencyLoader] + private void load() + { + Add(modSelect = new TestModSelectOverlay + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + }); + + var testMod = new TestModCustomizable(); + + AddStep("open", modSelect.Show); + AddAssert("button disabled", () => !modSelect.CustomizeButton.Enabled.Value); + AddStep("select mod", () => modSelect.SelectMod(testMod)); + AddAssert("button enabled", () => modSelect.CustomizeButton.Enabled.Value); + AddStep("open customization", () => modSelect.CustomizeButton.Click()); + AddAssert("controls exist", () => modSelect.GetControlSection(testMod) != null); + AddStep("deselect mod", () => modSelect.SelectMod(testMod)); + AddAssert("controls hidden", () => modSelect.ModSettingsContainer.Alpha == 0); + } + + private class TestModSelectOverlay : ModSelectOverlay + { + public new Container ModSettingsContainer => base.ModSettingsContainer; + public new TriangleButton CustomizeButton => base.CustomizeButton; + + public void SelectMod(Mod mod) => + ModSectionsContainer.Children.Single((s) => s.ModType == mod.Type) + .ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())).SelectNext(1); + + public ModControlSection GetControlSection(Mod mod) => + ModSettingsContent.Children.FirstOrDefault((s) => s.Mod == mod); + + protected override void LoadComplete() + { + base.LoadComplete(); + + foreach (var section in ModSectionsContainer) + if (section.ModType == ModType.Conversion) + section.Mods = new Mod[] { new TestModCustomizable() }; + else + section.Mods = new Mod[] { }; + } + } + + private class TestModCustomizable : Mod, IModHasSettings + { + public override string Name => "Customizable Mod"; + + public override string Acronym => "CM"; + + public override double ScoreMultiplier => 1.0; + + public override ModType Type => ModType.Conversion; + + public readonly BindableFloat sliderBindable = new BindableFloat + { + MinValue = 0, + MaxValue = 10, + }; + + public readonly BindableBool tickBindable = new BindableBool(); + + public Drawable[] CreateControls() + { + BindableFloat sliderControl = new BindableFloat(); + BindableBool tickControl = new BindableBool(); + + sliderControl.BindTo(sliderBindable); + tickControl.BindTo(tickBindable); + + return new Drawable[] + { + new SettingsSlider + { + LabelText = "Slider", + Bindable = sliderControl + }, + new SettingsCheckbox + { + LabelText = "Checkbox", + Bindable = tickControl + } + }; + } + } + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index a344bcc254..fc5ed1b3f3 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -331,6 +331,7 @@ namespace osu.Game.Overlays.Mods SelectedMods.ValueChanged += updateModSettings; Ruleset.ValueChanged += _ => ModSettingsContent.Clear(); + CustomizeButton.Enabled.Value = false; sampleOn = audio.Samples.Get(@"UI/check-on"); sampleOff = audio.Samples.Get(@"UI/check-off"); @@ -344,7 +345,12 @@ namespace osu.Game.Overlays.Mods if (added is IModHasSettings) ModSettingsContent.Add(new ModControlSection(added)); else if (removed is IModHasSettings) - ModSettingsContent.Remove(ModSettingsContent.Children.Where(section => section.Mod == removed).FirstOrDefault()); + ModSettingsContent.Remove(ModSettingsContent.Children.Where(section => section.Mod == removed).Single()); + + CustomizeButton.Enabled.Value = ModSettingsContent.Children.Count > 0; + + if (ModSettingsContainer.Alpha == 1 && ModSettingsContent.Children.Count == 0) + ModSettingsContainer.Alpha = 0; } public void DeselectAll() From 66253a0bd4142166cb7164bb0d9aca4712b9b6e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Nov 2019 02:21:50 +0900 Subject: [PATCH 2081/2815] Handle selection on mouse down instead of click This is how other elements work, and feels better with drag preselection --- .../Components/PathControlPointPiece.cs | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index cfcd266c7f..0353ba241c 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -29,8 +29,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private readonly Container marker; private readonly Drawable markerRing; - private bool isClicked; - [Resolved] private OsuColour colours { get; set; } @@ -101,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components markerRing.Alpha = IsSelected.Value ? 1 : 0; Color4 colour = isSegmentSeparator ? colours.Red : colours.Yellow; - if (IsHovered || isClicked || IsSelected.Value) + if (IsHovered || IsSelected.Value) colour = Color4.White; marker.Colour = colour; } @@ -126,18 +124,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => marker.ReceivePositionalInputAt(screenSpacePos); protected override bool OnMouseDown(MouseDownEvent e) - { - isClicked = true; - return false; - } - - protected override bool OnMouseUp(MouseUpEvent e) - { - isClicked = false; - return false; - } - - protected override bool OnClick(ClickEvent e) { if (RequestSelection != null) { @@ -148,6 +134,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return false; } + protected override bool OnMouseUp(MouseUpEvent e) => RequestSelection != null; + + protected override bool OnClick(ClickEvent e) => RequestSelection != null; + protected override bool OnDragStart(DragStartEvent e) => true; protected override bool OnDrag(DragEvent e) From 64d900b38770da44090c9124fb4ba1e83c39033c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 11:33:37 +0900 Subject: [PATCH 2082/2815] Disable input for non-selected blueprints --- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 0701513933..2923411ce1 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -82,6 +82,9 @@ namespace osu.Game.Rulesets.Edit } } + // When not selected, input is only required for the blueprint itself to receive IsHovering + protected override bool ShouldBeConsideredForInput(Drawable child) => State == SelectionState.Selected; + /// /// Selects this , causing it to become visible. /// From c8beb5296f512d8c932c248eeea651675902c8d1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 13:26:44 +0900 Subject: [PATCH 2083/2815] Use PlatformAction.Delete instead of Delete key --- .../Components/PathControlPointVisualiser.cs | 11 +++++++---- .../Edit/Compose/Components/SelectionHandler.cs | 17 +++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 1118cae344..f2a392feee 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose; @@ -15,7 +16,7 @@ using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { - public class PathControlPointVisualiser : CompositeDrawable + public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler { public Action ControlPointsChanged; @@ -84,11 +85,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } } - protected override bool OnKeyDown(KeyDownEvent e) + public bool OnPressed(PlatformAction action) { - switch (e.Key) + switch (action.ActionMethod) { - case Key.Delete: + case PlatformActionMethod.Delete: var newControlPoints = new List(); foreach (var piece in Pieces) @@ -127,5 +128,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return false; } + + public bool OnReleased(PlatformAction action) => action.ActionMethod == PlatformActionMethod.Delete; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index d7821eff07..664b9e3a8c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -8,6 +8,8 @@ using osu.Framework.Allocation; 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.States; using osu.Game.Graphics; @@ -22,7 +24,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// A component which outlines s and handles movement of selections. /// - public class SelectionHandler : CompositeDrawable + public class SelectionHandler : CompositeDrawable, IKeyBindingHandler { public const float BORDER_RADIUS = 2; @@ -72,22 +74,21 @@ namespace osu.Game.Screens.Edit.Compose.Components { } - protected override bool OnKeyDown(KeyDownEvent e) + public bool OnPressed(PlatformAction action) { - if (e.Repeat) - return base.OnKeyDown(e); - - switch (e.Key) + switch (action.ActionMethod) { - case Key.Delete: + case PlatformActionMethod.Delete: foreach (var h in selectedBlueprints.ToList()) placementHandler.Delete(h.DrawableObject.HitObject); return true; } - return base.OnKeyDown(e); + return false; } + public bool OnReleased(PlatformAction action) => action.ActionMethod == PlatformActionMethod.Delete; + #endregion #region Selection Handling From 7c20a589f2dac47c55bcad3b032d9e04fc99fc6d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 13:56:21 +0900 Subject: [PATCH 2084/2815] Remove unused usings --- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 1 - osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 2 -- 2 files changed, 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index f2a392feee..6962736157 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -12,7 +12,6 @@ using osu.Framework.Input.Events; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose; using osuTK; -using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 664b9e3a8c..1722476e53 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -10,14 +10,12 @@ 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.States; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osuTK; -using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { From fe374eabe04b3c51efa732c88a71ea6802b322ed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Nov 2019 15:47:01 +0900 Subject: [PATCH 2085/2815] 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 8b31be3f12..78c7583923 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -62,6 +62,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 0cb09d9b14..42b930b1af 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 719aced705..dfc9c2d60a 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,8 +118,8 @@ - - + + From 44d079167676370c41e2a9677b8a8d2f4dce3341 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Nov 2019 16:26:31 +0900 Subject: [PATCH 2086/2815] Update button usage --- osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs | 2 +- osu.Game/Graphics/UserInterface/OsuButton.cs | 2 +- osu.Game/Overlays/Settings/SidebarButton.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs index a8c2362910..b6b2b2f92d 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.Editor } } - private class StartStopButton : Button + private class StartStopButton : BasicButton { private IAdjustableClock adjustableClock; private bool started; diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index 4124d2ad58..319c3dd0b4 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -17,7 +17,7 @@ namespace osu.Game.Graphics.UserInterface /// /// A button with added default sound effects. /// - public class OsuButton : Button + public class OsuButton : BasicButton { private Box hover; diff --git a/osu.Game/Overlays/Settings/SidebarButton.cs b/osu.Game/Overlays/Settings/SidebarButton.cs index a94f76e7af..cdb4dc463a 100644 --- a/osu.Game/Overlays/Settings/SidebarButton.cs +++ b/osu.Game/Overlays/Settings/SidebarButton.cs @@ -17,7 +17,7 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings { - public class SidebarButton : Button + public class SidebarButton : BasicButton { private readonly SpriteIcon drawableIcon; private readonly SpriteText headerText; From 0db34a47f8314f1d46c48f44e04e31d8a80a9077 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 17:28:42 +0900 Subject: [PATCH 2087/2815] Fix selecting underneath selected blueprints --- .../Compose/Components/BlueprintContainer.cs | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index a145dea6af..7a3a303ff3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -254,15 +254,31 @@ namespace osu.Game.Screens.Edit.Compose.Components { Debug.Assert(!clickSelectionBegan); - foreach (SelectionBlueprint blueprint in selectionBlueprints.AliveBlueprints) + bool hoveringSelected = false; + + // Make sure any already-selected blueprints aren't being hovered over + foreach (SelectionBlueprint selected in selectionHandler.SelectedBlueprints) { - if (blueprint.IsHovered) + if (selected.IsHovered) { - selectionHandler.HandleSelectionRequested(blueprint, e.CurrentState); - clickSelectionBegan = true; + hoveringSelected = true; break; } } + + // Attempt a new selection at the mouse position + if (!hoveringSelected) + { + foreach (SelectionBlueprint blueprint in selectionBlueprints.AliveBlueprints) + { + if (blueprint.IsHovered) + { + selectionHandler.HandleSelectionRequested(blueprint, e.CurrentState); + clickSelectionBegan = true; + break; + } + } + } } /// From d77882c21bcc9ebb4379fd7e6ad6d04a1f2ea451 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 17:31:52 +0900 Subject: [PATCH 2088/2815] Fix slider selection input handled outside path --- .../Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs index d28cf7b492..78f4c4d992 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs @@ -43,5 +43,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components Size = body.Size; OriginPosition = body.PathOffset; } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => body.ReceivePositionalInputAt(screenSpacePos); } } From 861268eb1ded856d1ebf6fb21115af38e7baec49 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 14:52:33 +0900 Subject: [PATCH 2089/2815] Add basic structure for new follow point renderer --- .../TestSceneFollowPoints.cs | 315 ++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs new file mode 100644 index 0000000000..c33c94c9c8 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -0,0 +1,315 @@ +// 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 NUnit.Framework; +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.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Skinning; +using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneFollowPoints : OsuTestScene + { + private Container hitObjectContainer; + private FollowPointRenderer followPointRenderer; + + [SetUp] + public void Setup() => Schedule(() => + { + Children = new Drawable[] + { + hitObjectContainer = new Container { RelativeSizeAxes = Axes.Both }, + followPointRenderer = new FollowPointRenderer { RelativeSizeAxes = Axes.Both } + }; + }); + + [Test] + public void TestAddSingleHitObject() + { + addObject(new HitCircle { Position = new Vector2(100, 100) }); + } + + [Test] + public void TestRemoveSingleHitObject() + { + } + + [Test] + public void TestAddMultipleHitObjects() + { + } + + [Test] + public void TestRemoveEndObject() + { + } + + [Test] + public void TestRemoveStartObject() + { + } + + [Test] + public void TestRemoveMiddleObject() + { + } + + private void addObject(HitCircle hitCircle, Action func = null) + { + AddStep("add hitobject", () => + { + hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + var drawableCircle = new DrawableHitCircle(hitCircle); + + hitObjectContainer.Add(drawableCircle); + followPointRenderer.AddFollowPoints(drawableCircle); + + func?.Invoke(drawableCircle); + }); + } + + private void removeObject(Func func) + { + AddStep("remove hitobject", () => + { + var drawableObject = func?.Invoke(); + + hitObjectContainer.Remove(drawableObject); + followPointRenderer.RemoveFollowPoints(drawableObject); + }); + } + + private class FollowPointRenderer : CompositeDrawable + { + /// + /// Adds the s around a . + /// This includes s leading into , and s exiting . + /// + /// The to add s for. + public void AddFollowPoints(DrawableHitObject hitObject) + { + var startGroup = new FollowPointGroup(hitObject); + AddInternal(startGroup); + + // Groups are sorted by their start time when added, so the index can be used to post-process other surrounding groups + int startIndex = IndexOfInternal(startGroup); + + if (startIndex < InternalChildren.Count - 1) + { + // h1 -> -> -> h2 + // hitObject nextGroup + + var nextGroup = (FollowPointGroup)InternalChildren[startIndex + 1]; + startGroup.End = nextGroup.Start; + } + + if (startIndex > 0) + { + // h1 -> -> -> h2 + // prevGroup hitObject + + var previousGroup = (FollowPointGroup)InternalChildren[startIndex - 1]; + previousGroup.End = startGroup.Start; + } + } + + /// + /// Removes the s around a . + /// This includes s leading into , and s exiting . + /// + /// The to remove s for. + public void RemoveFollowPoints(DrawableHitObject hitObject) + { + var groups = findGroups(hitObject); + + // Regardless of the position of the hitobject in the beatmap, there will always be a group leading from the hitobject + RemoveInternal(groups.start); + + if (groups.end != null) + { + // When there were two groups referencing the same hitobject, merge them by updating the end group to point to the new end (the start group was already removed) + groups.end.End = groups.start.End; + } + } + + /// + /// Finds the s with as the start and end s. + /// + /// The to find the relevant of. + /// A tuple containing the end group (the where is the end of), + /// and the start group (the where is the start of). + private (FollowPointGroup start, FollowPointGroup end) findGroups(DrawableHitObject hitObject) + { + // endGroup startGroup + // h1 -> -> -> -> -> h2 -> -> -> -> -> h3 + // hitObject + + FollowPointGroup startGroup = null; // The group which the hitobject is the start in + FollowPointGroup endGroup = null; // The group which the hitobject is the end in + + int startIndex = 0; + + for (; startIndex < InternalChildren.Count; startIndex++) + { + var group = (FollowPointGroup)InternalChildren[startIndex]; + + if (group.Start == hitObject) + { + startGroup = group; + break; + } + } + + if (startIndex > 0) + endGroup = (FollowPointGroup)InternalChildren[startIndex - 1]; + + return (startGroup, endGroup); + } + + protected override int Compare(Drawable x, Drawable y) + { + var groupX = (FollowPointGroup)x; + var groupY = (FollowPointGroup)y; + + return groupX.Start.HitObject.StartTime.CompareTo(groupY.Start.HitObject.StartTime); + } + } + + private class FollowPointGroup : CompositeDrawable + { + private const int spacing = 32; + private const double preempt = 800; + + public FollowPointGroup(DrawableHitObject start) + { + Start = start; + } + + /// + /// The which s will exit from. + /// + [NotNull] + public DrawableHitObject Start { get; } + + private DrawableHitObject end; + + /// + /// The which s will enter. + /// + [CanBeNull] + public DrawableHitObject End + { + get => end; + set + { + end = value; + refreshFollowPoints(); + } + } + + private void refreshFollowPoints() + { + ClearInternal(); + + if (End == null) + return; + + OsuHitObject osuStart = (OsuHitObject)Start.HitObject; + OsuHitObject osuEnd = (OsuHitObject)End.HitObject; + + if (osuEnd.NewCombo) + return; + + if (osuStart is Spinner || osuEnd is Spinner) + return; + + Vector2 startPosition = osuStart.EndPosition; + Vector2 endPosition = osuEnd.Position; + double startTime = (osuStart as IHasEndTime)?.EndTime ?? osuStart.StartTime; + double endTime = osuEnd.StartTime; + + Vector2 distanceVector = endPosition - startPosition; + int distance = (int)distanceVector.Length; + float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI)); + double duration = endTime - startTime; + + for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing) + { + float fraction = (float)d / distance; + Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector; + Vector2 pointEndPosition = startPosition + fraction * distanceVector; + double fadeOutTime = startTime + fraction * duration; + double fadeInTime = fadeOutTime - preempt; + + FollowPoint fp; + + AddInternal(fp = new FollowPoint + { + Position = pointStartPosition, + Rotation = rotation, + Alpha = 0, + Scale = new Vector2(1.5f * osuEnd.Scale), + }); + + using (fp.BeginAbsoluteSequence(fadeInTime)) + { + fp.FadeIn(osuEnd.TimeFadeIn); + fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out); + fp.MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out); + fp.Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn); + } + + fp.Expire(true); + } + } + } + + private class FollowPoint : CompositeDrawable + { + private const float width = 8; + + public override bool RemoveWhenNotAlive => false; + + public FollowPoint() + { + Origin = Anchor.Centre; + + InternalChild = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.FollowPoint), _ => new Container + { + Masking = true, + AutoSizeAxes = Axes.Both, + CornerRadius = width / 2, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = Color4.White.Opacity(0.2f), + Radius = 4, + }, + Child = new Box + { + Size = new Vector2(width), + Blending = BlendingParameters.Additive, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Alpha = 0.5f, + } + }, confineMode: ConfineMode.NoScaling); + } + } + } +} From 7e60bc724097d12c180a6ed5ff521257dc332f5e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:12:32 +0900 Subject: [PATCH 2090/2815] Fix groups having 0 size --- osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index c33c94c9c8..ca7be9bb64 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -198,6 +198,7 @@ namespace osu.Game.Rulesets.Osu.Tests public FollowPointGroup(DrawableHitObject start) { Start = start; + RelativeSizeAxes = Axes.Both; } /// From c1850b2353c1e85b0153c301f0b82d980ab87211 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:12:42 +0900 Subject: [PATCH 2091/2815] Add some basic tests --- .../TestSceneFollowPoints.cs | 100 ++++++++++++++++-- 1 file changed, 90 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index ca7be9bb64..66aea68a3c 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Linq; using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Extensions.Color4Extensions; @@ -40,54 +42,132 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestAddSingleHitObject() { - addObject(new HitCircle { Position = new Vector2(100, 100) }); + addObjects(() => new[] { new HitCircle { Position = new Vector2(100, 100) } }); } [Test] public void TestRemoveSingleHitObject() { + DrawableHitObject obj = null; + + addObjects(() => new[] { new HitCircle { Position = new Vector2(100, 100) } }, o => obj = o); + removeObject(() => obj); } [Test] public void TestAddMultipleHitObjects() { + addObjects(() => new[] + { + new HitCircle { Position = new Vector2(100, 100) }, + new HitCircle { Position = new Vector2(200, 200) }, + new HitCircle { Position = new Vector2(300, 300) }, + new HitCircle { Position = new Vector2(400, 400) }, + new HitCircle { Position = new Vector2(500, 500) }, + }); } [Test] public void TestRemoveEndObject() { + var objects = new List(); + + AddStep("reset", () => objects.Clear()); + + addObjects(() => new[] + { + new HitCircle { Position = new Vector2(100, 100) }, + new HitCircle { Position = new Vector2(200, 200) }, + new HitCircle { Position = new Vector2(300, 300) }, + new HitCircle { Position = new Vector2(400, 400) }, + new HitCircle { Position = new Vector2(500, 500) }, + }, o => objects.Add(o)); + + removeObject(() => objects.Last()); } [Test] public void TestRemoveStartObject() { + var objects = new List(); + + AddStep("reset", () => objects.Clear()); + + addObjects(() => new[] + { + new HitCircle { Position = new Vector2(100, 100) }, + new HitCircle { Position = new Vector2(200, 200) }, + new HitCircle { Position = new Vector2(300, 300) }, + new HitCircle { Position = new Vector2(400, 400) }, + new HitCircle { Position = new Vector2(500, 500) }, + }, o => objects.Add(o)); + + removeObject(() => objects.First()); } [Test] public void TestRemoveMiddleObject() { + var objects = new List(); + + AddStep("reset", () => objects.Clear()); + + addObjects(() => new[] + { + new HitCircle { Position = new Vector2(100, 100) }, + new HitCircle { Position = new Vector2(200, 200) }, + new HitCircle { Position = new Vector2(300, 300) }, + new HitCircle { Position = new Vector2(400, 400) }, + new HitCircle { Position = new Vector2(500, 500) }, + }, o => objects.Add(o)); + + removeObject(() => objects[2]); } - private void addObject(HitCircle hitCircle, Action func = null) + [Test] + public void TestMoveHitObject() { - AddStep("add hitobject", () => + var objects = new List(); + + AddStep("reset", () => objects.Clear()); + + addObjects(() => new[] { - hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + new HitCircle { Position = new Vector2(100, 100) }, + new HitCircle { Position = new Vector2(200, 200) }, + new HitCircle { Position = new Vector2(300, 300) }, + new HitCircle { Position = new Vector2(400, 400) }, + new HitCircle { Position = new Vector2(500, 500) }, + }, o => objects.Add(o)); - var drawableCircle = new DrawableHitCircle(hitCircle); + AddStep("move hitobject", () => ((OsuHitObject)objects[2].HitObject).Position = new Vector2(300, 100)); + } - hitObjectContainer.Add(drawableCircle); - followPointRenderer.AddFollowPoints(drawableCircle); + private void addObjects(Func ctorFunc, Action storeFunc = null) + { + AddStep("add hitobjects", () => + { + var circles = ctorFunc(); - func?.Invoke(drawableCircle); + for (int i = 0; i < circles.Length; i++) + { + circles[i].StartTime = Time.Current + 1000 + 500 * (i + 1); + circles[i].ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + var drawableCircle = new DrawableHitCircle(circles[i]); + hitObjectContainer.Add(drawableCircle); + followPointRenderer.AddFollowPoints(drawableCircle); + + storeFunc?.Invoke(drawableCircle); + } }); } - private void removeObject(Func func) + private void removeObject(Func getFunc) { AddStep("remove hitobject", () => { - var drawableObject = func?.Invoke(); + var drawableObject = getFunc?.Invoke(); hitObjectContainer.Remove(drawableObject); followPointRenderer.RemoveFollowPoints(drawableObject); From f861d8099c1344c60f67db77ea955ad47c33013e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:13:50 +0900 Subject: [PATCH 2092/2815] Remove unnecessary class --- .../TestSceneFollowPoints.cs | 39 +------------------ 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index 66aea68a3c..ed1d8f5184 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -6,21 +6,17 @@ using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using NUnit.Framework; -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.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Skinning; +using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; using osu.Game.Tests.Visual; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Tests { @@ -359,38 +355,5 @@ namespace osu.Game.Rulesets.Osu.Tests } } } - - private class FollowPoint : CompositeDrawable - { - private const float width = 8; - - public override bool RemoveWhenNotAlive => false; - - public FollowPoint() - { - Origin = Anchor.Centre; - - InternalChild = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.FollowPoint), _ => new Container - { - Masking = true, - AutoSizeAxes = Axes.Both, - CornerRadius = width / 2, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = Color4.White.Opacity(0.2f), - Radius = 4, - }, - Child = new Box - { - Size = new Vector2(width), - Blending = BlendingParameters.Additive, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Alpha = 0.5f, - } - }, confineMode: ConfineMode.NoScaling); - } - } } } From 513ad96adf09130dcc613d9b15d934e257118909 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:15:13 +0900 Subject: [PATCH 2093/2815] Cleanup property --- .../TestSceneFollowPoints.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index ed1d8f5184..43d6cc9ab9 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -268,21 +268,22 @@ namespace osu.Game.Rulesets.Osu.Tests private class FollowPointGroup : CompositeDrawable { + // Todo: These shouldn't be constants private const int spacing = 32; private const double preempt = 800; + /// + /// The which s will exit from. + /// + [NotNull] + public readonly DrawableHitObject Start; + public FollowPointGroup(DrawableHitObject start) { Start = start; RelativeSizeAxes = Axes.Both; } - /// - /// The which s will exit from. - /// - [NotNull] - public DrawableHitObject Start { get; } - private DrawableHitObject end; /// From c0badf1dce9d5a2463f40ce698b431c25ed23852 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:17:51 +0900 Subject: [PATCH 2094/2815] Type to DrawableOsuHitObject --- .../TestSceneFollowPoints.cs | 55 +++++++++---------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index 43d6cc9ab9..bb3e6a5044 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -22,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Tests { public class TestSceneFollowPoints : OsuTestScene { - private Container hitObjectContainer; + private Container hitObjectContainer; private FollowPointRenderer followPointRenderer; [SetUp] @@ -30,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Tests { Children = new Drawable[] { - hitObjectContainer = new Container { RelativeSizeAxes = Axes.Both }, + hitObjectContainer = new Container { RelativeSizeAxes = Axes.Both }, followPointRenderer = new FollowPointRenderer { RelativeSizeAxes = Axes.Both } }; }); @@ -44,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestRemoveSingleHitObject() { - DrawableHitObject obj = null; + DrawableOsuHitObject obj = null; addObjects(() => new[] { new HitCircle { Position = new Vector2(100, 100) } }, o => obj = o); removeObject(() => obj); @@ -66,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestRemoveEndObject() { - var objects = new List(); + var objects = new List(); AddStep("reset", () => objects.Clear()); @@ -85,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestRemoveStartObject() { - var objects = new List(); + var objects = new List(); AddStep("reset", () => objects.Clear()); @@ -104,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestRemoveMiddleObject() { - var objects = new List(); + var objects = new List(); AddStep("reset", () => objects.Clear()); @@ -123,7 +122,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestMoveHitObject() { - var objects = new List(); + var objects = new List(); AddStep("reset", () => objects.Clear()); @@ -136,10 +135,10 @@ namespace osu.Game.Rulesets.Osu.Tests new HitCircle { Position = new Vector2(500, 500) }, }, o => objects.Add(o)); - AddStep("move hitobject", () => ((OsuHitObject)objects[2].HitObject).Position = new Vector2(300, 100)); + AddStep("move hitobject", () => objects[2].HitObject.Position = new Vector2(300, 100)); } - private void addObjects(Func ctorFunc, Action storeFunc = null) + private void addObjects(Func ctorFunc, Action storeFunc = null) { AddStep("add hitobjects", () => { @@ -159,7 +158,7 @@ namespace osu.Game.Rulesets.Osu.Tests }); } - private void removeObject(Func getFunc) + private void removeObject(Func getFunc) { AddStep("remove hitobject", () => { @@ -173,11 +172,11 @@ namespace osu.Game.Rulesets.Osu.Tests private class FollowPointRenderer : CompositeDrawable { /// - /// Adds the s around a . + /// Adds the s around a . /// This includes s leading into , and s exiting . /// - /// The to add s for. - public void AddFollowPoints(DrawableHitObject hitObject) + /// The to add s for. + public void AddFollowPoints(DrawableOsuHitObject hitObject) { var startGroup = new FollowPointGroup(hitObject); AddInternal(startGroup); @@ -205,11 +204,11 @@ namespace osu.Game.Rulesets.Osu.Tests } /// - /// Removes the s around a . + /// Removes the s around a . /// This includes s leading into , and s exiting . /// - /// The to remove s for. - public void RemoveFollowPoints(DrawableHitObject hitObject) + /// The to remove s for. + public void RemoveFollowPoints(DrawableOsuHitObject hitObject) { var groups = findGroups(hitObject); @@ -224,12 +223,12 @@ namespace osu.Game.Rulesets.Osu.Tests } /// - /// Finds the s with as the start and end s. + /// Finds the s with as the start and end s. /// - /// The to find the relevant of. + /// The to find the relevant of. /// A tuple containing the end group (the where is the end of), /// and the start group (the where is the start of). - private (FollowPointGroup start, FollowPointGroup end) findGroups(DrawableHitObject hitObject) + private (FollowPointGroup start, FollowPointGroup end) findGroups(DrawableOsuHitObject hitObject) { // endGroup startGroup // h1 -> -> -> -> -> h2 -> -> -> -> -> h3 @@ -273,24 +272,24 @@ namespace osu.Game.Rulesets.Osu.Tests private const double preempt = 800; /// - /// The which s will exit from. + /// The which s will exit from. /// [NotNull] - public readonly DrawableHitObject Start; + public readonly DrawableOsuHitObject Start; - public FollowPointGroup(DrawableHitObject start) + public FollowPointGroup(DrawableOsuHitObject start) { Start = start; RelativeSizeAxes = Axes.Both; } - private DrawableHitObject end; + private DrawableOsuHitObject end; /// - /// The which s will enter. + /// The which s will enter. /// [CanBeNull] - public DrawableHitObject End + public DrawableOsuHitObject End { get => end; set @@ -307,8 +306,8 @@ namespace osu.Game.Rulesets.Osu.Tests if (End == null) return; - OsuHitObject osuStart = (OsuHitObject)Start.HitObject; - OsuHitObject osuEnd = (OsuHitObject)End.HitObject; + OsuHitObject osuStart = Start.HitObject; + OsuHitObject osuEnd = End.HitObject; if (osuEnd.NewCombo) return; From e0ba35db755e97abdd33a07db109dbb3ecfff3a1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:22:37 +0900 Subject: [PATCH 2095/2815] Implement binding to hitobjects + refreshing --- .../TestSceneFollowPoints.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index bb3e6a5044..b914a8b29d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -283,6 +283,12 @@ namespace osu.Game.Rulesets.Osu.Tests RelativeSizeAxes = Axes.Both; } + protected override void LoadComplete() + { + base.LoadComplete(); + bindHitObject(Start); + } + private DrawableOsuHitObject end; /// @@ -295,10 +301,19 @@ namespace osu.Game.Rulesets.Osu.Tests set { end = value; + + bindHitObject(end); refreshFollowPoints(); } } + private void bindHitObject(DrawableOsuHitObject drawableObject) + { + drawableObject.HitObject.StartTimeBindable.BindValueChanged(_ => refreshFollowPoints()); + drawableObject.HitObject.PositionBindable.BindValueChanged(_ => refreshFollowPoints()); + drawableObject.HitObject.DefaultsApplied += refreshFollowPoints; + } + private void refreshFollowPoints() { ClearInternal(); From 02a7f92d18375176eee9827e17aa126b708f8106 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:34:13 +0900 Subject: [PATCH 2096/2815] Allow constructing arbitrary hitobject types --- .../TestSceneFollowPoints.cs | 62 ++++++++++++------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index b914a8b29d..68f37e5d1d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -35,24 +35,24 @@ namespace osu.Game.Rulesets.Osu.Tests }); [Test] - public void TestAddSingleHitObject() + public void TestAddSingleHitCircle() { - addObjects(() => new[] { new HitCircle { Position = new Vector2(100, 100) } }); + addObjects(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); } [Test] - public void TestRemoveSingleHitObject() + public void TestRemoveSingleHitCircle() { DrawableOsuHitObject obj = null; - addObjects(() => new[] { new HitCircle { Position = new Vector2(100, 100) } }, o => obj = o); + addObjects(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }, o => obj = o); removeObject(() => obj); } [Test] - public void TestAddMultipleHitObjects() + public void TestAddMultipleHitCircles() { - addObjects(() => new[] + addObjects(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) }, new HitCircle { Position = new Vector2(200, 200) }, @@ -63,13 +63,13 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestRemoveEndObject() + public void TestRemoveEndHitCircle() { var objects = new List(); AddStep("reset", () => objects.Clear()); - addObjects(() => new[] + addObjects(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) }, new HitCircle { Position = new Vector2(200, 200) }, @@ -82,13 +82,13 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestRemoveStartObject() + public void TestRemoveStartHitCircle() { var objects = new List(); AddStep("reset", () => objects.Clear()); - addObjects(() => new[] + addObjects(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) }, new HitCircle { Position = new Vector2(200, 200) }, @@ -101,13 +101,13 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestRemoveMiddleObject() + public void TestRemoveMiddleHitCircle() { var objects = new List(); AddStep("reset", () => objects.Clear()); - addObjects(() => new[] + addObjects(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) }, new HitCircle { Position = new Vector2(200, 200) }, @@ -120,13 +120,13 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestMoveHitObject() + public void TestMoveHitCircle() { var objects = new List(); AddStep("reset", () => objects.Clear()); - addObjects(() => new[] + addObjects(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) }, new HitCircle { Position = new Vector2(200, 200) }, @@ -138,22 +138,38 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("move hitobject", () => objects[2].HitObject.Position = new Vector2(300, 100)); } - private void addObjects(Func ctorFunc, Action storeFunc = null) + private void addObjects(Func ctorFunc, Action storeFunc = null) { AddStep("add hitobjects", () => { - var circles = ctorFunc(); + var objects = ctorFunc(); - for (int i = 0; i < circles.Length; i++) + for (int i = 0; i < objects.Length; i++) { - circles[i].StartTime = Time.Current + 1000 + 500 * (i + 1); - circles[i].ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + objects[i].StartTime = Time.Current + 1000 + 500 * (i + 1); + objects[i].ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - var drawableCircle = new DrawableHitCircle(circles[i]); - hitObjectContainer.Add(drawableCircle); - followPointRenderer.AddFollowPoints(drawableCircle); + DrawableOsuHitObject drawableObject = null; - storeFunc?.Invoke(drawableCircle); + switch (objects[i]) + { + case HitCircle circle: + drawableObject = new DrawableHitCircle(circle); + break; + + case Slider slider: + drawableObject = new DrawableSlider(slider); + break; + + case Spinner spinner: + drawableObject = new DrawableSpinner(spinner); + break; + } + + hitObjectContainer.Add(drawableObject); + followPointRenderer.AddFollowPoints(drawableObject); + + storeFunc?.Invoke(drawableObject); } }); } From bfe7309964b772d1b5a33aa6ee89be9b5359023f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:34:17 +0900 Subject: [PATCH 2097/2815] Fix nullref --- osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index 68f37e5d1d..9587c721c6 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -318,7 +318,9 @@ namespace osu.Game.Rulesets.Osu.Tests { end = value; - bindHitObject(end); + if (end != null) + bindHitObject(end); + refreshFollowPoints(); } } From ddfcda9e029e5385ca8969a58e9aac33d70fc75c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:34:24 +0900 Subject: [PATCH 2098/2815] Remove abstract ConnectionRenderer class --- .../Connections/ConnectionRenderer.cs | 21 ------------------- .../Connections/FollowPointRenderer.cs | 5 +++-- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +- 3 files changed, 4 insertions(+), 24 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs deleted file mode 100644 index 9106f4c7bd..0000000000 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs +++ /dev/null @@ -1,21 +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.Graphics.Containers; -using osu.Game.Rulesets.Objects; -using System.Collections.Generic; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections -{ - /// - /// Connects hit objects visually, for example with follow points. - /// - public abstract class ConnectionRenderer : LifetimeManagementContainer - where T : HitObject - { - /// - /// Hit objects to create connections for - /// - public abstract IEnumerable HitObjects { get; set; } - } -} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index a269b87c75..863ce869aa 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -5,11 +5,12 @@ using System; using System.Collections.Generic; using osuTK; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { - public class FollowPointRenderer : ConnectionRenderer + public class FollowPointRenderer : CompositeDrawable { private int pointDistance = 32; @@ -47,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections private IEnumerable hitObjects; - public override IEnumerable HitObjects + public IEnumerable HitObjects { get => hitObjects; set diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 69e53d6eea..cbb29ce387 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.UI { private readonly ApproachCircleProxyContainer approachCircles; private readonly JudgementContainer judgementLayer; - private readonly ConnectionRenderer connectionLayer; + private readonly FollowPointRenderer connectionLayer; public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); From 712253ff50ec387a324cc1a0e10bb3bb948d372c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:39:23 +0900 Subject: [PATCH 2099/2815] Replace follow point renderer with new implementation --- .../TestSceneFollowPoints.cs | 206 ------------------ .../Drawables/Connections/FollowPointGroup.cs | 120 ++++++++++ .../Connections/FollowPointRenderer.cs | 168 +++++++------- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 16 +- 4 files changed, 206 insertions(+), 304 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index 9587c721c6..3c447e5009 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -4,13 +4,11 @@ using System; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; @@ -184,209 +182,5 @@ namespace osu.Game.Rulesets.Osu.Tests followPointRenderer.RemoveFollowPoints(drawableObject); }); } - - private class FollowPointRenderer : CompositeDrawable - { - /// - /// Adds the s around a . - /// This includes s leading into , and s exiting . - /// - /// The to add s for. - public void AddFollowPoints(DrawableOsuHitObject hitObject) - { - var startGroup = new FollowPointGroup(hitObject); - AddInternal(startGroup); - - // Groups are sorted by their start time when added, so the index can be used to post-process other surrounding groups - int startIndex = IndexOfInternal(startGroup); - - if (startIndex < InternalChildren.Count - 1) - { - // h1 -> -> -> h2 - // hitObject nextGroup - - var nextGroup = (FollowPointGroup)InternalChildren[startIndex + 1]; - startGroup.End = nextGroup.Start; - } - - if (startIndex > 0) - { - // h1 -> -> -> h2 - // prevGroup hitObject - - var previousGroup = (FollowPointGroup)InternalChildren[startIndex - 1]; - previousGroup.End = startGroup.Start; - } - } - - /// - /// Removes the s around a . - /// This includes s leading into , and s exiting . - /// - /// The to remove s for. - public void RemoveFollowPoints(DrawableOsuHitObject hitObject) - { - var groups = findGroups(hitObject); - - // Regardless of the position of the hitobject in the beatmap, there will always be a group leading from the hitobject - RemoveInternal(groups.start); - - if (groups.end != null) - { - // When there were two groups referencing the same hitobject, merge them by updating the end group to point to the new end (the start group was already removed) - groups.end.End = groups.start.End; - } - } - - /// - /// Finds the s with as the start and end s. - /// - /// The to find the relevant of. - /// A tuple containing the end group (the where is the end of), - /// and the start group (the where is the start of). - private (FollowPointGroup start, FollowPointGroup end) findGroups(DrawableOsuHitObject hitObject) - { - // endGroup startGroup - // h1 -> -> -> -> -> h2 -> -> -> -> -> h3 - // hitObject - - FollowPointGroup startGroup = null; // The group which the hitobject is the start in - FollowPointGroup endGroup = null; // The group which the hitobject is the end in - - int startIndex = 0; - - for (; startIndex < InternalChildren.Count; startIndex++) - { - var group = (FollowPointGroup)InternalChildren[startIndex]; - - if (group.Start == hitObject) - { - startGroup = group; - break; - } - } - - if (startIndex > 0) - endGroup = (FollowPointGroup)InternalChildren[startIndex - 1]; - - return (startGroup, endGroup); - } - - protected override int Compare(Drawable x, Drawable y) - { - var groupX = (FollowPointGroup)x; - var groupY = (FollowPointGroup)y; - - return groupX.Start.HitObject.StartTime.CompareTo(groupY.Start.HitObject.StartTime); - } - } - - private class FollowPointGroup : CompositeDrawable - { - // Todo: These shouldn't be constants - private const int spacing = 32; - private const double preempt = 800; - - /// - /// The which s will exit from. - /// - [NotNull] - public readonly DrawableOsuHitObject Start; - - public FollowPointGroup(DrawableOsuHitObject start) - { - Start = start; - RelativeSizeAxes = Axes.Both; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - bindHitObject(Start); - } - - private DrawableOsuHitObject end; - - /// - /// The which s will enter. - /// - [CanBeNull] - public DrawableOsuHitObject End - { - get => end; - set - { - end = value; - - if (end != null) - bindHitObject(end); - - refreshFollowPoints(); - } - } - - private void bindHitObject(DrawableOsuHitObject drawableObject) - { - drawableObject.HitObject.StartTimeBindable.BindValueChanged(_ => refreshFollowPoints()); - drawableObject.HitObject.PositionBindable.BindValueChanged(_ => refreshFollowPoints()); - drawableObject.HitObject.DefaultsApplied += refreshFollowPoints; - } - - private void refreshFollowPoints() - { - ClearInternal(); - - if (End == null) - return; - - OsuHitObject osuStart = Start.HitObject; - OsuHitObject osuEnd = End.HitObject; - - if (osuEnd.NewCombo) - return; - - if (osuStart is Spinner || osuEnd is Spinner) - return; - - Vector2 startPosition = osuStart.EndPosition; - Vector2 endPosition = osuEnd.Position; - double startTime = (osuStart as IHasEndTime)?.EndTime ?? osuStart.StartTime; - double endTime = osuEnd.StartTime; - - Vector2 distanceVector = endPosition - startPosition; - int distance = (int)distanceVector.Length; - float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI)); - double duration = endTime - startTime; - - for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing) - { - float fraction = (float)d / distance; - Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector; - Vector2 pointEndPosition = startPosition + fraction * distanceVector; - double fadeOutTime = startTime + fraction * duration; - double fadeInTime = fadeOutTime - preempt; - - FollowPoint fp; - - AddInternal(fp = new FollowPoint - { - Position = pointStartPosition, - Rotation = rotation, - Alpha = 0, - Scale = new Vector2(1.5f * osuEnd.Scale), - }); - - using (fp.BeginAbsoluteSequence(fadeInTime)) - { - fp.FadeIn(osuEnd.TimeFadeIn); - fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out); - fp.MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out); - fp.Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn); - } - - fp.Expire(true); - } - } - } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs new file mode 100644 index 0000000000..06aadcc342 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs @@ -0,0 +1,120 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Types; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections +{ + public class FollowPointGroup : CompositeDrawable + { + // Todo: These shouldn't be constants + private const int spacing = 32; + private const double preempt = 800; + + /// + /// The which s will exit from. + /// + [NotNull] + public readonly DrawableOsuHitObject Start; + + public FollowPointGroup(DrawableOsuHitObject start) + { + Start = start; + RelativeSizeAxes = Axes.Both; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + bindHitObject(Start); + } + + private DrawableOsuHitObject end; + + /// + /// The which s will enter. + /// + [CanBeNull] + public DrawableOsuHitObject End + { + get => end; + set + { + end = value; + + if (end != null) + bindHitObject(end); + + refreshFollowPoints(); + } + } + + private void bindHitObject(DrawableOsuHitObject drawableObject) + { + drawableObject.HitObject.StartTimeBindable.BindValueChanged(_ => refreshFollowPoints()); + drawableObject.HitObject.PositionBindable.BindValueChanged(_ => refreshFollowPoints()); + drawableObject.HitObject.DefaultsApplied += refreshFollowPoints; + } + + private void refreshFollowPoints() + { + ClearInternal(); + + if (End == null) + return; + + OsuHitObject osuStart = Start.HitObject; + OsuHitObject osuEnd = End.HitObject; + + if (osuEnd.NewCombo) + return; + + if (osuStart is Spinner || osuEnd is Spinner) + return; + + Vector2 startPosition = osuStart.EndPosition; + Vector2 endPosition = osuEnd.Position; + double startTime = (osuStart as IHasEndTime)?.EndTime ?? osuStart.StartTime; + double endTime = osuEnd.StartTime; + + Vector2 distanceVector = endPosition - startPosition; + int distance = (int)distanceVector.Length; + float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI)); + double duration = endTime - startTime; + + for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing) + { + float fraction = (float)d / distance; + Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector; + Vector2 pointEndPosition = startPosition + fraction * distanceVector; + double fadeOutTime = startTime + fraction * duration; + double fadeInTime = fadeOutTime - preempt; + + FollowPoint fp; + + AddInternal(fp = new FollowPoint + { + Position = pointStartPosition, + Rotation = rotation, + Alpha = 0, + Scale = new Vector2(1.5f * osuEnd.Scale), + }); + + using (fp.BeginAbsoluteSequence(fadeInTime)) + { + fp.FadeIn(osuEnd.TimeFadeIn); + fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out); + fp.MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out); + fp.Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn); + } + + fp.Expire(true); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 863ce869aa..47c35746f0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -1,122 +1,104 @@ // 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 osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { public class FollowPointRenderer : CompositeDrawable { - private int pointDistance = 32; + /// + /// Adds the s around a . + /// This includes s leading into , and s exiting . + /// + /// The to add s for. + public void AddFollowPoints(DrawableOsuHitObject hitObject) + { + var startGroup = new FollowPointGroup(hitObject); + AddInternal(startGroup); + + // Groups are sorted by their start time when added, so the index can be used to post-process other surrounding groups + int startIndex = IndexOfInternal(startGroup); + + if (startIndex < InternalChildren.Count - 1) + { + // h1 -> -> -> h2 + // hitObject nextGroup + + var nextGroup = (FollowPointGroup)InternalChildren[startIndex + 1]; + startGroup.End = nextGroup.Start; + } + + if (startIndex > 0) + { + // h1 -> -> -> h2 + // prevGroup hitObject + + var previousGroup = (FollowPointGroup)InternalChildren[startIndex - 1]; + previousGroup.End = startGroup.Start; + } + } /// - /// Determines how much space there is between points. + /// Removes the s around a . + /// This includes s leading into , and s exiting . /// - public int PointDistance + /// The to remove s for. + public void RemoveFollowPoints(DrawableOsuHitObject hitObject) { - get => pointDistance; - set - { - if (pointDistance == value) return; + var groups = findGroups(hitObject); - pointDistance = value; - update(); + // Regardless of the position of the hitobject in the beatmap, there will always be a group leading from the hitobject + RemoveInternal(groups.start); + + if (groups.end != null) + { + // When there were two groups referencing the same hitobject, merge them by updating the end group to point to the new end (the start group was already removed) + groups.end.End = groups.start.End; } } - private int preEmpt = 800; - /// - /// Follow points to the next hitobject start appearing for this many milliseconds before an hitobject's end time. + /// Finds the s with as the start and end s. /// - public int PreEmpt + /// The to find the relevant of. + /// A tuple containing the end group (the where is the end of), + /// and the start group (the where is the start of). + private (FollowPointGroup start, FollowPointGroup end) findGroups(DrawableOsuHitObject hitObject) { - get => preEmpt; - set + // endGroup startGroup + // h1 -> -> -> -> -> h2 -> -> -> -> -> h3 + // hitObject + + FollowPointGroup startGroup = null; // The group which the hitobject is the start in + FollowPointGroup endGroup = null; // The group which the hitobject is the end in + + int startIndex = 0; + + for (; startIndex < InternalChildren.Count; startIndex++) { - if (preEmpt == value) return; + var group = (FollowPointGroup)InternalChildren[startIndex]; - preEmpt = value; - update(); - } - } - - private IEnumerable hitObjects; - - public IEnumerable HitObjects - { - get => hitObjects; - set - { - hitObjects = value; - update(); - } - } - - public override bool RemoveCompletedTransforms => false; - - private void update() - { - ClearInternal(); - - if (hitObjects == null) - return; - - OsuHitObject prevHitObject = null; - - foreach (var currHitObject in hitObjects) - { - if (prevHitObject != null && !currHitObject.NewCombo && !(prevHitObject is Spinner) && !(currHitObject is Spinner)) + if (group.Start == hitObject) { - Vector2 startPosition = prevHitObject.EndPosition; - Vector2 endPosition = currHitObject.Position; - double startTime = (prevHitObject as IHasEndTime)?.EndTime ?? prevHitObject.StartTime; - double endTime = currHitObject.StartTime; - - Vector2 distanceVector = endPosition - startPosition; - int distance = (int)distanceVector.Length; - float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI)); - double duration = endTime - startTime; - - for (int d = (int)(PointDistance * 1.5); d < distance - PointDistance; d += PointDistance) - { - float fraction = (float)d / distance; - Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector; - Vector2 pointEndPosition = startPosition + fraction * distanceVector; - double fadeOutTime = startTime + fraction * duration; - double fadeInTime = fadeOutTime - PreEmpt; - - FollowPoint fp; - - AddInternal(fp = new FollowPoint - { - Position = pointStartPosition, - Rotation = rotation, - Alpha = 0, - Scale = new Vector2(1.5f * currHitObject.Scale), - }); - - using (fp.BeginAbsoluteSequence(fadeInTime)) - { - fp.FadeIn(currHitObject.TimeFadeIn); - fp.ScaleTo(currHitObject.Scale, currHitObject.TimeFadeIn, Easing.Out); - - fp.MoveTo(pointEndPosition, currHitObject.TimeFadeIn, Easing.Out); - - fp.Delay(fadeOutTime - fadeInTime).FadeOut(currHitObject.TimeFadeIn); - } - - fp.Expire(true); - } + startGroup = group; + break; } - - prevHitObject = currHitObject; } + + if (startIndex > 0) + endGroup = (FollowPointGroup)InternalChildren[startIndex - 1]; + + return (startGroup, endGroup); + } + + protected override int Compare(Drawable x, Drawable y) + { + var groupX = (FollowPointGroup)x; + var groupY = (FollowPointGroup)y; + + return groupX.Start.HitObject.StartTime.CompareTo(groupY.Start.HitObject.StartTime); } } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index cbb29ce387..6d1ea4bbfc 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -9,7 +9,6 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; using osu.Game.Rulesets.UI; -using System.Linq; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Skinning; @@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.UI { private readonly ApproachCircleProxyContainer approachCircles; private readonly JudgementContainer judgementLayer; - private readonly FollowPointRenderer connectionLayer; + private readonly FollowPointRenderer followPoints; public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); @@ -30,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.UI { InternalChildren = new Drawable[] { - connectionLayer = new FollowPointRenderer + followPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both, Depth = 2, @@ -64,11 +63,18 @@ namespace osu.Game.Rulesets.Osu.UI }; base.Add(h); + + followPoints.AddFollowPoints((DrawableOsuHitObject)h); } - public override void PostProcess() + public override bool Remove(DrawableHitObject h) { - connectionLayer.HitObjects = HitObjectContainer.Objects.Select(d => d.HitObject).OfType(); + bool result = base.Remove(h); + + if (result) + followPoints.RemoveFollowPoints((DrawableOsuHitObject)h); + + return result; } private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) From a19e26f8aa6c8d1bbb5bf80c499704cb14f6f729 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 19:21:39 +0900 Subject: [PATCH 2100/2815] Improve performance of refreshes --- .../Drawables/Connections/FollowPointGroup.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs index 06aadcc342..750e9559fc 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections protected override void LoadComplete() { base.LoadComplete(); - bindHitObject(Start); + bindEvents(Start); } private DrawableOsuHitObject end; @@ -48,20 +48,21 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections end = value; if (end != null) - bindHitObject(end); + bindEvents(end); - refreshFollowPoints(); + refresh(); } } - private void bindHitObject(DrawableOsuHitObject drawableObject) + private void bindEvents(DrawableOsuHitObject drawableObject) { - drawableObject.HitObject.StartTimeBindable.BindValueChanged(_ => refreshFollowPoints()); - drawableObject.HitObject.PositionBindable.BindValueChanged(_ => refreshFollowPoints()); - drawableObject.HitObject.DefaultsApplied += refreshFollowPoints; + drawableObject.HitObject.PositionBindable.BindValueChanged(_ => scheduleRefresh()); + drawableObject.HitObject.DefaultsApplied += scheduleRefresh; } - private void refreshFollowPoints() + private void scheduleRefresh() => Scheduler.AddOnce(refresh); + + private void refresh() { ClearInternal(); From 3b6064336b63f6bd00f7bdee699cb6dae2903279 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 19:22:07 +0900 Subject: [PATCH 2101/2815] Implement group re-ordering based on start time --- .../Drawables/Connections/FollowPointGroup.cs | 12 +- .../Connections/FollowPointRenderer.cs | 115 ++++++++---------- 2 files changed, 65 insertions(+), 62 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs index 750e9559fc..79d69acd67 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs @@ -3,6 +3,7 @@ using System; using JetBrains.Annotations; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Types; @@ -16,16 +17,25 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections private const int spacing = 32; private const double preempt = 800; + public readonly Bindable StartTime = new Bindable(); + /// /// The which s will exit from. /// [NotNull] public readonly DrawableOsuHitObject Start; - public FollowPointGroup(DrawableOsuHitObject start) + /// + /// Creates a new . + /// + /// The which s will exit from. + public FollowPointGroup([NotNull] DrawableOsuHitObject start) { Start = start; + RelativeSizeAxes = Axes.Both; + + StartTime.BindTo(Start.HitObject.StartTimeBindable); } protected override void LoadComplete() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 47c35746f0..892442f0a7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.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.Collections.Generic; +using System.Linq; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -8,97 +11,87 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { public class FollowPointRenderer : CompositeDrawable { + private readonly List groups = new List(); + /// /// Adds the s around a . /// This includes s leading into , and s exiting . /// /// The to add s for. public void AddFollowPoints(DrawableOsuHitObject hitObject) - { - var startGroup = new FollowPointGroup(hitObject); - AddInternal(startGroup); - - // Groups are sorted by their start time when added, so the index can be used to post-process other surrounding groups - int startIndex = IndexOfInternal(startGroup); - - if (startIndex < InternalChildren.Count - 1) - { - // h1 -> -> -> h2 - // hitObject nextGroup - - var nextGroup = (FollowPointGroup)InternalChildren[startIndex + 1]; - startGroup.End = nextGroup.Start; - } - - if (startIndex > 0) - { - // h1 -> -> -> h2 - // prevGroup hitObject - - var previousGroup = (FollowPointGroup)InternalChildren[startIndex - 1]; - previousGroup.End = startGroup.Start; - } - } + => addGroup(new FollowPointGroup(hitObject).With(g => g.StartTime.BindValueChanged(_ => onStartTimeChanged(g)))); /// /// Removes the s around a . /// This includes s leading into , and s exiting . /// /// The to remove s for. - public void RemoveFollowPoints(DrawableOsuHitObject hitObject) + public void RemoveFollowPoints(DrawableOsuHitObject hitObject) => removeGroup(groups.Single(g => g.Start == hitObject)); + + /// + /// Adds a to this . + /// + /// The to add. + /// The index of in . + private int addGroup(FollowPointGroup group) { - var groups = findGroups(hitObject); + AddInternal(group); - // Regardless of the position of the hitobject in the beatmap, there will always be a group leading from the hitobject - RemoveInternal(groups.start); + // Groups are sorted by their start time when added such that the index can be used to post-process other surrounding groups + int index = groups.AddInPlace(group, Comparer.Create((g1, g2) => g1.StartTime.Value.CompareTo(g2.StartTime.Value))); - if (groups.end != null) + if (index < groups.Count - 1) { - // When there were two groups referencing the same hitobject, merge them by updating the end group to point to the new end (the start group was already removed) - groups.end.End = groups.start.End; + // Update the group's end point to the next hitobject + // h1 -> -> -> h2 + // hitObject nextGroup + + FollowPointGroup nextGroup = groups[index + 1]; + group.End = nextGroup.Start; } + + if (index > 0) + { + // Previous group's end point to the current group's start point + // h1 -> -> -> h2 + // prevGroup hitObject + + FollowPointGroup previousGroup = groups[index - 1]; + previousGroup.End = group.Start; + } + + return index; } /// - /// Finds the s with as the start and end s. + /// Removes a from this . /// - /// The to find the relevant of. - /// A tuple containing the end group (the where is the end of), - /// and the start group (the where is the start of). - private (FollowPointGroup start, FollowPointGroup end) findGroups(DrawableOsuHitObject hitObject) + /// The to remove. + /// Whether was removed. + private bool removeGroup(FollowPointGroup group) { - // endGroup startGroup - // h1 -> -> -> -> -> h2 -> -> -> -> -> h3 - // hitObject + RemoveInternal(group); - FollowPointGroup startGroup = null; // The group which the hitobject is the start in - FollowPointGroup endGroup = null; // The group which the hitobject is the end in + int index = groups.IndexOf(group); - int startIndex = 0; - - for (; startIndex < InternalChildren.Count; startIndex++) + if (index > 0) { - var group = (FollowPointGroup)InternalChildren[startIndex]; - - if (group.Start == hitObject) - { - startGroup = group; - break; - } + // Update the previous group's end point to the next group's start point + // h1 -> -> -> h2 -> -> -> h3 + // prevGroup group nextGroup + // The current group's end point is used since there may not be a next group + FollowPointGroup previousGroup = groups[index - 1]; + previousGroup.End = group.End; } - if (startIndex > 0) - endGroup = (FollowPointGroup)InternalChildren[startIndex - 1]; - - return (startGroup, endGroup); + return groups.Remove(group); } - protected override int Compare(Drawable x, Drawable y) + private void onStartTimeChanged(FollowPointGroup group) { - var groupX = (FollowPointGroup)x; - var groupY = (FollowPointGroup)y; - - return groupX.Start.HitObject.StartTime.CompareTo(groupY.Start.HitObject.StartTime); + // Naive but can be improved if performance becomes problematic + removeGroup(group); + addGroup(group); } } } From 1ef2b81041dfd9225ffb7e792b8c55bfa76d6ff1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 22:08:50 +0900 Subject: [PATCH 2102/2815] Fix follow point lifetime not being updated correctly --- .../Drawables/Connections/FollowPointGroup.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs index 79d69acd67..47196f4893 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs @@ -118,13 +118,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections using (fp.BeginAbsoluteSequence(fadeInTime)) { - fp.FadeIn(osuEnd.TimeFadeIn); - fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out); - fp.MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out); - fp.Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn); - } + // See: Expire calls are separated due to https://github.com/ppy/osu-framework/issues/2941 - fp.Expire(true); + fp.FadeIn(osuEnd.TimeFadeIn).Expire(true); + + fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out) + .MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out) + .Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn) + .Expire(); + } } } } From f77de7d88013f30bd3567b4af2f7d223029b50e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Nov 2019 18:25:38 +0900 Subject: [PATCH 2103/2815] Simplify implementation --- .../Compose/Components/BlueprintContainer.cs | 26 +++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 7a3a303ff3..02dd60eac1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -254,31 +254,19 @@ namespace osu.Game.Screens.Edit.Compose.Components { Debug.Assert(!clickSelectionBegan); - bool hoveringSelected = false; + // If a select blueprint is already hovered, disallow changes in selection. + if (selectionHandler.SelectedBlueprints.Any(s => s.IsHovered)) + return; - // Make sure any already-selected blueprints aren't being hovered over - foreach (SelectionBlueprint selected in selectionHandler.SelectedBlueprints) + foreach (SelectionBlueprint blueprint in selectionBlueprints.AliveBlueprints) { - if (selected.IsHovered) + if (blueprint.IsHovered) { - hoveringSelected = true; + selectionHandler.HandleSelectionRequested(blueprint, e.CurrentState); + clickSelectionBegan = true; break; } } - - // Attempt a new selection at the mouse position - if (!hoveringSelected) - { - foreach (SelectionBlueprint blueprint in selectionBlueprints.AliveBlueprints) - { - if (blueprint.IsHovered) - { - selectionHandler.HandleSelectionRequested(blueprint, e.CurrentState); - clickSelectionBegan = true; - break; - } - } - } } /// From f4b93ec9438f49d852bb7172ad3a6b8c368fd5d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Nov 2019 18:29:08 +0900 Subject: [PATCH 2104/2815] Add exception when holding control --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 02dd60eac1..288c712bde 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -255,7 +255,9 @@ namespace osu.Game.Screens.Edit.Compose.Components Debug.Assert(!clickSelectionBegan); // If a select blueprint is already hovered, disallow changes in selection. - if (selectionHandler.SelectedBlueprints.Any(s => s.IsHovered)) + // Exception is made when holding control, as deselection should still be allowed. + if (!e.CurrentState.Keyboard.ControlPressed && + selectionHandler.SelectedBlueprints.Any(s => s.IsHovered)) return; foreach (SelectionBlueprint blueprint in selectionBlueprints.AliveBlueprints) From 6c58faf30cab3f8f36d2b8d77621f0bca2529f1a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 19:31:48 +0900 Subject: [PATCH 2105/2815] Fix group ends potentially not being updated correctly --- .../Objects/Drawables/Connections/FollowPointRenderer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 892442f0a7..e29cf6e128 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -49,6 +49,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections FollowPointGroup nextGroup = groups[index + 1]; group.End = nextGroup.Start; } + else + group.End = null; if (index > 0) { From f2118b0eba6fa022023ef453b11b9ed61dcf2580 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 19:31:58 +0900 Subject: [PATCH 2106/2815] Add automated test cases --- .../TestSceneFollowPoints.cs | 178 +++++++++++------- .../Connections/FollowPointRenderer.cs | 2 + 2 files changed, 113 insertions(+), 67 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index 3c447e5009..aac0119dc7 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -2,8 +2,6 @@ // 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.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -27,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Tests { Children = new Drawable[] { - hitObjectContainer = new Container { RelativeSizeAxes = Axes.Both }, + hitObjectContainer = new TestHitObjectContainer { RelativeSizeAxes = Axes.Both }, followPointRenderer = new FollowPointRenderer { RelativeSizeAxes = Axes.Both } }; }); @@ -35,108 +33,97 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestAddSingleHitCircle() { - addObjects(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); + addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); + + assertGroups(); } [Test] public void TestRemoveSingleHitCircle() { - DrawableOsuHitObject obj = null; + addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); - addObjects(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }, o => obj = o); - removeObject(() => obj); + removeObjectStep(() => getObject(0)); + + assertGroups(); } [Test] public void TestAddMultipleHitCircles() { - addObjects(() => new OsuHitObject[] - { - new HitCircle { Position = new Vector2(100, 100) }, - new HitCircle { Position = new Vector2(200, 200) }, - new HitCircle { Position = new Vector2(300, 300) }, - new HitCircle { Position = new Vector2(400, 400) }, - new HitCircle { Position = new Vector2(500, 500) }, - }); + addMultipleObjectsStep(); + + assertGroups(); } [Test] public void TestRemoveEndHitCircle() { - var objects = new List(); + addMultipleObjectsStep(); - AddStep("reset", () => objects.Clear()); + removeObjectStep(() => getObject(4)); - addObjects(() => new OsuHitObject[] - { - new HitCircle { Position = new Vector2(100, 100) }, - new HitCircle { Position = new Vector2(200, 200) }, - new HitCircle { Position = new Vector2(300, 300) }, - new HitCircle { Position = new Vector2(400, 400) }, - new HitCircle { Position = new Vector2(500, 500) }, - }, o => objects.Add(o)); - - removeObject(() => objects.Last()); + assertGroups(); } [Test] public void TestRemoveStartHitCircle() { - var objects = new List(); + addMultipleObjectsStep(); - AddStep("reset", () => objects.Clear()); + removeObjectStep(() => getObject(0)); - addObjects(() => new OsuHitObject[] - { - new HitCircle { Position = new Vector2(100, 100) }, - new HitCircle { Position = new Vector2(200, 200) }, - new HitCircle { Position = new Vector2(300, 300) }, - new HitCircle { Position = new Vector2(400, 400) }, - new HitCircle { Position = new Vector2(500, 500) }, - }, o => objects.Add(o)); - - removeObject(() => objects.First()); + assertGroups(); } [Test] public void TestRemoveMiddleHitCircle() { - var objects = new List(); + addMultipleObjectsStep(); - AddStep("reset", () => objects.Clear()); + removeObjectStep(() => getObject(2)); - addObjects(() => new OsuHitObject[] - { - new HitCircle { Position = new Vector2(100, 100) }, - new HitCircle { Position = new Vector2(200, 200) }, - new HitCircle { Position = new Vector2(300, 300) }, - new HitCircle { Position = new Vector2(400, 400) }, - new HitCircle { Position = new Vector2(500, 500) }, - }, o => objects.Add(o)); - - removeObject(() => objects[2]); + assertGroups(); } [Test] public void TestMoveHitCircle() { - var objects = new List(); + addMultipleObjectsStep(); - AddStep("reset", () => objects.Clear()); + AddStep("move hitobject", () => getObject(2).HitObject.Position = new Vector2(300, 100)); - addObjects(() => new OsuHitObject[] - { - new HitCircle { Position = new Vector2(100, 100) }, - new HitCircle { Position = new Vector2(200, 200) }, - new HitCircle { Position = new Vector2(300, 300) }, - new HitCircle { Position = new Vector2(400, 400) }, - new HitCircle { Position = new Vector2(500, 500) }, - }, o => objects.Add(o)); - - AddStep("move hitobject", () => objects[2].HitObject.Position = new Vector2(300, 100)); + assertGroups(); } - private void addObjects(Func ctorFunc, Action storeFunc = null) + [TestCase(0, 0)] // Start -> Start + [TestCase(0, 2)] // Start -> Middle + [TestCase(0, 5)] // Start -> End + [TestCase(2, 0)] // Middle -> Start + [TestCase(1, 3)] // Middle -> Middle (forwards) + [TestCase(3, 1)] // Middle -> Middle (backwards) + [TestCase(4, 0)] // End -> Start + [TestCase(4, 2)] // End -> Middle + [TestCase(4, 4)] // End -> End + public void TestReorderHitObjects(int startIndex, int endIndex) + { + addMultipleObjectsStep(); + + reorderObjectStep(startIndex, endIndex); + + assertGroups(); + } + + private void addMultipleObjectsStep() => addObjectsStep(() => new OsuHitObject[] + { + new HitCircle { Position = new Vector2(100, 100) }, + new HitCircle { Position = new Vector2(200, 200) }, + new HitCircle { Position = new Vector2(300, 300) }, + new HitCircle { Position = new Vector2(400, 400) }, + new HitCircle { Position = new Vector2(500, 500) }, + }); + + private void addObjectsStep(Func ctorFunc) { AddStep("add hitobjects", () => { @@ -166,13 +153,11 @@ namespace osu.Game.Rulesets.Osu.Tests hitObjectContainer.Add(drawableObject); followPointRenderer.AddFollowPoints(drawableObject); - - storeFunc?.Invoke(drawableObject); } }); } - private void removeObject(Func getFunc) + private void removeObjectStep(Func getFunc) { AddStep("remove hitobject", () => { @@ -182,5 +167,64 @@ namespace osu.Game.Rulesets.Osu.Tests followPointRenderer.RemoveFollowPoints(drawableObject); }); } + + private void reorderObjectStep(int startIndex, int endIndex) + { + AddStep($"move object {startIndex} to {endIndex}", () => + { + DrawableOsuHitObject toReorder = getObject(startIndex); + + double targetTime; + if (endIndex < hitObjectContainer.Count) + targetTime = getObject(endIndex).HitObject.StartTime - 1; + else + targetTime = getObject(hitObjectContainer.Count - 1).HitObject.StartTime + 1; + + hitObjectContainer.Remove(toReorder); + toReorder.HitObject.StartTime = targetTime; + hitObjectContainer.Add(toReorder); + }); + } + + private void assertGroups() + { + AddAssert("has correct group count", () => followPointRenderer.Groups.Count == hitObjectContainer.Count); + AddAssert("group endpoints are correct", () => + { + for (int i = 0; i < hitObjectContainer.Count; i++) + { + DrawableOsuHitObject expectedStart = getObject(i); + DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null; + + if (getGroup(i).Start != expectedStart) + throw new AssertionException($"Object {i} expected to be the start of group {i}."); + + if (getGroup(i).End != expectedEnd) + throw new AssertionException($"Object {(expectedEnd == null ? "null" : i.ToString())} expected to be the end of group {i}."); + } + + return true; + }); + } + + private DrawableOsuHitObject getObject(int index) => hitObjectContainer[index]; + + private FollowPointGroup getGroup(int index) => followPointRenderer.Groups[index]; + + private class TestHitObjectContainer : Container + { + protected override int Compare(Drawable x, Drawable y) + { + var osuX = (DrawableOsuHitObject)x; + var osuY = (DrawableOsuHitObject)y; + + int compare = osuX.HitObject.StartTime.CompareTo(osuY.HitObject.StartTime); + + if (compare == 0) + return base.Compare(x, y); + + return compare; + } + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index e29cf6e128..e5e8efae8e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -11,6 +11,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { public class FollowPointRenderer : CompositeDrawable { + internal IReadOnlyList Groups => groups; + private readonly List groups = new List(); /// From d0286037696594660cc0641d94e68ca616ba9bdf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 22:50:01 +0900 Subject: [PATCH 2107/2815] Rename tests --- .../TestSceneFollowPoints.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index aac0119dc7..79a1bb579f 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Tests }); [Test] - public void TestAddSingleHitCircle() + public void TestAddObject() { addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestRemoveSingleHitCircle() + public void TestRemoveObject() { addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestAddMultipleHitCircles() + public void TestAddMultipleObjects() { addMultipleObjectsStep(); @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestRemoveEndHitCircle() + public void TestRemoveEndObject() { addMultipleObjectsStep(); @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestRemoveStartHitCircle() + public void TestRemoveStartObject() { addMultipleObjectsStep(); @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestRemoveMiddleHitCircle() + public void TestRemoveMiddleObject() { addMultipleObjectsStep(); @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestMoveHitCircle() + public void TestMoveObject() { addMultipleObjectsStep(); @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Osu.Tests [TestCase(4, 0)] // End -> Start [TestCase(4, 2)] // End -> Middle [TestCase(4, 4)] // End -> End - public void TestReorderHitObjects(int startIndex, int endIndex) + public void TestReorderObjects(int startIndex, int endIndex) { addMultipleObjectsStep(); From 68a81e0eb0f813fc3710ea09aa273d75ac2b8671 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 22:50:21 +0900 Subject: [PATCH 2108/2815] Fix follow point transforms not working after rewind --- .../Objects/Drawables/Connections/FollowPoint.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index 89ffddf4cb..7ba044b462 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections public override bool RemoveWhenNotAlive => false; + public override bool RemoveCompletedTransforms => false; + public FollowPoint() { Origin = Anchor.Centre; From d762ec959c9240594a46ba2228095fea4b7e10ca Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 22:50:38 +0900 Subject: [PATCH 2109/2815] Schedule group refresh when loaded --- .../Objects/Drawables/Connections/FollowPointGroup.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs index 47196f4893..c77ecd355f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs @@ -60,7 +60,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections if (end != null) bindEvents(end); - refresh(); + if (IsLoaded) + scheduleRefresh(); + else + refresh(); } } From 0a2af2b0fe067b3caece643b1acbdc29b427a823 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 23:02:39 +0900 Subject: [PATCH 2110/2815] Apply transform override at a higher level --- .../Objects/Drawables/Connections/FollowPoint.cs | 2 -- .../Objects/Drawables/Connections/FollowPointRenderer.cs | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index 7ba044b462..89ffddf4cb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -18,8 +18,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections public override bool RemoveWhenNotAlive => false; - public override bool RemoveCompletedTransforms => false; - public FollowPoint() { Origin = Anchor.Centre; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index e5e8efae8e..aa9ec8d96d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections private readonly List groups = new List(); + public override bool RemoveCompletedTransforms => false; + /// /// Adds the s around a . /// This includes s leading into , and s exiting . From aff275ea21784999ca29e37a695da2df430332a7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 23:03:05 +0900 Subject: [PATCH 2111/2815] Revert "Fix follow point lifetime not being updated correctly" This reverts commit 1ef2b81041dfd9225ffb7e792b8c55bfa76d6ff1. --- .../Drawables/Connections/FollowPointGroup.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs index c77ecd355f..9d651373ff 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs @@ -121,15 +121,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections using (fp.BeginAbsoluteSequence(fadeInTime)) { - // See: Expire calls are separated due to https://github.com/ppy/osu-framework/issues/2941 - - fp.FadeIn(osuEnd.TimeFadeIn).Expire(true); - - fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out) - .MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out) - .Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn) - .Expire(); + fp.FadeIn(osuEnd.TimeFadeIn); + fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out); + fp.MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out); + fp.Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn); } + + fp.Expire(true); } } } From 68ca5cb26a6b1f0d56ba258c0edb29afb808c646 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 23:20:46 +0900 Subject: [PATCH 2112/2815] Adjust comments --- .../Drawables/Connections/FollowPoint.cs | 3 +++ .../Drawables/Connections/FollowPointGroup.cs | 6 ++++++ .../Connections/FollowPointRenderer.cs | 19 ++++++++++++++----- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index 89ffddf4cb..db34ae1d87 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -12,6 +12,9 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { + /// + /// A single follow point positioned between two adjacent s. + /// public class FollowPoint : Container { private const float width = 8; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs index 9d651373ff..168d2b8532 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs @@ -11,12 +11,18 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { + /// + /// Visualises the s between two s. + /// public class FollowPointGroup : CompositeDrawable { // Todo: These shouldn't be constants private const int spacing = 32; private const double preempt = 800; + /// + /// The start time of . + /// public readonly Bindable StartTime = new Bindable(); /// diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index aa9ec8d96d..afd86e004d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -9,8 +9,14 @@ using osu.Framework.Graphics.Containers; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { + /// + /// Visualises groups of s. + /// public class FollowPointRenderer : CompositeDrawable { + /// + /// All the s contained by this . + /// internal IReadOnlyList Groups => groups; private readonly List groups = new List(); @@ -46,21 +52,24 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections if (index < groups.Count - 1) { - // Update the group's end point to the next hitobject + // Update the group's end point to the next group's start point // h1 -> -> -> h2 - // hitObject nextGroup + // group nextGroup FollowPointGroup nextGroup = groups[index + 1]; group.End = nextGroup.Start; } else + { + // The end point may be non-null during re-ordering group.End = null; + } if (index > 0) { - // Previous group's end point to the current group's start point + // Update the previous group's end point to the current group's start point // h1 -> -> -> h2 - // prevGroup hitObject + // prevGroup group FollowPointGroup previousGroup = groups[index - 1]; previousGroup.End = group.Start; @@ -95,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections private void onStartTimeChanged(FollowPointGroup group) { - // Naive but can be improved if performance becomes problematic + // Naive but can be improved if performance becomes an issue removeGroup(group); addGroup(group); } From 1f40641d05cc9a1da02d00eaf4ba14a30c367299 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 10:32:09 +0900 Subject: [PATCH 2113/2815] Add failing test --- osu.Game.Tests/Visual/Gameplay/TestScenePause.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 64022b2410..6e8975f11b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -237,6 +237,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("player not exited", () => Player.IsCurrentScreen()); AddStep("exit", () => Player.Exit()); confirmExited(); + confirmNoTrackAdjustments(); } private void confirmPaused() @@ -258,6 +259,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("player exited", () => !Player.IsCurrentScreen()); } + private void confirmNoTrackAdjustments() + { + AddAssert("track has no adjustments", () => Beatmap.Value.Track.AggregateFrequency.Value == 1); + } + private void restart() => AddStep("restart", () => Player.Restart()); private void pause() => AddStep("pause", () => Player.Pause()); private void resume() => AddStep("resume", () => Player.Resume()); From cd1dd0f8981d2ed7c5c03b67d401ebc1ec0b4a62 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 10:38:04 +0900 Subject: [PATCH 2114/2815] Fix adjustments not being removed correctly on retry from pause --- osu.Game/Screens/Play/GameplayClockContainer.cs | 16 ++++++++++------ osu.Game/Screens/Play/Player.cs | 2 -- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index f2efbe6073..2f2028ff53 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -162,16 +162,12 @@ namespace osu.Game.Screens.Play if (sourceClock != beatmap.Track) return; + removeSourceClockAdjustments(); + sourceClock = new TrackVirtual(beatmap.Track.Length); adjustableClock.ChangeSource(sourceClock); } - public void ResetLocalAdjustments() - { - // In the case of replays, we may have changed the playback rate. - UserPlaybackRate.Value = 1; - } - protected override void Update() { if (!IsPaused.Value) @@ -198,6 +194,14 @@ namespace osu.Game.Screens.Play protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); + + removeSourceClockAdjustments(); + sourceClock = null; + } + + private void removeSourceClockAdjustments() + { + sourceClock.ResetSpeedAdjustments(); (sourceClock as IAdjustableAudioComponent)?.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a3c39d9cc1..a9b0649fab 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -536,8 +536,6 @@ namespace osu.Game.Screens.Play return true; } - GameplayClockContainer.ResetLocalAdjustments(); - // GameplayClockContainer performs seeks / start / stop operations on the beatmap's track. // as we are no longer the current screen, we cannot guarantee the track is still usable. GameplayClockContainer.StopUsingBeatmapClock(); From 892ef017ef3b85ba184e35202a3eb14f59a51c09 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 6 Nov 2019 03:05:03 +0000 Subject: [PATCH 2115/2815] Bump ppy.osu.Framework.iOS from 2019.1029.0 to 2019.1106.0 Bumps [ppy.osu.Framework.iOS](https://github.com/ppy/osu-framework) from 2019.1029.0 to 2019.1106.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.1029.0...2019.1106.0) Signed-off-by: dependabot-preview[bot] --- osu.iOS.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.iOS.props b/osu.iOS.props index 719aced705..d257948831 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -119,7 +119,7 @@ - + From 1d83f51a804f9a0972455433666f42219ed057c9 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 6 Nov 2019 03:05:05 +0000 Subject: [PATCH 2116/2815] Bump ppy.osu.Framework.Android from 2019.1029.0 to 2019.1106.0 Bumps [ppy.osu.Framework.Android](https://github.com/ppy/osu-framework) from 2019.1029.0 to 2019.1106.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.1029.0...2019.1106.0) Signed-off-by: dependabot-preview[bot] --- osu.Android.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Android.props b/osu.Android.props index 8b31be3f12..d1860acbf9 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -62,6 +62,6 @@ - + From 8cf349c1eefa1996a1d5f9446181150bcf574ae8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 12:10:53 +0900 Subject: [PATCH 2117/2815] Update once more --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 42b930b1af..dbe6559c40 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + From f20bfe7a554849d6d1fc762741e0fd4f084a4e07 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 12:16:48 +0900 Subject: [PATCH 2118/2815] Fix extra semicolon --- osu.Game/Screens/Edit/Timing/DifficultySection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs index bd363c3158..cd33425ee5 100644 --- a/osu.Game/Screens/Edit/Timing/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Timing/DifficultySection.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier::0.##}x"; }); + ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier:0.##}x"; }); } protected override DifficultyControlPoint CreatePoint() From 9dd7f997d2c01836a43ad9630261e095e14a8c36 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 12:17:18 +0900 Subject: [PATCH 2119/2815] Reoder SampleSection to match others --- osu.Game/Screens/Edit/Timing/SampleSection.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs index 0477ad4e78..e665db0a4d 100644 --- a/osu.Game/Screens/Edit/Timing/SampleSection.cs +++ b/osu.Game/Screens/Edit/Timing/SampleSection.cs @@ -22,17 +22,6 @@ namespace osu.Game.Screens.Edit.Timing }); } - protected override SampleControlPoint CreatePoint() - { - var reference = Beatmap.Value.Beatmap.ControlPointInfo.SamplePointAt(SelectedGroup.Value.Time); - - return new SampleControlPoint - { - SampleBank = reference.SampleBank, - SampleVolume = reference.SampleVolume, - }; - } - protected override void LoadComplete() { base.LoadComplete(); @@ -43,5 +32,16 @@ namespace osu.Game.Screens.Edit.Timing volume.Text = $"Volume: {point.NewValue?.SampleVolume}%"; }); } + + protected override SampleControlPoint CreatePoint() + { + var reference = Beatmap.Value.Beatmap.ControlPointInfo.SamplePointAt(SelectedGroup.Value.Time); + + return new SampleControlPoint + { + SampleBank = reference.SampleBank, + SampleVolume = reference.SampleVolume, + }; + } } } From 7cd4cb8a935c797fd0d977198b99f7515bc225e8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 12:32:12 +0900 Subject: [PATCH 2120/2815] Rename selectedPoints to selectedGroup --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 6ae01e056f..9317448262 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -20,7 +20,7 @@ namespace osu.Game.Screens.Edit.Timing public class TimingScreen : EditorScreenWithTimeline { [Cached] - private Bindable selectedPoints = new Bindable(); + private Bindable selectedGroup = new Bindable(); [Resolved] private IAdjustableClock clock { get; set; } @@ -47,7 +47,7 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - selectedPoints.BindValueChanged(selected => { clock.Seek(selected.NewValue.Time); }); + selectedGroup.BindValueChanged(selected => { clock.Seek(selected.NewValue.Time); }); } public class ControlPointList : CompositeDrawable From 322a1f0a860eff545c166426e026f14da48437f3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 12:45:35 +0900 Subject: [PATCH 2121/2815] Fix potential nullref --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 9317448262..d9da3ff92d 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -47,7 +47,11 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - selectedGroup.BindValueChanged(selected => { clock.Seek(selected.NewValue.Time); }); + selectedGroup.BindValueChanged(selected => + { + if (selected.NewValue != null) + clock.Seek(selected.NewValue.Time); + }); } public class ControlPointList : CompositeDrawable From 4ce3450cfc6a003c3ff220d6f57cd566d49613f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 14:08:52 +0900 Subject: [PATCH 2122/2815] Move button implementation to OsuButton --- .../Editor/TestSceneEditorComposeTimeline.cs | 4 +- osu.Game/Graphics/UserInterface/OsuButton.cs | 106 ++++++++++++++---- osu.Game/Overlays/Settings/SidebarButton.cs | 28 +---- osu.Game/Overlays/SettingsPanel.cs | 4 +- 4 files changed, 87 insertions(+), 55 deletions(-) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs index b6b2b2f92d..6e5b3b93e9 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs @@ -10,9 +10,9 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; using osuTK.Graphics; @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.Editor } } - private class StartStopButton : BasicButton + private class StartStopButton : OsuButton { private IAdjustableClock adjustableClock; private bool started; diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index 319c3dd0b4..2750e61f0d 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -1,10 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; 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.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; @@ -17,55 +19,106 @@ namespace osu.Game.Graphics.UserInterface /// /// A button with added default sound effects. /// - public class OsuButton : BasicButton + public class OsuButton : Button { - private Box hover; + public string Text + { + get => SpriteText?.Text; + set + { + if (SpriteText != null) + SpriteText.Text = value; + } + } + + private Color4? backgroundColour; + + public Color4 BackgroundColour + { + set + { + backgroundColour = value; + Background.FadeColour(value); + } + } + + protected override Container Content { get; } + + protected Box Hover; + protected Box Background; + protected SpriteText SpriteText; public OsuButton() { Height = 40; - Content.Masking = true; - Content.CornerRadius = 5; + AddInternal(Content = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + CornerRadius = 5, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + Background = new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + }, + Hover = new Box + { + Alpha = 0, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Colour = Color4.White.Opacity(.1f), + Blending = BlendingParameters.Additive, + Depth = float.MinValue + }, + SpriteText = CreateText(), + new HoverClickSounds(HoverSampleSet.Loud), + } + }); + + Enabled.BindValueChanged(enabledChanged, true); } [BackgroundDependencyLoader] private void load(OsuColour colours) { - BackgroundColour = colours.BlueDark; - - AddRange(new Drawable[] - { - hover = new Box - { - RelativeSizeAxes = Axes.Both, - Blending = BlendingParameters.Additive, - Colour = Color4.White.Opacity(0.1f), - Alpha = 0, - Depth = -1 - }, - new HoverClickSounds(HoverSampleSet.Loud), - }); + if (backgroundColour == null) + BackgroundColour = colours.BlueDark; Enabled.ValueChanged += enabledChanged; Enabled.TriggerChange(); } - private void enabledChanged(ValueChangedEvent e) + protected override bool OnClick(ClickEvent e) { - this.FadeColour(e.NewValue ? Color4.White : Color4.Gray, 200, Easing.OutQuint); + if (Enabled.Value) + { + Debug.Assert(backgroundColour != null); + Background.FlashColour(backgroundColour.Value, 200); + } + + return base.OnClick(e); } protected override bool OnHover(HoverEvent e) { - hover.FadeIn(200); - return true; + if (Enabled.Value) + Hover.FadeIn(200, Easing.OutQuint); + + return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - hover.FadeOut(200); base.OnHoverLost(e); + + Hover.FadeOut(300); } protected override bool OnMouseDown(MouseDownEvent e) @@ -80,12 +133,17 @@ namespace osu.Game.Graphics.UserInterface return base.OnMouseUp(e); } - protected override SpriteText CreateText() => new OsuSpriteText + protected virtual SpriteText CreateText() => new OsuSpriteText { Depth = -1, Origin = Anchor.Centre, Anchor = Anchor.Centre, Font = OsuFont.GetFont(weight: FontWeight.Bold) }; + + private void enabledChanged(ValueChangedEvent e) + { + this.FadeColour(e.NewValue ? Color4.White : Color4.Gray, 200, Easing.OutQuint); + } } } diff --git a/osu.Game/Overlays/Settings/SidebarButton.cs b/osu.Game/Overlays/Settings/SidebarButton.cs index cdb4dc463a..5930c89d29 100644 --- a/osu.Game/Overlays/Settings/SidebarButton.cs +++ b/osu.Game/Overlays/Settings/SidebarButton.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 osuTK; using osuTK.Graphics; using osu.Framework.Allocation; @@ -9,21 +8,18 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings { - public class SidebarButton : BasicButton + public class SidebarButton : OsuButton { private readonly SpriteIcon drawableIcon; private readonly SpriteText headerText; private readonly Box selectionIndicator; private readonly Container text; - public new Action Action; private SettingsSection section; @@ -62,9 +58,6 @@ namespace osu.Game.Overlays.Settings public SidebarButton() { - BackgroundColour = OsuColour.Gray(60); - Background.Alpha = 0; - Height = Sidebar.DEFAULT_WIDTH; RelativeSizeAxes = Axes.X; @@ -99,7 +92,6 @@ namespace osu.Game.Overlays.Settings Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, }, - new HoverClickSounds(HoverSampleSet.Loud), }); } @@ -108,23 +100,5 @@ namespace osu.Game.Overlays.Settings { selectionIndicator.Colour = colours.Yellow; } - - protected override bool OnClick(ClickEvent e) - { - Action?.Invoke(section); - return base.OnClick(e); - } - - protected override bool OnHover(HoverEvent e) - { - Background.FadeTo(0.4f, 200); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - Background.FadeTo(0, 200); - base.OnHoverLost(e); - } } } diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index d028664fe0..2948231c4b 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -123,9 +123,9 @@ namespace osu.Game.Overlays var button = new SidebarButton { Section = section, - Action = s => + Action = () => { - SectionsContainer.ScrollTo(s); + SectionsContainer.ScrollTo(section); Sidebar.State = ExpandedState.Contracted; }, }; From ebfb5d050d740e9df7ecd1d14d6ce716d57039c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 14:36:43 +0900 Subject: [PATCH 2123/2815] Move section update code to abstract method to avoid incorrect BindValue usage --- osu.Game/Screens/Edit/Timing/DifficultySection.cs | 7 +++---- osu.Game/Screens/Edit/Timing/EffectSection.cs | 12 ++++-------- osu.Game/Screens/Edit/Timing/SampleSection.cs | 12 ++++-------- osu.Game/Screens/Edit/Timing/Section.cs | 4 ++++ osu.Game/Screens/Edit/Timing/TimingSection.cs | 12 ++++-------- 5 files changed, 19 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs index cd33425ee5..05e45014eb 100644 --- a/osu.Game/Screens/Edit/Timing/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Timing/DifficultySection.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; @@ -20,11 +21,9 @@ namespace osu.Game.Screens.Edit.Timing }); } - protected override void LoadComplete() + protected override void OnControlPointChanged(ValueChangedEvent point) { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier:0.##}x"; }); + multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier:0.##}x"; } protected override DifficultyControlPoint CreatePoint() diff --git a/osu.Game/Screens/Edit/Timing/EffectSection.cs b/osu.Game/Screens/Edit/Timing/EffectSection.cs index bafdfd0b0d..2b37ffaaaf 100644 --- a/osu.Game/Screens/Edit/Timing/EffectSection.cs +++ b/osu.Game/Screens/Edit/Timing/EffectSection.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; @@ -22,15 +23,10 @@ namespace osu.Game.Screens.Edit.Timing }); } - protected override void LoadComplete() + protected override void OnControlPointChanged(ValueChangedEvent point) { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => - { - kiai.Text = $"Kiai: {(point.NewValue?.KiaiMode == true ? "on" : "off")}"; - omitBarLine.Text = $"Skip Bar Line: {(point.NewValue?.OmitFirstBarLine == true ? "on" : "off")}"; - }); + kiai.Text = $"Kiai: {(point.NewValue?.KiaiMode == true ? "on" : "off")}"; + omitBarLine.Text = $"Skip Bar Line: {(point.NewValue?.OmitFirstBarLine == true ? "on" : "off")}"; } protected override EffectControlPoint CreatePoint() diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs index e665db0a4d..1bedb1ff42 100644 --- a/osu.Game/Screens/Edit/Timing/SampleSection.cs +++ b/osu.Game/Screens/Edit/Timing/SampleSection.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; @@ -22,15 +23,10 @@ namespace osu.Game.Screens.Edit.Timing }); } - protected override void LoadComplete() + protected override void OnControlPointChanged(ValueChangedEvent point) { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => - { - bank.Text = $"Bank: {point.NewValue?.SampleBank}"; - volume.Text = $"Volume: {point.NewValue?.SampleVolume}%"; - }); + bank.Text = $"Bank: {point.NewValue?.SampleBank}"; + volume.Text = $"Volume: {point.NewValue?.SampleVolume}%"; } protected override SampleControlPoint CreatePoint() diff --git a/osu.Game/Screens/Edit/Timing/Section.cs b/osu.Game/Screens/Edit/Timing/Section.cs index c6140ff497..ccf1582486 100644 --- a/osu.Game/Screens/Edit/Timing/Section.cs +++ b/osu.Game/Screens/Edit/Timing/Section.cs @@ -119,8 +119,12 @@ namespace osu.Game.Screens.Edit.Timing ControlPoint.Value = points.NewValue?.ControlPoints.OfType().FirstOrDefault(); checkbox.Current.Value = ControlPoint.Value != null; }, true); + + ControlPoint.BindValueChanged(OnControlPointChanged, true); } + protected abstract void OnControlPointChanged(ValueChangedEvent point); + protected abstract T CreatePoint(); } } diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 8609da4c4d..15007ffdb3 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; @@ -22,15 +23,10 @@ namespace osu.Game.Screens.Edit.Timing }); } - protected override void LoadComplete() + protected override void OnControlPointChanged(ValueChangedEvent point) { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => - { - bpm.Text = $"BPM: {point.NewValue?.BPM:0.##}"; - timeSignature.Text = $"Signature: {point.NewValue?.TimeSignature}"; - }); + bpm.Text = $"BPM: {point.NewValue?.BPM:0.##}"; + timeSignature.Text = $"Signature: {point.NewValue?.TimeSignature}"; } protected override TimingControlPoint CreatePoint() From 5e416bd18d5dc77b458cc1d1d59db2372d1b6ec8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 14:47:31 +0900 Subject: [PATCH 2124/2815] Fix tournament client crashing if beatmap added with an ID of zero --- osu.Game.Tournament/TournamentGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index dbfa70704b..21552882ef 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -215,7 +215,7 @@ namespace osu.Game.Tournament foreach (var r in ladder.Rounds) foreach (var b in r.Beatmaps) - if (b.BeatmapInfo == null) + if (b.BeatmapInfo == null && b.ID > 0) { var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID }); req.Perform(API); From 0b09fb293e0ad9bea8657a84e19dd135c1eb566d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 15:09:08 +0900 Subject: [PATCH 2125/2815] Fix background being coloured --- osu.Game/Overlays/Settings/SidebarButton.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Settings/SidebarButton.cs b/osu.Game/Overlays/Settings/SidebarButton.cs index 5930c89d29..68836bc6b3 100644 --- a/osu.Game/Overlays/Settings/SidebarButton.cs +++ b/osu.Game/Overlays/Settings/SidebarButton.cs @@ -61,6 +61,8 @@ namespace osu.Game.Overlays.Settings Height = Sidebar.DEFAULT_WIDTH; RelativeSizeAxes = Axes.X; + BackgroundColour = Color4.Black; + AddRange(new Drawable[] { text = new Container From 020b08b4500c811702380b2c3286427a36d2e3b3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 14:55:05 +0900 Subject: [PATCH 2126/2815] Initial implementation of limited distance snap --- .../TestSceneOsuDistanceSnapGrid.cs | 2 +- .../Edit/OsuDistanceSnapGrid.cs | 4 +-- .../Edit/OsuHitObjectComposer.cs | 33 ++++++++++++------- .../Editor/TestSceneDistanceSnapGrid.cs | 2 +- .../Components/CircularDistanceSnapGrid.cs | 12 ++++--- .../Compose/Components/DistanceSnapGrid.cs | 21 +++++++++++- 6 files changed, 52 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs index a9a6097182..aba1d8fa1d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs @@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Osu.Tests public new float DistanceSpacing => base.DistanceSpacing; public TestOsuDistanceSnapGrid(OsuHitObject hitObject) - : base(hitObject) + : base(hitObject, null) { } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs index 79cd51a7f4..9b00204d51 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs @@ -8,8 +8,8 @@ namespace osu.Game.Rulesets.Osu.Edit { public class OsuDistanceSnapGrid : CircularDistanceSnapGrid { - public OsuDistanceSnapGrid(OsuHitObject hitObject) - : base(hitObject, hitObject.StackedEndPosition) + public OsuDistanceSnapGrid(OsuHitObject hitObject, OsuHitObject nextHitObject) + : base(hitObject, nextHitObject, hitObject.StackedEndPosition) { Masking = true; } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index fcf2772219..a021f70598 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.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 osu.Game.Beatmaps; @@ -60,25 +61,33 @@ namespace osu.Game.Rulesets.Osu.Edit var objects = selectedHitObjects.ToList(); if (objects.Count == 0) + return createGrid(h => h.StartTime <= EditorClock.CurrentTime); + + double minTime = objects.Min(h => h.StartTime); + return createGrid(h => h.StartTime < minTime); + } + + private OsuDistanceSnapGrid createGrid(Func hitObjectSelector) + { + int lastIndex = -1; + + for (int i = 0; i < EditorBeatmap.HitObjects.Count; i++) { - var lastObject = EditorBeatmap.HitObjects.LastOrDefault(h => h.StartTime <= EditorClock.CurrentTime); + HitObject hitObject = EditorBeatmap.HitObjects[i]; - if (lastObject == null) - return null; + if (!hitObjectSelector(hitObject)) + break; - return new OsuDistanceSnapGrid(lastObject); + lastIndex = i; } - else - { - double minTime = objects.Min(h => h.StartTime); - var lastObject = EditorBeatmap.HitObjects.LastOrDefault(h => h.StartTime < minTime); + if (lastIndex == -1) + return null; - if (lastObject == null) - return null; + OsuHitObject lastObject = EditorBeatmap.HitObjects[lastIndex]; + OsuHitObject nextObject = lastIndex == EditorBeatmap.HitObjects.Count - 1 ? null : EditorBeatmap.HitObjects[lastIndex + 1]; - return new OsuDistanceSnapGrid(lastObject); - } + return new OsuDistanceSnapGrid(lastObject, nextObject); } } } diff --git a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs index b8c31d5dbb..0681dff528 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Editor public new float DistanceSpacing => base.DistanceSpacing; public TestDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) - : base(hitObject, centrePosition) + : base(hitObject, null, centrePosition) { } diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index f45115e1e4..0f2bae6305 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -12,8 +12,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { public abstract class CircularDistanceSnapGrid : DistanceSnapGrid { - protected CircularDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) - : base(hitObject, centrePosition) + protected CircularDistanceSnapGrid(HitObject hitObject, HitObject nextHitObject, Vector2 centrePosition) + : base(hitObject, nextHitObject, centrePosition) { } @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Edit.Compose.Components float dx = Math.Max(centrePosition.X, DrawWidth - centrePosition.X); float dy = Math.Max(centrePosition.Y, DrawHeight - centrePosition.Y); float maxDistance = new Vector2(dx, dy).Length; - int requiredCircles = (int)(maxDistance / DistanceSpacing); + int requiredCircles = Math.Min(MaxIntervals, (int)(maxDistance / DistanceSpacing)); for (int i = 0; i < requiredCircles; i++) { @@ -65,15 +65,17 @@ namespace osu.Game.Screens.Edit.Compose.Components public override (Vector2 position, double time) GetSnappedPosition(Vector2 position) { - Vector2 direction = position - CentrePosition; + if (MaxIntervals == 0) + return (CentrePosition, StartTime); + Vector2 direction = position - CentrePosition; if (direction == Vector2.Zero) direction = new Vector2(0.001f, 0.001f); float distance = direction.Length; float radius = DistanceSpacing; - int radialCount = Math.Max(1, (int)Math.Round(distance / radius)); + int radialCount = MathHelper.Clamp((int)Math.Round(distance / radius), 1, MaxIntervals); Vector2 normalisedDirection = direction * new Vector2(1f / distance); Vector2 snappedPosition = CentrePosition + normalisedDirection * radialCount * radius; diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 193474093f..475b6e7274 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.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.Caching; using osu.Framework.Graphics; @@ -29,6 +30,11 @@ namespace osu.Game.Screens.Edit.Compose.Components /// protected double StartTime { get; private set; } + /// + /// The maximum number of distance snapping intervals allowed. + /// + protected int MaxIntervals { get; private set; } + /// /// The position which the grid is centred on. /// The first beat snapping tick is located at + in the desired direction. @@ -49,12 +55,15 @@ namespace osu.Game.Screens.Edit.Compose.Components private readonly Cached gridCache = new Cached(); private readonly HitObject hitObject; + private readonly HitObject nextHitObject; - protected DistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) + protected DistanceSnapGrid(HitObject hitObject, [CanBeNull] HitObject nextHitObject, Vector2 centrePosition) { this.hitObject = hitObject; + this.nextHitObject = nextHitObject; CentrePosition = centrePosition; + RelativeSizeAxes = Axes.Both; } @@ -74,6 +83,16 @@ namespace osu.Game.Screens.Edit.Compose.Components private void updateSpacing() { DistanceSpacing = SnapProvider.GetBeatSnapDistanceAt(StartTime); + + if (nextHitObject == null) + MaxIntervals = int.MaxValue; + else + { + // +1 is added since a snapped hitobject may have its start time slightly less than the snapped time due to floating point errors + double maxDuration = nextHitObject.StartTime - StartTime + 1; + MaxIntervals = (int)(maxDuration / SnapProvider.DistanceToDuration(StartTime, DistanceSpacing)); + } + gridCache.Invalidate(); } From c1a6cb1defaa7fc0008f1de0db821d3faf253fdb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 15:58:47 +0900 Subject: [PATCH 2127/2815] Fix audio preview muting game audio indefinitely when beatmap panel is off-screen --- osu.Game/Audio/PreviewTrack.cs | 5 ++++- osu.Game/Audio/PreviewTrackManager.cs | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Audio/PreviewTrack.cs b/osu.Game/Audio/PreviewTrack.cs index 22ce7d4711..937ad7e45a 100644 --- a/osu.Game/Audio/PreviewTrack.cs +++ b/osu.Game/Audio/PreviewTrack.cs @@ -13,11 +13,13 @@ namespace osu.Game.Audio { /// /// Invoked when this has stopped playing. + /// Not invoked in a thread-safe context. /// public event Action Stopped; /// /// Invoked when this has started playing. + /// Not invoked in a thread-safe context. /// public event Action Started; @@ -29,7 +31,7 @@ namespace osu.Game.Audio { track = GetTrack(); if (track != null) - track.Completed += () => Schedule(Stop); + track.Completed += Stop; } /// @@ -93,6 +95,7 @@ namespace osu.Game.Audio hasStarted = false; track.Stop(); + Stopped?.Invoke(); } diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index e12c46ef16..fad2b5a5e8 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -46,18 +46,18 @@ namespace osu.Game.Audio { var track = CreatePreviewTrack(beatmapSetInfo, trackStore); - track.Started += () => + track.Started += () => Schedule(() => { current?.Stop(); current = track; audio.Tracks.AddAdjustment(AdjustableProperty.Volume, muteBindable); - }; + }); - track.Stopped += () => + track.Stopped += () => Schedule(() => { current = null; audio.Tracks.RemoveAdjustment(AdjustableProperty.Volume, muteBindable); - }; + }); return track; } From 2588534eda26d2a00f6cf070f0d150a9ee556eb1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 16:04:20 +0900 Subject: [PATCH 2128/2815] Skin entire selection, add xmldocs --- .../Edit/OsuHitObjectComposer.cs | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index a021f70598..812afaaa24 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -64,30 +64,37 @@ namespace osu.Game.Rulesets.Osu.Edit return createGrid(h => h.StartTime <= EditorClock.CurrentTime); double minTime = objects.Min(h => h.StartTime); - return createGrid(h => h.StartTime < minTime); + return createGrid(h => h.StartTime < minTime, objects.Count + 1); } - private OsuDistanceSnapGrid createGrid(Func hitObjectSelector) + /// + /// Creates a grid from the last matching a predicate to a target . + /// + /// A predicate that matches s where the grid can start from. + /// Only the last matching the predicate is used. + /// An offset from the selected via at which the grid should stop. + /// The from a selected to a target . + private OsuDistanceSnapGrid createGrid(Func sourceSelector, int targetOffset = 1) { - int lastIndex = -1; + if (targetOffset < 1) throw new ArgumentOutOfRangeException(nameof(targetOffset)); + + int sourceIndex = -1; for (int i = 0; i < EditorBeatmap.HitObjects.Count; i++) { - HitObject hitObject = EditorBeatmap.HitObjects[i]; - - if (!hitObjectSelector(hitObject)) + if (!sourceSelector(EditorBeatmap.HitObjects[i])) break; - lastIndex = i; + sourceIndex = i; } - if (lastIndex == -1) + if (sourceIndex == -1) return null; - OsuHitObject lastObject = EditorBeatmap.HitObjects[lastIndex]; - OsuHitObject nextObject = lastIndex == EditorBeatmap.HitObjects.Count - 1 ? null : EditorBeatmap.HitObjects[lastIndex + 1]; + OsuHitObject sourceObject = EditorBeatmap.HitObjects[sourceIndex]; + OsuHitObject targetObject = sourceIndex + targetOffset < EditorBeatmap.HitObjects.Count ? EditorBeatmap.HitObjects[sourceIndex + targetOffset] : null; - return new OsuDistanceSnapGrid(lastObject, nextObject); + return new OsuDistanceSnapGrid(sourceObject, targetObject); } } } From d985d048574ef01800fd3973d65583e273d4c45a Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Wed, 6 Nov 2019 14:07:05 +0700 Subject: [PATCH 2129/2815] Add background colour to music player ProgressBar --- osu.Game/Overlays/NowPlayingOverlay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 6b79f2af07..8c7e717a82 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -174,6 +174,7 @@ namespace osu.Game.Overlays Anchor = Anchor.BottomCentre, Height = progress_height, FillColour = colours.Yellow, + BackgroundColour = colours.YellowDarker.Opacity(0.5f), OnSeek = musicController.SeekTo } }, From 02c21a1379e24a7ec2bb4c2b47c2c6efbcd0a103 Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Wed, 6 Nov 2019 14:11:47 +0700 Subject: [PATCH 2130/2815] Make progress bar hoverable --- osu.Game/Overlays/NowPlayingOverlay.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 8c7e717a82..4f73cbfacd 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -168,11 +168,11 @@ namespace osu.Game.Overlays }, } }, - progressBar = new ProgressBar + progressBar = new HoverableProgressBar { Origin = Anchor.BottomCentre, Anchor = Anchor.BottomCentre, - Height = progress_height, + Height = progress_height / 2, FillColour = colours.Yellow, BackgroundColour = colours.YellowDarker.Opacity(0.5f), OnSeek = musicController.SeekTo @@ -402,5 +402,20 @@ namespace osu.Game.Overlays return base.OnDragEnd(e); } } + + private class HoverableProgressBar : ProgressBar + { + protected override bool OnHover(HoverEvent e) + { + this.ResizeHeightTo(progress_height, 500, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + this.ResizeHeightTo(progress_height / 2, 500, Easing.OutQuint); + base.OnHoverLost(e); + } + } } } From b83ceab1c1b2ba594efa02d166f364a32142e4ac Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 16:20:13 +0900 Subject: [PATCH 2131/2815] Add tests --- .../TestSceneOsuDistanceSnapGrid.cs | 47 ++++++++++++++----- .../Editor/TestSceneDistanceSnapGrid.cs | 39 +++++++++++---- 2 files changed, 64 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs index aba1d8fa1d..eff4d919b0 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs @@ -42,11 +42,19 @@ namespace osu.Game.Rulesets.Osu.Tests [Cached(typeof(IDistanceSnapProvider))] private readonly SnapProvider snapProvider = new SnapProvider(); - private readonly TestOsuDistanceSnapGrid grid; + private TestOsuDistanceSnapGrid grid; public TestSceneOsuDistanceSnapGrid() { editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + } + + [SetUp] + public void Setup() => Schedule(() => + { + editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1; + editorBeatmap.ControlPointInfo.Clear(); + editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); Children = new Drawable[] { @@ -58,14 +66,6 @@ namespace osu.Game.Rulesets.Osu.Tests grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }), new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position } }; - } - - [SetUp] - public void Setup() => Schedule(() => - { - editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1; - editorBeatmap.ControlPointInfo.Clear(); - editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); }); [TestCase(1)] @@ -102,6 +102,27 @@ namespace osu.Game.Rulesets.Osu.Tests assertSnappedDistance((float)beat_length * 2); } + [Test] + public void TestLimitedDistance() + { + AddStep("create limited grid", () => + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.SlateGray + }, + grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }, new HitCircle { StartTime = 200 }), + new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position } + }; + }); + + AddStep("move mouse outside grid", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 3f))); + assertSnappedDistance((float)beat_length * 2); + } + private void assertSnappedDistance(float expectedDistance) => AddAssert($"snap distance = {expectedDistance}", () => { Vector2 snappedPosition = grid.GetSnappedPosition(grid.ToLocalSpace(InputManager.CurrentState.Mouse.Position)).position; @@ -152,8 +173,8 @@ namespace osu.Game.Rulesets.Osu.Tests { public new float DistanceSpacing => base.DistanceSpacing; - public TestOsuDistanceSnapGrid(OsuHitObject hitObject) - : base(hitObject, null) + public TestOsuDistanceSnapGrid(OsuHitObject hitObject, OsuHitObject nextHitObject = null) + : base(hitObject, nextHitObject) { } } @@ -164,9 +185,9 @@ namespace osu.Game.Rulesets.Osu.Tests public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length; - public float DurationToDistance(double referenceTime, double duration) => 0; + public float DurationToDistance(double referenceTime, double duration) => (float)duration; - public double DistanceToDuration(double referenceTime, float distance) => 0; + public double DistanceToDuration(double referenceTime, float distance) => distance; public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0; diff --git a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs index 0681dff528..e4c987923c 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs @@ -32,7 +32,11 @@ namespace osu.Game.Tests.Visual.Editor { editorBeatmap = new EditorBeatmap(new OsuBeatmap()); editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); + } + [SetUp] + public void Setup() => Schedule(() => + { Children = new Drawable[] { new Box @@ -42,7 +46,7 @@ namespace osu.Game.Tests.Visual.Editor }, new TestDistanceSnapGrid(new HitObject(), grid_position) }; - } + }); [TestCase(1)] [TestCase(2)] @@ -57,12 +61,29 @@ namespace osu.Game.Tests.Visual.Editor AddStep($"set beat divisor = {divisor}", () => BeatDivisor.Value = divisor); } + [Test] + public void TestLimitedDistance() + { + AddStep("create limited grid", () => + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.SlateGray + }, + new TestDistanceSnapGrid(new HitObject(), grid_position, new HitObject { StartTime = 100 }) + }; + }); + } + private class TestDistanceSnapGrid : DistanceSnapGrid { public new float DistanceSpacing => base.DistanceSpacing; - public TestDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) - : base(hitObject, null, centrePosition) + public TestDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition, HitObject nextHitObject = null) + : base(hitObject, nextHitObject, centrePosition) { } @@ -77,7 +98,7 @@ namespace osu.Game.Tests.Visual.Editor int beatIndex = 0; - for (float s = centrePosition.X + DistanceSpacing; s <= DrawWidth; s += DistanceSpacing, beatIndex++) + for (float s = centrePosition.X + DistanceSpacing; s <= DrawWidth && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++) { AddInternal(new Circle { @@ -90,7 +111,7 @@ namespace osu.Game.Tests.Visual.Editor beatIndex = 0; - for (float s = centrePosition.X - DistanceSpacing; s >= 0; s -= DistanceSpacing, beatIndex++) + for (float s = centrePosition.X - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++) { AddInternal(new Circle { @@ -103,7 +124,7 @@ namespace osu.Game.Tests.Visual.Editor beatIndex = 0; - for (float s = centrePosition.Y + DistanceSpacing; s <= DrawHeight; s += DistanceSpacing, beatIndex++) + for (float s = centrePosition.Y + DistanceSpacing; s <= DrawHeight && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++) { AddInternal(new Circle { @@ -116,7 +137,7 @@ namespace osu.Game.Tests.Visual.Editor beatIndex = 0; - for (float s = centrePosition.Y - DistanceSpacing; s >= 0; s -= DistanceSpacing, beatIndex++) + for (float s = centrePosition.Y - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++) { AddInternal(new Circle { @@ -138,9 +159,9 @@ namespace osu.Game.Tests.Visual.Editor public float GetBeatSnapDistanceAt(double referenceTime) => 10; - public float DurationToDistance(double referenceTime, double duration) => 0; + public float DurationToDistance(double referenceTime, double duration) => (float)duration; - public double DistanceToDuration(double referenceTime, float distance) => 0; + public double DistanceToDuration(double referenceTime, float distance) => distance; public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0; From ee544e174a3971716fcd9a6ea0f284c2cb213da3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 16:33:42 +0900 Subject: [PATCH 2132/2815] Group -> Connection --- .../TestSceneFollowPoints.cs | 4 +- ...PointGroup.cs => FollowPointConnection.cs} | 6 +- .../Connections/FollowPointRenderer.cs | 76 +++++++++---------- 3 files changed, 43 insertions(+), 43 deletions(-) rename osu.Game.Rulesets.Osu/Objects/Drawables/Connections/{FollowPointGroup.cs => FollowPointConnection.cs} (95%) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index 79a1bb579f..94ca2d4cd1 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -188,7 +188,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void assertGroups() { - AddAssert("has correct group count", () => followPointRenderer.Groups.Count == hitObjectContainer.Count); + AddAssert("has correct group count", () => followPointRenderer.Connections.Count == hitObjectContainer.Count); AddAssert("group endpoints are correct", () => { for (int i = 0; i < hitObjectContainer.Count; i++) @@ -209,7 +209,7 @@ namespace osu.Game.Rulesets.Osu.Tests private DrawableOsuHitObject getObject(int index) => hitObjectContainer[index]; - private FollowPointGroup getGroup(int index) => followPointRenderer.Groups[index]; + private FollowPointConnection getGroup(int index) => followPointRenderer.Connections[index]; private class TestHitObjectContainer : Container { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs similarity index 95% rename from osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 168d2b8532..1e032eb977 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections /// /// Visualises the s between two s. /// - public class FollowPointGroup : CompositeDrawable + public class FollowPointConnection : CompositeDrawable { // Todo: These shouldn't be constants private const int spacing = 32; @@ -32,10 +32,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections public readonly DrawableOsuHitObject Start; /// - /// Creates a new . + /// Creates a new . /// /// The which s will exit from. - public FollowPointGroup([NotNull] DrawableOsuHitObject start) + public FollowPointConnection([NotNull] DrawableOsuHitObject start) { Start = start; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index afd86e004d..00ef0ba0d0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -10,16 +10,16 @@ using osu.Framework.Graphics.Containers; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { /// - /// Visualises groups of s. + /// Visualises connections between s. /// public class FollowPointRenderer : CompositeDrawable { /// - /// All the s contained by this . + /// All the s contained by this . /// - internal IReadOnlyList Groups => groups; + internal IReadOnlyList Connections => connections; - private readonly List groups = new List(); + private readonly List connections = new List(); public override bool RemoveCompletedTransforms => false; @@ -29,84 +29,84 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections /// /// The to add s for. public void AddFollowPoints(DrawableOsuHitObject hitObject) - => addGroup(new FollowPointGroup(hitObject).With(g => g.StartTime.BindValueChanged(_ => onStartTimeChanged(g)))); + => addConnection(new FollowPointConnection(hitObject).With(g => g.StartTime.BindValueChanged(_ => onStartTimeChanged(g)))); /// /// Removes the s around a . /// This includes s leading into , and s exiting . /// /// The to remove s for. - public void RemoveFollowPoints(DrawableOsuHitObject hitObject) => removeGroup(groups.Single(g => g.Start == hitObject)); + public void RemoveFollowPoints(DrawableOsuHitObject hitObject) => removeGroup(connections.Single(g => g.Start == hitObject)); /// - /// Adds a to this . + /// Adds a to this . /// - /// The to add. - /// The index of in . - private int addGroup(FollowPointGroup group) + /// The to add. + /// The index of in . + private int addConnection(FollowPointConnection connection) { - AddInternal(group); + AddInternal(connection); - // Groups are sorted by their start time when added such that the index can be used to post-process other surrounding groups - int index = groups.AddInPlace(group, Comparer.Create((g1, g2) => g1.StartTime.Value.CompareTo(g2.StartTime.Value))); + // Groups are sorted by their start time when added such that the index can be used to post-process other surrounding connections + int index = connections.AddInPlace(connection, Comparer.Create((g1, g2) => g1.StartTime.Value.CompareTo(g2.StartTime.Value))); - if (index < groups.Count - 1) + if (index < connections.Count - 1) { - // Update the group's end point to the next group's start point + // Update the connection's end point to the next connection's start point // h1 -> -> -> h2 - // group nextGroup + // connection nextGroup - FollowPointGroup nextGroup = groups[index + 1]; - group.End = nextGroup.Start; + FollowPointConnection nextConnection = connections[index + 1]; + connection.End = nextConnection.Start; } else { // The end point may be non-null during re-ordering - group.End = null; + connection.End = null; } if (index > 0) { - // Update the previous group's end point to the current group's start point + // Update the previous connection's end point to the current connection's start point // h1 -> -> -> h2 - // prevGroup group + // prevGroup connection - FollowPointGroup previousGroup = groups[index - 1]; - previousGroup.End = group.Start; + FollowPointConnection previousConnection = connections[index - 1]; + previousConnection.End = connection.Start; } return index; } /// - /// Removes a from this . + /// Removes a from this . /// - /// The to remove. - /// Whether was removed. - private bool removeGroup(FollowPointGroup group) + /// The to remove. + /// Whether was removed. + private bool removeGroup(FollowPointConnection connection) { - RemoveInternal(group); + RemoveInternal(connection); - int index = groups.IndexOf(group); + int index = connections.IndexOf(connection); if (index > 0) { - // Update the previous group's end point to the next group's start point + // Update the previous connection's end point to the next connection's start point // h1 -> -> -> h2 -> -> -> h3 - // prevGroup group nextGroup - // The current group's end point is used since there may not be a next group - FollowPointGroup previousGroup = groups[index - 1]; - previousGroup.End = group.End; + // prevGroup connection nextGroup + // The current connection's end point is used since there may not be a next connection + FollowPointConnection previousConnection = connections[index - 1]; + previousConnection.End = connection.End; } - return groups.Remove(group); + return connections.Remove(connection); } - private void onStartTimeChanged(FollowPointGroup group) + private void onStartTimeChanged(FollowPointConnection connection) { // Naive but can be improved if performance becomes an issue - removeGroup(group); - addGroup(group); + removeGroup(connection); + addConnection(connection); } } } From 7b5b3ff15c21d378d607b2e4cccafe2a8f366bc3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 16:36:12 +0900 Subject: [PATCH 2133/2815] Remove unused returns --- .../Objects/Drawables/Connections/FollowPointRenderer.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 00ef0ba0d0..be192080f9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections /// /// The to add. /// The index of in . - private int addConnection(FollowPointConnection connection) + private void addConnection(FollowPointConnection connection) { AddInternal(connection); @@ -74,8 +74,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections FollowPointConnection previousConnection = connections[index - 1]; previousConnection.End = connection.Start; } - - return index; } /// @@ -83,7 +81,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections /// /// The to remove. /// Whether was removed. - private bool removeGroup(FollowPointConnection connection) + private void removeGroup(FollowPointConnection connection) { RemoveInternal(connection); @@ -99,7 +97,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections previousConnection.End = connection.End; } - return connections.Remove(connection); + connections.Remove(connection); } private void onStartTimeChanged(FollowPointConnection connection) From 2c1bfd62efaba5e37651de1ea97911674c1c5539 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 17:27:41 +0900 Subject: [PATCH 2134/2815] Disallow selections to be moved outside of the playfield --- .../Edit/ManiaSelectionHandler.cs | 4 +-- .../Edit/OsuSelectionHandler.cs | 25 +++++++++++++++++-- .../Compose/Components/BlueprintContainer.cs | 3 ++- .../Compose/Components/SelectionHandler.cs | 5 ++-- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 732231b0d9..9cdf045b5b 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Edit editorClock = clock; } - public override void HandleMovement(MoveSelectionEvent moveEvent) + public override bool HandleMovement(MoveSelectionEvent moveEvent) { var maniaBlueprint = (ManiaSelectionBlueprint)moveEvent.Blueprint; int lastColumn = maniaBlueprint.DrawableObject.HitObject.Column; @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Edit performDragMovement(moveEvent); performColumnMovement(lastColumn, moveEvent); - base.HandleMovement(moveEvent); + return true; } /// diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 472267eb66..9418565907 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -4,13 +4,34 @@ using System.Linq; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; +using osuTK; namespace osu.Game.Rulesets.Osu.Edit { public class OsuSelectionHandler : SelectionHandler { - public override void HandleMovement(MoveSelectionEvent moveEvent) + public override bool HandleMovement(MoveSelectionEvent moveEvent) { + Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue); + Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue); + + // Go through all hitobjects to make sure they would remain in the bounds of the editor after movement, before any movement is attempted + foreach (var h in SelectedHitObjects.OfType()) + { + if (h is Spinner) + { + // Spinners don't support position adjustments + continue; + } + + // Stacking is not considered + minPosition = Vector2.ComponentMin(minPosition, Vector2.ComponentMin(h.EndPosition + moveEvent.InstantDelta, h.Position + moveEvent.InstantDelta)); + maxPosition = Vector2.ComponentMax(maxPosition, Vector2.ComponentMax(h.EndPosition + moveEvent.InstantDelta, h.Position + moveEvent.InstantDelta)); + } + + if (minPosition.X < 0 || minPosition.Y < 0 || maxPosition.X > DrawWidth || maxPosition.Y > DrawHeight) + return false; + foreach (var h in SelectedHitObjects.OfType()) { if (h is Spinner) @@ -22,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Edit h.Position += moveEvent.InstantDelta; } - base.HandleMovement(moveEvent); + return true; } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 288c712bde..c4d8176c7a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -367,7 +367,8 @@ namespace osu.Game.Screens.Edit.Compose.Components (Vector2 snappedPosition, double snappedTime) = composer.GetSnappedPosition(ToLocalSpace(movePosition), draggedObject.StartTime); // Move the hitobjects - selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, startPosition, ToScreenSpace(snappedPosition))); + if (!selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, startPosition, ToScreenSpace(snappedPosition)))) + return true; // Apply the start time at the newly snapped-to position double offset = snappedTime - draggedObject.StartTime; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 1722476e53..44bf22cfe1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -68,9 +68,8 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Handles the selected s being moved. /// /// The move event. - public virtual void HandleMovement(MoveSelectionEvent moveEvent) - { - } + /// Whether any s were moved. + public virtual bool HandleMovement(MoveSelectionEvent moveEvent) => false; public bool OnPressed(PlatformAction action) { From ee4839b7e7e3f44b4be35b536a5abcacf96aa8cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 18:09:36 +0900 Subject: [PATCH 2135/2815] Reduce delay on parallax --- osu.Game/Graphics/Containers/ParallaxContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/ParallaxContainer.cs b/osu.Game/Graphics/Containers/ParallaxContainer.cs index f65a0a469a..a5ebac5b1f 100644 --- a/osu.Game/Graphics/Containers/ParallaxContainer.cs +++ b/osu.Game/Graphics/Containers/ParallaxContainer.cs @@ -71,7 +71,7 @@ namespace osu.Game.Graphics.Containers double elapsed = MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000); - content.Position = Interpolation.ValueAt(elapsed, content.Position, offset, 0, 1000, Easing.OutQuint); + content.Position = Interpolation.ValueAt(elapsed, content.Position, offset, 0, 100, Easing.OutQuint); content.Scale = Interpolation.ValueAt(elapsed, content.Scale, new Vector2(1 + System.Math.Abs(ParallaxAmount)), 0, 1000, Easing.OutQuint); } From aaa06396f0e8699a3737dbb34293acb9a9784c0b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 18:11:56 +0900 Subject: [PATCH 2136/2815] Reduce editor parallax --- osu.Game/Screens/Edit/Editor.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 35408e4003..e97c89c2dc 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -36,6 +36,8 @@ namespace osu.Game.Screens.Edit { protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg4"); + public override float BackgroundParallaxAmount => 0.1f; + public override bool AllowBackButton => false; public override bool HideOverlaysOnEnter => true; From 3680e7c7047853f936075675855a2410b93b3721 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 18:15:57 +0900 Subject: [PATCH 2137/2815] Seek editor when hit objects are double clicked --- .../Edit/Compose/Components/BlueprintContainer.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 288c712bde..1ce7863d60 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Input; using osu.Framework.Input.Events; +using osu.Framework.Timing; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; @@ -30,6 +31,9 @@ namespace osu.Game.Screens.Edit.Compose.Components private SelectionHandler selectionHandler; private InputManager inputManager; + [Resolved] + private IAdjustableClock adjustableClock { get; set; } + [Resolved] private HitObjectComposer composer { get; set; } @@ -106,6 +110,17 @@ namespace osu.Game.Screens.Edit.Compose.Components return true; } + protected override bool OnDoubleClick(DoubleClickEvent e) + { + SelectionBlueprint clickedBlueprint = selectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered); + + if (clickedBlueprint == null) + return false; + + adjustableClock?.Seek(clickedBlueprint.DrawableObject.HitObject.StartTime); + return true; + } + protected override bool OnMouseUp(MouseUpEvent e) { // Special case for when a drag happened instead of a click From 4330507da8862d9d6cb8118bba95ac375bbd5972 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 6 Nov 2019 12:46:25 +0300 Subject: [PATCH 2138/2815] Use api.IsLoggedIn --- osu.Game.Tests/Visual/Online/TestSceneVotePill.cs | 12 +++++++++--- osu.Game/Overlays/Comments/VotePill.cs | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs index 22e11aa464..c113bf0a04 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs @@ -10,6 +10,7 @@ using osu.Framework.Allocation; using osu.Game.Online.API.Requests.Responses; using osu.Game.Users; using osu.Framework.MathUtils; +using osu.Game.Online.API; namespace osu.Game.Tests.Visual.Online { @@ -21,6 +22,11 @@ namespace osu.Game.Tests.Visual.Online typeof(VotePill) }; + protected override bool UseOnlineAPI => true; + + [Resolved] + private IAPIProvider api { get; set; } + private VotePill votePill; [BackgroundDependencyLoader] @@ -33,7 +39,7 @@ namespace osu.Game.Tests.Visual.Online VotesCount = 2, }; - AddStep("Log in", () => API.LocalUser.Value = new User + AddStep("Log in", () => api.LocalUser.Value = new User { Id = RNG.Next(2, 100000) }); @@ -45,7 +51,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("Click", () => votePill.Click()); AddAssert("Loading", () => votePill.IsLoading); - AddStep("Log out", API.Logout); + AddStep("Log out", api.Logout); AddStep("Random comment", () => addVotePill(randomComment)); AddStep("Click", () => votePill.Click()); AddAssert("Not loading", () => !votePill.IsLoading); @@ -54,7 +60,7 @@ namespace osu.Game.Tests.Visual.Online private Comment getUserComment() => new Comment { IsVoted = false, - UserId = API.LocalUser.Value.Id, + UserId = api.LocalUser.Value.Id, VotesCount = 10, }; diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index ad17264229..a5a19f5111 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.Comments AccentColour = borderContainer.BorderColour = sideNumber.Colour = colours.GreenLight; hoverLayer.Colour = Color4.Black.Opacity(0.5f); - if (api.LocalUser.Value.Id != comment.UserId && api.LocalUser.Value.Id != 1) + if (api.LocalUser.Value.Id != comment.UserId && api.IsLoggedIn) Action = onAction; } From 5589329e16aec04e473f41310ffb307bd08e0f51 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 6 Nov 2019 12:54:04 +0300 Subject: [PATCH 2139/2815] Split tests --- .../Visual/Online/TestSceneVotePill.cs | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs index c113bf0a04..c93fbabb41 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs @@ -29,16 +29,9 @@ namespace osu.Game.Tests.Visual.Online private VotePill votePill; - [BackgroundDependencyLoader] - private void load() + [Test] + public void TestUserCommentPill() { - var randomComment = new Comment - { - IsVoted = false, - UserId = 4444, - VotesCount = 2, - }; - AddStep("Log in", () => api.LocalUser.Value = new User { Id = RNG.Next(2, 100000) @@ -46,13 +39,25 @@ namespace osu.Game.Tests.Visual.Online AddStep("User comment", () => addVotePill(getUserComment())); AddStep("Click", () => votePill.Click()); AddAssert("Not loading", () => !votePill.IsLoading); + } - AddStep("Random comment", () => addVotePill(randomComment)); + [Test] + public void TestRandomCommentPill() + { + AddStep("Log in", () => api.LocalUser.Value = new User + { + Id = RNG.Next(2, 100000) + }); + AddStep("Random comment", () => addVotePill(getRandomComment())); AddStep("Click", () => votePill.Click()); AddAssert("Loading", () => votePill.IsLoading); + } + [Test] + public void TestOfflineRandomCommentPill() + { AddStep("Log out", api.Logout); - AddStep("Random comment", () => addVotePill(randomComment)); + AddStep("Random comment", () => addVotePill(getRandomComment())); AddStep("Click", () => votePill.Click()); AddAssert("Not loading", () => !votePill.IsLoading); } @@ -64,6 +69,13 @@ namespace osu.Game.Tests.Visual.Online VotesCount = 10, }; + private Comment getRandomComment() => new Comment + { + IsVoted = false, + UserId = 4444, + VotesCount = 2, + }; + private void addVotePill(Comment comment) { Clear(); From 31f2d6a842e380077b4866e948b8b50dfce864e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 19:06:24 +0900 Subject: [PATCH 2140/2815] Fix regressed test --- .../Components/TestScenePreviewTrackManager.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs index df6740421b..f3f6444149 100644 --- a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs +++ b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs @@ -34,6 +34,7 @@ namespace osu.Game.Tests.Visual.Components PreviewTrack track = null; AddStep("get track", () => track = getOwnedTrack()); + AddUntilStep("wait loaded", () => track.IsLoaded); AddStep("start", () => track.Start()); AddAssert("started", () => track.IsRunning); AddStep("stop", () => track.Stop()); @@ -52,6 +53,8 @@ namespace osu.Game.Tests.Visual.Components track2 = getOwnedTrack(); }); + AddUntilStep("wait loaded", () => track1.IsLoaded && track2.IsLoaded); + AddStep("start track 1", () => track1.Start()); AddStep("start track 2", () => track2.Start()); AddAssert("track 1 stopped", () => !track1.IsRunning); @@ -64,6 +67,7 @@ namespace osu.Game.Tests.Visual.Components PreviewTrack track = null; AddStep("get track", () => track = getOwnedTrack()); + AddUntilStep("wait loaded", () => track.IsLoaded); AddStep("start", () => track.Start()); AddStep("stop by owner", () => trackManager.StopAnyPlaying(this)); AddAssert("stopped", () => !track.IsRunning); @@ -76,6 +80,7 @@ namespace osu.Game.Tests.Visual.Components PreviewTrack track = null; AddStep("get track", () => Add(owner = new TestTrackOwner(track = getTrack()))); + AddUntilStep("wait loaded", () => track.IsLoaded); AddStep("start", () => track.Start()); AddStep("attempt stop", () => trackManager.StopAnyPlaying(this)); AddAssert("not stopped", () => track.IsRunning); @@ -89,16 +94,24 @@ namespace osu.Game.Tests.Visual.Components { var track = getTrack(); - Add(track); + LoadComponentAsync(track, Add); return track; } private class TestTrackOwner : CompositeDrawable, IPreviewTrackOwner { + private readonly PreviewTrack track; + public TestTrackOwner(PreviewTrack track) { - AddInternal(track); + this.track = track; + } + + [BackgroundDependencyLoader] + private void load() + { + LoadComponentAsync(track, AddInternal); } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) From 423e26a4d147cef6525e9e88b3e86497e3978189 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 6 Nov 2019 13:07:25 +0300 Subject: [PATCH 2141/2815] Fix header text could be sticked together --- osu.Game/Overlays/Rankings/Tables/RankingsTable.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index 16dca06c44..07e2257e8f 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -76,6 +76,7 @@ namespace osu.Game.Overlays.Rankings.Tables Text = text; Font = OsuFont.GetFont(size: 12); + Margin = new MarginPadding { Horizontal = 10 }; } [BackgroundDependencyLoader] From 6ecea0e4c1e3ce8a42a5880baf7c6a7730d85844 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 6 Nov 2019 18:15:49 +0300 Subject: [PATCH 2142/2815] Fix DummyAPIAccess being potentially incorrect --- osu.Game/Online/API/DummyAPIAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 6c04c77dc0..28132765d3 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -19,7 +19,7 @@ namespace osu.Game.Online.API public Bindable Activity { get; } = new Bindable(); - public bool IsLoggedIn => true; + public bool IsLoggedIn => State == APIState.Online; public string ProvidedUsername => LocalUser.Value.Username; From 8395df596b8cc14000aa3548d0b96f81c727fab2 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 6 Nov 2019 18:24:30 +0300 Subject: [PATCH 2143/2815] Don't use online API for tests --- osu.Game.Tests/Visual/Online/TestSceneVotePill.cs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs index c93fbabb41..5bdafdfec4 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs @@ -6,11 +6,9 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Overlays.Comments; -using osu.Framework.Allocation; using osu.Game.Online.API.Requests.Responses; using osu.Game.Users; using osu.Framework.MathUtils; -using osu.Game.Online.API; namespace osu.Game.Tests.Visual.Online { @@ -22,17 +20,12 @@ namespace osu.Game.Tests.Visual.Online typeof(VotePill) }; - protected override bool UseOnlineAPI => true; - - [Resolved] - private IAPIProvider api { get; set; } - private VotePill votePill; [Test] public void TestUserCommentPill() { - AddStep("Log in", () => api.LocalUser.Value = new User + AddStep("Log in", () => API.LocalUser.Value = new User { Id = RNG.Next(2, 100000) }); @@ -44,7 +37,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestRandomCommentPill() { - AddStep("Log in", () => api.LocalUser.Value = new User + AddStep("Log in", () => API.LocalUser.Value = new User { Id = RNG.Next(2, 100000) }); @@ -56,7 +49,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestOfflineRandomCommentPill() { - AddStep("Log out", api.Logout); + AddStep("Log out", API.Logout); AddStep("Random comment", () => addVotePill(getRandomComment())); AddStep("Click", () => votePill.Click()); AddAssert("Not loading", () => !votePill.IsLoading); @@ -65,7 +58,7 @@ namespace osu.Game.Tests.Visual.Online private Comment getUserComment() => new Comment { IsVoted = false, - UserId = api.LocalUser.Value.Id, + UserId = API.LocalUser.Value.Id, VotesCount = 10, }; From 59aa9b59d62a5c830e12454aae1bb5d0f1e15cde Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 6 Nov 2019 18:32:09 +0300 Subject: [PATCH 2144/2815] Move login to it's own function --- osu.Game.Tests/Visual/Online/TestSceneVotePill.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs index 5bdafdfec4..8197cf72de 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs @@ -7,8 +7,6 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Overlays.Comments; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Users; -using osu.Framework.MathUtils; namespace osu.Game.Tests.Visual.Online { @@ -25,10 +23,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestUserCommentPill() { - AddStep("Log in", () => API.LocalUser.Value = new User - { - Id = RNG.Next(2, 100000) - }); + AddStep("Log in", logIn); AddStep("User comment", () => addVotePill(getUserComment())); AddStep("Click", () => votePill.Click()); AddAssert("Not loading", () => !votePill.IsLoading); @@ -37,10 +32,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestRandomCommentPill() { - AddStep("Log in", () => API.LocalUser.Value = new User - { - Id = RNG.Next(2, 100000) - }); + AddStep("Log in", logIn); AddStep("Random comment", () => addVotePill(getRandomComment())); AddStep("Click", () => votePill.Click()); AddAssert("Loading", () => votePill.IsLoading); @@ -55,6 +47,8 @@ namespace osu.Game.Tests.Visual.Online AddAssert("Not loading", () => !votePill.IsLoading); } + private void logIn() => API.Login("localUser", "password"); + private Comment getUserComment() => new Comment { IsVoted = false, From 55230a36dd089d49513f074ddcfc8b3070db056e Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 6 Nov 2019 19:58:07 +0300 Subject: [PATCH 2145/2815] Set correct legacy version --- osu.Game/Skinning/LegacySkinConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkinConfiguration.cs b/osu.Game/Skinning/LegacySkinConfiguration.cs index 051d10747b..f9bf78b8ad 100644 --- a/osu.Game/Skinning/LegacySkinConfiguration.cs +++ b/osu.Game/Skinning/LegacySkinConfiguration.cs @@ -5,7 +5,7 @@ namespace osu.Game.Skinning { public class LegacySkinConfiguration : DefaultSkinConfiguration { - public const decimal LATEST_VERSION = 2.5m; + public const decimal LATEST_VERSION = 2.7m; /// /// Legacy version of this skin. From 502dcc566978b9bc2420d102e76ecbf51260b875 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 6 Nov 2019 20:23:22 +0300 Subject: [PATCH 2146/2815] Fix incorrect skin version case --- osu.Game/Skinning/LegacySkinDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs index ea087353a0..7dcb7ea3ac 100644 --- a/osu.Game/Skinning/LegacySkinDecoder.cs +++ b/osu.Game/Skinning/LegacySkinDecoder.cs @@ -34,7 +34,7 @@ namespace osu.Game.Skinning return; case @"Version": - if (pair.Value == "latest" || pair.Value == "User") + if (pair.Value == "latest") skin.LegacyVersion = LegacySkinConfiguration.LATEST_VERSION; else if (decimal.TryParse(pair.Value, out var version)) skin.LegacyVersion = version; From 947602f70afcc6b58d473f0f36ceb28bddfe41b7 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 6 Nov 2019 20:24:19 +0300 Subject: [PATCH 2147/2815] Specify why legacy version is nullable --- osu.Game/Skinning/LegacySkinConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkinConfiguration.cs b/osu.Game/Skinning/LegacySkinConfiguration.cs index f9bf78b8ad..33c29cd47c 100644 --- a/osu.Game/Skinning/LegacySkinConfiguration.cs +++ b/osu.Game/Skinning/LegacySkinConfiguration.cs @@ -8,7 +8,7 @@ namespace osu.Game.Skinning public const decimal LATEST_VERSION = 2.7m; /// - /// Legacy version of this skin. + /// Legacy version of this skin. Null if no version was set to allow fallback to a parent skin version. /// public decimal? LegacyVersion { get; internal set; } } From ba14345107fc46fc1e00656d58a96e89a69d2abd Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 6 Nov 2019 20:27:55 +0300 Subject: [PATCH 2148/2815] Specify culture and number style --- osu.Game/Skinning/LegacySkinDecoder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs index 7dcb7ea3ac..88ba7b23b7 100644 --- a/osu.Game/Skinning/LegacySkinDecoder.cs +++ b/osu.Game/Skinning/LegacySkinDecoder.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.Globalization; using osu.Game.Beatmaps.Formats; namespace osu.Game.Skinning @@ -36,7 +37,7 @@ namespace osu.Game.Skinning case @"Version": if (pair.Value == "latest") skin.LegacyVersion = LegacySkinConfiguration.LATEST_VERSION; - else if (decimal.TryParse(pair.Value, out var version)) + else if (decimal.TryParse(pair.Value, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var version)) skin.LegacyVersion = version; return; From 69d9a0ae1a0813db3f722df54abdf1fdb53c819d Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 6 Nov 2019 20:30:26 +0300 Subject: [PATCH 2149/2815] Use null check and pattern matching --- 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 f6a366b21d..c76a61084d 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -77,8 +77,8 @@ namespace osu.Game.Skinning switch (legacy) { case LegacySkinConfigurations.Version: - if (Configuration.LegacyVersion.HasValue) - return SkinUtils.As(new Bindable(Configuration.LegacyVersion.Value)); + if (Configuration.LegacyVersion != null) + return SkinUtils.As(new Bindable((decimal)Configuration.LegacyVersion)); break; } From 198a1750c3db0e2b6a2467478333a08d8a65e6db Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 6 Nov 2019 20:46:02 +0300 Subject: [PATCH 2150/2815] Use `is` pattern matching --- 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 c76a61084d..1730effc29 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -77,8 +77,8 @@ namespace osu.Game.Skinning switch (legacy) { case LegacySkinConfigurations.Version: - if (Configuration.LegacyVersion != null) - return SkinUtils.As(new Bindable((decimal)Configuration.LegacyVersion)); + if (Configuration.LegacyVersion is decimal version) + return SkinUtils.As(new Bindable(version)); break; } From d400e4a5f6a576b83a355bd34ef405d774382811 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Nov 2019 09:30:50 +0900 Subject: [PATCH 2151/2815] Add shared constant for parallax amount --- osu.Game/Graphics/Containers/ParallaxContainer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/ParallaxContainer.cs b/osu.Game/Graphics/Containers/ParallaxContainer.cs index a5ebac5b1f..86f922e4b8 100644 --- a/osu.Game/Graphics/Containers/ParallaxContainer.cs +++ b/osu.Game/Graphics/Containers/ParallaxContainer.cs @@ -69,9 +69,11 @@ namespace osu.Game.Graphics.Containers { Vector2 offset = (input.CurrentState.Mouse == null ? Vector2.Zero : ToLocalSpace(input.CurrentState.Mouse.Position) - DrawSize / 2) * ParallaxAmount; - double elapsed = MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000); + const float parallax_duration = 100; - content.Position = Interpolation.ValueAt(elapsed, content.Position, offset, 0, 100, Easing.OutQuint); + double elapsed = MathHelper.Clamp(Clock.ElapsedFrameTime, 0, parallax_duration); + + content.Position = Interpolation.ValueAt(elapsed, content.Position, offset, 0, parallax_duration, Easing.OutQuint); content.Scale = Interpolation.ValueAt(elapsed, content.Scale, new Vector2(1 + System.Math.Abs(ParallaxAmount)), 0, 1000, Easing.OutQuint); } From 8141509a71cd88cd02eb12f7c84f1832e2cb3a95 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Nov 2019 12:59:05 +0900 Subject: [PATCH 2152/2815] Split hold note blueprints into separate file --- .../HoldNoteNoteSelectionBlueprint.cs | 29 +++++++++++++++++++ .../Blueprints/HoldNoteSelectionBlueprint.cs | 22 -------------- 2 files changed, 29 insertions(+), 22 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs new file mode 100644 index 0000000000..3bbfcae10c --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs @@ -0,0 +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.Objects.Drawables; + +namespace osu.Game.Rulesets.Mania.Edit.Blueprints +{ + public class HoldNoteNoteSelectionBlueprint : NoteSelectionBlueprint + { + public HoldNoteNoteSelectionBlueprint(DrawableNote note) + : base(note) + { + Select(); + } + + protected override void Update() + { + base.Update(); + + Anchor = DrawableObject.Anchor; + Origin = DrawableObject.Origin; + + Position = DrawableObject.DrawPosition; + } + + // Todo: This is temporary, since the note masks don't do anything special yet. In the future they will handle input. + public override bool HandlePositionalInput => false; + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index 3a9eb1f043..a0a3a9d0f7 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -64,27 +64,5 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints } public override Quad SelectionQuad => ScreenSpaceDrawQuad; - - private class HoldNoteNoteSelectionBlueprint : NoteSelectionBlueprint - { - public HoldNoteNoteSelectionBlueprint(DrawableNote note) - : base(note) - { - Select(); - } - - protected override void Update() - { - base.Update(); - - Anchor = DrawableObject.Anchor; - Origin = DrawableObject.Origin; - - Position = DrawableObject.DrawPosition; - } - - // Todo: This is temporary, since the note masks don't do anything special yet. In the future they will handle input. - public override bool HandlePositionalInput => false; - } } } From 8d42e45fd3aa9b6d6dec99d1373d340096f8982e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Nov 2019 13:23:00 +0900 Subject: [PATCH 2153/2815] Make holdnote notes update lazily --- .../HoldNoteNoteSelectionBlueprint.cs | 24 ++++++++++++++----- .../Edit/Blueprints/HoldNotePosition.cs | 11 +++++++++ .../Blueprints/HoldNoteSelectionBlueprint.cs | 4 ++-- 3 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePosition.cs diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs index 3bbfcae10c..5aaf4c09fd 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs @@ -1,15 +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.Framework.Graphics; +using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects.Drawables; namespace osu.Game.Rulesets.Mania.Edit.Blueprints { - public class HoldNoteNoteSelectionBlueprint : NoteSelectionBlueprint + public class HoldNoteNoteSelectionBlueprint : ManiaSelectionBlueprint { - public HoldNoteNoteSelectionBlueprint(DrawableNote note) - : base(note) + protected new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject; + + private readonly HoldNotePosition position; + + public HoldNoteNoteSelectionBlueprint(DrawableHoldNote holdNote, HoldNotePosition position) + : base(holdNote) { + this.position = position; + InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.X }; + Select(); } @@ -17,10 +26,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { base.Update(); - Anchor = DrawableObject.Anchor; - Origin = DrawableObject.Origin; + DrawableNote note = position == HoldNotePosition.Start ? DrawableObject.Head : DrawableObject.Tail; - Position = DrawableObject.DrawPosition; + Anchor = note.Anchor; + Origin = note.Origin; + + Size = note.DrawSize; + Position = note.DrawPosition; } // Todo: This is temporary, since the note masks don't do anything special yet. In the future they will handle input. diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePosition.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePosition.cs new file mode 100644 index 0000000000..219dad566d --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePosition.cs @@ -0,0 +1,11 @@ +// 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.Edit.Blueprints +{ + public enum HoldNotePosition + { + Start, + End + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index a0a3a9d0f7..244cfa0b69 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -40,8 +40,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints InternalChildren = new Drawable[] { - new HoldNoteNoteSelectionBlueprint(DrawableObject.Head), - new HoldNoteNoteSelectionBlueprint(DrawableObject.Tail), + new HoldNoteNoteSelectionBlueprint(DrawableObject, HoldNotePosition.Start), + new HoldNoteNoteSelectionBlueprint(DrawableObject, HoldNotePosition.End), new BodyPiece { AccentColour = Color4.Transparent, From 1f9f03dc66aaff484ae279b78cc0c3562c9e4348 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Nov 2019 13:31:06 +0900 Subject: [PATCH 2154/2815] Add temporary fix for null references --- .../Blueprints/HoldNoteNoteSelectionBlueprint.cs | 14 +++++++++----- .../Blueprints/HoldNoteSelectionBlueprint.cs | 16 ++++++++++------ .../Edit/Blueprints/NoteSelectionBlueprint.cs | 4 +++- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs index 5aaf4c09fd..acce41db6f 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs @@ -26,13 +26,17 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { base.Update(); - DrawableNote note = position == HoldNotePosition.Start ? DrawableObject.Head : DrawableObject.Tail; + // Todo: This shouldn't exist, mania should not reference the drawable hitobject directly. + if (DrawableObject.IsLoaded) + { + DrawableNote note = position == HoldNotePosition.Start ? DrawableObject.Head : DrawableObject.Tail; - Anchor = note.Anchor; - Origin = note.Origin; + Anchor = note.Anchor; + Origin = note.Origin; - Size = note.DrawSize; - Position = note.DrawPosition; + Size = note.DrawSize; + Position = note.DrawPosition; + } } // Todo: This is temporary, since the note masks don't do anything special yet. In the future they will handle input. diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index 244cfa0b69..56c0b671a0 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -54,13 +54,17 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { base.Update(); - Size = DrawableObject.DrawSize + new Vector2(0, DrawableObject.Tail.DrawHeight); + // Todo: This shouldn't exist, mania should not reference the drawable hitobject directly. + if (DrawableObject.IsLoaded) + { + Size = DrawableObject.DrawSize + new Vector2(0, DrawableObject.Tail.DrawHeight); - // This is a side-effect of not matching the hitobject's anchors/origins, which is kinda hard to do - // When scrolling upwards our origin is already at the top of the head note (which is the intended location), - // but when scrolling downwards our origin is at the _bottom_ of the tail note (where we need to be at the _top_ of the tail note) - if (direction.Value == ScrollingDirection.Down) - Y -= DrawableObject.Tail.DrawHeight; + // This is a side-effect of not matching the hitobject's anchors/origins, which is kinda hard to do + // When scrolling upwards our origin is already at the top of the head note (which is the intended location), + // but when scrolling downwards our origin is at the _bottom_ of the tail note (where we need to be at the _top_ of the tail note) + if (direction.Value == ScrollingDirection.Down) + Y -= DrawableObject.Tail.DrawHeight; + } } public override Quad SelectionQuad => ScreenSpaceDrawQuad; diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs index b83c4aa9aa..2bff33c4cf 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs @@ -19,7 +19,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { base.Update(); - Size = DrawableObject.DrawSize; + // Todo: This shouldn't exist, mania should not reference the drawable hitobject directly. + if (DrawableObject.IsLoaded) + Size = DrawableObject.DrawSize; } } } From b1da81571f77132b4267c92b217d5c7ca5c4916e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Nov 2019 14:00:12 +0900 Subject: [PATCH 2155/2815] Implement slider head control point snapping --- .../Sliders/Components/PathControlPointPiece.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 0353ba241c..155e814596 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Lines; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Objects; using osuTK; using osuTK.Graphics; @@ -29,6 +30,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private readonly Container marker; private readonly Drawable markerRing; + [Resolved(CanBeNull = true)] + private IDistanceSnapProvider snapProvider { get; set; } + [Resolved] private OsuColour colours { get; set; } @@ -146,12 +150,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (Index == 0) { - // Special handling for the head - only the position of the slider changes - slider.Position += e.Delta; + // Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account + (Vector2 snappedPosition, double snappedTime) = snapProvider?.GetSnappedPosition(e.MousePosition, slider.StartTime) ?? (e.MousePosition, slider.StartTime); + Vector2 movementDelta = snappedPosition - slider.Position; + + slider.Position += movementDelta; + slider.StartTime = snappedTime; // Since control points are relative to the position of the slider, they all need to be offset backwards by the delta for (int i = 1; i < newControlPoints.Length; i++) - newControlPoints[i] -= e.Delta; + newControlPoints[i] -= movementDelta; } else newControlPoints[Index] += e.Delta; From f3ddc4c00b60c3e08b68de08f6409ea967df58ca Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Nov 2019 14:08:02 +0900 Subject: [PATCH 2156/2815] Advance editor clock after a snapped placement --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 1c942e52ce..805fc2b46f 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -40,6 +40,9 @@ namespace osu.Game.Rulesets.Edit [Resolved] protected IFrameBasedClock EditorClock { get; private set; } + [Resolved] + private IAdjustableClock adjustableClock { get; set; } + [Resolved] private BindableBeatDivisor beatDivisor { get; set; } @@ -256,6 +259,9 @@ namespace osu.Game.Rulesets.Edit public void EndPlacement(HitObject hitObject) { EditorBeatmap.Add(hitObject); + + adjustableClock.Seek(hitObject.StartTime); + showGridFor(Enumerable.Empty()); } From f2084df0bb25c9336237f47de7c6d6e4434cb18d Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 7 Nov 2019 06:19:49 +0000 Subject: [PATCH 2157/2815] Bump Microsoft.NET.Test.Sdk from 16.3.0 to 16.4.0 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 16.3.0 to 16.4.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v16.3...v16.4.0) Signed-off-by: dependabot-preview[bot] --- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 4b629902cb..1dbe9b39ee 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index 30511d672d..8fc4dbfe72 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index 3aea9e0387..fddf176fd0 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index 717e795112..b5bd384e05 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index aa29fc802c..c5998c9cfc 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -3,7 +3,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 371ffcdf9e..d58a724c27 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -5,7 +5,7 @@ - + From 20d6eceecff465a37d8c6f9a602ec3fe48ad40bb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Nov 2019 16:03:35 +0900 Subject: [PATCH 2158/2815] Move DrawableOsuMenuItem out of OsuMenu --- .../UserInterface/DrawableOsuMenuItem.cs | 133 ++++++++++++++++++ osu.Game/Graphics/UserInterface/OsuMenu.cs | 123 ---------------- 2 files changed, 133 insertions(+), 123 deletions(-) create mode 100644 osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs diff --git a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs new file mode 100644 index 0000000000..efa0b3d69c --- /dev/null +++ b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.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.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Sprites; +using osuTK.Graphics; + +namespace osu.Game.Graphics.UserInterface +{ + public class DrawableOsuMenuItem : Menu.DrawableMenuItem + { + private const int margin_horizontal = 17; + private const int text_size = 17; + private const int transition_length = 80; + public const int MARGIN_VERTICAL = 4; + + private SampleChannel sampleClick; + private SampleChannel sampleHover; + + private TextContainer text; + + public DrawableOsuMenuItem(MenuItem item) + : base(item) + { + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + sampleHover = audio.Samples.Get(@"UI/generic-hover"); + sampleClick = audio.Samples.Get(@"UI/generic-select"); + + BackgroundColour = Color4.Transparent; + BackgroundColourHover = OsuColour.FromHex(@"172023"); + + updateTextColour(); + } + + private void updateTextColour() + { + switch ((Item as OsuMenuItem)?.Type) + { + default: + case MenuItemType.Standard: + text.Colour = Color4.White; + break; + + case MenuItemType.Destructive: + text.Colour = Color4.Red; + break; + + case MenuItemType.Highlighted: + text.Colour = OsuColour.FromHex(@"ffcc22"); + break; + } + } + + protected override bool OnHover(HoverEvent e) + { + sampleHover.Play(); + text.BoldText.FadeIn(transition_length, Easing.OutQuint); + text.NormalText.FadeOut(transition_length, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + text.BoldText.FadeOut(transition_length, Easing.OutQuint); + text.NormalText.FadeIn(transition_length, Easing.OutQuint); + base.OnHoverLost(e); + } + + protected override bool OnClick(ClickEvent e) + { + sampleClick.Play(); + return base.OnClick(e); + } + + protected sealed override Drawable CreateContent() => text = CreateTextContainer(); + protected virtual TextContainer CreateTextContainer() => new TextContainer(); + + protected class TextContainer : Container, IHasText + { + public string Text + { + get => NormalText.Text; + set + { + NormalText.Text = value; + BoldText.Text = value; + } + } + + public readonly SpriteText NormalText; + public readonly SpriteText BoldText; + + public TextContainer() + { + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + + AutoSizeAxes = Axes.Both; + + Children = new Drawable[] + { + NormalText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: text_size), + Margin = new MarginPadding { Horizontal = margin_horizontal, Vertical = MARGIN_VERTICAL }, + }, + BoldText = new OsuSpriteText + { + AlwaysPresent = true, + Alpha = 0, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), + Margin = new MarginPadding { Horizontal = margin_horizontal, Vertical = MARGIN_VERTICAL }, + } + }; + } + } + } +} diff --git a/osu.Game/Graphics/UserInterface/OsuMenu.cs b/osu.Game/Graphics/UserInterface/OsuMenu.cs index c4c6950eb1..be642e6afb 100644 --- a/osu.Game/Graphics/UserInterface/OsuMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuMenu.cs @@ -1,18 +1,12 @@ // 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.Audio; -using osu.Framework.Audio.Sample; using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; using osuTK; namespace osu.Game.Graphics.UserInterface @@ -53,122 +47,5 @@ namespace osu.Game.Graphics.UserInterface { Anchor = Direction == Direction.Horizontal ? Anchor.BottomLeft : Anchor.TopRight }; - - protected class DrawableOsuMenuItem : DrawableMenuItem - { - private const int margin_horizontal = 17; - private const int text_size = 17; - private const int transition_length = 80; - public const int MARGIN_VERTICAL = 4; - - private SampleChannel sampleClick; - private SampleChannel sampleHover; - - private TextContainer text; - - public DrawableOsuMenuItem(MenuItem item) - : base(item) - { - } - - [BackgroundDependencyLoader] - private void load(AudioManager audio) - { - sampleHover = audio.Samples.Get(@"UI/generic-hover"); - sampleClick = audio.Samples.Get(@"UI/generic-select"); - - BackgroundColour = Color4.Transparent; - BackgroundColourHover = OsuColour.FromHex(@"172023"); - - updateTextColour(); - } - - private void updateTextColour() - { - switch ((Item as OsuMenuItem)?.Type) - { - default: - case MenuItemType.Standard: - text.Colour = Color4.White; - break; - - case MenuItemType.Destructive: - text.Colour = Color4.Red; - break; - - case MenuItemType.Highlighted: - text.Colour = OsuColour.FromHex(@"ffcc22"); - break; - } - } - - protected override bool OnHover(HoverEvent e) - { - sampleHover.Play(); - text.BoldText.FadeIn(transition_length, Easing.OutQuint); - text.NormalText.FadeOut(transition_length, Easing.OutQuint); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - text.BoldText.FadeOut(transition_length, Easing.OutQuint); - text.NormalText.FadeIn(transition_length, Easing.OutQuint); - base.OnHoverLost(e); - } - - protected override bool OnClick(ClickEvent e) - { - sampleClick.Play(); - return base.OnClick(e); - } - - protected sealed override Drawable CreateContent() => text = CreateTextContainer(); - protected virtual TextContainer CreateTextContainer() => new TextContainer(); - - protected class TextContainer : Container, IHasText - { - public string Text - { - get => NormalText.Text; - set - { - NormalText.Text = value; - BoldText.Text = value; - } - } - - public readonly SpriteText NormalText; - public readonly SpriteText BoldText; - - public TextContainer() - { - Anchor = Anchor.CentreLeft; - Origin = Anchor.CentreLeft; - - AutoSizeAxes = Axes.Both; - - Children = new Drawable[] - { - NormalText = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: text_size), - Margin = new MarginPadding { Horizontal = margin_horizontal, Vertical = MARGIN_VERTICAL }, - }, - BoldText = new OsuSpriteText - { - AlwaysPresent = true, - Alpha = 0, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), - Margin = new MarginPadding { Horizontal = margin_horizontal, Vertical = MARGIN_VERTICAL }, - } - }; - } - } - } } } From 29672c48e126bb39829426f4174be8207e0f0bc6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Nov 2019 16:04:13 +0900 Subject: [PATCH 2159/2815] Make simple OsuMenuItem ctor invoke the complex one --- osu.Game/Graphics/UserInterface/OsuMenuItem.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuMenuItem.cs b/osu.Game/Graphics/UserInterface/OsuMenuItem.cs index b7aa666302..0fe41937ce 100644 --- a/osu.Game/Graphics/UserInterface/OsuMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/OsuMenuItem.cs @@ -11,9 +11,8 @@ namespace osu.Game.Graphics.UserInterface public readonly MenuItemType Type; public OsuMenuItem(string text, MenuItemType type = MenuItemType.Standard) - : base(text) + : this(text, type, null) { - Type = type; } public OsuMenuItem(string text, MenuItemType type, Action action) From c3a3b4091bfd92841521822191b1caea5f6730fb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Nov 2019 22:26:35 +0900 Subject: [PATCH 2160/2815] Add basic implementation of a toggleable menu item --- .../UserInterface/TestSceneToggleMenuItem.cs | 33 ++++++++++ .../UserInterface/DrawableOsuMenuItem.cs | 8 +-- .../UserInterface/DrawableToggleMenuItem.cs | 60 +++++++++++++++++++ osu.Game/Graphics/UserInterface/OsuMenu.cs | 11 +++- .../Graphics/UserInterface/ToggleMenuItem.cs | 25 ++++++++ 5 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneToggleMenuItem.cs create mode 100644 osu.Game/Graphics/UserInterface/DrawableToggleMenuItem.cs create mode 100644 osu.Game/Graphics/UserInterface/ToggleMenuItem.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneToggleMenuItem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneToggleMenuItem.cs new file mode 100644 index 0000000000..97257b5226 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneToggleMenuItem.cs @@ -0,0 +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 System; +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneToggleMenuItem : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(OsuMenu), + typeof(ToggleMenuItem), + typeof(DrawableToggleMenuItem) + }; + + public TestSceneToggleMenuItem() + { + Add(new OsuMenu(Direction.Vertical, true) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Items = new[] + { + new ToggleMenuItem("Toggle"), + } + }); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs index efa0b3d69c..591ed3df83 100644 --- a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs @@ -16,10 +16,10 @@ namespace osu.Game.Graphics.UserInterface { public class DrawableOsuMenuItem : Menu.DrawableMenuItem { - private const int margin_horizontal = 17; + public const int MARGIN_HORIZONTAL = 17; + public const int MARGIN_VERTICAL = 4; private const int text_size = 17; private const int transition_length = 80; - public const int MARGIN_VERTICAL = 4; private SampleChannel sampleClick; private SampleChannel sampleHover; @@ -115,7 +115,7 @@ namespace osu.Game.Graphics.UserInterface Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Font = OsuFont.GetFont(size: text_size), - Margin = new MarginPadding { Horizontal = margin_horizontal, Vertical = MARGIN_VERTICAL }, + Margin = new MarginPadding { Horizontal = MARGIN_HORIZONTAL, Vertical = MARGIN_VERTICAL }, }, BoldText = new OsuSpriteText { @@ -124,7 +124,7 @@ namespace osu.Game.Graphics.UserInterface Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), - Margin = new MarginPadding { Horizontal = margin_horizontal, Vertical = MARGIN_VERTICAL }, + Margin = new MarginPadding { Horizontal = MARGIN_HORIZONTAL, Vertical = MARGIN_VERTICAL }, } }; } diff --git a/osu.Game/Graphics/UserInterface/DrawableToggleMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableToggleMenuItem.cs new file mode 100644 index 0000000000..cf0f154486 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/DrawableToggleMenuItem.cs @@ -0,0 +1,60 @@ +// 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; +using osu.Framework.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Graphics.UserInterface +{ + public class DrawableToggleMenuItem : DrawableOsuMenuItem + { + protected new ToggleMenuItem Item => (ToggleMenuItem)base.Item; + + public DrawableToggleMenuItem(ToggleMenuItem item) + : base(item) + { + } + + protected override TextContainer CreateTextContainer() => new ToggleTextContainer + { + State = { BindTarget = Item.State } + }; + + private class ToggleTextContainer : TextContainer + { + public readonly Bindable State = new Bindable(); + + private readonly SpriteIcon checkmark; + + public ToggleTextContainer() + { + Add(checkmark = new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Solid.Check, + Size = new Vector2(10), + Margin = new MarginPadding { Horizontal = MARGIN_HORIZONTAL }, + AlwaysPresent = true, + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + State.BindValueChanged(state => checkmark.Alpha = state.NewValue ? 1 : 0, true); + } + + protected override void Update() + { + base.Update(); + + // Todo: This is bad. This can maybe be done better with a refactor of DrawableOsuMenuItem. + checkmark.X = BoldText.DrawWidth + 10; + } + } + } +} diff --git a/osu.Game/Graphics/UserInterface/OsuMenu.cs b/osu.Game/Graphics/UserInterface/OsuMenu.cs index be642e6afb..da32a2c861 100644 --- a/osu.Game/Graphics/UserInterface/OsuMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuMenu.cs @@ -39,7 +39,16 @@ namespace osu.Game.Graphics.UserInterface } } - protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableOsuMenuItem(item); + protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) + { + switch (item) + { + case ToggleMenuItem toggle: + return new DrawableToggleMenuItem(toggle); + } + + return new DrawableOsuMenuItem(item); + } protected override ScrollContainer CreateScrollContainer(Direction direction) => new OsuScrollContainer(direction); diff --git a/osu.Game/Graphics/UserInterface/ToggleMenuItem.cs b/osu.Game/Graphics/UserInterface/ToggleMenuItem.cs new file mode 100644 index 0000000000..7b2bb32f47 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/ToggleMenuItem.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 System; +using osu.Framework.Bindables; + +namespace osu.Game.Graphics.UserInterface +{ + public class ToggleMenuItem : OsuMenuItem + { + public readonly BindableBool State = new BindableBool(); + + public ToggleMenuItem(string text, MenuItemType type = MenuItemType.Standard) + : this(text, type, null) + { + } + + public ToggleMenuItem(string text, MenuItemType type, Action action) + : base(text, type) + { + Action.Value = () => State.Toggle(); + State.BindValueChanged(state => action?.Invoke(state.NewValue)); + } + } +} From 5235d2b3191948818b2e744083b3683a51ab9975 Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 7 Nov 2019 14:38:06 -0800 Subject: [PATCH 2161/2815] Fix home button not closing login and now playing overlays --- osu.Game/OsuGame.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 1f823e6eba..3980390611 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -601,14 +601,14 @@ namespace osu.Game loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add, true); loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add, true); - loadComponentSingleFile(new LoginOverlay + var login = loadComponentSingleFile(new LoginOverlay { GetToolbarHeight = () => ToolbarOffset, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, }, rightFloatingOverlayContent.Add, true); - loadComponentSingleFile(new NowPlayingOverlay + var nowPlaying = loadComponentSingleFile(new NowPlayingOverlay { GetToolbarHeight = () => ToolbarOffset, Anchor = Anchor.TopRight, @@ -671,6 +671,8 @@ namespace osu.Game }; } + overlays.AddRange(new OverlayContainer[] { login, nowPlaying }); + OverlayActivationMode.ValueChanged += mode => { if (mode.NewValue != OverlayActivation.All) CloseAllOverlays(); From 4fe69dbc896d77fd1efea4c469254fdbb3563fe0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Nov 2019 11:13:53 +0900 Subject: [PATCH 2162/2815] Fix context menu sub-menu display --- osu.Game/Graphics/UserInterface/OsuContextMenu.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs index cea8427296..4b629080e1 100644 --- a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.UserInterface; namespace osu.Game.Graphics.UserInterface { @@ -35,5 +36,7 @@ namespace osu.Game.Graphics.UserInterface protected override void AnimateOpen() => this.FadeIn(fade_duration, Easing.OutQuint); protected override void AnimateClose() => this.FadeOut(fade_duration, Easing.OutQuint); + + protected override Menu CreateSubMenu() => new OsuContextMenu(); } } From ce08d664a52fef8633accc8f11b53ecb7d758906 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Nov 2019 11:15:03 +0900 Subject: [PATCH 2163/2815] Abstract statefulness of new menu item type --- .../TestSceneStatefulMenuItem.cs | 90 +++++++++++++++++++ .../UserInterface/TestSceneToggleMenuItem.cs | 33 ------- ...enuItem.cs => DrawableStatefulMenuItem.cs} | 44 +++++---- osu.Game/Graphics/UserInterface/OsuMenu.cs | 4 +- .../UserInterface/StatefulMenuItem.cs | 56 ++++++++++++ .../Graphics/UserInterface/ToggleMenuItem.cs | 11 ++- 6 files changed, 181 insertions(+), 57 deletions(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs delete mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneToggleMenuItem.cs rename osu.Game/Graphics/UserInterface/{DrawableToggleMenuItem.cs => DrawableStatefulMenuItem.cs} (51%) create mode 100644 osu.Game/Graphics/UserInterface/StatefulMenuItem.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs new file mode 100644 index 0000000000..fbb35ba09f --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.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 System; +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneStatefulMenuItem : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(OsuMenu), + typeof(ToggleMenuItem), + typeof(DrawableStatefulMenuItem) + }; + + public TestSceneStatefulMenuItem() + { + Add(new OsuMenu(Direction.Vertical, true) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Items = new[] + { + new TestMenuItem("First", MenuItemType.Standard, getNextState), + new TestMenuItem("Second", MenuItemType.Standard, getNextState) { State = { Value = TestStates.State2 } }, + new TestMenuItem("Third", MenuItemType.Standard, getNextState) { State = { Value = TestStates.State3 } }, + } + }); + } + + private TestStates getNextState(TestStates state) + { + switch (state) + { + case TestStates.State1: + return TestStates.State2; + + case TestStates.State2: + return TestStates.State3; + + case TestStates.State3: + return TestStates.State1; + } + + return TestStates.State1; + } + + private class TestMenuItem : StatefulMenuItem + { + public TestMenuItem(string text, MenuItemType type = MenuItemType.Standard) + : base(text, type) + { + } + + public TestMenuItem(string text, MenuItemType type, Func changeStateFunc) + : base(text, type, changeStateFunc) + { + } + + public override IconUsage? GetIconForState(TestStates state) + { + switch (state) + { + case TestStates.State1: + return FontAwesome.Solid.DiceOne; + + case TestStates.State2: + return FontAwesome.Solid.DiceTwo; + + case TestStates.State3: + return FontAwesome.Solid.DiceThree; + } + + return null; + } + } + + private enum TestStates + { + State1, + State2, + State3 + } + } +} diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneToggleMenuItem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneToggleMenuItem.cs deleted file mode 100644 index 97257b5226..0000000000 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneToggleMenuItem.cs +++ /dev/null @@ -1,33 +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 System; -using System.Collections.Generic; -using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Tests.Visual.UserInterface -{ - public class TestSceneToggleMenuItem : OsuTestScene - { - public override IReadOnlyList RequiredTypes => new[] - { - typeof(OsuMenu), - typeof(ToggleMenuItem), - typeof(DrawableToggleMenuItem) - }; - - public TestSceneToggleMenuItem() - { - Add(new OsuMenu(Direction.Vertical, true) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Items = new[] - { - new ToggleMenuItem("Toggle"), - } - }); - } - } -} diff --git a/osu.Game/Graphics/UserInterface/DrawableToggleMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs similarity index 51% rename from osu.Game/Graphics/UserInterface/DrawableToggleMenuItem.cs rename to osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs index cf0f154486..3dc99f2dbe 100644 --- a/osu.Game/Graphics/UserInterface/DrawableToggleMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs @@ -8,33 +8,33 @@ using osuTK; namespace osu.Game.Graphics.UserInterface { - public class DrawableToggleMenuItem : DrawableOsuMenuItem + public class DrawableStatefulMenuItem : DrawableOsuMenuItem { - protected new ToggleMenuItem Item => (ToggleMenuItem)base.Item; + protected new StatefulMenuItem Item => (StatefulMenuItem)base.Item; - public DrawableToggleMenuItem(ToggleMenuItem item) + public DrawableStatefulMenuItem(StatefulMenuItem item) : base(item) { } - protected override TextContainer CreateTextContainer() => new ToggleTextContainer - { - State = { BindTarget = Item.State } - }; + protected override TextContainer CreateTextContainer() => new ToggleTextContainer(Item); private class ToggleTextContainer : TextContainer { - public readonly Bindable State = new Bindable(); + private readonly StatefulMenuItem menuItem; + private readonly Bindable state; + private readonly SpriteIcon stateIcon; - private readonly SpriteIcon checkmark; - - public ToggleTextContainer() + public ToggleTextContainer(StatefulMenuItem menuItem) { - Add(checkmark = new SpriteIcon + this.menuItem = menuItem; + + state = menuItem.State.GetBoundCopy(); + + Add(stateIcon = new SpriteIcon { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Icon = FontAwesome.Solid.Check, Size = new Vector2(10), Margin = new MarginPadding { Horizontal = MARGIN_HORIZONTAL }, AlwaysPresent = true, @@ -44,8 +44,7 @@ namespace osu.Game.Graphics.UserInterface protected override void LoadComplete() { base.LoadComplete(); - - State.BindValueChanged(state => checkmark.Alpha = state.NewValue ? 1 : 0, true); + state.BindValueChanged(updateState, true); } protected override void Update() @@ -53,7 +52,20 @@ namespace osu.Game.Graphics.UserInterface base.Update(); // Todo: This is bad. This can maybe be done better with a refactor of DrawableOsuMenuItem. - checkmark.X = BoldText.DrawWidth + 10; + stateIcon.X = BoldText.DrawWidth + 10; + } + + private void updateState(ValueChangedEvent state) + { + var icon = menuItem.GetIconForState(state.NewValue); + + if (icon == null) + stateIcon.Alpha = 0; + else + { + stateIcon.Alpha = 1; + stateIcon.Icon = icon.Value; + } } } } diff --git a/osu.Game/Graphics/UserInterface/OsuMenu.cs b/osu.Game/Graphics/UserInterface/OsuMenu.cs index da32a2c861..e7bf4f66ee 100644 --- a/osu.Game/Graphics/UserInterface/OsuMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuMenu.cs @@ -43,8 +43,8 @@ namespace osu.Game.Graphics.UserInterface { switch (item) { - case ToggleMenuItem toggle: - return new DrawableToggleMenuItem(toggle); + case StatefulMenuItem stateful: + return new DrawableStatefulMenuItem(stateful); } return new DrawableOsuMenuItem(item); diff --git a/osu.Game/Graphics/UserInterface/StatefulMenuItem.cs b/osu.Game/Graphics/UserInterface/StatefulMenuItem.cs new file mode 100644 index 0000000000..ede2c9df48 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/StatefulMenuItem.cs @@ -0,0 +1,56 @@ +// 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.Framework.Graphics.Sprites; + +namespace osu.Game.Graphics.UserInterface +{ + public abstract class StatefulMenuItem : OsuMenuItem + { + public readonly Bindable State = new Bindable(); + + protected StatefulMenuItem(string text, MenuItemType type = MenuItemType.Standard) + : this(text, type, null) + { + } + + protected StatefulMenuItem(string text, MenuItemType type, Func changeStateFunc) + : base(text, type) + { + Action.Value = () => State.Value = changeStateFunc?.Invoke(State.Value) ?? State.Value; + } + + public abstract IconUsage? GetIconForState(object state); + } + + public abstract class StatefulMenuItem : StatefulMenuItem + where T : struct + { + public new readonly Bindable State = new Bindable(); + + protected StatefulMenuItem(string text, MenuItemType type = MenuItemType.Standard) + : this(text, type, null) + { + } + + protected StatefulMenuItem(string text, MenuItemType type, Func changeStateFunc) + : base(text, type, o => changeStateFunc?.Invoke((T)o) ?? o) + { + base.State.BindValueChanged(state => + { + if (state.NewValue == null) + base.State.Value = default(T); + + State.Value = (T)base.State.Value; + }, true); + + State.BindValueChanged(state => base.State.Value = state.NewValue); + } + + public sealed override IconUsage? GetIconForState(object state) => GetIconForState((T)state); + + public abstract IconUsage? GetIconForState(T state); + } +} diff --git a/osu.Game/Graphics/UserInterface/ToggleMenuItem.cs b/osu.Game/Graphics/UserInterface/ToggleMenuItem.cs index 7b2bb32f47..a0ed745c5a 100644 --- a/osu.Game/Graphics/UserInterface/ToggleMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/ToggleMenuItem.cs @@ -2,24 +2,23 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Bindables; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Graphics.UserInterface { - public class ToggleMenuItem : OsuMenuItem + public class ToggleMenuItem : StatefulMenuItem { - public readonly BindableBool State = new BindableBool(); - public ToggleMenuItem(string text, MenuItemType type = MenuItemType.Standard) : this(text, type, null) { } public ToggleMenuItem(string text, MenuItemType type, Action action) - : base(text, type) + : base(text, type, value => !value) { - Action.Value = () => State.Toggle(); State.BindValueChanged(state => action?.Invoke(state.NewValue)); } + + public override IconUsage? GetIconForState(bool state) => state ? (IconUsage?)FontAwesome.Solid.Check : null; } } From 30f877c4ab7751066bd6a0ca2dc45178de355b69 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Nov 2019 12:47:42 +0900 Subject: [PATCH 2164/2815] Implement a three-state menu item --- .../TestSceneThreeStateMenuItem.cs | 35 ++++++++++++ .../UserInterface/ThreeStateMenuItem.cs | 53 +++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneThreeStateMenuItem.cs create mode 100644 osu.Game/Graphics/UserInterface/ThreeStateMenuItem.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneThreeStateMenuItem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneThreeStateMenuItem.cs new file mode 100644 index 0000000000..caa07e7b60 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneThreeStateMenuItem.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.Collections.Generic; +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneThreeStateMenuItem : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(OsuMenu), + typeof(ThreeStateMenuItem), + typeof(DrawableStatefulMenuItem) + }; + + public TestSceneThreeStateMenuItem() + { + Add(new OsuMenu(Direction.Vertical, true) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Items = new[] + { + new ThreeStateMenuItem("First"), + new ThreeStateMenuItem("Second") { State = { Value = ThreeStates.Indeterminate } }, + new ThreeStateMenuItem("Third") { State = { Value = ThreeStates.Enabled } }, + } + }); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/ThreeStateMenuItem.cs b/osu.Game/Graphics/UserInterface/ThreeStateMenuItem.cs new file mode 100644 index 0000000000..107bfe208b --- /dev/null +++ b/osu.Game/Graphics/UserInterface/ThreeStateMenuItem.cs @@ -0,0 +1,53 @@ +// 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; + +namespace osu.Game.Graphics.UserInterface +{ + public class ThreeStateMenuItem : StatefulMenuItem + { + public ThreeStateMenuItem(string text, MenuItemType type = MenuItemType.Standard) + : base(text, type, getNextState) + { + } + + public override IconUsage? GetIconForState(ThreeStates state) + { + switch (state) + { + case ThreeStates.Indeterminate: + return FontAwesome.Regular.Circle; + + case ThreeStates.Enabled: + return FontAwesome.Solid.Check; + } + + return null; + } + + private static ThreeStates getNextState(ThreeStates state) + { + switch (state) + { + case ThreeStates.Disabled: + return ThreeStates.Enabled; + + case ThreeStates.Indeterminate: + return ThreeStates.Enabled; + + case ThreeStates.Enabled: + return ThreeStates.Disabled; + } + + return ThreeStates.Disabled; + } + } + + public enum ThreeStates + { + Disabled, + Indeterminate, + Enabled + } +} From 0a15a13fabd66d5676dfd12e1117772a31ae605a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Nov 2019 12:55:22 +0900 Subject: [PATCH 2165/2815] Reorder parameters --- .../TestSceneStatefulMenuItem.cs | 4 ++-- .../UserInterface/StatefulMenuItem.cs | 20 +++++++++++-------- .../UserInterface/ThreeStateMenuItem.cs | 8 +++++++- .../Graphics/UserInterface/ToggleMenuItem.cs | 3 +-- 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs index fbb35ba09f..00678a6ab4 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs @@ -53,12 +53,12 @@ namespace osu.Game.Tests.Visual.UserInterface private class TestMenuItem : StatefulMenuItem { public TestMenuItem(string text, MenuItemType type = MenuItemType.Standard) - : base(text, type) + : this(text, type, null) { } public TestMenuItem(string text, MenuItemType type, Func changeStateFunc) - : base(text, type, changeStateFunc) + : base(text, changeStateFunc, type) { } diff --git a/osu.Game/Graphics/UserInterface/StatefulMenuItem.cs b/osu.Game/Graphics/UserInterface/StatefulMenuItem.cs index ede2c9df48..b5f9a3d635 100644 --- a/osu.Game/Graphics/UserInterface/StatefulMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/StatefulMenuItem.cs @@ -11,15 +11,19 @@ namespace osu.Game.Graphics.UserInterface { public readonly Bindable State = new Bindable(); - protected StatefulMenuItem(string text, MenuItemType type = MenuItemType.Standard) - : this(text, type, null) + protected StatefulMenuItem(string text, Func changeStateFunc, MenuItemType type = MenuItemType.Standard) + : this(text, changeStateFunc, type, null) { } - protected StatefulMenuItem(string text, MenuItemType type, Func changeStateFunc) + protected StatefulMenuItem(string text, Func changeStateFunc, MenuItemType type, Action action) : base(text, type) { - Action.Value = () => State.Value = changeStateFunc?.Invoke(State.Value) ?? State.Value; + Action.Value = () => + { + State.Value = changeStateFunc?.Invoke(State.Value) ?? State.Value; + action?.Invoke(State.Value); + }; } public abstract IconUsage? GetIconForState(object state); @@ -30,13 +34,13 @@ namespace osu.Game.Graphics.UserInterface { public new readonly Bindable State = new Bindable(); - protected StatefulMenuItem(string text, MenuItemType type = MenuItemType.Standard) - : this(text, type, null) + protected StatefulMenuItem(string text, Func changeStateFunc, MenuItemType type = MenuItemType.Standard) + : this(text, changeStateFunc, type, null) { } - protected StatefulMenuItem(string text, MenuItemType type, Func changeStateFunc) - : base(text, type, o => changeStateFunc?.Invoke((T)o) ?? o) + protected StatefulMenuItem(string text, Func changeStateFunc, MenuItemType type, Action action) + : base(text, o => changeStateFunc?.Invoke((T)o) ?? o, type, o => action?.Invoke((T)o)) { base.State.BindValueChanged(state => { diff --git a/osu.Game/Graphics/UserInterface/ThreeStateMenuItem.cs b/osu.Game/Graphics/UserInterface/ThreeStateMenuItem.cs index 107bfe208b..d41a95daa1 100644 --- a/osu.Game/Graphics/UserInterface/ThreeStateMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/ThreeStateMenuItem.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.Sprites; namespace osu.Game.Graphics.UserInterface @@ -8,7 +9,12 @@ namespace osu.Game.Graphics.UserInterface public class ThreeStateMenuItem : StatefulMenuItem { public ThreeStateMenuItem(string text, MenuItemType type = MenuItemType.Standard) - : base(text, type, getNextState) + : this(text, type, null) + { + } + + public ThreeStateMenuItem(string text, MenuItemType type, Action action) + : base(text, getNextState, type, action) { } diff --git a/osu.Game/Graphics/UserInterface/ToggleMenuItem.cs b/osu.Game/Graphics/UserInterface/ToggleMenuItem.cs index a0ed745c5a..f5a442b317 100644 --- a/osu.Game/Graphics/UserInterface/ToggleMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/ToggleMenuItem.cs @@ -14,9 +14,8 @@ namespace osu.Game.Graphics.UserInterface } public ToggleMenuItem(string text, MenuItemType type, Action action) - : base(text, type, value => !value) + : base(text, value => !value, type, action) { - State.BindValueChanged(state => action?.Invoke(state.NewValue)); } public override IconUsage? GetIconForState(bool state) => state ? (IconUsage?)FontAwesome.Solid.Check : null; From 011bf095166d88d560087bbf83d07567309c585f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Nov 2019 13:14:23 +0900 Subject: [PATCH 2166/2815] Add xmldocs and cleanup --- .../TestSceneStatefulMenuItem.cs | 5 ++- .../UserInterface/StatefulMenuItem.cs | 45 +++++++++++++++++++ .../UserInterface/ThreeStateMenuItem.cs | 45 +++++++++++++++++-- .../Graphics/UserInterface/ToggleMenuItem.cs | 14 ++++++ 4 files changed, 104 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs index 00678a6ab4..d010abc862 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs @@ -74,9 +74,10 @@ namespace osu.Game.Tests.Visual.UserInterface case TestStates.State3: return FontAwesome.Solid.DiceThree; - } - return null; + default: + throw new ArgumentOutOfRangeException(nameof(state), state, null); + } } } diff --git a/osu.Game/Graphics/UserInterface/StatefulMenuItem.cs b/osu.Game/Graphics/UserInterface/StatefulMenuItem.cs index b5f9a3d635..0d7b36e51b 100644 --- a/osu.Game/Graphics/UserInterface/StatefulMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/StatefulMenuItem.cs @@ -7,15 +7,34 @@ using osu.Framework.Graphics.Sprites; namespace osu.Game.Graphics.UserInterface { + /// + /// An which contains and displays a state. + /// public abstract class StatefulMenuItem : OsuMenuItem { + /// + /// The current state that should be displayed. + /// public readonly Bindable State = new Bindable(); + /// + /// Creates a new . + /// + /// The text to display. + /// A function that mutates a state to another state after this is pressed. + /// The type of action which this performs. protected StatefulMenuItem(string text, Func changeStateFunc, MenuItemType type = MenuItemType.Standard) : this(text, changeStateFunc, type, null) { } + /// + /// Creates a new . + /// + /// The text to display. + /// A function that mutates a state to another state after this is pressed. + /// The type of action which this performs. + /// A delegate to be invoked when this is pressed. protected StatefulMenuItem(string text, Func changeStateFunc, MenuItemType type, Action action) : base(text, type) { @@ -26,19 +45,40 @@ namespace osu.Game.Graphics.UserInterface }; } + /// + /// Retrieves the icon to be displayed for a state. + /// + /// The state to retrieve the relevant icon for. + /// The icon to be displayed for . public abstract IconUsage? GetIconForState(object state); } public abstract class StatefulMenuItem : StatefulMenuItem where T : struct { + /// + /// The current state that should be displayed. + /// public new readonly Bindable State = new Bindable(); + /// + /// Creates a new . + /// + /// The text to display. + /// A function that mutates a state to another state after this is pressed. + /// The type of action which this performs. protected StatefulMenuItem(string text, Func changeStateFunc, MenuItemType type = MenuItemType.Standard) : this(text, changeStateFunc, type, null) { } + /// + /// Creates a new . + /// + /// The text to display. + /// A function that mutates a state to another state after this is pressed. + /// The type of action which this performs. + /// A delegate to be invoked when this is pressed. protected StatefulMenuItem(string text, Func changeStateFunc, MenuItemType type, Action action) : base(text, o => changeStateFunc?.Invoke((T)o) ?? o, type, o => action?.Invoke((T)o)) { @@ -55,6 +95,11 @@ namespace osu.Game.Graphics.UserInterface public sealed override IconUsage? GetIconForState(object state) => GetIconForState((T)state); + /// + /// Retrieves the icon to be displayed for a state. + /// + /// The state to retrieve the relevant icon for. + /// The icon to be displayed for . public abstract IconUsage? GetIconForState(T state); } } diff --git a/osu.Game/Graphics/UserInterface/ThreeStateMenuItem.cs b/osu.Game/Graphics/UserInterface/ThreeStateMenuItem.cs index d41a95daa1..e33c68bebd 100644 --- a/osu.Game/Graphics/UserInterface/ThreeStateMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/ThreeStateMenuItem.cs @@ -6,15 +6,41 @@ using osu.Framework.Graphics.Sprites; namespace osu.Game.Graphics.UserInterface { + /// + /// An with three possible states. + /// public class ThreeStateMenuItem : StatefulMenuItem { + /// + /// Creates a new . + /// + /// The text to display. + /// The type of action which this performs. public ThreeStateMenuItem(string text, MenuItemType type = MenuItemType.Standard) : this(text, type, null) { } + /// + /// Creates a new . + /// + /// The text to display. + /// The type of action which this performs. + /// A delegate to be invoked when this is pressed. public ThreeStateMenuItem(string text, MenuItemType type, Action action) - : base(text, getNextState, type, action) + : this(text, getNextState, type, action) + { + } + + /// + /// Creates a new . + /// + /// The text to display. + /// A function that mutates a state to another state after this is pressed. + /// The type of action which this performs. + /// A delegate to be invoked when this is pressed. + protected ThreeStateMenuItem(string text, Func changeStateFunc, MenuItemType type, Action action) + : base(text, changeStateFunc, type, action) { } @@ -44,16 +70,29 @@ namespace osu.Game.Graphics.UserInterface case ThreeStates.Enabled: return ThreeStates.Disabled; - } - return ThreeStates.Disabled; + default: + throw new ArgumentOutOfRangeException(nameof(state), state, null); + } } } public enum ThreeStates { + /// + /// The current state is disabled. + /// Disabled, + + /// + /// The current state is a combination of and . + /// The state becomes if the is pressed. + /// Indeterminate, + + /// + /// The current state is enabled. + /// Enabled } } diff --git a/osu.Game/Graphics/UserInterface/ToggleMenuItem.cs b/osu.Game/Graphics/UserInterface/ToggleMenuItem.cs index f5a442b317..f9ff9859dd 100644 --- a/osu.Game/Graphics/UserInterface/ToggleMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/ToggleMenuItem.cs @@ -6,13 +6,27 @@ using osu.Framework.Graphics.Sprites; namespace osu.Game.Graphics.UserInterface { + /// + /// An which displays an enabled or disabled state. + /// public class ToggleMenuItem : StatefulMenuItem { + /// + /// Creates a new . + /// + /// The text to display. + /// The type of action which this performs. public ToggleMenuItem(string text, MenuItemType type = MenuItemType.Standard) : this(text, type, null) { } + /// + /// Creates a new . + /// + /// The text to display. + /// The type of action which this performs. + /// A delegate to be invoked when this is pressed. public ToggleMenuItem(string text, MenuItemType type, Action action) : base(text, value => !value, type, action) { From a2c265c1478f1b33937d8da76343eabd688046ad Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Nov 2019 13:16:09 +0900 Subject: [PATCH 2167/2815] Separate ThreeStates into its own file --- .../UserInterface/ThreeStateMenuItem.cs | 19 ------------- .../Graphics/UserInterface/ThreeStates.cs | 27 +++++++++++++++++++ 2 files changed, 27 insertions(+), 19 deletions(-) create mode 100644 osu.Game/Graphics/UserInterface/ThreeStates.cs diff --git a/osu.Game/Graphics/UserInterface/ThreeStateMenuItem.cs b/osu.Game/Graphics/UserInterface/ThreeStateMenuItem.cs index e33c68bebd..ebb6436196 100644 --- a/osu.Game/Graphics/UserInterface/ThreeStateMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/ThreeStateMenuItem.cs @@ -76,23 +76,4 @@ namespace osu.Game.Graphics.UserInterface } } } - - public enum ThreeStates - { - /// - /// The current state is disabled. - /// - Disabled, - - /// - /// The current state is a combination of and . - /// The state becomes if the is pressed. - /// - Indeterminate, - - /// - /// The current state is enabled. - /// - Enabled - } } diff --git a/osu.Game/Graphics/UserInterface/ThreeStates.cs b/osu.Game/Graphics/UserInterface/ThreeStates.cs new file mode 100644 index 0000000000..f099250937 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/ThreeStates.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. + +namespace osu.Game.Graphics.UserInterface +{ + /// + /// An on/off state with an extra indeterminate state. + /// + public enum ThreeStates + { + /// + /// The current state is disabled. + /// + Disabled, + + /// + /// The current state is a combination of and . + /// The state becomes if the is pressed. + /// + Indeterminate, + + /// + /// The current state is enabled. + /// + Enabled + } +} From e574aa0e94ee16366a7c4086f2eb80042edb494f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Nov 2019 13:33:15 +0900 Subject: [PATCH 2168/2815] Add toggle menu item test --- .../UserInterface/TestSceneToggleMenuItem.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneToggleMenuItem.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneToggleMenuItem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneToggleMenuItem.cs new file mode 100644 index 0000000000..2abda56a28 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneToggleMenuItem.cs @@ -0,0 +1,34 @@ +// 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.Game.Graphics.UserInterface; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneToggleMenuItem : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(OsuMenu), + typeof(ToggleMenuItem), + typeof(DrawableStatefulMenuItem) + }; + + public TestSceneToggleMenuItem() + { + Add(new OsuMenu(Direction.Vertical, true) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Items = new[] + { + new ToggleMenuItem("First"), + new ToggleMenuItem("Second") { State = { Value = true } } + } + }); + } + } +} From 92d8526370e8cdc166460b30c58e0d47c4cc2654 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Nov 2019 13:34:27 +0900 Subject: [PATCH 2169/2815] Adjust test namespaces --- .../{ => MenuItems}/TestSceneStatefulMenuItem.cs | 4 ++-- .../{ => MenuItems}/TestSceneThreeStateMenuItem.cs | 2 +- .../UserInterface/{ => MenuItems}/TestSceneToggleMenuItem.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game.Tests/Visual/UserInterface/{ => MenuItems}/TestSceneStatefulMenuItem.cs (96%) rename osu.Game.Tests/Visual/UserInterface/{ => MenuItems}/TestSceneThreeStateMenuItem.cs (95%) rename osu.Game.Tests/Visual/UserInterface/{ => MenuItems}/TestSceneToggleMenuItem.cs (94%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs b/osu.Game.Tests/Visual/UserInterface/MenuItems/TestSceneStatefulMenuItem.cs similarity index 96% rename from osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs rename to osu.Game.Tests/Visual/UserInterface/MenuItems/TestSceneStatefulMenuItem.cs index d010abc862..95e9aea97f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs +++ b/osu.Game.Tests/Visual/UserInterface/MenuItems/TestSceneStatefulMenuItem.cs @@ -7,14 +7,14 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -namespace osu.Game.Tests.Visual.UserInterface +namespace osu.Game.Tests.Visual.UserInterface.MenuItems { public class TestSceneStatefulMenuItem : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(OsuMenu), - typeof(ToggleMenuItem), + typeof(StatefulMenuItem), typeof(DrawableStatefulMenuItem) }; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneThreeStateMenuItem.cs b/osu.Game.Tests/Visual/UserInterface/MenuItems/TestSceneThreeStateMenuItem.cs similarity index 95% rename from osu.Game.Tests/Visual/UserInterface/TestSceneThreeStateMenuItem.cs rename to osu.Game.Tests/Visual/UserInterface/MenuItems/TestSceneThreeStateMenuItem.cs index caa07e7b60..e035e43630 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneThreeStateMenuItem.cs +++ b/osu.Game.Tests/Visual/UserInterface/MenuItems/TestSceneThreeStateMenuItem.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; -namespace osu.Game.Tests.Visual.UserInterface +namespace osu.Game.Tests.Visual.UserInterface.MenuItems { public class TestSceneThreeStateMenuItem : OsuTestScene { diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneToggleMenuItem.cs b/osu.Game.Tests/Visual/UserInterface/MenuItems/TestSceneToggleMenuItem.cs similarity index 94% rename from osu.Game.Tests/Visual/UserInterface/TestSceneToggleMenuItem.cs rename to osu.Game.Tests/Visual/UserInterface/MenuItems/TestSceneToggleMenuItem.cs index 2abda56a28..92875740cf 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneToggleMenuItem.cs +++ b/osu.Game.Tests/Visual/UserInterface/MenuItems/TestSceneToggleMenuItem.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; -namespace osu.Game.Tests.Visual.UserInterface +namespace osu.Game.Tests.Visual.UserInterface.MenuItems { public class TestSceneToggleMenuItem : OsuTestScene { From abb3a6ca5bf610f50ff8519a4847bf1b650a2c67 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Nov 2019 22:51:49 +0900 Subject: [PATCH 2170/2815] Initial right click context menu implementation --- .../Compose/Components/BlueprintContainer.cs | 22 +++ .../Compose/Components/SelectionHandler.cs | 67 +++++++- osu.Game/Screens/Edit/Editor.cs | 143 +++++++++--------- 3 files changed, 162 insertions(+), 70 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 8fa022f129..af0ac22a70 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -17,6 +17,7 @@ using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osuTK; +using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { @@ -95,12 +96,18 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnMouseDown(MouseDownEvent e) { + if (e.Button == MouseButton.Right) + return false; + beginClickSelection(e); return true; } protected override bool OnClick(ClickEvent e) { + if (e.Button == MouseButton.Right) + return false; + // Deselection should only occur if no selected blueprints are hovered // A special case for when a blueprint was selected via this click is added since OnClick() may occur outside the hitobject and should not trigger deselection if (endClickSelection() || selectionHandler.SelectedBlueprints.Any(b => b.IsHovered)) @@ -112,6 +119,9 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnDoubleClick(DoubleClickEvent e) { + if (e.Button == MouseButton.Right) + return false; + SelectionBlueprint clickedBlueprint = selectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered); if (clickedBlueprint == null) @@ -123,6 +133,9 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnMouseUp(MouseUpEvent e) { + if (e.Button == MouseButton.Right) + return false; + // Special case for when a drag happened instead of a click Schedule(() => endClickSelection()); return true; @@ -141,6 +154,9 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnDragStart(DragStartEvent e) { + if (e.Button == MouseButton.Right) + return false; + if (!beginSelectionMovement()) { dragBox.UpdateDrag(e); @@ -152,6 +168,9 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnDrag(DragEvent e) { + if (e.Button == MouseButton.Right) + return false; + if (!moveCurrentSelection(e)) dragBox.UpdateDrag(e); @@ -160,6 +179,9 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnDragEnd(DragEndEvent e) { + if (e.Button == MouseButton.Right) + return false; + if (!finishSelectionMovement()) { dragBox.FadeOut(250, Easing.OutQuint); diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 44bf22cfe1..779cd86318 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -7,11 +7,16 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Framework.Input.States; +using osu.Game.Audio; using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -22,7 +27,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// A component which outlines s and handles movement of selections. /// - public class SelectionHandler : CompositeDrawable, IKeyBindingHandler + public class SelectionHandler : CompositeDrawable, IKeyBindingHandler, IHasContextMenu { public const float BORDER_RADIUS = 2; @@ -142,6 +147,8 @@ namespace osu.Game.Screens.Edit.Compose.Components #endregion + #region Outline Display + /// /// Updates whether this is visible. /// @@ -176,5 +183,63 @@ namespace osu.Game.Screens.Edit.Compose.Components outline.Size = bottomRight - topLeft; outline.Position = topLeft; } + + #endregion + + #region Context Menu + + public virtual MenuItem[] ContextMenuItems + { + get + { + if (!SelectedBlueprints.Any()) + return Array.Empty(); + + return new MenuItem[] + { + new OsuMenuItem("hit sound") + { + Items = new[] + { + createHitSampleMenuItem(HitSampleInfo.HIT_WHISTLE), + createHitSampleMenuItem(HitSampleInfo.HIT_CLAP), + createHitSampleMenuItem(HitSampleInfo.HIT_FINISH) + } + } + }; + } + } + + private MenuItem createHitSampleMenuItem(string sampleName) + { + return new ToggleMenuItem(sampleName, MenuItemType.Standard, setHitSampleState) + { + State = { Value = getHitSampleState() } + }; + + void setHitSampleState(bool enabled) + { + if (enabled) + { + foreach (var h in SelectedHitObjects) + { + // Make sure there isn't already an existing sample + if (h.Samples.Any(s => s.Name == sampleName)) + continue; + + h.Samples.Add(new HitSampleInfo { Name = sampleName }); + } + } + else + { + foreach (var h in SelectedHitObjects) + h.Samples.RemoveAll(s => s.Name == sampleName); + } + } + + bool getHitSampleState() => SelectedHitObjects.All(h => h.Samples.Any(s => s.Name == sampleName)); + } + + #endregion } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 35408e4003..1ae3fd71ab 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -24,6 +24,7 @@ using osuTK.Input; using System.Collections.Generic; using osu.Framework; using osu.Framework.Input.Bindings; +using osu.Game.Graphics.Cursor; using osu.Game.Input.Bindings; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Setup; @@ -85,87 +86,91 @@ namespace osu.Game.Screens.Edit fileMenuItems.Add(new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit)); - InternalChildren = new[] + InternalChild = new OsuContextMenuContainer { - new Container + RelativeSizeAxes = Axes.Both, + Children = new[] { - Name = "Screen container", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 40, Bottom = 60 }, - Child = screenContainer = new Container + new Container { + Name = "Screen container", RelativeSizeAxes = Axes.Both, - Masking = true - } - }, - new Container - { - Name = "Top bar", - RelativeSizeAxes = Axes.X, - Height = 40, - Child = menuBar = new EditorMenuBar - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - Items = new[] - { - new MenuItem("File") - { - Items = fileMenuItems - } - } - } - }, - new Container - { - Name = "Bottom bar", - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - Height = 60, - Children = new Drawable[] - { - bottomBackground = new Box { RelativeSizeAxes = Axes.Both }, - new Container + Padding = new MarginPadding { Top = 40, Bottom = 60 }, + Child = screenContainer = new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Vertical = 5, Horizontal = 10 }, - Child = new GridContainer + Masking = true + } + }, + new Container + { + Name = "Top bar", + RelativeSizeAxes = Axes.X, + Height = 40, + Child = menuBar = new EditorMenuBar + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Items = new[] + { + new MenuItem("File") + { + Items = fileMenuItems + } + } + } + }, + new Container + { + Name = "Bottom bar", + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = 60, + Children = new Drawable[] + { + bottomBackground = new Box { RelativeSizeAxes = Axes.Both }, + new Container { RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + Padding = new MarginPadding { Vertical = 5, Horizontal = 10 }, + Child = new GridContainer { - new Dimension(GridSizeMode.Absolute, 220), - new Dimension(), - new Dimension(GridSizeMode.Absolute, 220) - }, - Content = new[] - { - new Drawable[] + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] { - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = 10 }, - Child = new TimeInfoContainer { RelativeSizeAxes = Axes.Both }, - }, - new SummaryTimeline - { - RelativeSizeAxes = Axes.Both, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = 10 }, - Child = new PlaybackControl { RelativeSizeAxes = Axes.Both }, - } + new Dimension(GridSizeMode.Absolute, 220), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 220) }, - } - }, + Content = new[] + { + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = 10 }, + Child = new TimeInfoContainer { RelativeSizeAxes = Axes.Both }, + }, + new SummaryTimeline + { + RelativeSizeAxes = Axes.Both, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = 10 }, + Child = new PlaybackControl { RelativeSizeAxes = Axes.Both }, + } + }, + } + }, + } } - } - }, + }, + } }; menuBar.Mode.ValueChanged += onModeChanged; From 573d11503e27e7a99202a94ee08b2912e65bcb1f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Nov 2019 11:13:24 +0900 Subject: [PATCH 2171/2815] Remove unused using --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 779cd86318..43dbeb35f7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; using osu.Framework.Input.States; using osu.Game.Audio; using osu.Game.Graphics; From 046f0b0fe5947f57ae312ee80c88e6e3d42b2529 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Nov 2019 13:40:47 +0900 Subject: [PATCH 2172/2815] Allow right-clicks to trigger selection --- .../Edit/Compose/Components/BlueprintContainer.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index af0ac22a70..e7877f962e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -96,11 +96,8 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnMouseDown(MouseDownEvent e) { - if (e.Button == MouseButton.Right) - return false; - beginClickSelection(e); - return true; + return e.Button == MouseButton.Left; } protected override bool OnClick(ClickEvent e) @@ -133,12 +130,9 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnMouseUp(MouseUpEvent e) { - if (e.Button == MouseButton.Right) - return false; - // Special case for when a drag happened instead of a click Schedule(() => endClickSelection()); - return true; + return e.Button == MouseButton.Left; } protected override bool OnMouseMove(MouseMoveEvent e) From 864b8db638b6b3a70eb3f6189d2a9761127b3e1d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Nov 2019 13:52:36 +0900 Subject: [PATCH 2173/2815] Use three states for the hitsound menu items --- .../Compose/Components/SelectionHandler.cs | 56 ++++++++++++------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 43dbeb35f7..dc147a92cd 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -196,47 +196,61 @@ namespace osu.Game.Screens.Edit.Compose.Components return new MenuItem[] { - new OsuMenuItem("hit sound") + new OsuMenuItem("Sound") { Items = new[] { - createHitSampleMenuItem(HitSampleInfo.HIT_WHISTLE), - createHitSampleMenuItem(HitSampleInfo.HIT_CLAP), - createHitSampleMenuItem(HitSampleInfo.HIT_FINISH) + createHitSampleMenuItem("Whistle", HitSampleInfo.HIT_WHISTLE), + createHitSampleMenuItem("Clap", HitSampleInfo.HIT_CLAP), + createHitSampleMenuItem("Finish", HitSampleInfo.HIT_FINISH) } } }; } } - private MenuItem createHitSampleMenuItem(string sampleName) + private MenuItem createHitSampleMenuItem(string name, string sampleName) { - return new ToggleMenuItem(sampleName, MenuItemType.Standard, setHitSampleState) + return new ThreeStateMenuItem(name, MenuItemType.Standard, setHitSampleState) { State = { Value = getHitSampleState() } }; - void setHitSampleState(bool enabled) + void setHitSampleState(ThreeStates state) { - if (enabled) + switch (state) { - foreach (var h in SelectedHitObjects) - { - // Make sure there isn't already an existing sample - if (h.Samples.Any(s => s.Name == sampleName)) - continue; + case ThreeStates.Disabled: + foreach (var h in SelectedHitObjects) + h.Samples.RemoveAll(s => s.Name == sampleName); + break; - h.Samples.Add(new HitSampleInfo { Name = sampleName }); - } - } - else - { - foreach (var h in SelectedHitObjects) - h.Samples.RemoveAll(s => s.Name == sampleName); + case ThreeStates.Enabled: + foreach (var h in SelectedHitObjects) + { + // Make sure there isn't already an existing sample + if (h.Samples.Any(s => s.Name == sampleName)) + continue; + + h.Samples.Add(new HitSampleInfo { Name = sampleName }); + } + + break; } } - bool getHitSampleState() => SelectedHitObjects.All(h => h.Samples.Any(s => s.Name == sampleName)); + ThreeStates getHitSampleState() + { + int countExisting = SelectedHitObjects.Count(h => h.Samples.Any(s => s.Name == sampleName)); + + if (countExisting == 0) + return ThreeStates.Disabled; + + if (countExisting < SelectedHitObjects.Count()) + return ThreeStates.Indeterminate; + + return ThreeStates.Enabled; + } } #endregion From 6fc1be64c2c4adbb7335288d765c77e3343a5e73 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Nov 2019 14:04:57 +0900 Subject: [PATCH 2174/2815] Make hitobject samples a bindable list --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 2 +- .../Beatmaps/ManiaBeatmapConverter.cs | 2 +- .../Legacy/DistanceObjectPatternGenerator.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs | 6 +++--- osu.Game.Rulesets.Osu/Objects/Slider.cs | 6 +++--- .../Beatmaps/TaikoBeatmapConverter.cs | 6 +++--- osu.Game/Rulesets/Objects/HitObject.cs | 12 ++++++++---- .../Objects/Legacy/Catch/ConvertHitObjectParser.cs | 2 +- .../Objects/Legacy/ConvertHitObjectParser.cs | 4 ++-- osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs | 2 +- .../Objects/Legacy/Mania/ConvertHitObjectParser.cs | 2 +- .../Objects/Legacy/Osu/ConvertHitObjectParser.cs | 2 +- .../Objects/Legacy/Taiko/ConvertHitObjectParser.cs | 2 +- osu.Game/Rulesets/Objects/Types/IHasRepeats.cs | 2 +- .../Edit/Compose/Components/SelectionHandler.cs | 3 ++- 15 files changed, 30 insertions(+), 25 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 0952e8981a..80a3af0aa0 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Catch.Objects public double Distance => Path.Distance; - public List> NodeSamples { get; set; } = new List>(); + public List> NodeSamples { get; set; } = new List>(); public double? LegacyLastTickOffset { get; set; } } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index e10602312e..6c5bb304bf 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -255,7 +255,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// /// The time to retrieve the sample info list from. /// - private List sampleInfoListAt(double time) + private IList sampleInfoListAt(double time) { var curveData = HitObject as IHasCurve; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index ea418eedb4..6297a68e08 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -472,7 +472,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// /// The time to retrieve the sample info list from. /// - private List sampleInfoListAt(double time) + private IList sampleInfoListAt(double time) { var curveData = HitObject as IHasCurve; diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index a955911bd5..46a2a36ac7 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -275,7 +275,7 @@ namespace osu.Game.Rulesets.Osu.Tests private Drawable createCatmull(int repeats = 0) { - var repeatSamples = new List>(); + var repeatSamples = new List>(); for (int i = 0; i < repeats; i++) repeatSamples.Add(new List()); @@ -297,9 +297,9 @@ namespace osu.Game.Rulesets.Osu.Tests return createDrawable(slider, 3, 1); } - private List> createEmptySamples(int repeats) + private List> createEmptySamples(int repeats) { - var repeatSamples = new List>(); + var repeatSamples = new List>(); for (int i = 0; i < repeats; i++) repeatSamples.Add(new List()); return repeatSamples; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index f60b7e67b2..c41026d954 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Objects /// internal float LazyTravelDistance; - public List> NodeSamples { get; set; } = new List>(); + public List> NodeSamples { get; set; } = new List>(); private int repeatCount; @@ -128,7 +128,7 @@ namespace osu.Game.Rulesets.Osu.Objects foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset)) { - var firstSample = Samples.Find(s => s.Name == HitSampleInfo.HIT_NORMAL) + var firstSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL) ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933) var sampleList = new List(); @@ -203,7 +203,7 @@ namespace osu.Game.Rulesets.Osu.Objects TailCircle.Position = EndPosition; } - private List getNodeSamples(int nodeIndex) => + private IList getNodeSamples(int nodeIndex) => nodeIndex < NodeSamples.Count ? NodeSamples[nodeIndex] : Samples; public override Judgement CreateJudgement() => new OsuJudgement(); diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index f0cf8d9c7d..180e0d8309 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps var curveData = obj as IHasCurve; // Old osu! used hit sounding to determine various hit type information - List samples = obj.Samples; + IList samples = obj.Samples; bool strong = samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH); @@ -117,13 +117,13 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) { - List> allSamples = curveData != null ? curveData.NodeSamples : new List>(new[] { samples }); + List> allSamples = curveData != null ? curveData.NodeSamples : new List>(new[] { samples }); int i = 0; for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing) { - List currentSamples = allSamples[i]; + IList currentSamples = allSamples[i]; bool isRim = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE); strong = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_FINISH); diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 6211fe50e6..ee0705ec5a 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Objects set => StartTimeBindable.Value = value; } - private List samples; + public readonly BindableList SamplesBindable = new BindableList(); /// /// The samples to be played when this hit object is hit. @@ -54,10 +54,14 @@ namespace osu.Game.Rulesets.Objects /// and can be treated as the default samples for the hit object. /// /// - public List Samples + public IList Samples { - get => samples ?? (samples = new List()); - set => samples = value; + get => SamplesBindable; + set + { + SamplesBindable.Clear(); + SamplesBindable.AddRange(value); + } } [JsonIgnore] diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index 71e321f205..545cfe07f8 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch } protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount, - List> nodeSamples) + List> nodeSamples) { newCombo |= forceNewCombo; comboOffset += extraComboOffset; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index e990938291..bbf46a0c8f 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Objects.Legacy } // Generate the final per-node samples - var nodeSamples = new List>(nodes); + var nodeSamples = new List>(nodes); for (int i = 0; i < nodes; i++) nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); @@ -279,7 +279,7 @@ namespace osu.Game.Rulesets.Objects.Legacy /// The samples to be played when the slider nodes are hit. This includes the head and tail of the slider. /// The hit object. protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount, - List> nodeSamples); + List> nodeSamples); /// /// Creates a legacy Spinner-type hit object. diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index ff6b9be8b5..8d523022d6 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Objects.Legacy public double Distance => Path.Distance; - public List> NodeSamples { get; set; } + public List> NodeSamples { get; set; } public int RepeatCount { get; set; } public double EndTime => StartTime + this.SpanCount() * Distance / Velocity; diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs index 94aba95e90..8012b4230f 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania } protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount, - List> nodeSamples) + List> nodeSamples) { return new ConvertSlider { diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs index 65102f1e89..99872e630d 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu } protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount, - List> nodeSamples) + List> nodeSamples) { newCombo |= forceNewCombo; comboOffset += extraComboOffset; diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs index eb598f1368..9dc0c01932 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko } protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount, - List> nodeSamples) + List> nodeSamples) { return new ConvertSlider { diff --git a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs index 697adeda98..b22752e902 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Objects.Types /// n-1: The last repeat.
/// n: The last node. ///
- List> NodeSamples { get; } + List> NodeSamples { get; } } public static class HasRepeatsExtensions diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index dc147a92cd..5f0bfb6d70 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -222,7 +222,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { case ThreeStates.Disabled: foreach (var h in SelectedHitObjects) - h.Samples.RemoveAll(s => s.Name == sampleName); + h.SamplesBindable.RemoveAll(s => s.Name == sampleName); + break; case ThreeStates.Enabled: From 53e6186b6d4421bdb4abb9feca0674ea827603fe Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Nov 2019 14:59:47 +0900 Subject: [PATCH 2175/2815] Fix drawable hitobject samples not updating --- .../Objects/Drawables/DrawableHitObject.cs | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 1d9b66f88d..592cddeff6 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -78,6 +78,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public JudgementResult Result { get; private set; } + private BindableList samplesBindable; private Bindable startTimeBindable; private Bindable comboIndexBindable; @@ -108,20 +109,7 @@ namespace osu.Game.Rulesets.Objects.Drawables throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); } - var samples = GetSamples().ToArray(); - - if (samples.Length > 0) - { - if (HitObject.SampleControlPoint == null) - throw new ArgumentNullException(nameof(HitObject.SampleControlPoint), $"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}." - + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}."); - - samples = samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).ToArray(); - foreach (var s in samples) - s.Namespace = SampleNamespace; - - AddInternal(Samples = new SkinnableSound(samples)); - } + loadSamples(); } protected override void LoadComplete() @@ -139,10 +127,34 @@ namespace osu.Game.Rulesets.Objects.Drawables comboIndexBindable.BindValueChanged(_ => updateAccentColour(), true); } + samplesBindable = HitObject.SamplesBindable.GetBoundCopy(); + samplesBindable.ItemsAdded += _ => scheduleLoadSamples(); + samplesBindable.ItemsRemoved += _ => scheduleLoadSamples(); + updateState(ArmedState.Idle, true); onDefaultsApplied(); } + private void scheduleLoadSamples() => Scheduler.AddOnce(loadSamples); + + private void loadSamples() + { + var samples = GetSamples().ToArray(); + + if (samples.Length <= 0) + return; + + if (HitObject.SampleControlPoint == null) + throw new ArgumentNullException(nameof(HitObject.SampleControlPoint), $"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}." + + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}."); + + samples = samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).ToArray(); + foreach (var s in samples) + s.Namespace = SampleNamespace; + + AddInternal(Samples = new SkinnableSound(samples)); + } + private void onDefaultsApplied() => apply(HitObject); private void apply(HitObject hitObject) From df31acb2940ca5333e019e0340c2fdd67ff5f9e9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Nov 2019 15:39:07 +0900 Subject: [PATCH 2176/2815] Fix slider nested hitobject samples not getting updated --- .../TestSceneSlider.cs | 75 +++++++++++++++---- osu.Game.Rulesets.Osu/Objects/Slider.cs | 47 ++++++++---- 2 files changed, 93 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index 46a2a36ac7..5c656bf594 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -126,6 +126,67 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("body positioned correctly", () => slider.Position == slider.HitObject.StackedPosition); } + [Test] + public void TestChangeSamplesWithNoNodeSamples() + { + DrawableSlider slider = null; + + AddStep("create slider", () => + { + slider = (DrawableSlider)createSlider(repeats: 1); + Add(slider); + }); + + AddStep("change samples", () => slider.HitObject.Samples = new[] + { + new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP }, + new HitSampleInfo { Name = HitSampleInfo.HIT_WHISTLE }, + }); + + AddAssert("head samples updated", () => assertSamples(((Slider)slider.HitObject).HeadCircle)); + AddAssert("tick samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType().All(assertTickSamples)); + AddAssert("repeat samples updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType().All(assertSamples)); + AddAssert("tail has no samples", () => ((Slider)slider.HitObject).TailCircle.Samples.Count == 0); + + bool assertTickSamples(SliderTick tick) => tick.Samples.Single().Name == "slidertick"; + + bool assertSamples(HitObject hitObject) + { + return hitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP) + && hitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_WHISTLE); + } + } + + [Test] + public void TestChangeSamplesWithNodeSamples() + { + DrawableSlider slider = null; + + AddStep("create slider", () => + { + slider = (DrawableSlider)createSlider(repeats: 1); + + for (int i = 0; i < 2; i++) + ((Slider)slider.HitObject).NodeSamples.Add(new List { new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH } }); + + Add(slider); + }); + + AddStep("change samples", () => slider.HitObject.Samples = new[] + { + new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP }, + new HitSampleInfo { Name = HitSampleInfo.HIT_WHISTLE }, + }); + + AddAssert("head samples not updated", () => assertSamples(((Slider)slider.HitObject).HeadCircle)); + AddAssert("tick samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType().All(assertTickSamples)); + AddAssert("repeat samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType().All(assertSamples)); + AddAssert("tail has no samples", () => ((Slider)slider.HitObject).TailCircle.Samples.Count == 0); + + bool assertTickSamples(SliderTick tick) => tick.Samples.Single().Name == "slidertick"; + bool assertSamples(HitObject hitObject) => hitObject.Samples.All(s => s.Name != HitSampleInfo.HIT_CLAP && s.Name != HitSampleInfo.HIT_WHISTLE); + } + private Drawable testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats); private Drawable testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10); @@ -143,7 +204,6 @@ namespace osu.Game.Rulesets.Osu.Tests new Vector2(52, -34) }, 700), RepeatCount = repeats, - NodeSamples = createEmptySamples(repeats), StackHeight = 10 }; @@ -174,7 +234,6 @@ namespace osu.Game.Rulesets.Osu.Tests new Vector2(distance, 0), }, distance), RepeatCount = repeats, - NodeSamples = createEmptySamples(repeats), StackHeight = stackHeight }; @@ -194,7 +253,6 @@ namespace osu.Game.Rulesets.Osu.Tests new Vector2(400, 0) }, 600), RepeatCount = repeats, - NodeSamples = createEmptySamples(repeats) }; return createDrawable(slider, 2, 3); @@ -218,7 +276,6 @@ namespace osu.Game.Rulesets.Osu.Tests new Vector2(430, 0) }), RepeatCount = repeats, - NodeSamples = createEmptySamples(repeats) }; return createDrawable(slider, 2, 3); @@ -241,7 +298,6 @@ namespace osu.Game.Rulesets.Osu.Tests new Vector2(430, 0) }), RepeatCount = repeats, - NodeSamples = createEmptySamples(repeats) }; return createDrawable(slider, 2, 3); @@ -265,7 +321,6 @@ namespace osu.Game.Rulesets.Osu.Tests new Vector2(0, -200) }), RepeatCount = repeats, - NodeSamples = createEmptySamples(repeats) }; return createDrawable(slider, 2, 3); @@ -297,14 +352,6 @@ namespace osu.Game.Rulesets.Osu.Tests return createDrawable(slider, 3, 1); } - private List> createEmptySamples(int repeats) - { - var repeatSamples = new List>(); - for (int i = 0; i < repeats; i++) - repeatSamples.Add(new List()); - return repeatSamples; - } - private Drawable createDrawable(Slider slider, float circleSize, double speedMultiplier) { var cpi = new ControlPointInfo(); diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index c41026d954..f3399af2de 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -108,6 +108,12 @@ namespace osu.Game.Rulesets.Osu.Objects public HitCircle HeadCircle; public SliderTailCircle TailCircle; + public Slider() + { + SamplesBindable.ItemsAdded += _ => updateNestedSamples(); + SamplesBindable.ItemsRemoved += _ => updateNestedSamples(); + } + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); @@ -128,18 +134,6 @@ namespace osu.Game.Rulesets.Osu.Objects foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset)) { - var firstSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL) - ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933) - var sampleList = new List(); - - if (firstSample != null) - sampleList.Add(new HitSampleInfo - { - Bank = firstSample.Bank, - Volume = firstSample.Volume, - Name = @"slidertick", - }); - switch (e.Type) { case SliderEventType.Tick: @@ -151,7 +145,6 @@ namespace osu.Game.Rulesets.Osu.Objects Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, Scale = Scale, - Samples = sampleList }); break; @@ -161,7 +154,6 @@ namespace osu.Game.Rulesets.Osu.Objects StartTime = e.Time, Position = Position, StackHeight = StackHeight, - Samples = getNodeSamples(0), SampleControlPoint = SampleControlPoint, }); break; @@ -187,11 +179,12 @@ namespace osu.Game.Rulesets.Osu.Objects Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, Scale = Scale, - Samples = getNodeSamples(e.SpanIndex + 1) }); break; } } + + updateNestedSamples(); } private void updateNestedPositions() @@ -203,6 +196,30 @@ namespace osu.Game.Rulesets.Osu.Objects TailCircle.Position = EndPosition; } + private void updateNestedSamples() + { + var firstSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL) + ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933) + var sampleList = new List(); + + if (firstSample != null) + sampleList.Add(new HitSampleInfo + { + Bank = firstSample.Bank, + Volume = firstSample.Volume, + Name = @"slidertick", + }); + + foreach (var tick in NestedHitObjects.OfType()) + tick.Samples = sampleList; + + foreach (var repeat in NestedHitObjects.OfType()) + repeat.Samples = getNodeSamples(repeat.RepeatIndex + 1); + + if (HeadCircle != null) + HeadCircle.Samples = getNodeSamples(0); + } + private IList getNodeSamples(int nodeIndex) => nodeIndex < NodeSamples.Count ? NodeSamples[nodeIndex] : Samples; From 5fe764b2db98b30113fdc9f2fc7690df8db97ed8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Nov 2019 16:19:34 +0900 Subject: [PATCH 2177/2815] Fix tournament videos stuttering when changing scenes --- .../Components/TourneyVideo.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index 4f4660f645..f28a2773b6 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Video; +using osu.Framework.Timing; using osu.Game.Graphics; namespace osu.Game.Tournament.Components @@ -15,6 +16,9 @@ namespace osu.Game.Tournament.Components { private readonly VideoSprite video; + private ManualClock manualClock; + private IFrameBasedClock sourceClock; + public TourneyVideo(Stream stream) { if (stream == null) @@ -41,5 +45,22 @@ namespace osu.Game.Tournament.Components video.Loop = value; } } + + protected override void LoadComplete() + { + base.LoadComplete(); + + sourceClock = Clock; + Clock = new FramedClock(manualClock = new ManualClock()); + } + + protected override void Update() + { + base.Update(); + + // we want to avoid seeking as much as possible, because we care about performance, not sync. + // to avoid seeking completely, we only increment out local clock when in an updating state. + manualClock.CurrentTime += sourceClock.ElapsedFrameTime; + } } } From a9b4106075614da8a60eae0afa3f30b85f7bf53d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Nov 2019 16:19:43 +0900 Subject: [PATCH 2178/2815] Remove unnecessary (for now) scheduling --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 592cddeff6..9c72a1a220 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -128,15 +128,13 @@ namespace osu.Game.Rulesets.Objects.Drawables } samplesBindable = HitObject.SamplesBindable.GetBoundCopy(); - samplesBindable.ItemsAdded += _ => scheduleLoadSamples(); - samplesBindable.ItemsRemoved += _ => scheduleLoadSamples(); + samplesBindable.ItemsAdded += _ => loadSamples(); + samplesBindable.ItemsRemoved += _ => loadSamples(); updateState(ArmedState.Idle, true); onDefaultsApplied(); } - private void scheduleLoadSamples() => Scheduler.AddOnce(loadSamples); - private void loadSamples() { var samples = GetSamples().ToArray(); From b4cb4c124366097b79239f267033eed6e7c3dffb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Nov 2019 16:19:55 +0900 Subject: [PATCH 2179/2815] Remove previous samples on change --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 9c72a1a220..818571bb23 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Objects.Drawables // Todo: Rulesets should be overriding the resources instead, but we need to figure out where/when to apply overrides first protected virtual string SampleNamespace => null; - protected SkinnableSound Samples; + protected SkinnableSound Samples { get; private set; } protected virtual IEnumerable GetSamples() => HitObject.Samples; @@ -137,6 +137,12 @@ namespace osu.Game.Rulesets.Objects.Drawables private void loadSamples() { + if (Samples != null) + { + RemoveInternal(Samples); + Samples = null; + } + var samples = GetSamples().ToArray(); if (samples.Length <= 0) From df08a95734e8184847a55210f2da9eed9a6c30f6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Nov 2019 16:46:58 +0900 Subject: [PATCH 2180/2815] Separate addition/removal into separate methods --- .../Compose/Components/SelectionHandler.cs | 44 ++++++++++++++----- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 5f0bfb6d70..7b61640c89 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -185,6 +185,36 @@ namespace osu.Game.Screens.Edit.Compose.Components #endregion + #region Sample Changes + + /// + /// Adds a hit sample to all selected s. + /// + /// The name of the hit sample. + public void AddHitSample(string sampleName) + { + foreach (var h in SelectedHitObjects) + { + // Make sure there isn't already an existing sample + if (h.Samples.Any(s => s.Name == sampleName)) + continue; + + h.Samples.Add(new HitSampleInfo { Name = sampleName }); + } + } + + /// + /// Removes a hit sample from all selected s. + /// + /// The name of the hit sample. + public void RemoveHitSample(string sampleName) + { + foreach (var h in SelectedHitObjects) + h.SamplesBindable.RemoveAll(s => s.Name == sampleName); + } + + #endregion + #region Context Menu public virtual MenuItem[] ContextMenuItems @@ -221,21 +251,11 @@ namespace osu.Game.Screens.Edit.Compose.Components switch (state) { case ThreeStates.Disabled: - foreach (var h in SelectedHitObjects) - h.SamplesBindable.RemoveAll(s => s.Name == sampleName); - + RemoveHitSample(sampleName); break; case ThreeStates.Enabled: - foreach (var h in SelectedHitObjects) - { - // Make sure there isn't already an existing sample - if (h.Samples.Any(s => s.Name == sampleName)) - continue; - - h.Samples.Add(new HitSampleInfo { Name = sampleName }); - } - + AddHitSample(sampleName); break; } } From 8bcbc93501626e11d230532647f3dfff374121aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Nov 2019 17:00:47 +0900 Subject: [PATCH 2181/2815] Fix tournament buttons playing sound effects --- .../Screens/Drawings/DrawingsScreen.cs | 9 +++---- .../Screens/Editors/TeamEditorScreen.cs | 3 +-- .../Screens/Editors/TournamentEditorScreen.cs | 3 +-- .../Screens/Gameplay/GameplayScreen.cs | 4 +-- .../Screens/MapPool/MapPoolScreen.cs | 10 ++++---- osu.Game.Tournament/TournamentGameBase.cs | 3 +-- osu.Game.Tournament/TournamentSceneManager.cs | 25 +++++++++---------- osu.Game.Tournament/TourneyButton.cs | 15 +++++++++++ osu.Game/Graphics/UserInterface/OsuButton.cs | 6 +++-- 9 files changed, 45 insertions(+), 33 deletions(-) create mode 100644 osu.Game.Tournament/TourneyButton.cs diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index 3a14b6d9c2..e8906466f3 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -15,7 +15,6 @@ using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; using osu.Game.Tournament.Screens.Drawings.Components; @@ -128,21 +127,21 @@ namespace osu.Game.Tournament.Screens.Drawings // Control panel container new ControlPanel { - new OsuButton + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Begin random", Action = teamsContainer.StartScrolling, }, - new OsuButton + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Stop random", Action = teamsContainer.StopScrolling, }, - new OsuButton + new TourneyButton { RelativeSizeAxes = Axes.X, @@ -150,7 +149,7 @@ namespace osu.Game.Tournament.Screens.Drawings Action = reloadTeams }, new ControlPanel.Spacer(), - new OsuButton + new TourneyButton { RelativeSizeAxes = Axes.X, diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index a4479f3cfd..e1f6d16623 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Settings; @@ -32,7 +31,7 @@ namespace osu.Game.Tournament.Screens.Editors [BackgroundDependencyLoader] private void load() { - ControlPanel.Add(new OsuButton + ControlPanel.Add(new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Add all countries", diff --git a/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs index 50d3207345..32cf6bbcc8 100644 --- a/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; using osu.Game.Tournament.Components; using osuTK; @@ -56,7 +55,7 @@ namespace osu.Game.Tournament.Screens.Editors { Children = new Drawable[] { - new OsuButton + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Add new", diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs index b9a74bfe16..6a3095d42d 100644 --- a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs +++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs @@ -103,13 +103,13 @@ namespace osu.Game.Tournament.Screens.Gameplay { Children = new Drawable[] { - warmupButton = new OsuButton + warmupButton = new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Toggle warmup", Action = () => warmup.Toggle() }, - new OsuButton + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Toggle chat", diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index ec55bb5b54..7a5fc2cd06 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -60,32 +60,32 @@ namespace osu.Game.Tournament.Screens.MapPool { Text = "Current Mode" }, - buttonRedBan = new OsuButton + buttonRedBan = new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Red Ban", Action = () => setMode(TeamColour.Red, ChoiceType.Ban) }, - buttonBlueBan = new OsuButton + buttonBlueBan = new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Blue Ban", Action = () => setMode(TeamColour.Blue, ChoiceType.Ban) }, - buttonRedPick = new OsuButton + buttonRedPick = new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Red Pick", Action = () => setMode(TeamColour.Red, ChoiceType.Pick) }, - buttonBluePick = new OsuButton + buttonBluePick = new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Blue Pick", Action = () => setMode(TeamColour.Blue, ChoiceType.Pick) }, new ControlPanel.Spacer(), - new OsuButton + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Reset", diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 21552882ef..3b7718c61d 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -18,7 +18,6 @@ using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests; using osu.Game.Tournament.IPC; using osu.Game.Tournament.Models; @@ -76,7 +75,7 @@ namespace osu.Game.Tournament AddRange(new[] { - new OsuButton + new TourneyButton { Text = "Save Changes", Width = 140, diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index 02ee1c8603..7164141dea 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Platform; -using osu.Game.Graphics.UserInterface; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; using osu.Game.Tournament.Screens; @@ -107,23 +106,23 @@ namespace osu.Game.Tournament Direction = FillDirection.Vertical, Children = new Drawable[] { - new OsuButton { RelativeSizeAxes = Axes.X, Text = "Setup", Action = () => SetScreen(typeof(SetupScreen)) }, + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Setup", Action = () => SetScreen(typeof(SetupScreen)) }, new Container { RelativeSizeAxes = Axes.X, Height = 50 }, - new OsuButton { RelativeSizeAxes = Axes.X, Text = "Team Editor", Action = () => SetScreen(typeof(TeamEditorScreen)) }, - new OsuButton { RelativeSizeAxes = Axes.X, Text = "Rounds Editor", Action = () => SetScreen(typeof(RoundEditorScreen)) }, - new OsuButton { RelativeSizeAxes = Axes.X, Text = "Bracket Editor", Action = () => SetScreen(typeof(LadderEditorScreen)) }, + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Team Editor", Action = () => SetScreen(typeof(TeamEditorScreen)) }, + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Rounds Editor", Action = () => SetScreen(typeof(RoundEditorScreen)) }, + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Bracket Editor", Action = () => SetScreen(typeof(LadderEditorScreen)) }, new Container { RelativeSizeAxes = Axes.X, Height = 50 }, - new OsuButton { RelativeSizeAxes = Axes.X, Text = "Drawings", Action = () => SetScreen(typeof(DrawingsScreen)) }, - new OsuButton { RelativeSizeAxes = Axes.X, Text = "Showcase", Action = () => SetScreen(typeof(ShowcaseScreen)) }, + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Drawings", Action = () => SetScreen(typeof(DrawingsScreen)) }, + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Showcase", Action = () => SetScreen(typeof(ShowcaseScreen)) }, new Container { RelativeSizeAxes = Axes.X, Height = 50 }, - new OsuButton { RelativeSizeAxes = Axes.X, Text = "Schedule", Action = () => SetScreen(typeof(ScheduleScreen)) }, - new OsuButton { RelativeSizeAxes = Axes.X, Text = "Bracket", Action = () => SetScreen(typeof(LadderScreen)) }, + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Schedule", Action = () => SetScreen(typeof(ScheduleScreen)) }, + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Bracket", Action = () => SetScreen(typeof(LadderScreen)) }, new Container { RelativeSizeAxes = Axes.X, Height = 50 }, - new OsuButton { RelativeSizeAxes = Axes.X, Text = "TeamIntro", Action = () => SetScreen(typeof(TeamIntroScreen)) }, - new OsuButton { RelativeSizeAxes = Axes.X, Text = "MapPool", Action = () => SetScreen(typeof(MapPoolScreen)) }, - new OsuButton { RelativeSizeAxes = Axes.X, Text = "Gameplay", Action = () => SetScreen(typeof(GameplayScreen)) }, + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "TeamIntro", Action = () => SetScreen(typeof(TeamIntroScreen)) }, + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "MapPool", Action = () => SetScreen(typeof(MapPoolScreen)) }, + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Gameplay", Action = () => SetScreen(typeof(GameplayScreen)) }, new Container { RelativeSizeAxes = Axes.X, Height = 50 }, - new OsuButton { RelativeSizeAxes = Axes.X, Text = "Win", Action = () => SetScreen(typeof(TeamWinScreen)) }, + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Win", Action = () => SetScreen(typeof(TeamWinScreen)) }, } }, }, diff --git a/osu.Game.Tournament/TourneyButton.cs b/osu.Game.Tournament/TourneyButton.cs new file mode 100644 index 0000000000..12872d3197 --- /dev/null +++ b/osu.Game.Tournament/TourneyButton.cs @@ -0,0 +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 osu.Game.Graphics.UserInterface; + +namespace osu.Game.Tournament +{ + public class TourneyButton : OsuButton + { + public TourneyButton() + : base(null) + { + } + } +} diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index 2750e61f0d..c6a9aa1c97 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -48,7 +48,7 @@ namespace osu.Game.Graphics.UserInterface protected Box Background; protected SpriteText SpriteText; - public OsuButton() + public OsuButton(HoverSampleSet? hoverSounds = HoverSampleSet.Loud) { Height = 40; @@ -78,10 +78,12 @@ namespace osu.Game.Graphics.UserInterface Depth = float.MinValue }, SpriteText = CreateText(), - new HoverClickSounds(HoverSampleSet.Loud), } }); + if (hoverSounds.HasValue) + AddInternal(new HoverClickSounds(hoverSounds.Value)); + Enabled.BindValueChanged(enabledChanged, true); } From 380859e06e11137716ec7b2748214a58a213b0b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Nov 2019 17:13:45 +0900 Subject: [PATCH 2182/2815] Set clock on video directly; ignore long frames --- .../Components/TourneyVideo.cs | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index f28a2773b6..8582b1634c 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -16,8 +16,7 @@ namespace osu.Game.Tournament.Components { private readonly VideoSprite video; - private ManualClock manualClock; - private IFrameBasedClock sourceClock; + private readonly ManualClock manualClock; public TourneyVideo(Stream stream) { @@ -34,6 +33,7 @@ namespace osu.Game.Tournament.Components { RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, + Clock = new FramedClock(manualClock = new ManualClock()) }; } @@ -46,21 +46,16 @@ namespace osu.Game.Tournament.Components } } - protected override void LoadComplete() - { - base.LoadComplete(); - - sourceClock = Clock; - Clock = new FramedClock(manualClock = new ManualClock()); - } - protected override void Update() { base.Update(); - // we want to avoid seeking as much as possible, because we care about performance, not sync. - // to avoid seeking completely, we only increment out local clock when in an updating state. - manualClock.CurrentTime += sourceClock.ElapsedFrameTime; + if (manualClock != null && Clock.ElapsedFrameTime < 100) + { + // we want to avoid seeking as much as possible, because we care about performance, not sync. + // to avoid seeking completely, we only increment out local clock when in an updating state. + manualClock.CurrentTime += Clock.ElapsedFrameTime; + } } } } From 651a1e2ae10fb5001aa0a7e7fa466fd0e429ca3e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Nov 2019 17:20:20 +0900 Subject: [PATCH 2183/2815] Highlight selected tournament screen's button --- osu.Game.Tournament/TournamentSceneManager.cs | 85 +++++++++++++++---- 1 file changed, 67 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index 7164141dea..69248b6692 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Platform; +using osu.Game.Graphics; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; using osu.Game.Tournament.Screens; @@ -35,6 +36,7 @@ namespace osu.Game.Tournament private TournamentMatchChatDisplay chat = new TournamentMatchChatDisplay(); private Container chatContainer; + private FillFlowContainer buttons; public TournamentSceneManager() { @@ -100,29 +102,32 @@ namespace osu.Game.Tournament Colour = Color4.Black, RelativeSizeAxes = Axes.Both, }, - new FillFlowContainer + buttons = new FillFlowContainer { RelativeSizeAxes = Axes.Both, Direction = FillDirection.Vertical, + Spacing = new Vector2(2), + Padding = new MarginPadding(2), Children = new Drawable[] { - new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Setup", Action = () => SetScreen(typeof(SetupScreen)) }, - new Container { RelativeSizeAxes = Axes.X, Height = 50 }, - new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Team Editor", Action = () => SetScreen(typeof(TeamEditorScreen)) }, - new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Rounds Editor", Action = () => SetScreen(typeof(RoundEditorScreen)) }, - new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Bracket Editor", Action = () => SetScreen(typeof(LadderEditorScreen)) }, - new Container { RelativeSizeAxes = Axes.X, Height = 50 }, - new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Drawings", Action = () => SetScreen(typeof(DrawingsScreen)) }, - new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Showcase", Action = () => SetScreen(typeof(ShowcaseScreen)) }, - new Container { RelativeSizeAxes = Axes.X, Height = 50 }, - new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Schedule", Action = () => SetScreen(typeof(ScheduleScreen)) }, - new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Bracket", Action = () => SetScreen(typeof(LadderScreen)) }, - new Container { RelativeSizeAxes = Axes.X, Height = 50 }, - new TourneyButton { RelativeSizeAxes = Axes.X, Text = "TeamIntro", Action = () => SetScreen(typeof(TeamIntroScreen)) }, - new TourneyButton { RelativeSizeAxes = Axes.X, Text = "MapPool", Action = () => SetScreen(typeof(MapPoolScreen)) }, - new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Gameplay", Action = () => SetScreen(typeof(GameplayScreen)) }, - new Container { RelativeSizeAxes = Axes.X, Height = 50 }, - new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Win", Action = () => SetScreen(typeof(TeamWinScreen)) }, + new ScreenButton(typeof(SetupScreen)) { Text = "Setup", RequestSelection = SetScreen }, + new Separator(), + new ScreenButton(typeof(TeamEditorScreen)) { Text = "Team Editor", RequestSelection = SetScreen }, + new ScreenButton(typeof(RoundEditorScreen)) { Text = "Rounds Editor", RequestSelection = SetScreen }, + new ScreenButton(typeof(LadderEditorScreen)) { Text = "Bracket Editor", RequestSelection = SetScreen }, + new Separator(), + new ScreenButton(typeof(ScheduleScreen)) { Text = "Schedule", RequestSelection = SetScreen }, + new ScreenButton(typeof(LadderScreen)) { Text = "Bracket", RequestSelection = SetScreen }, + new Separator(), + new ScreenButton(typeof(TeamIntroScreen)) { Text = "TeamIntro", RequestSelection = SetScreen }, + new Separator(), + new ScreenButton(typeof(MapPoolScreen)) { Text = "MapPool", RequestSelection = SetScreen }, + new ScreenButton(typeof(GameplayScreen)) { Text = "Gameplay", RequestSelection = SetScreen }, + new Separator(), + new ScreenButton(typeof(TeamWinScreen)) { Text = "Win", RequestSelection = SetScreen }, + new Separator(), + new ScreenButton(typeof(DrawingsScreen)) { Text = "Drawings", RequestSelection = SetScreen }, + new ScreenButton(typeof(ShowcaseScreen)) { Text = "Showcase", RequestSelection = SetScreen }, } }, }, @@ -162,6 +167,50 @@ namespace osu.Game.Tournament chatContainer.FadeOut(100); break; } + + foreach (var s in buttons.OfType()) + s.IsSelected = screenType == s.Type; + } + + private class Separator : CompositeDrawable + { + public Separator() + { + RelativeSizeAxes = Axes.X; + Height = 20; + } + } + + private class ScreenButton : TourneyButton + { + public readonly Type Type; + + public ScreenButton(Type type) + { + Type = type; + BackgroundColour = OsuColour.Gray(0.2f); + Action = () => RequestSelection(type); + + RelativeSizeAxes = Axes.X; + } + + private bool isSelected; + + public Action RequestSelection; + + public bool IsSelected + { + get => isSelected; + set + { + if (value == isSelected) + return; + + isSelected = value; + BackgroundColour = isSelected ? Color4.SkyBlue : OsuColour.Gray(0.2f); + SpriteText.Colour = isSelected ? Color4.Black : Color4.White; + } + } } } } From eb9fff96bacec5ca553e0565336d11b9e341bd33 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Nov 2019 17:12:47 +0900 Subject: [PATCH 2184/2815] Read default beat divisor from beatmap --- osu.Game/Screens/Edit/Editor.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 35408e4003..97f01fd9f3 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -64,7 +64,10 @@ namespace osu.Game.Screens.Edit { this.host = host; - // TODO: should probably be done at a DrawableRuleset level to share logic with Player. + beatDivisor.Value = Beatmap.Value.BeatmapInfo.BeatDivisor; + beatDivisor.BindValueChanged(divisor => Beatmap.Value.BeatmapInfo.BeatDivisor = divisor.NewValue); + + // Todo: should probably be done at a DrawableRuleset level to share logic with Player. var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock(); clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false }; clock.ChangeSource(sourceClock); From 998e1dfe47aa0b1a2e05a63508194d0a831e208b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Nov 2019 17:23:47 +0900 Subject: [PATCH 2185/2815] Fix non-1/1 initial beat divisor control display --- .../Edit/Compose/Components/BeatDivisorControl.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 4d89e43ee5..9e4691e4dd 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -218,12 +218,17 @@ namespace osu.Game.Screens.Edit.Compose.Components } AddInternal(marker = new Marker()); + } - CurrentNumber.ValueChanged += div => + protected override void LoadComplete() + { + base.LoadComplete(); + + CurrentNumber.BindValueChanged(div => { marker.MoveToX(getMappedPosition(div.NewValue), 100, Easing.OutQuint); marker.Flash(); - }; + }, true); } protected override void UpdateValue(float value) From 36cc79f04f94c1c077fe8575a97e9b012f973d84 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Nov 2019 17:24:02 +0900 Subject: [PATCH 2186/2815] Softly handle invalid beat divisors instead of throwing --- osu.Game/Screens/Edit/BindableBeatDivisor.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/BindableBeatDivisor.cs index a724f354e7..ce95d81f54 100644 --- a/osu.Game/Screens/Edit/BindableBeatDivisor.cs +++ b/osu.Game/Screens/Edit/BindableBeatDivisor.cs @@ -29,7 +29,10 @@ namespace osu.Game.Screens.Edit set { if (!VALID_DIVISORS.Contains(value)) - throw new ArgumentOutOfRangeException($"Provided divisor is not in {nameof(VALID_DIVISORS)}"); + { + // If it doesn't match, value will be 0, but will be clamped to the valid range via DefaultMinValue + value = Array.FindLast(VALID_DIVISORS, d => d < value); + } base.Value = value; } From e90492831462e68adf09fa71203eb19e775d9f8e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Nov 2019 17:42:19 +0900 Subject: [PATCH 2187/2815] Seek to first hitobject when entering editor --- osu.Game/Screens/Edit/Editor.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 35408e4003..f932517387 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -69,6 +69,14 @@ namespace osu.Game.Screens.Edit clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false }; clock.ChangeSource(sourceClock); + if (Beatmap.Value.Beatmap.HitObjects.Count > 0) + { + double targetTime = Beatmap.Value.Beatmap.HitObjects[0].StartTime; + double beatLength = Beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(targetTime).BeatLength; + + clock.Seek(Math.Max(0, targetTime - beatLength)); + } + dependencies.CacheAs(clock); dependencies.CacheAs(clock); dependencies.Cache(beatDivisor); From 7b8ed1ac9ad0567189d327670e37b3b4dd7ad26c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Nov 2019 17:26:24 +0900 Subject: [PATCH 2188/2815] Improve tournament screen transitions --- .../Screens/TournamentScreen.cs | 12 ++--- osu.Game.Tournament/TournamentSceneManager.cs | 54 +++++++++++++------ 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tournament/Screens/TournamentScreen.cs b/osu.Game.Tournament/Screens/TournamentScreen.cs index 9d58ca2240..0b5b3e728b 100644 --- a/osu.Game.Tournament/Screens/TournamentScreen.cs +++ b/osu.Game.Tournament/Screens/TournamentScreen.cs @@ -10,6 +10,8 @@ namespace osu.Game.Tournament.Screens { public abstract class TournamentScreen : CompositeDrawable { + public const double FADE_DELAY = 200; + [Resolved] protected LadderInfo LadderInfo { get; private set; } @@ -18,14 +20,8 @@ namespace osu.Game.Tournament.Screens RelativeSizeAxes = Axes.Both; } - public override void Hide() - { - this.FadeOut(200); - } + public override void Hide() => this.FadeOut(FADE_DELAY); - public override void Show() - { - this.FadeIn(200); - } + public override void Show() => this.FadeIn(FADE_DELAY); } } diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index 02ee1c8603..d66f980202 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Platform; +using osu.Framework.Threading; using osu.Game.Graphics.UserInterface; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; @@ -130,37 +131,58 @@ namespace osu.Game.Tournament }, }; + foreach (var drawable in screens) + drawable.Hide(); + SetScreen(typeof(SetupScreen)); } + private float depth; + + private Drawable currentScreen; + private ScheduledDelegate scheduledHide; + public void SetScreen(Type screenType) { - var screen = screens.FirstOrDefault(s => s.GetType() == screenType); - if (screen == null) return; + var target = screens.FirstOrDefault(s => s.GetType() == screenType); - foreach (var s in screens.Children) + if (target == null || currentScreen == target) return; + + if (scheduledHide?.Completed == false) { - if (s == screen) - { - s.Show(); - if (s is IProvideVideo) - video.FadeOut(200); - else - video.Show(); - } - else - s.Hide(); + scheduledHide.RunTask(); + scheduledHide.Cancel(); // see https://github.com/ppy/osu-framework/issues/2967 + scheduledHide = null; } - switch (screen) + var lastScreen = currentScreen; + currentScreen = target; + + if (currentScreen is IProvideVideo) + { + video.FadeOut(200); + + // delay the hide to avoid a double-fade transition. + scheduledHide = Scheduler.AddDelayed(() => lastScreen?.Hide(), TournamentScreen.FADE_DELAY); + } + else + { + lastScreen?.Hide(); + video.Show(); + } + + screens.ChangeChildDepth(currentScreen, depth--); + currentScreen.Show(); + + switch (currentScreen) { case GameplayScreen _: case MapPoolScreen _: - chatContainer.FadeIn(100); + chatContainer.FadeIn(TournamentScreen.FADE_DELAY); break; default: - chatContainer.FadeOut(100); + chatContainer.FadeOut(TournamentScreen.FADE_DELAY); break; } } From 12b855d68feffd9b2053284de58d0c20c7bcdb36 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Nov 2019 17:56:11 +0900 Subject: [PATCH 2189/2815] Make drawing screen transition properly --- osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index 3a14b6d9c2..f78124a66a 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -24,7 +24,7 @@ using osuTK.Graphics; namespace osu.Game.Tournament.Screens.Drawings { - public class DrawingsScreen : CompositeDrawable + public class DrawingsScreen : TournamentScreen { private const string results_filename = "drawings_results.txt"; From a849bc074654a65d25500f14ade4cff330683ce0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Nov 2019 18:51:01 +0900 Subject: [PATCH 2190/2815] Move implementation into resetTrack for safety --- osu.Game/Screens/Edit/Editor.cs | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index f932517387..ca3b4fdce5 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -69,14 +69,6 @@ namespace osu.Game.Screens.Edit clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false }; clock.ChangeSource(sourceClock); - if (Beatmap.Value.Beatmap.HitObjects.Count > 0) - { - double targetTime = Beatmap.Value.Beatmap.HitObjects[0].StartTime; - double beatLength = Beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(targetTime).BeatLength; - - clock.Seek(Math.Max(0, targetTime - beatLength)); - } - dependencies.CacheAs(clock); dependencies.CacheAs(clock); dependencies.Cache(beatDivisor); @@ -249,7 +241,8 @@ namespace osu.Game.Screens.Edit base.OnEntering(last); Background.FadeColour(Color4.DarkGray, 500); - resetTrack(); + + resetTrack(true); } public override bool OnExiting(IScreen next) @@ -260,10 +253,24 @@ namespace osu.Game.Screens.Edit return base.OnExiting(next); } - private void resetTrack() + private void resetTrack(bool seekToStart = false) { Beatmap.Value.Track?.ResetSpeedAdjustments(); Beatmap.Value.Track?.Stop(); + + if (seekToStart) + { + double targetTime = 0; + + if (Beatmap.Value.Beatmap.HitObjects.Count > 0) + { + // seek to one beat length before the first hitobject + targetTime = Beatmap.Value.Beatmap.HitObjects[0].StartTime; + targetTime -= Beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(targetTime).BeatLength; + } + + clock.Seek(Math.Max(0, targetTime)); + } } private void exportBeatmap() => host.OpenFileExternally(Beatmap.Value.Save()); From 8ef9ccc39edf317f14a79b5bcb5a6fcc2ca091ea Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 8 Nov 2019 13:19:06 +0300 Subject: [PATCH 2191/2815] Schedule new track assignment after stopping current track --- osu.Game/Audio/PreviewTrackManager.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index fad2b5a5e8..08a69fa265 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -46,12 +46,18 @@ namespace osu.Game.Audio { var track = CreatePreviewTrack(beatmapSetInfo, trackStore); - track.Started += () => Schedule(() => + track.Started += () => { + // Stopping track should not be within the below schedule since its stop event schedules a null assign to current. + // Due to that, assigning the new track to current must be scheduled after the null assign to avoid current track loss. current?.Stop(); - current = track; - audio.Tracks.AddAdjustment(AdjustableProperty.Volume, muteBindable); - }); + + Schedule(() => + { + current = track; + audio.Tracks.AddAdjustment(AdjustableProperty.Volume, muteBindable); + }); + }; track.Stopped += () => Schedule(() => { From 901a8d597b81617c7032e9a16cbd57c5b0181511 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 8 Nov 2019 13:20:02 +0300 Subject: [PATCH 2192/2815] Add test steps ensuring correct behaviour --- .../Visual/Components/TestScenePreviewTrackManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs index f3f6444149..7d46958496 100644 --- a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs +++ b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs @@ -59,6 +59,9 @@ namespace osu.Game.Tests.Visual.Components AddStep("start track 2", () => track2.Start()); AddAssert("track 1 stopped", () => !track1.IsRunning); AddAssert("track 2 started", () => track2.IsRunning); + AddStep("start track 1", () => track1.Start()); + AddAssert("track 2 stopped", () => !track2.IsRunning); + AddAssert("track 1 started", () => track1.IsRunning); } [Test] From c280f1ab0826ae9b1fe3768ca9b8450d5d98d42f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Nov 2019 19:35:55 +0900 Subject: [PATCH 2193/2815] Fix incorrect DI usage in some tournament screen tests --- .../Screens/TestSceneTeamIntroScreen.cs | 5 ++--- osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs index 3d340e393c..e36b594ff2 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs @@ -3,7 +3,6 @@ using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Tournament.Models; using osu.Game.Tournament.Screens.TeamIntro; @@ -13,7 +12,7 @@ namespace osu.Game.Tournament.Tests.Screens public class TestSceneTeamIntroScreen : LadderTestScene { [Cached] - private readonly Bindable currentMatch = new Bindable(); + private readonly LadderInfo ladder = new LadderInfo(); [BackgroundDependencyLoader] private void load() @@ -22,7 +21,7 @@ namespace osu.Game.Tournament.Tests.Screens match.Team1.Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "USA"); match.Team2.Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "JPN"); match.Round.Value = Ladder.Rounds.FirstOrDefault(g => g.Name.Value == "Finals"); - currentMatch.Value = match; + ladder.CurrentMatch.Value = match; Add(new TeamIntroScreen { diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs index 6f5e17a36e..5cb35a506f 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs @@ -3,7 +3,6 @@ using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Tournament.Models; using osu.Game.Tournament.Screens.TeamWin; @@ -13,7 +12,7 @@ namespace osu.Game.Tournament.Tests.Screens public class TestSceneTeamWinScreen : LadderTestScene { [Cached] - private readonly Bindable currentMatch = new Bindable(); + private readonly LadderInfo ladder = new LadderInfo(); [BackgroundDependencyLoader] private void load() @@ -22,7 +21,7 @@ namespace osu.Game.Tournament.Tests.Screens match.Team1.Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "USA"); match.Team2.Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "JPN"); match.Round.Value = Ladder.Rounds.FirstOrDefault(g => g.Name.Value == "Finals"); - currentMatch.Value = match; + ladder.CurrentMatch.Value = match; Add(new TeamWinScreen { From 97ea07db0e6ec5406f8b73cdc05618b8aad35793 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Nov 2019 19:44:47 +0900 Subject: [PATCH 2194/2815] Add delete option to the right-click menu --- .../Edit/Compose/Components/SelectionHandler.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 7b61640c89..9b7082dec5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -80,8 +80,7 @@ namespace osu.Game.Screens.Edit.Compose.Components switch (action.ActionMethod) { case PlatformActionMethod.Delete: - foreach (var h in selectedBlueprints.ToList()) - placementHandler.Delete(h.DrawableObject.HitObject); + deleteSelected(); return true; } @@ -144,6 +143,12 @@ namespace osu.Game.Screens.Edit.Compose.Components UpdateVisibility(); } + private void deleteSelected() + { + foreach (var h in selectedBlueprints.ToList()) + placementHandler.Delete(h.DrawableObject.HitObject); + } + #endregion #region Outline Display @@ -234,7 +239,8 @@ namespace osu.Game.Screens.Edit.Compose.Components createHitSampleMenuItem("Clap", HitSampleInfo.HIT_CLAP), createHitSampleMenuItem("Finish", HitSampleInfo.HIT_FINISH) } - } + }, + new OsuMenuItem("Delete", MenuItemType.Destructive, deleteSelected), }; } } From 9ed8b3a125ec5b549d4d67e96775dd5d7b7897e6 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Fri, 8 Nov 2019 21:15:24 +0800 Subject: [PATCH 2195/2815] Use original dotsettings, with word IOS added to dictionary. --- osu.sln.DotSettings | 829 ++++++++++++++++++++++---------------------- 1 file changed, 409 insertions(+), 420 deletions(-) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 0a65fad9df..eb9e3ffafb 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -125,7 +125,6 @@ WARNING WARNING WARNING - WARNING WARNING WARNING ERROR @@ -202,7 +201,6 @@ HINT HINT - HINT WARNING WARNING WARNING @@ -273,8 +271,6 @@ GC GL GLSL - 2D - 3D HID HUD ID @@ -291,400 +287,399 @@ RGB RNG SHA - RGB SRGB TK - SS - PP - GMT - QAT - BNG + SS + PP + GMT + QAT + BNG UI False HINT <?xml version="1.0" encoding="utf-16"?> <Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> - <TypePattern DisplayName="COM interfaces or structs"> - <TypePattern.Match> - <Or> - <And> - <Kind Is="Interface" /> - <Or> - <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> - <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> - </Or> - </And> - <Kind Is="Struct" /> - </Or> - </TypePattern.Match> - </TypePattern> - <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> - <TypePattern.Match> - <And> - <Kind Is="Class" /> - <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> - </And> - </TypePattern.Match> - <Entry DisplayName="Setup/Teardown Methods"> - <Entry.Match> - <And> - <Kind Is="Method" /> - <Or> - <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="All other members" /> - <Entry Priority="100" DisplayName="Test Methods"> - <Entry.Match> - <And> - <Kind Is="Method" /> - <HasAttribute Name="NUnit.Framework.TestAttribute" /> - </And> - </Entry.Match> - <Entry.SortBy> - <Name /> - </Entry.SortBy> - </Entry> - </TypePattern> - <TypePattern DisplayName="Default Pattern"> - <Group DisplayName="Fields/Properties"> - <Group DisplayName="Public Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Public Properties"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - <Group DisplayName="Internal Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Internal Properties"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - <Group DisplayName="Protected Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Protected Properties"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - <Group DisplayName="Private Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Private Properties"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Constructor/Destructor"> - <Entry DisplayName="Ctor"> - <Entry.Match> - <Kind Is="Constructor" /> - </Entry.Match> - </Entry> - <Region Name="Disposal"> - <Entry DisplayName="Dtor"> - <Entry.Match> - <Kind Is="Destructor" /> - </Entry.Match> - </Entry> - <Entry DisplayName="Dispose()"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Kind Is="Method" /> - <Name Is="Dispose" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Dispose(true)"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Or> - <Virtual /> - <Override /> - </Or> - <Kind Is="Method" /> - <Name Is="Dispose" /> - </And> - </Entry.Match> - </Entry> - </Region> - </Group> - <Group DisplayName="Methods"> - <Group DisplayName="Public"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Internal"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Protected"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Private"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - </Group> - </TypePattern> + <TypePattern DisplayName="COM interfaces or structs"> + <TypePattern.Match> + <Or> + <And> + <Kind Is="Interface" /> + <Or> + <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> + <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> + </Or> + </And> + <Kind Is="Struct" /> + </Or> + </TypePattern.Match> + </TypePattern> + <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> + <TypePattern.Match> + <And> + <Kind Is="Class" /> + <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> + </And> + </TypePattern.Match> + <Entry DisplayName="Setup/Teardown Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <Or> + <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="All other members" /> + <Entry Priority="100" DisplayName="Test Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <HasAttribute Name="NUnit.Framework.TestAttribute" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + </TypePattern> + <TypePattern DisplayName="Default Pattern"> + <Group DisplayName="Fields/Properties"> + <Group DisplayName="Public Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Public Properties"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Internal Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Internal Properties"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Protected Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Protected Properties"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Private Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Private Properties"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Constructor/Destructor"> + <Entry DisplayName="Ctor"> + <Entry.Match> + <Kind Is="Constructor" /> + </Entry.Match> + </Entry> + <Region Name="Disposal"> + <Entry DisplayName="Dtor"> + <Entry.Match> + <Kind Is="Destructor" /> + </Entry.Match> + </Entry> + <Entry DisplayName="Dispose()"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Method" /> + <Name Is="Dispose" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Dispose(true)"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Or> + <Virtual /> + <Override /> + </Or> + <Kind Is="Method" /> + <Name Is="Dispose" /> + </And> + </Entry.Match> + </Entry> + </Region> + </Group> + <Group DisplayName="Methods"> + <Group DisplayName="Public"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Internal"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Protected"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Private"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + </Group> + </TypePattern> </Patterns> Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. See the LICENCE file in the repository root for full licence text. @@ -744,7 +739,6 @@ See the LICENCE file in the repository root for full licence text. <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - True True True @@ -780,7 +774,7 @@ Origin = Anchor.$anchor$, True InternalChildren = new Drawable[] { - $END$ + $END$ }; True True @@ -793,12 +787,12 @@ Origin = Anchor.$anchor$, True new GridContainer { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] { $END$ }, - new Drawable[] { } - } + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { $END$ }, + new Drawable[] { } + } }; True True @@ -811,12 +805,12 @@ Origin = Anchor.$anchor$, True new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - $END$ - } + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + $END$ + } }, True True @@ -829,11 +823,11 @@ Origin = Anchor.$anchor$, True new Container { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - $END$ - } + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + $END$ + } }, True True @@ -847,7 +841,7 @@ Origin = Anchor.$anchor$, [BackgroundDependencyLoader] private void load() { - $END$ + $END$ } True True @@ -860,8 +854,8 @@ private void load() True new Box { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, }, True True @@ -874,9 +868,8 @@ private void load() True Children = new Drawable[] { - $END$ + $END$ }; - True True True True @@ -894,8 +887,4 @@ private void load() True True True - True - True - True - True - True + True From a0f58ba9c47da15216ee769b9483d01c40d38f42 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Fri, 8 Nov 2019 21:17:59 +0800 Subject: [PATCH 2196/2815] Add missing project to iOS filter. --- osu.iOS.slnf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.iOS.slnf b/osu.iOS.slnf index 33a1497a54..48b1a095a1 100644 --- a/osu.iOS.slnf +++ b/osu.iOS.slnf @@ -12,7 +12,8 @@ "osu.Game.Rulesets.Taiko\\osu.Game.Rulesets.Taiko.csproj", "osu.Game.Tests.iOS\\osu.Game.Tests.iOS.csproj", "osu.Game.Tests\\osu.Game.Tests.csproj", - "osu.Game\\osu.Game.csproj" + "osu.Game\\osu.Game.csproj", + "osu.iOS\\osu.iOS.csproj" ] } } \ No newline at end of file From ff225c3691312bbb9d79bbc733c5ed760d5c9b25 Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 8 Nov 2019 06:04:18 -0800 Subject: [PATCH 2197/2815] Remove toolbarElements --- osu.Game/OsuGame.cs | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 3980390611..145b7de9f2 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -102,8 +102,6 @@ namespace osu.Game private readonly List overlays = new List(); - private readonly List toolbarElements = new List(); - private readonly List visibleBlockingOverlays = new List(); public OsuGame(string[] args = null) @@ -134,17 +132,13 @@ namespace osu.Game /// /// Close all game-wide overlays. /// - /// Whether the toolbar (and accompanying controls) should also be hidden. - public void CloseAllOverlays(bool hideToolbarElements = true) + /// Whether the toolbar should also be hidden. + public void CloseAllOverlays(bool hideToolbar = true) { foreach (var overlay in overlays) overlay.Hide(); - if (hideToolbarElements) - { - foreach (var overlay in toolbarElements) - overlay.Hide(); - } + if (hideToolbar) Toolbar.Hide(); } private DependencyContainer dependencies; @@ -570,11 +564,7 @@ namespace osu.Game CloseAllOverlays(false); menuScreen?.MakeCurrent(); }, - }, d => - { - topMostOverlayContent.Add(d); - toolbarElements.Add(d); - }); + }, topMostOverlayContent.Add); loadComponentSingleFile(volume = new VolumeOverlay(), leftFloatingOverlayContent.Add, true); @@ -613,11 +603,7 @@ namespace osu.Game GetToolbarHeight = () => ToolbarOffset, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - }, d => - { - rightFloatingOverlayContent.Add(d); - toolbarElements.Add(d); - }, true); + }, rightFloatingOverlayContent.Add, true); loadComponentSingleFile(new AccountCreationOverlay(), topMostOverlayContent.Add, true); loadComponentSingleFile(new DialogOverlay(), topMostOverlayContent.Add, true); From 2dd514012aa945d5f1d077b77c3cd0e6e9dc45e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Nov 2019 23:28:22 +0900 Subject: [PATCH 2198/2815] 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 d1860acbf9..7bfc27109f 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -62,6 +62,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index dbe6559c40..a646fc01d1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 4010ef1d74..e3a49ea1b3 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,8 +118,8 @@ - - + + From b675024161746b0f77d89a974f7ac905196e98d1 Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 8 Nov 2019 20:28:24 -0800 Subject: [PATCH 2199/2815] Remove horizontal padding on toolbar ruleset selector --- osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index 2c79f5bc0e..8f2dbce6f7 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -18,8 +18,6 @@ namespace osu.Game.Overlays.Toolbar { public class ToolbarRulesetSelector : RulesetSelector { - private const float padding = 10; - protected Drawable ModeButtonLine { get; private set; } public ToolbarRulesetSelector() @@ -39,7 +37,7 @@ namespace osu.Game.Overlays.Toolbar }, ModeButtonLine = new Container { - Size = new Vector2(padding * 2 + ToolbarButton.WIDTH, 3), + Size = new Vector2(ToolbarButton.WIDTH, 3), Anchor = Anchor.BottomLeft, Origin = Anchor.TopLeft, Masking = true, @@ -91,7 +89,6 @@ namespace osu.Game.Overlays.Toolbar RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Direction = FillDirection.Horizontal, - Padding = new MarginPadding { Left = padding, Right = padding }, }; protected override bool OnKeyDown(KeyDownEvent e) From 5c476416080276e01a7000b1644ea35170693672 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 9 Nov 2019 16:23:22 +0900 Subject: [PATCH 2200/2815] Reorder conditional --- osu.Game/Overlays/Comments/VotePill.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index a5a19f5111..ab35a477aa 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.Comments AccentColour = borderContainer.BorderColour = sideNumber.Colour = colours.GreenLight; hoverLayer.Colour = Color4.Black.Opacity(0.5f); - if (api.LocalUser.Value.Id != comment.UserId && api.IsLoggedIn) + if (api.IsLoggedIn && api.LocalUser.Value.Id != comment.UserId) Action = onAction; } From 20ed6c4d520444955e7fa690a9e06b6d1391ef9e Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 10 Nov 2019 23:07:51 +0300 Subject: [PATCH 2201/2815] Use track check solution for this --- osu.Game/Audio/PreviewTrackManager.cs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index 08a69fa265..f104dc8f9f 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -46,21 +46,20 @@ namespace osu.Game.Audio { var track = CreatePreviewTrack(beatmapSetInfo, trackStore); - track.Started += () => + track.Started += () => Schedule(() => { - // Stopping track should not be within the below schedule since its stop event schedules a null assign to current. - // Due to that, assigning the new track to current must be scheduled after the null assign to avoid current track loss. - current?.Stop(); + var last = current; + current = track; + last?.Stop(); - Schedule(() => - { - current = track; - audio.Tracks.AddAdjustment(AdjustableProperty.Volume, muteBindable); - }); - }; + audio.Tracks.AddAdjustment(AdjustableProperty.Volume, muteBindable); + }); track.Stopped += () => Schedule(() => { + if (current != track) + return; + current = null; audio.Tracks.RemoveAdjustment(AdjustableProperty.Volume, muteBindable); }); From 9d61d73cedf9088af374f18f1018f7e32f64b3cd Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 10 Nov 2019 23:09:04 +0300 Subject: [PATCH 2202/2815] Change Track and TrackManagerPreviewTrack accessibilities --- osu.Game/Audio/PreviewTrack.cs | 25 +++++++++++++------------ osu.Game/Audio/PreviewTrackManager.cs | 2 +- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/osu.Game/Audio/PreviewTrack.cs b/osu.Game/Audio/PreviewTrack.cs index 1234554e79..b39d2bc8f8 100644 --- a/osu.Game/Audio/PreviewTrack.cs +++ b/osu.Game/Audio/PreviewTrack.cs @@ -24,36 +24,37 @@ namespace osu.Game.Audio /// public event Action Started; - private Track track; + protected Track Track; + private bool hasStarted; [BackgroundDependencyLoader] private void load() { - track = GetTrack(); - if (track != null) - track.Completed += Stop; + Track = GetTrack(); + if (Track != null) + Track.Completed += Stop; } /// /// Length of the track. /// - public double Length => track?.Length ?? 0; + public double Length => Track?.Length ?? 0; /// /// The current track time. /// - public double CurrentTime => track?.CurrentTime ?? 0; + public double CurrentTime => Track?.CurrentTime ?? 0; /// /// Whether the track is loaded. /// - public bool TrackLoaded => track?.IsLoaded ?? false; + public bool TrackLoaded => Track?.IsLoaded ?? false; /// /// Whether the track is playing. /// - public bool IsRunning => track?.IsRunning ?? false; + public bool IsRunning => Track?.IsRunning ?? false; private ScheduledDelegate startDelegate; @@ -63,7 +64,7 @@ namespace osu.Game.Audio /// Whether the track is started or already playing. public bool Start() { - if (track == null) + if (Track == null) return false; startDelegate = Schedule(() => @@ -73,7 +74,7 @@ namespace osu.Game.Audio hasStarted = true; - track.Restart(); + Track.Restart(); Started?.Invoke(); }); @@ -87,7 +88,7 @@ namespace osu.Game.Audio { startDelegate?.Cancel(); - if (track == null) + if (Track == null) return; if (!hasStarted) @@ -95,7 +96,7 @@ namespace osu.Game.Audio hasStarted = false; - track.Stop(); + Track.Stop(); Stopped?.Invoke(); } diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index f104dc8f9f..1c1b4c3059 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -90,7 +90,7 @@ namespace osu.Game.Audio /// protected virtual TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackStore) => new TrackManagerPreviewTrack(beatmapSetInfo, trackStore); - protected class TrackManagerPreviewTrack : PreviewTrack + public class TrackManagerPreviewTrack : PreviewTrack { public IPreviewTrackOwner Owner { get; private set; } From 9a00898c0d3dae94f922782fc3eebc871959d04b Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 10 Nov 2019 23:21:46 +0300 Subject: [PATCH 2203/2815] Add test for non-present preview tracks To avoid reverting what #6738 solved --- .../TestScenePreviewTrackManager.cs | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs index 7d46958496..3a9fce90cd 100644 --- a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs +++ b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs @@ -7,6 +7,7 @@ using osu.Framework.Audio.Track; using osu.Framework.Graphics.Containers; using osu.Game.Audio; using osu.Game.Beatmaps; +using static osu.Game.Tests.Visual.Components.TestScenePreviewTrackManager.TestPreviewTrackManager; namespace osu.Game.Tests.Visual.Components { @@ -91,9 +92,25 @@ namespace osu.Game.Tests.Visual.Components AddAssert("stopped", () => !track.IsRunning); } - private PreviewTrack getTrack() => trackManager.Get(null); + [Test] + public void TestNonPresentTrack() + { + TestPreviewTrack track = null; - private PreviewTrack getOwnedTrack() + AddStep("get non-present track", () => + { + Add(new TestTrackOwner(track = getTrack())); + track.Alpha = 0; + }); + AddUntilStep("wait loaded", () => track.IsLoaded); + AddStep("start", () => track.Start()); + AddStep("seek to end", () => track.Track.Seek(track.Track.Length)); + AddAssert("track stopped", () => !track.IsRunning); + } + + private TestPreviewTrack getTrack() => (TestPreviewTrack)trackManager.Get(null); + + private TestPreviewTrack getOwnedTrack() { var track = getTrack(); @@ -125,14 +142,16 @@ namespace osu.Game.Tests.Visual.Components } } - private class TestPreviewTrackManager : PreviewTrackManager + public class TestPreviewTrackManager : PreviewTrackManager { protected override TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackStore) => new TestPreviewTrack(beatmapSetInfo, trackStore); - protected class TestPreviewTrack : TrackManagerPreviewTrack + public class TestPreviewTrack : TrackManagerPreviewTrack { private readonly ITrackStore trackManager; + public new Track Track => base.Track; + public TestPreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackManager) : base(beatmapSetInfo, trackManager) { From 8f4916ad2d7fee51b0c28acad32a89fdf6c8f3ec Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 10 Nov 2019 23:53:31 +0300 Subject: [PATCH 2204/2815] Add inline comment --- osu.Game/Audio/PreviewTrackManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index 1c1b4c3059..8f723194e3 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -48,6 +48,7 @@ namespace osu.Game.Audio track.Started += () => Schedule(() => { + // Assign the new track to current before stopping last track to avoid assigning null to current. var last = current; current = track; last?.Stop(); From 93954c8da0996307f277473bf4ac894d97d06395 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 10 Nov 2019 23:58:07 +0300 Subject: [PATCH 2205/2815] Use BindableList for selected mods --- .../Online/TestSceneLeaderboardModSelector.cs | 31 +++++++++++++------ .../BeatmapSet/LeaderboardModSelector.cs | 20 +++++------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs index 3a64ac79f6..799528220b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Bindables; using osu.Game.Rulesets; +using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Tests.Visual.Online { @@ -26,10 +27,10 @@ namespace osu.Game.Tests.Visual.Online public TestSceneLeaderboardModSelector() { LeaderboardModSelector modSelector; - FillFlowContainer selectedMods; + FillFlowContainer selectedMods; Bindable ruleset = new Bindable(); - Add(selectedMods = new FillFlowContainer + Add(selectedMods = new FillFlowContainer { Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, @@ -42,16 +43,28 @@ namespace osu.Game.Tests.Visual.Online Ruleset = { BindTarget = ruleset } }); - modSelector.SelectedMods.BindValueChanged(mods => + modSelector.SelectedMods.ItemsAdded += mods => { - selectedMods.Clear(); + mods.ForEach(mod => selectedMods.Add(new SpriteText + { + Text = mod.Acronym, + })); + }; - foreach (var mod in mods.NewValue) - selectedMods.Add(new SpriteText + modSelector.SelectedMods.ItemsRemoved += mods => + { + mods.ForEach(mod => + { + foreach (var selected in selectedMods) { - Text = mod.Acronym, - }); - }); + if (selected.Text == mod.Acronym) + { + selectedMods.Remove(selected); + break; + } + } + }); + }; AddStep("osu ruleset", () => ruleset.Value = new OsuRuleset().RulesetInfo); AddStep("mania ruleset", () => ruleset.Value = new ManiaRuleset().RulesetInfo); diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index 4b6fd5bfc2..75c24cb710 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -5,7 +5,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Game.Rulesets.Mods; using osu.Framework.Bindables; -using System.Collections.Generic; using osu.Game.Rulesets; using osuTK; using osu.Game.Rulesets.UI; @@ -21,7 +20,7 @@ namespace osu.Game.Overlays.BeatmapSet { public class LeaderboardModSelector : CompositeDrawable { - public readonly Bindable> SelectedMods = new Bindable>(); + public readonly BindableList SelectedMods = new BindableList(); public readonly Bindable Ruleset = new Bindable(); private readonly FillFlowContainer modsContainer; @@ -47,8 +46,7 @@ namespace osu.Game.Overlays.BeatmapSet private void onRulesetChanged(ValueChangedEvent ruleset) { - SelectedMods.Value = new List(); - + SelectedMods.Clear(); modsContainer.Clear(); if (ruleset.NewValue == null) @@ -67,22 +65,18 @@ namespace osu.Game.Overlays.BeatmapSet private void selectionChanged(Mod mod, bool selected) { - var mods = SelectedMods.Value.ToList(); - if (selected) - mods.Add(mod); + SelectedMods.Add(mod); else - mods.Remove(mod); + SelectedMods.Remove(mod); - SelectedMods.Value = mods; - - if (!mods.Any() && !IsHovered) + if (!SelectedMods.Any() && !IsHovered) highlightAll(); } protected override bool OnHover(HoverEvent e) { - if (!SelectedMods.Value.Any()) + if (!SelectedMods.Any()) modsContainer.ForEach(button => { if (!button.IsHovered) @@ -96,7 +90,7 @@ namespace osu.Game.Overlays.BeatmapSet { base.OnHoverLost(e); - if (!SelectedMods.Value.Any()) + if (!SelectedMods.Any()) highlightAll(); } From 5e70e1e53f6c3be0cf4eb0348095cb78e712682f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Nov 2019 11:31:42 +0900 Subject: [PATCH 2206/2815] Update .gitignore --- .gitignore | 100 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 80 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index e60058ab35..e6b5db5904 100644 --- a/.gitignore +++ b/.gitignore @@ -10,14 +10,8 @@ # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs -### Cake ### -tools/** -build/tools/** - -fastlane/report.xml - # Build results -bin/[Dd]ebug/ +[Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ @@ -104,7 +98,6 @@ $tf/ _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user -inspectcode # JustCode is a .NET coding add-in .JustCode @@ -254,20 +247,87 @@ paket-files/ # FAKE - F# Make .fake/ -# JetBrains Rider -.idea/.idea.osu/.idea/*.xml -.idea/.idea.osu/.idea/codeStyles/*.xml -.idea/.idea.osu/.idea/dataSources/*.xml -.idea/.idea.osu/.idea/dictionaries/*.xml -.idea/.idea.osu/*.iml -*.sln.iml - -# CodeRush -.cr/ - # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc -Staging/ +# Cake # +/tools/** +/build/tools/** +/build/temp/** + +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +# fastlane +fastlane/report.xml + +# inspectcode inspectcodereport.xml +inspectcode From 93f1c3c1520c95ecd7494e36bbdbc9fd4f50bbc3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Nov 2019 11:31:55 +0900 Subject: [PATCH 2207/2815] Update rider configurations --- .idea/.gitignore | 0 .idea/.idea.osu.Desktop/.idea/.name | 1 + .../.idea/codeStyles/codeStyleConfig.xml | 5 +++++ .idea/.idea.osu.Desktop/.idea/dataSources.xml | 14 ++++++++++++++ .idea/.idea.osu.Desktop/.idea/encodings.xml | 4 ++++ .idea/.idea.osu.Desktop/.idea/indexLayout.xml | 8 ++++++++ .idea/.idea.osu.Desktop/.idea/misc.xml | 6 ++++++ .idea/.idea.osu.Desktop/.idea/modules.xml | 8 ++++++++ .../.idea/projectSettingsUpdater.xml | 6 ++++++ .../runConfigurations/CatchRuleset__Tests_.xml | 0 .../runConfigurations/ManiaRuleset__Tests_.xml | 0 .../runConfigurations/OsuRuleset__Tests_.xml | 0 .../runConfigurations/TaikoRuleset__Tests_.xml | 0 .../.idea/runConfigurations/Tournament.xml | 0 .../runConfigurations/Tournament__Tests_.xml | 0 .../.idea/runConfigurations/osu_.xml | 0 .../.idea/runConfigurations/osu___Tests_.xml | 0 .idea/.idea.osu.Desktop/.idea/vcs.xml | 16 ++++++++++++++++ .idea/.idea.osu/.idea/indexLayout.xml | 8 ++++++++ .idea/.idea.osu/.idea/modules.xml | 8 ++++++++ .idea/.idea.osu/.idea/projectSettingsUpdater.xml | 6 ++++++ .idea/.idea.osu/.idea/vcs.xml | 6 ++++++ 22 files changed, 96 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/.idea.osu.Desktop/.idea/.name create mode 100644 .idea/.idea.osu.Desktop/.idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/.idea.osu.Desktop/.idea/dataSources.xml create mode 100644 .idea/.idea.osu.Desktop/.idea/encodings.xml create mode 100644 .idea/.idea.osu.Desktop/.idea/indexLayout.xml create mode 100644 .idea/.idea.osu.Desktop/.idea/misc.xml create mode 100644 .idea/.idea.osu.Desktop/.idea/modules.xml create mode 100644 .idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml rename .idea/{.idea.osu => .idea.osu.Desktop}/.idea/runConfigurations/CatchRuleset__Tests_.xml (100%) rename .idea/{.idea.osu => .idea.osu.Desktop}/.idea/runConfigurations/ManiaRuleset__Tests_.xml (100%) rename .idea/{.idea.osu => .idea.osu.Desktop}/.idea/runConfigurations/OsuRuleset__Tests_.xml (100%) rename .idea/{.idea.osu => .idea.osu.Desktop}/.idea/runConfigurations/TaikoRuleset__Tests_.xml (100%) rename .idea/{.idea.osu => .idea.osu.Desktop}/.idea/runConfigurations/Tournament.xml (100%) rename .idea/{.idea.osu => .idea.osu.Desktop}/.idea/runConfigurations/Tournament__Tests_.xml (100%) rename .idea/{.idea.osu => .idea.osu.Desktop}/.idea/runConfigurations/osu_.xml (100%) rename .idea/{.idea.osu => .idea.osu.Desktop}/.idea/runConfigurations/osu___Tests_.xml (100%) create mode 100644 .idea/.idea.osu.Desktop/.idea/vcs.xml create mode 100644 .idea/.idea.osu/.idea/indexLayout.xml create mode 100644 .idea/.idea.osu/.idea/modules.xml create mode 100644 .idea/.idea.osu/.idea/projectSettingsUpdater.xml create mode 100644 .idea/.idea.osu/.idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.idea/.idea.osu.Desktop/.idea/.name b/.idea/.idea.osu.Desktop/.idea/.name new file mode 100644 index 0000000000..12bf4aebba --- /dev/null +++ b/.idea/.idea.osu.Desktop/.idea/.name @@ -0,0 +1 @@ +osu.Desktop \ No newline at end of file diff --git a/.idea/.idea.osu.Desktop/.idea/codeStyles/codeStyleConfig.xml b/.idea/.idea.osu.Desktop/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000000..a55e7a179b --- /dev/null +++ b/.idea/.idea.osu.Desktop/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.osu.Desktop/.idea/dataSources.xml b/.idea/.idea.osu.Desktop/.idea/dataSources.xml new file mode 100644 index 0000000000..10f8c1c84d --- /dev/null +++ b/.idea/.idea.osu.Desktop/.idea/dataSources.xml @@ -0,0 +1,14 @@ + + + + + sqlite.xerial + true + org.sqlite.JDBC + jdbc:sqlite:$USER_HOME$/.local/share/osu/client.db + + + + + + \ No newline at end of file diff --git a/.idea/.idea.osu.Desktop/.idea/encodings.xml b/.idea/.idea.osu.Desktop/.idea/encodings.xml new file mode 100644 index 0000000000..15a15b218a --- /dev/null +++ b/.idea/.idea.osu.Desktop/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.osu.Desktop/.idea/indexLayout.xml b/.idea/.idea.osu.Desktop/.idea/indexLayout.xml new file mode 100644 index 0000000000..27ba142e96 --- /dev/null +++ b/.idea/.idea.osu.Desktop/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.osu.Desktop/.idea/misc.xml b/.idea/.idea.osu.Desktop/.idea/misc.xml new file mode 100644 index 0000000000..1d8c84d0af --- /dev/null +++ b/.idea/.idea.osu.Desktop/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/.idea.osu.Desktop/.idea/modules.xml b/.idea/.idea.osu.Desktop/.idea/modules.xml new file mode 100644 index 0000000000..fe63f5faf3 --- /dev/null +++ b/.idea/.idea.osu.Desktop/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml b/.idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml new file mode 100644 index 0000000000..7515e76054 --- /dev/null +++ b/.idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/.idea.osu/.idea/runConfigurations/CatchRuleset__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/CatchRuleset__Tests_.xml similarity index 100% rename from .idea/.idea.osu/.idea/runConfigurations/CatchRuleset__Tests_.xml rename to .idea/.idea.osu.Desktop/.idea/runConfigurations/CatchRuleset__Tests_.xml diff --git a/.idea/.idea.osu/.idea/runConfigurations/ManiaRuleset__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/ManiaRuleset__Tests_.xml similarity index 100% rename from .idea/.idea.osu/.idea/runConfigurations/ManiaRuleset__Tests_.xml rename to .idea/.idea.osu.Desktop/.idea/runConfigurations/ManiaRuleset__Tests_.xml diff --git a/.idea/.idea.osu/.idea/runConfigurations/OsuRuleset__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/OsuRuleset__Tests_.xml similarity index 100% rename from .idea/.idea.osu/.idea/runConfigurations/OsuRuleset__Tests_.xml rename to .idea/.idea.osu.Desktop/.idea/runConfigurations/OsuRuleset__Tests_.xml diff --git a/.idea/.idea.osu/.idea/runConfigurations/TaikoRuleset__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/TaikoRuleset__Tests_.xml similarity index 100% rename from .idea/.idea.osu/.idea/runConfigurations/TaikoRuleset__Tests_.xml rename to .idea/.idea.osu.Desktop/.idea/runConfigurations/TaikoRuleset__Tests_.xml diff --git a/.idea/.idea.osu/.idea/runConfigurations/Tournament.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament.xml similarity index 100% rename from .idea/.idea.osu/.idea/runConfigurations/Tournament.xml rename to .idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament.xml diff --git a/.idea/.idea.osu/.idea/runConfigurations/Tournament__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament__Tests_.xml similarity index 100% rename from .idea/.idea.osu/.idea/runConfigurations/Tournament__Tests_.xml rename to .idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament__Tests_.xml diff --git a/.idea/.idea.osu/.idea/runConfigurations/osu_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_.xml similarity index 100% rename from .idea/.idea.osu/.idea/runConfigurations/osu_.xml rename to .idea/.idea.osu.Desktop/.idea/runConfigurations/osu_.xml diff --git a/.idea/.idea.osu/.idea/runConfigurations/osu___Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___Tests_.xml similarity index 100% rename from .idea/.idea.osu/.idea/runConfigurations/osu___Tests_.xml rename to .idea/.idea.osu.Desktop/.idea/runConfigurations/osu___Tests_.xml diff --git a/.idea/.idea.osu.Desktop/.idea/vcs.xml b/.idea/.idea.osu.Desktop/.idea/vcs.xml new file mode 100644 index 0000000000..3de04b744c --- /dev/null +++ b/.idea/.idea.osu.Desktop/.idea/vcs.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.osu/.idea/indexLayout.xml b/.idea/.idea.osu/.idea/indexLayout.xml new file mode 100644 index 0000000000..27ba142e96 --- /dev/null +++ b/.idea/.idea.osu/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.osu/.idea/modules.xml b/.idea/.idea.osu/.idea/modules.xml new file mode 100644 index 0000000000..0360fdbc5e --- /dev/null +++ b/.idea/.idea.osu/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.osu/.idea/projectSettingsUpdater.xml b/.idea/.idea.osu/.idea/projectSettingsUpdater.xml new file mode 100644 index 0000000000..7515e76054 --- /dev/null +++ b/.idea/.idea.osu/.idea/projectSettingsUpdater.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/.idea.osu/.idea/vcs.xml b/.idea/.idea.osu/.idea/vcs.xml new file mode 100644 index 0000000000..94a25f7f4c --- /dev/null +++ b/.idea/.idea.osu/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file From 990a82cd06593da8d7a4b6e30b24b26dc98d00ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Nov 2019 11:32:06 +0900 Subject: [PATCH 2208/2815] Update readme --- README.md | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 1e2d539697..67eb3254e1 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,8 @@ git pull Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this provided [below](#contributing). -> Visual Studio Code users must run the `Restore` task before any build attempt. +- Visual Studio / Rider users should load the project via one of the platform-specific .slnf files, rather than the main .sln. This will allow access to template run configurations. +- Visual Studio Code users must run the `Restore` task before any build attempt. You can also build and run osu! from the command-line with a single command: @@ -69,18 +70,6 @@ If you are not interested in debugging osu!, you can add `-c Release` to gain pe If the build fails, try to restore nuget packages with `dotnet restore`. -#### A note for Linux users - -On Linux, the environment variable `LD_LIBRARY_PATH` must point to the build directory, located at `osu.Desktop/bin/Debug/$NETCORE_VERSION`. - -`$NETCORE_VERSION` is the version of the targeted .NET Core SDK. You can check it by running `grep TargetFramework osu.Desktop/osu.Desktop.csproj | sed -r 's/.*>(.*)<\/.*/\1/'`. - -For example, you can run osu! with the following command: - -```shell -LD_LIBRARY_PATH="$(pwd)/osu.Desktop/bin/Debug/netcoreapp3.0" dotnet run --project osu.Desktop -``` - ### Testing with resource/framework modifications Sometimes it may be necessary to cross-test changes in [osu-resources](https://github.com/ppy/osu-resources) or [osu-framework](https://github.com/ppy/osu-framework). This can be achieved by running some commands as documented on the [osu-resources](https://github.com/ppy/osu-resources/wiki/Testing-local-resources-checkout-with-other-projects) and [osu-framework](https://github.com/ppy/osu-framework/wiki/Testing-local-framework-checkout-with-other-projects) wiki pages. From e924a5d51e0596ab003d07c38ee3417bacb431a4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 11 Nov 2019 13:04:38 +0900 Subject: [PATCH 2209/2815] Disable ruleset input in the editor --- osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs index af565f8896..4710465536 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs @@ -64,6 +64,10 @@ namespace osu.Game.Rulesets.Edit drawableRuleset.Playfield.PostProcess(); } + public override bool PropagatePositionalInputSubTree => false; + + public override bool PropagateNonPositionalInputSubTree => false; + public PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => drawableRuleset.CreatePlayfieldAdjustmentContainer(); protected override void Dispose(bool isDisposing) From 3b13ad480af5afa7f0fe15c300a5e02bcf0fb4d7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 11 Nov 2019 13:06:41 +0900 Subject: [PATCH 2210/2815] Increase fade-out time of hitobjects in the editor --- .../Edit/DrawableOsuEditRuleset.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index cc08d356f9..e262fa3c60 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -2,8 +2,12 @@ // 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.Game.Beatmaps; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using osuTK; @@ -17,6 +21,25 @@ namespace osu.Game.Rulesets.Osu.Edit { } + public override DrawableHitObject CreateDrawableRepresentation(OsuHitObject h) + => base.CreateDrawableRepresentation(h)?.With(d => d.ApplyCustomUpdateState += updateState); + + private void updateState(DrawableHitObject hitObject, ArmedState state) + { + switch (state) + { + case ArmedState.Miss: + // Get the existing fade out transform + var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha)); + if (existing == null) + return; + + using (hitObject.BeginAbsoluteSequence(existing.StartTime)) + hitObject.FadeOut(500).Expire(); + break; + } + } + protected override Playfield CreatePlayfield() => new OsuPlayfieldNoCursor(); public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer { Size = Vector2.One }; From f3dc38e3427f9c52364bcc11a8ea36d065b0934c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 11 Nov 2019 13:41:10 +0900 Subject: [PATCH 2211/2815] Add Ctrl+A to select all (esc to deselect all) --- .../Compose/Components/BlueprintContainer.cs | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 8fa022f129..7528ac8aa6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Input; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Rulesets.Edit; @@ -17,10 +18,11 @@ using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osuTK; +using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { - public class BlueprintContainer : CompositeDrawable + public class BlueprintContainer : CompositeDrawable, IKeyBindingHandler { public event Action> SelectionChanged; @@ -169,6 +171,37 @@ namespace osu.Game.Screens.Edit.Compose.Components return true; } + protected override bool OnKeyDown(KeyDownEvent e) + { + switch (e.Key) + { + case Key.Escape: + if (!selectionHandler.SelectedBlueprints.Any()) + return false; + + deselectAll(); + return true; + } + + return false; + } + + protected override bool OnKeyUp(KeyUpEvent e) => false; + + public bool OnPressed(PlatformAction action) + { + switch (action.ActionType) + { + case PlatformActionType.SelectAll: + selectAll(); + return true; + } + + return false; + } + + public bool OnReleased(PlatformAction action) => false; + protected override void Update() { base.Update(); @@ -314,6 +347,15 @@ namespace osu.Game.Screens.Edit.Compose.Components } } + /// + /// Selects all s. + /// + private void selectAll() + { + selectionBlueprints.ToList().ForEach(m => m.Select()); + selectionHandler.UpdateVisibility(); + } + /// /// Deselects all selected s. /// From dc88bd3d611a1a5c31c3768eb637725b6a6bf02d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Nov 2019 13:49:12 +0900 Subject: [PATCH 2212/2815] Add local preserving container to OsuTestScene to ensure correct test dimensions --- osu.Game/Tests/Visual/OsuTestScene.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 96b39b303e..daa1a56221 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -10,12 +10,16 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Database; +using osu.Game.Graphics.Containers; using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -93,6 +97,10 @@ namespace osu.Game.Tests.Visual return Dependencies; } + protected override Container Content => content ?? base.Content; + + private readonly Container content; + protected OsuTestScene() { localStorage = new Lazy(() => new NativeStorage($"{GetType().Name}-{Guid.NewGuid()}")); @@ -104,6 +112,8 @@ namespace osu.Game.Tests.Visual usage.Migrate(); return factory; }); + + base.Content.Add(content = new DrawSizePreservingFillContainer()); } [Resolved] From 8ac708ada5f44773ed593f02f5badd144d9a93f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Nov 2019 13:58:35 +0900 Subject: [PATCH 2213/2815] Move scaling container to OsuGame so OsuGameBase doesn't apply UI scale --- osu.Game/OsuGame.cs | 2 ++ osu.Game/OsuGameBase.cs | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 1f823e6eba..c375ccda81 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -393,6 +393,8 @@ namespace osu.Game protected virtual Loader CreateLoader() => new Loader(); + protected override Container CreateScalingContainer() => new ScalingContainer(ScalingMode.Everything); + #region Beatmap progression private void beatmapChanged(ValueChangedEvent beatmap) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 194a439b06..947df75783 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -26,7 +26,6 @@ using osu.Framework.Input; using osu.Framework.Logging; using osu.Game.Audio; using osu.Game.Database; -using osu.Game.Graphics.Containers; using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.IO; @@ -228,7 +227,7 @@ namespace osu.Game Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both } }; - base.Content.Add(new ScalingContainer(ScalingMode.Everything) { Child = MenuCursorContainer }); + base.Content.Add(CreateScalingContainer().WithChild(MenuCursorContainer)); KeyBindingStore.Register(globalBinding); dependencies.Cache(globalBinding); @@ -238,6 +237,8 @@ namespace osu.Game Add(previewTrackManager); } + protected virtual Container CreateScalingContainer() => new DrawSizePreservingFillContainer(); + protected override void LoadComplete() { base.LoadComplete(); From 13fd95d513da835f427e59095b606ced479ff29b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Nov 2019 14:03:29 +0900 Subject: [PATCH 2214/2815] Remove misplaced usings --- osu.Game/Tests/Visual/OsuTestScene.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index daa1a56221..345fff90aa 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -17,9 +17,7 @@ using osu.Framework.Platform; using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; -using osu.Game.Configuration; using osu.Game.Database; -using osu.Game.Graphics.Containers; using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; From fced262c41859aa3fc01f3ffcb3eeded6610245e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Nov 2019 17:03:40 +0900 Subject: [PATCH 2215/2815] Add labelled dropdown component --- .../UserInterfaceV2/LabelledDropdown.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 osu.Game/Graphics/UserInterfaceV2/LabelledDropdown.cs diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledDropdown.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledDropdown.cs new file mode 100644 index 0000000000..7e50fa7bc6 --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledDropdown.cs @@ -0,0 +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 System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class LabelledDropdown : LabelledComponent, T> + { + public LabelledDropdown() + : base(true) + { + } + + public IEnumerable Items + { + get => Component.Items; + set => Component.Items = value; + } + + protected override OsuDropdown CreateComponent() => new OsuDropdown + { + RelativeSizeAxes = Axes.X, + Width = 0.5f, + }; + } +} From 702a1c496bd6e2a146dd5a7b2a82d42752f90e42 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Nov 2019 17:03:50 +0900 Subject: [PATCH 2216/2815] Add ruleset selection to tournament client --- osu.Game.Tournament/Models/LadderInfo.cs | 3 +++ osu.Game.Tournament/Screens/SetupScreen.cs | 13 ++++++++++++- osu.Game.Tournament/TournamentGameBase.cs | 2 ++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Models/LadderInfo.cs b/osu.Game.Tournament/Models/LadderInfo.cs index 547c4eab08..5db0b01547 100644 --- a/osu.Game.Tournament/Models/LadderInfo.cs +++ b/osu.Game.Tournament/Models/LadderInfo.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using Newtonsoft.Json; using osu.Framework.Bindables; +using osu.Game.Rulesets; namespace osu.Game.Tournament.Models { @@ -14,6 +15,8 @@ namespace osu.Game.Tournament.Models [Serializable] public class LadderInfo { + public Bindable Ruleset = new Bindable(); + public BindableList Matches = new BindableList(); public BindableList Rounds = new BindableList(); public BindableList Teams = new BindableList(); diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index a67daa2756..b511d572da 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -10,6 +10,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Online.API; using osu.Game.Overlays; +using osu.Game.Rulesets; using osu.Game.Tournament.IPC; using osuTK; using osuTK.Graphics; @@ -28,6 +29,9 @@ namespace osu.Game.Tournament.Screens [Resolved] private IAPIProvider api { get; set; } + [Resolved] + private RulesetStore rulesets { get; set; } + [BackgroundDependencyLoader] private void load() { @@ -85,7 +89,14 @@ namespace osu.Game.Tournament.Screens Value = api?.LocalUser.Value.Username, Failing = api?.IsLoggedIn != true, Description = "In order to access the API and display metadata, a login is required." - } + }, + new LabelledDropdown + { + Label = "Ruleset", + Description = "Decides what stats are displayed and which ranks are retrieved for players", + Items = rulesets.AvailableRulesets, + Current = LadderInfo.Ruleset, + }, }; } diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 3b7718c61d..59edd18341 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -126,6 +126,8 @@ namespace osu.Game.Tournament ladder = new LadderInfo(); } + Ruleset.BindTo(ladder.Ruleset); + dependencies.Cache(ladder); bool addedInfo = false; From 5d96e6d90a30b85d27866d70ce9ff5f693247e0f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Nov 2019 17:39:48 +0900 Subject: [PATCH 2217/2815] Populate users centrally, using the correct ruleset --- .../Screens/Editors/TeamEditorScreen.cs | 26 +++------------ osu.Game.Tournament/TournamentGameBase.cs | 32 ++++++++++++++++--- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index e1f6d16623..11c2732d62 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Online.API; -using osu.Game.Online.API.Requests; using osu.Game.Overlays.Settings; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; @@ -24,7 +23,7 @@ namespace osu.Game.Tournament.Screens.Editors public class TeamEditorScreen : TournamentEditorScreen { [Resolved] - private Framework.Game game { get; set; } + private TournamentGameBase game { get; set; } protected override BindableList Storage => LadderInfo.Teams; @@ -198,6 +197,9 @@ namespace osu.Game.Tournament.Screens.Editors [Resolved] protected IAPIProvider API { get; private set; } + [Resolved] + private TournamentGameBase game { get; set; } + private readonly Bindable userId = new Bindable(); private readonly Container drawableContainer; @@ -280,25 +282,7 @@ namespace osu.Game.Tournament.Screens.Editors return; } - var req = new GetUserRequest(user.Id); - - req.Success += res => - { - // TODO: this should be done in a better way. - user.Username = res.Username; - user.Country = res.Country; - user.Cover = res.Cover; - - updatePanel(); - }; - - req.Failure += _ => - { - user.Id = 1; - updatePanel(); - }; - - API.Queue(req); + game.PopulateUser(user, updatePanel, updatePanel); }, true); } diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 59edd18341..b0ca099a86 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.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.Drawing; using System.IO; using System.Linq; @@ -21,11 +22,13 @@ using osu.Game.Graphics; using osu.Game.Online.API.Requests; using osu.Game.Tournament.IPC; using osu.Game.Tournament.Models; +using osu.Game.Users; using osuTK.Graphics; using osuTK.Input; namespace osu.Game.Tournament { + [Cached(typeof(TournamentGameBase))] public abstract class TournamentGameBase : OsuGameBase { private const string bracket_filename = "bracket.json"; @@ -197,10 +200,7 @@ namespace osu.Game.Tournament foreach (var p in t.Players) if (string.IsNullOrEmpty(p.Username)) { - var req = new GetUserRequest(p.Id); - req.Perform(API); - p.Username = req.Result.Username; - + PopulateUser(p); addedInfo = true; } @@ -228,6 +228,30 @@ namespace osu.Game.Tournament return addedInfo; } + public void PopulateUser(User user, Action success = null, Action failure = null) + { + var req = new GetUserRequest(user.Id, Ruleset.Value); + + req.Success += res => + { + user.Username = res.Username; + user.Statistics = res.Statistics; + user.Username = res.Username; + user.Country = res.Country; + user.Cover = res.Cover; + + success?.Invoke(); + }; + + req.Failure += _ => + { + user.Id = 1; + failure?.Invoke(); + }; + + API.Queue(req); + } + protected override void LoadComplete() { MenuCursorContainer.Cursor.AlwaysPresent = true; // required for tooltip display From 6d3d7c5d95823d9915e197116e7f327dad54592b Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 11 Nov 2019 11:57:14 +0300 Subject: [PATCH 2218/2815] Remove unnecessary use of local --- osu.Game/Audio/PreviewTrackManager.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index 8f723194e3..72b33c4073 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -48,11 +48,8 @@ namespace osu.Game.Audio track.Started += () => Schedule(() => { - // Assign the new track to current before stopping last track to avoid assigning null to current. - var last = current; + current?.Stop(); current = track; - last?.Stop(); - audio.Tracks.AddAdjustment(AdjustableProperty.Volume, muteBindable); }); From a345fd8a86ef706aed8f004ccd1d380e5ee15495 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2019 09:26:56 +0000 Subject: [PATCH 2219/2815] Bump Newtonsoft.Json from 12.0.2 to 12.0.3 Bumps [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) from 12.0.2 to 12.0.3. - [Release notes](https://github.com/JamesNK/Newtonsoft.Json/releases) - [Commits](https://github.com/JamesNK/Newtonsoft.Json/compare/12.0.2...12.0.3) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 42f746c286..baa1c14071 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -19,7 +19,7 @@ - + From ce4843be22ff62e95362da8a2f24530c1fb9b91c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Nov 2019 18:42:32 +0900 Subject: [PATCH 2220/2815] Move tests to parent namespace for now --- .../UserInterface/{MenuItems => }/TestSceneStatefulMenuItem.cs | 2 +- .../{MenuItems => }/TestSceneThreeStateMenuItem.cs | 2 +- .../UserInterface/{MenuItems => }/TestSceneToggleMenuItem.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename osu.Game.Tests/Visual/UserInterface/{MenuItems => }/TestSceneStatefulMenuItem.cs (98%) rename osu.Game.Tests/Visual/UserInterface/{MenuItems => }/TestSceneThreeStateMenuItem.cs (95%) rename osu.Game.Tests/Visual/UserInterface/{MenuItems => }/TestSceneToggleMenuItem.cs (94%) diff --git a/osu.Game.Tests/Visual/UserInterface/MenuItems/TestSceneStatefulMenuItem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs similarity index 98% rename from osu.Game.Tests/Visual/UserInterface/MenuItems/TestSceneStatefulMenuItem.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs index 95e9aea97f..fd30da39e6 100644 --- a/osu.Game.Tests/Visual/UserInterface/MenuItems/TestSceneStatefulMenuItem.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -namespace osu.Game.Tests.Visual.UserInterface.MenuItems +namespace osu.Game.Tests.Visual.UserInterface { public class TestSceneStatefulMenuItem : OsuTestScene { diff --git a/osu.Game.Tests/Visual/UserInterface/MenuItems/TestSceneThreeStateMenuItem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneThreeStateMenuItem.cs similarity index 95% rename from osu.Game.Tests/Visual/UserInterface/MenuItems/TestSceneThreeStateMenuItem.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneThreeStateMenuItem.cs index e035e43630..caa07e7b60 100644 --- a/osu.Game.Tests/Visual/UserInterface/MenuItems/TestSceneThreeStateMenuItem.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneThreeStateMenuItem.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; -namespace osu.Game.Tests.Visual.UserInterface.MenuItems +namespace osu.Game.Tests.Visual.UserInterface { public class TestSceneThreeStateMenuItem : OsuTestScene { diff --git a/osu.Game.Tests/Visual/UserInterface/MenuItems/TestSceneToggleMenuItem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneToggleMenuItem.cs similarity index 94% rename from osu.Game.Tests/Visual/UserInterface/MenuItems/TestSceneToggleMenuItem.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneToggleMenuItem.cs index 92875740cf..2abda56a28 100644 --- a/osu.Game.Tests/Visual/UserInterface/MenuItems/TestSceneToggleMenuItem.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneToggleMenuItem.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; -namespace osu.Game.Tests.Visual.UserInterface.MenuItems +namespace osu.Game.Tests.Visual.UserInterface { public class TestSceneToggleMenuItem : OsuTestScene { From bed62e0d2f2b62d19a76bfb051c04263900706c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Nov 2019 18:56:18 +0900 Subject: [PATCH 2221/2815] Rename ThreeState -> TernaryState and add basic tests --- .../UserInterface/TestSceneTernaryMenuItem.cs | 65 +++++++++++++++++++ .../TestSceneThreeStateMenuItem.cs | 35 ---------- .../{ThreeStates.cs => TernaryState.cs} | 14 ++-- .../UserInterface/ThreeStateMenuItem.cs | 28 ++++---- 4 files changed, 86 insertions(+), 56 deletions(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneTernaryMenuItem.cs delete mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneThreeStateMenuItem.cs rename osu.Game/Graphics/UserInterface/{ThreeStates.cs => TernaryState.cs} (56%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneTernaryMenuItem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneTernaryMenuItem.cs new file mode 100644 index 0000000000..fdb8c330c5 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneTernaryMenuItem.cs @@ -0,0 +1,65 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneTernaryMenuItem : ManualInputManagerTestScene + { + private readonly OsuMenu menu; + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(OsuMenu), + typeof(ThreeStateMenuItem), + typeof(DrawableStatefulMenuItem) + }; + + private readonly Bindable state = new Bindable(TernaryState.Indeterminate); + + public TestSceneTernaryMenuItem() + { + Add(menu = new OsuMenu(Direction.Vertical, true) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Items = new[] + { + new ThreeStateMenuItem("First"), + new ThreeStateMenuItem("Second") { State = { BindTarget = state } }, + new ThreeStateMenuItem("Third") { State = { Value = TernaryState.True } }, + } + }); + + checkState(TernaryState.Indeterminate); + + click(); + checkState(TernaryState.True); + + click(); + checkState(TernaryState.False); + + click(); + checkState(TernaryState.True); + + click(); + checkState(TernaryState.False); + } + + private void click() => + AddStep("click", () => + { + InputManager.MoveMouseTo(menu.ScreenSpaceDrawQuad.Centre); + InputManager.Click(MouseButton.Left); + }); + + private void checkState(TernaryState expected) + => AddAssert($"state is {expected}", () => state.Value == expected); + } +} diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneThreeStateMenuItem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneThreeStateMenuItem.cs deleted file mode 100644 index caa07e7b60..0000000000 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneThreeStateMenuItem.cs +++ /dev/null @@ -1,35 +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 System; -using System.Collections.Generic; -using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Tests.Visual.UserInterface -{ - public class TestSceneThreeStateMenuItem : OsuTestScene - { - public override IReadOnlyList RequiredTypes => new[] - { - typeof(OsuMenu), - typeof(ThreeStateMenuItem), - typeof(DrawableStatefulMenuItem) - }; - - public TestSceneThreeStateMenuItem() - { - Add(new OsuMenu(Direction.Vertical, true) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Items = new[] - { - new ThreeStateMenuItem("First"), - new ThreeStateMenuItem("Second") { State = { Value = ThreeStates.Indeterminate } }, - new ThreeStateMenuItem("Third") { State = { Value = ThreeStates.Enabled } }, - } - }); - } - } -} diff --git a/osu.Game/Graphics/UserInterface/ThreeStates.cs b/osu.Game/Graphics/UserInterface/TernaryState.cs similarity index 56% rename from osu.Game/Graphics/UserInterface/ThreeStates.cs rename to osu.Game/Graphics/UserInterface/TernaryState.cs index f099250937..784122e35c 100644 --- a/osu.Game/Graphics/UserInterface/ThreeStates.cs +++ b/osu.Game/Graphics/UserInterface/TernaryState.cs @@ -6,22 +6,22 @@ namespace osu.Game.Graphics.UserInterface /// /// An on/off state with an extra indeterminate state. /// - public enum ThreeStates + public enum TernaryState { /// - /// The current state is disabled. + /// The current state is false. /// - Disabled, + False, /// - /// The current state is a combination of and . - /// The state becomes if the is pressed. + /// The current state is a combination of and . + /// The state becomes if the is pressed. /// Indeterminate, /// - /// The current state is enabled. + /// The current state is true. /// - Enabled + True } } diff --git a/osu.Game/Graphics/UserInterface/ThreeStateMenuItem.cs b/osu.Game/Graphics/UserInterface/ThreeStateMenuItem.cs index ebb6436196..c5b9edf3c4 100644 --- a/osu.Game/Graphics/UserInterface/ThreeStateMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/ThreeStateMenuItem.cs @@ -9,7 +9,7 @@ namespace osu.Game.Graphics.UserInterface /// /// An with three possible states. /// - public class ThreeStateMenuItem : StatefulMenuItem + public class ThreeStateMenuItem : StatefulMenuItem { /// /// Creates a new . @@ -27,7 +27,7 @@ namespace osu.Game.Graphics.UserInterface /// The text to display. /// The type of action which this performs. /// A delegate to be invoked when this is pressed. - public ThreeStateMenuItem(string text, MenuItemType type, Action action) + public ThreeStateMenuItem(string text, MenuItemType type, Action action) : this(text, getNextState, type, action) { } @@ -39,37 +39,37 @@ namespace osu.Game.Graphics.UserInterface /// A function that mutates a state to another state after this is pressed. /// The type of action which this performs. /// A delegate to be invoked when this is pressed. - protected ThreeStateMenuItem(string text, Func changeStateFunc, MenuItemType type, Action action) + protected ThreeStateMenuItem(string text, Func changeStateFunc, MenuItemType type, Action action) : base(text, changeStateFunc, type, action) { } - public override IconUsage? GetIconForState(ThreeStates state) + public override IconUsage? GetIconForState(TernaryState state) { switch (state) { - case ThreeStates.Indeterminate: - return FontAwesome.Regular.Circle; + case TernaryState.Indeterminate: + return FontAwesome.Solid.DotCircle; - case ThreeStates.Enabled: + case TernaryState.True: return FontAwesome.Solid.Check; } return null; } - private static ThreeStates getNextState(ThreeStates state) + private static TernaryState getNextState(TernaryState state) { switch (state) { - case ThreeStates.Disabled: - return ThreeStates.Enabled; + case TernaryState.False: + return TernaryState.True; - case ThreeStates.Indeterminate: - return ThreeStates.Enabled; + case TernaryState.Indeterminate: + return TernaryState.True; - case ThreeStates.Enabled: - return ThreeStates.Disabled; + case TernaryState.True: + return TernaryState.False; default: throw new ArgumentOutOfRangeException(nameof(state), state, null); From 82cc6aa0c5b2b251a229808b0a6494ba684a7cbb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Nov 2019 19:00:14 +0900 Subject: [PATCH 2222/2815] Remove unused constructor --- .../Visual/UserInterface/TestSceneStatefulMenuItem.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs index fd30da39e6..bd7f73c6d8 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs @@ -52,11 +52,6 @@ namespace osu.Game.Tests.Visual.UserInterface private class TestMenuItem : StatefulMenuItem { - public TestMenuItem(string text, MenuItemType type = MenuItemType.Standard) - : this(text, type, null) - { - } - public TestMenuItem(string text, MenuItemType type, Func changeStateFunc) : base(text, changeStateFunc, type) { From 54da8e4035cd3b033eee59839e5c7428a297d25d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Nov 2019 19:09:38 +0900 Subject: [PATCH 2223/2815] Combine similar tests --- .../TestSceneStatefulMenuItem.cs | 81 ++++++++++++++++--- .../UserInterface/TestSceneTernaryMenuItem.cs | 65 --------------- 2 files changed, 70 insertions(+), 76 deletions(-) delete mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneTernaryMenuItem.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs index bd7f73c6d8..1eff30d15e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs @@ -3,33 +3,92 @@ using System; using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface { - public class TestSceneStatefulMenuItem : OsuTestScene + public class TestSceneStatefulMenuItem : ManualInputManagerTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(OsuMenu), typeof(StatefulMenuItem), - typeof(DrawableStatefulMenuItem) + typeof(ThreeStateMenuItem), + typeof(DrawableStatefulMenuItem), }; - public TestSceneStatefulMenuItem() + [Test] + public void TestTernaryMenuItem() { - Add(new OsuMenu(Direction.Vertical, true) + OsuMenu menu = null; + + Bindable state = new Bindable(TernaryState.Indeterminate); + + AddStep("create menu", () => { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Items = new[] + state.Value = TernaryState.Indeterminate; + + Child = menu = new OsuMenu(Direction.Vertical, true) { - new TestMenuItem("First", MenuItemType.Standard, getNextState), - new TestMenuItem("Second", MenuItemType.Standard, getNextState) { State = { Value = TestStates.State2 } }, - new TestMenuItem("Third", MenuItemType.Standard, getNextState) { State = { Value = TestStates.State3 } }, - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Items = new[] + { + new ThreeStateMenuItem("First"), + new ThreeStateMenuItem("Second") { State = { BindTarget = state } }, + new ThreeStateMenuItem("Third") { State = { Value = TernaryState.True } }, + } + }; + }); + + checkState(TernaryState.Indeterminate); + + click(); + checkState(TernaryState.True); + + click(); + checkState(TernaryState.False); + + click(); + checkState(TernaryState.True); + + click(); + checkState(TernaryState.False); + + AddStep("change state via bindable", () => state.Value = TernaryState.True); + + void click() => + AddStep("click", () => + { + InputManager.MoveMouseTo(menu.ScreenSpaceDrawQuad.Centre); + InputManager.Click(MouseButton.Left); + }); + + void checkState(TernaryState expected) + => AddAssert($"state is {expected}", () => state.Value == expected); + } + + [Test] + public void TestCustomState() + { + AddStep("create menu", () => + { + Child = new OsuMenu(Direction.Vertical, true) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Items = new[] + { + new TestMenuItem("First", MenuItemType.Standard, getNextState), + new TestMenuItem("Second", MenuItemType.Standard, getNextState) { State = { Value = TestStates.State2 } }, + new TestMenuItem("Third", MenuItemType.Standard, getNextState) { State = { Value = TestStates.State3 } }, + } + }; }); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneTernaryMenuItem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneTernaryMenuItem.cs deleted file mode 100644 index fdb8c330c5..0000000000 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneTernaryMenuItem.cs +++ /dev/null @@ -1,65 +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 System; -using System.Collections.Generic; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; -using osuTK.Input; - -namespace osu.Game.Tests.Visual.UserInterface -{ - public class TestSceneTernaryMenuItem : ManualInputManagerTestScene - { - private readonly OsuMenu menu; - - public override IReadOnlyList RequiredTypes => new[] - { - typeof(OsuMenu), - typeof(ThreeStateMenuItem), - typeof(DrawableStatefulMenuItem) - }; - - private readonly Bindable state = new Bindable(TernaryState.Indeterminate); - - public TestSceneTernaryMenuItem() - { - Add(menu = new OsuMenu(Direction.Vertical, true) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Items = new[] - { - new ThreeStateMenuItem("First"), - new ThreeStateMenuItem("Second") { State = { BindTarget = state } }, - new ThreeStateMenuItem("Third") { State = { Value = TernaryState.True } }, - } - }); - - checkState(TernaryState.Indeterminate); - - click(); - checkState(TernaryState.True); - - click(); - checkState(TernaryState.False); - - click(); - checkState(TernaryState.True); - - click(); - checkState(TernaryState.False); - } - - private void click() => - AddStep("click", () => - { - InputManager.MoveMouseTo(menu.ScreenSpaceDrawQuad.Centre); - InputManager.Click(MouseButton.Left); - }); - - private void checkState(TernaryState expected) - => AddAssert($"state is {expected}", () => state.Value == expected); - } -} From 9c1a9c29e45bac8624e2374192d6c2b60b4cc3f3 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 11 Nov 2019 18:40:28 +0800 Subject: [PATCH 2224/2815] Remove redundant package references in iOS props. --- osu.iOS.props | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.iOS.props b/osu.iOS.props index d2d7d490e9..0184e45c15 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -73,12 +73,6 @@ - - - - - - From 3d1c31f2ae57d010e1845d4a77dcd4b4b395ab74 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 11 Nov 2019 19:45:52 +0800 Subject: [PATCH 2225/2815] Copy configs from framework repo. --- .editorconfig | 167 ++++++++- osu.sln.DotSettings | 846 +++++++++++++++++++++++--------------------- 2 files changed, 594 insertions(+), 419 deletions(-) diff --git a/.editorconfig b/.editorconfig index 0dd7ef8ed1..8f9d0ca9b0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,16 +12,171 @@ trim_trailing_whitespace = true #PascalCase for public and protected members dotnet_naming_style.pascalcase.capitalization = pascal_case -dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal -dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event,delegate -dotnet_naming_rule.public_members_pascalcase.severity = suggestion +dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected +dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event +dotnet_naming_rule.public_members_pascalcase.severity = error dotnet_naming_rule.public_members_pascalcase.symbols = public_members dotnet_naming_rule.public_members_pascalcase.style = pascalcase #camelCase for private members dotnet_naming_style.camelcase.capitalization = camel_case + dotnet_naming_symbols.private_members.applicable_accessibilities = private -dotnet_naming_symbols.private_members.applicable_kinds = property,method,field,event,delegate -dotnet_naming_rule.private_members_camelcase.severity = suggestion +dotnet_naming_symbols.private_members.applicable_kinds = property,method,field,event +dotnet_naming_rule.private_members_camelcase.severity = warning dotnet_naming_rule.private_members_camelcase.symbols = private_members -dotnet_naming_rule.private_members_camelcase.style = camelcase \ No newline at end of file +dotnet_naming_rule.private_members_camelcase.style = camelcase + +dotnet_naming_symbols.local_function.applicable_kinds = local_function +dotnet_naming_rule.local_function_camelcase.severity = warning +dotnet_naming_rule.local_function_camelcase.symbols = local_function +dotnet_naming_rule.local_function_camelcase.style = camelcase + +#all_lower for private and local constants/static readonlys +dotnet_naming_style.all_lower.capitalization = all_lower +dotnet_naming_style.all_lower.word_separator = _ + +dotnet_naming_symbols.private_constants.applicable_accessibilities = private +dotnet_naming_symbols.private_constants.required_modifiers = const +dotnet_naming_symbols.private_constants.applicable_kinds = field +dotnet_naming_rule.private_const_all_lower.severity = warning +dotnet_naming_rule.private_const_all_lower.symbols = private_constants +dotnet_naming_rule.private_const_all_lower.style = all_lower + +dotnet_naming_symbols.private_static_readonly.applicable_accessibilities = private +dotnet_naming_symbols.private_static_readonly.required_modifiers = static,readonly +dotnet_naming_symbols.private_static_readonly.applicable_kinds = field +dotnet_naming_rule.private_static_readonly_all_lower.severity = warning +dotnet_naming_rule.private_static_readonly_all_lower.symbols = private_static_readonly +dotnet_naming_rule.private_static_readonly_all_lower.style = all_lower + +dotnet_naming_symbols.local_constants.applicable_kinds = local +dotnet_naming_symbols.local_constants.required_modifiers = const +dotnet_naming_rule.local_const_all_lower.severity = warning +dotnet_naming_rule.local_const_all_lower.symbols = local_constants +dotnet_naming_rule.local_const_all_lower.style = all_lower + +#ALL_UPPER for non private constants/static readonlys +dotnet_naming_style.all_upper.capitalization = all_upper +dotnet_naming_style.all_upper.word_separator = _ + +dotnet_naming_symbols.public_constants.applicable_accessibilities = public,internal,protected,protected_internal,private_protected +dotnet_naming_symbols.public_constants.required_modifiers = const +dotnet_naming_symbols.public_constants.applicable_kinds = field +dotnet_naming_rule.public_const_all_upper.severity = warning +dotnet_naming_rule.public_const_all_upper.symbols = public_constants +dotnet_naming_rule.public_const_all_upper.style = all_upper + +dotnet_naming_symbols.public_static_readonly.applicable_accessibilities = public,internal,protected,protected_internal,private_protected +dotnet_naming_symbols.public_static_readonly.required_modifiers = static,readonly +dotnet_naming_symbols.public_static_readonly.applicable_kinds = field +dotnet_naming_rule.public_static_readonly_all_upper.severity = warning +dotnet_naming_rule.public_static_readonly_all_upper.symbols = public_static_readonly +dotnet_naming_rule.public_static_readonly_all_upper.style = all_upper + +#Roslyn formating options + +#Formatting - indentation options +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = false +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +#Formatting - new line options +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_open_brace = all +#csharp_new_line_before_members_in_anonymous_types = true +#csharp_new_line_before_members_in_object_initializers = true # Currently no effect in VS/dotnet format (16.4), and makes Rider confusing +csharp_new_line_between_query_expression_clauses = true + +#Formatting - organize using options +dotnet_sort_system_directives_first = true + +#Formatting - spacing options +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false + +#Formatting - wrapping options +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#Roslyn language styles + +#Style - type names +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent +csharp_style_var_when_type_is_apparent = true:none +csharp_style_var_for_built_in_types = true:none +csharp_style_var_elsewhere = true:silent + +#Style - modifiers +dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning +csharp_preferred_modifier_order = public,private,protected,internal,new,abstract,virtual,sealed,override,static,readonly,extern,unsafe,volatile,async:warning + +#Style - parentheses +# Skipped because roslyn cannot separate +-*/ with << >> + +#Style - expression bodies +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_constructors = false:none +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_methods = true:silent +csharp_style_expression_bodied_operators = true:silent +csharp_style_expression_bodied_properties = true:silent + +#Style - expression preferences +dotnet_style_object_initializer = true:warning +dotnet_style_collection_initializer = true:warning +dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_prefer_compound_assignment = true:warning + +#Style - null/type checks +dotnet_style_coalesce_expression = true:warning +dotnet_style_null_propagation = true:warning +csharp_style_pattern_matching_over_is_with_cast_check = true:silent +csharp_style_pattern_matching_over_as_with_null_check = true:silent +csharp_style_throw_expression = true:silent +csharp_style_conditional_delegate_call = true:suggestion + +#Style - unused +dotnet_code_quality_unused_parameters = non_public:silent +csharp_style_unused_value_expression_statement_preference = discard_variable:silent +csharp_style_unused_value_assignment_preference = discard_variable:suggestion + +#Style - variable declaration +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_deconstructed_variable_declaration = true:silent + +#Style - other C# 7.x features +csharp_style_expression_bodied_local_functions = true:silent +dotnet_style_prefer_inferred_tuple_names = true:warning +csharp_prefer_simple_default_expression = true:warning +csharp_style_pattern_local_over_anonymous_function = true:warning +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent + +#Supressing roslyn built-in analyzers +# Suppress: EC112 + +#Field can be readonly +dotnet_diagnostic.IDE0044.severity = silent +#Private method is unused +dotnet_diagnostic.IDE0051.severity = silent +#Private member is unused +dotnet_diagnostic.IDE0052.severity = silent + +#Rules for disposable +dotnet_diagnostic.IDE0067.severity = none +dotnet_diagnostic.IDE0068.severity = none +dotnet_diagnostic.IDE0069.severity = none \ No newline at end of file diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index eb9e3ffafb..44c5c05bc0 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -12,7 +12,7 @@ HINT HINT WARNING - + WARNING True WARNING WARNING @@ -70,11 +70,20 @@ WARNING WARNING DO_NOT_SHOW + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING HINT WARNING DO_NOT_SHOW WARNING HINT + DO_NOT_SHOW HINT HINT ERROR @@ -125,6 +134,7 @@ WARNING WARNING WARNING + WARNING WARNING WARNING ERROR @@ -171,7 +181,7 @@ WARNING WARNING WARNING - HINT + WARNING WARNING WARNING WARNING @@ -201,6 +211,7 @@ HINT HINT + HINT WARNING WARNING WARNING @@ -218,9 +229,14 @@ WARNING <?xml version="1.0" encoding="utf-16"?><Profile name="Code Cleanup (peppy)"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseVar><BehavourStyle>CAN_CHANGE_TO_EXPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_EXPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_EXPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" /><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers></Profile> Code Cleanup (peppy) - Required - Required - Required + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline Explicit ExpressionBody ExpressionBody @@ -239,6 +255,10 @@ 1 NEXT_LINE MULTILINE + True + True + True + True NEXT_LINE 1 1 @@ -289,397 +309,397 @@ SHA SRGB TK - SS - PP - GMT - QAT - BNG + SS + PP + GMT + QAT + BNG UI False HINT <?xml version="1.0" encoding="utf-16"?> <Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> - <TypePattern DisplayName="COM interfaces or structs"> - <TypePattern.Match> - <Or> - <And> - <Kind Is="Interface" /> - <Or> - <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> - <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> - </Or> - </And> - <Kind Is="Struct" /> - </Or> - </TypePattern.Match> - </TypePattern> - <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> - <TypePattern.Match> - <And> - <Kind Is="Class" /> - <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> - </And> - </TypePattern.Match> - <Entry DisplayName="Setup/Teardown Methods"> - <Entry.Match> - <And> - <Kind Is="Method" /> - <Or> - <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="All other members" /> - <Entry Priority="100" DisplayName="Test Methods"> - <Entry.Match> - <And> - <Kind Is="Method" /> - <HasAttribute Name="NUnit.Framework.TestAttribute" /> - </And> - </Entry.Match> - <Entry.SortBy> - <Name /> - </Entry.SortBy> - </Entry> - </TypePattern> - <TypePattern DisplayName="Default Pattern"> - <Group DisplayName="Fields/Properties"> - <Group DisplayName="Public Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Public Properties"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - <Group DisplayName="Internal Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Internal Properties"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - <Group DisplayName="Protected Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Protected Properties"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - <Group DisplayName="Private Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Private Properties"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Constructor/Destructor"> - <Entry DisplayName="Ctor"> - <Entry.Match> - <Kind Is="Constructor" /> - </Entry.Match> - </Entry> - <Region Name="Disposal"> - <Entry DisplayName="Dtor"> - <Entry.Match> - <Kind Is="Destructor" /> - </Entry.Match> - </Entry> - <Entry DisplayName="Dispose()"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Kind Is="Method" /> - <Name Is="Dispose" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Dispose(true)"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Or> - <Virtual /> - <Override /> - </Or> - <Kind Is="Method" /> - <Name Is="Dispose" /> - </And> - </Entry.Match> - </Entry> - </Region> - </Group> - <Group DisplayName="Methods"> - <Group DisplayName="Public"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Internal"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Protected"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Private"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - </Group> - </TypePattern> + <TypePattern DisplayName="COM interfaces or structs"> + <TypePattern.Match> + <Or> + <And> + <Kind Is="Interface" /> + <Or> + <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> + <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> + </Or> + </And> + <Kind Is="Struct" /> + </Or> + </TypePattern.Match> + </TypePattern> + <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> + <TypePattern.Match> + <And> + <Kind Is="Class" /> + <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> + </And> + </TypePattern.Match> + <Entry DisplayName="Setup/Teardown Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <Or> + <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="All other members" /> + <Entry Priority="100" DisplayName="Test Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <HasAttribute Name="NUnit.Framework.TestAttribute" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + </TypePattern> + <TypePattern DisplayName="Default Pattern"> + <Group DisplayName="Fields/Properties"> + <Group DisplayName="Public Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Public Properties"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Internal Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Internal Properties"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Protected Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Protected Properties"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Private Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Private Properties"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Constructor/Destructor"> + <Entry DisplayName="Ctor"> + <Entry.Match> + <Kind Is="Constructor" /> + </Entry.Match> + </Entry> + <Region Name="Disposal"> + <Entry DisplayName="Dtor"> + <Entry.Match> + <Kind Is="Destructor" /> + </Entry.Match> + </Entry> + <Entry DisplayName="Dispose()"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Method" /> + <Name Is="Dispose" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Dispose(true)"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Or> + <Virtual /> + <Override /> + </Or> + <Kind Is="Method" /> + <Name Is="Dispose" /> + </And> + </Entry.Match> + </Entry> + </Region> + </Group> + <Group DisplayName="Methods"> + <Group DisplayName="Public"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Internal"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Protected"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Private"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + </Group> + </TypePattern> </Patterns> Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. See the LICENCE file in the repository root for full licence text. @@ -774,7 +794,7 @@ Origin = Anchor.$anchor$, True InternalChildren = new Drawable[] { - $END$ + $END$ }; True True @@ -787,12 +807,12 @@ Origin = Anchor.$anchor$, True new GridContainer { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] { $END$ }, - new Drawable[] { } - } + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { $END$ }, + new Drawable[] { } + } }; True True @@ -805,12 +825,12 @@ Origin = Anchor.$anchor$, True new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - $END$ - } + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + $END$ + } }, True True @@ -823,11 +843,11 @@ Origin = Anchor.$anchor$, True new Container { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - $END$ - } + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + $END$ + } }, True True @@ -841,7 +861,7 @@ Origin = Anchor.$anchor$, [BackgroundDependencyLoader] private void load() { - $END$ + $END$ } True True @@ -854,8 +874,8 @@ private void load() True new Box { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, }, True True @@ -868,7 +888,7 @@ private void load() True Children = new Drawable[] { - $END$ + $END$ }; True True From ccc8aa6fa4296bf6061527544269caf55c5baff6 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 11 Nov 2019 19:53:22 +0800 Subject: [PATCH 2226/2815] Apply brace style. --- .../TestSceneHyperDash.cs | 2 + .../Beatmaps/CatchBeatmapProcessor.cs | 4 ++ .../Objects/BananaShower.cs | 2 + .../Legacy/HitObjectPatternGenerator.cs | 4 ++ .../TestSceneDrawableJudgement.cs | 2 + .../TestSceneHitCircleLongCombo.cs | 2 + osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 4 ++ osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 + .../NonVisual/FramedReplayInputHandlerTest.cs | 2 + osu.Game.Tests/Skins/LegacySkinDecoderTest.cs | 2 + .../Online/TestSceneStandAloneChatDisplay.cs | 4 ++ .../SongSelect/TestSceneBeatmapCarousel.cs | 2 + .../SongSelect/TestScenePlaySongSelect.cs | 2 + osu.Game.Tests/Visual/TestSceneOsuGame.cs | 4 ++ .../TestSceneTournamentMatchChatDisplay.cs | 28 +++++------ .../Components/TournamentBeatmapPanel.cs | 2 + .../Components/TourneyVideo.cs | 2 + osu.Game.Tournament/IPC/FileBasedIPC.cs | 2 + .../Screens/Editors/RoundEditorScreen.cs | 2 + .../Screens/Ladder/LadderScreen.cs | 6 ++- .../Screens/TeamIntro/TeamIntroScreen.cs | 2 + osu.Game.Tournament/TournamentGameBase.cs | 48 +++++++++++-------- .../Formats/LegacyStoryboardDecoder.cs | 24 +++++----- osu.Game/Beatmaps/WorkingBeatmap.cs | 6 ++- osu.Game/Database/ArchiveModelManager.cs | 8 ++++ .../Graphics/Containers/ShakeContainer.cs | 2 + .../Graphics/UserInterface/OsuTabControl.cs | 2 + .../Graphics/UserInterface/ScoreCounter.cs | 2 + osu.Game/Input/KeyBindingStore.cs | 2 + osu.Game/Online/Leaderboards/Leaderboard.cs | 6 +++ osu.Game/OsuGame.cs | 2 + osu.Game/OsuGameBase.cs | 2 + osu.Game/Overlays/Changelog/ChangelogBuild.cs | 6 +++ .../Changelog/ChangelogSingleBuild.cs | 2 + .../Overlays/Comments/CommentsContainer.cs | 2 + osu.Game/Overlays/Direct/DirectPanel.cs | 2 + osu.Game/Overlays/Mods/ModButton.cs | 2 + osu.Game/Overlays/Mods/ModSection.cs | 2 + .../Difficulty/Utils/LimitedCapacityStack.cs | 2 + .../Objects/Drawables/DrawableHitObject.cs | 2 + .../Objects/Legacy/ConvertHitObjectParser.cs | 2 + osu.Game/Rulesets/Objects/SliderPath.cs | 2 + osu.Game/Rulesets/RulesetStore.cs | 2 + osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 + osu.Game/Rulesets/UI/Playfield.cs | 4 ++ .../Screens/Multi/Components/BeatmapTitle.cs | 2 + .../Screens/Play/HUD/HoldForMenuButton.cs | 2 + osu.Game/Screens/Play/SkipOverlay.cs | 3 ++ .../Select/Carousel/CarouselBeatmap.cs | 4 ++ osu.Game/Screens/Select/SongSelect.cs | 4 ++ osu.Game/Skinning/LegacySkin.cs | 2 + osu.Game/Skinning/LegacySkinExtensions.cs | 6 ++- osu.Game/Skinning/SkinnableSound.cs | 8 ++++ osu.Game/Storyboards/CommandTimelineGroup.cs | 2 + osu.Game/Storyboards/StoryboardSprite.cs | 3 ++ .../Tests/Beatmaps/BeatmapConversionTest.cs | 4 ++ osu.Game/Tests/Visual/OsuGridTestScene.cs | 6 ++- osu.Game/Users/UserCoverBackground.cs | 2 + 58 files changed, 214 insertions(+), 52 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs index 7b8c699f2c..da36673930 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs @@ -40,8 +40,10 @@ namespace osu.Game.Rulesets.Catch.Tests beatmap.HitObjects.Add(new Fruit { StartTime = 1008, X = 56 / 512f, }); for (int i = 0; i < 512; i++) + { if (i % 5 < 3) beatmap.HitObjects.Add(new Fruit { X = i % 10 < 5 ? 0.02f : 0.98f, StartTime = 2000 + i * 100, NewCombo = i % 8 == 0 }); + } return beatmap; } diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index 5ab47c1611..5d0c6116d7 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -196,9 +196,13 @@ namespace osu.Game.Rulesets.Catch.Beatmaps if (currentObject is Fruit) objectWithDroplets.Add(currentObject); if (currentObject is JuiceStream) + { foreach (var currentJuiceElement in currentObject.NestedHitObjects) + { if (!(currentJuiceElement is TinyDroplet)) objectWithDroplets.Add((CatchHitObject)currentJuiceElement); + } + } } objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime)); diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs index 6d44e4660e..267e6d12c7 100644 --- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs @@ -27,11 +27,13 @@ namespace osu.Game.Rulesets.Catch.Objects return; for (double i = StartTime; i <= EndTime; i += spacing) + { AddNested(new Banana { Samples = Samples, StartTime = i }); + } } public double EndTime => StartTime + Duration; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs index decd159ee9..ada960a78d 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs @@ -109,8 +109,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { // Generate a new pattern by copying the last hit objects in reverse-column order for (int i = RandomStart; i < TotalColumns; i++) + { if (PreviousPattern.ColumnHasObject(i)) addToPattern(pattern, RandomStart + TotalColumns - i - 1); + } return pattern; } @@ -132,8 +134,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { // Generate a new pattern by placing on the already filled columns for (int i = RandomStart; i < TotalColumns; i++) + { if (PreviousPattern.ColumnHasObject(i)) addToPattern(pattern, i); + } return pattern; } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs index 433ec6bd25..ac627aa23e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs @@ -24,12 +24,14 @@ namespace osu.Game.Rulesets.Osu.Tests public TestSceneDrawableJudgement() { foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType().Skip(1)) + { AddStep("Show " + result.GetDescription(), () => SetContents(() => new DrawableOsuJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null) { Anchor = Anchor.Centre, Origin = Anchor.Centre, })); + } } } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs index 95c2810e94..b99cd523ff 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs @@ -29,8 +29,10 @@ namespace osu.Game.Rulesets.Osu.Tests }; for (int i = 0; i < 512; i++) + { if (i % 32 < 20) beatmap.HitObjects.Add(new HitCircle { Position = new Vector2(256, 192), StartTime = i * 100 }); + } return beatmap; } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 17fcd03dd5..1664a37a66 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -55,8 +55,10 @@ namespace osu.Game.Rulesets.Osu.Mods } for (int i = 0; i < amountWiggles; i++) + { using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * wiggle_duration, true)) wiggle(); + } // Keep wiggling sliders and spinners for their duration if (!(osuObject is IHasEndTime endTime)) @@ -65,8 +67,10 @@ namespace osu.Game.Rulesets.Osu.Mods amountWiggles = (int)(endTime.Duration / wiggle_duration); for (int i = 0; i < amountWiggles; i++) + { using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * wiggle_duration, true)) wiggle(); + } } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index f60b7e67b2..3e23d09741 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -133,12 +133,14 @@ namespace osu.Game.Rulesets.Osu.Objects var sampleList = new List(); if (firstSample != null) + { sampleList.Add(new HitSampleInfo { Bank = firstSample.Bank, Volume = firstSample.Volume, Name = @"slidertick", }); + } switch (e.Type) { diff --git a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs index 18cbd4e7c5..7df7df22ea 100644 --- a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs +++ b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs @@ -225,8 +225,10 @@ namespace osu.Game.Tests.NonVisual private void fastForwardToPoint(double destination) { for (int i = 0; i < 1000; i++) + { if (handler.SetFrameFromTime(destination) == null) return; + } throw new TimeoutException("Seek was never fulfilled"); } diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs index 0d96dd08da..ea9d51a9b8 100644 --- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs +++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs @@ -26,6 +26,7 @@ namespace osu.Game.Tests.Skins List expectedColors; if (hasColours) + { expectedColors = new List { new Color4(142, 199, 255, 255), @@ -33,6 +34,7 @@ namespace osu.Game.Tests.Skins new Color4(128, 255, 255, 255), new Color4(100, 100, 100, 100), }; + } else expectedColors = new DefaultSkin().Configuration.ComboColours; diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 01400bf1d9..28b5693ef4 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -130,12 +130,14 @@ namespace osu.Game.Tests.Visual.Online AddRepeatStep("add many messages", () => { for (int i = 0; i < messages_per_call; i++) + { testChannel.AddNewMessages(new Message(sequence++) { Sender = longUsernameUser, Content = "Many messages! " + Guid.NewGuid(), Timestamp = DateTimeOffset.Now }); + } }, Channel.MAX_HISTORY / messages_per_call + 5); AddAssert("Ensure no adjacent day separators", () => @@ -143,8 +145,10 @@ namespace osu.Game.Tests.Visual.Online var indices = chatDisplay.FillFlow.OfType().Select(ds => chatDisplay.FillFlow.IndexOf(ds)); foreach (var i in indices) + { if (i < chatDisplay.FillFlow.Count && chatDisplay.FillFlow[i + 1] is DrawableChannel.DaySeparator) return false; + } return true; }); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 8b82567a8d..aa63bc1cf6 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -467,8 +467,10 @@ namespace osu.Game.Tests.Visual.SongSelect private void advanceSelection(bool diff, int direction = 1, int count = 1) { if (count == 1) + { AddStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () => carousel.SelectNext(direction, !diff)); + } else { AddRepeatStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () => diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index efe7fee5e4..794d135b06 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -132,11 +132,13 @@ namespace osu.Game.Tests.Visual.SongSelect changeRuleset(1); if (rulesetsInSameBeatmap) + { AddStep("import multi-ruleset map", () => { var usableRulesets = rulesets.AvailableRulesets.Where(r => r.ID != 2).ToArray(); manager.Import(createTestBeatmapSet(0, usableRulesets)).Wait(); }); + } else { addRulesetImportStep(1); diff --git a/osu.Game.Tests/Visual/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/TestSceneOsuGame.cs index fcc3a3596f..36cd49d839 100644 --- a/osu.Game.Tests/Visual/TestSceneOsuGame.cs +++ b/osu.Game.Tests/Visual/TestSceneOsuGame.cs @@ -109,16 +109,20 @@ namespace osu.Game.Tests.Visual AddAssert("check OsuGame DI members", () => { foreach (var type in requiredGameDependencies) + { if (game.Dependencies.Get(type) == null) throw new Exception($"{type} has not been cached"); + } return true; }); AddAssert("check OsuGameBase DI members", () => { foreach (var type in requiredGameBaseDependencies) + { if (gameBase.Dependencies.Get(type) == null) throw new Exception($"{type} has not been cached"); + } return true; }); diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentMatchChatDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentMatchChatDisplay.cs index 41d32d9448..d77a0b75a5 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentMatchChatDisplay.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentMatchChatDisplay.cs @@ -103,20 +103,20 @@ namespace osu.Game.Tournament.Tests.Components })); AddStep("multiple messages", () => testChannel.AddNewMessages(new Message(nextMessageId()) - { - Sender = admin, - Content = "I spam you!" - }, - new Message(nextMessageId()) - { - Sender = admin, - Content = "I spam you!!!1" - }, - new Message(nextMessageId()) - { - Sender = admin, - Content = "I spam you!1!1" - })); + { + Sender = admin, + Content = "I spam you!" + }, + new Message(nextMessageId()) + { + Sender = admin, + Content = "I spam you!!!1" + }, + new Message(nextMessageId()) + { + Sender = admin, + Content = "I spam you!1!1" + })); AddStep("change channel to 2", () => chatDisplay.Channel.Value = testChannel2); diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs index f6c1be0e36..0908814537 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -131,6 +131,7 @@ namespace osu.Game.Tournament.Components }); if (!string.IsNullOrEmpty(mods)) + { AddInternal(new Sprite { Texture = textures.Get($"mods/{mods}"), @@ -139,6 +140,7 @@ namespace osu.Game.Tournament.Components Margin = new MarginPadding(20), Scale = new Vector2(0.5f) }); + } } private void matchChanged(ValueChangedEvent match) diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index 8582b1634c..206689ca1a 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -29,12 +29,14 @@ namespace osu.Game.Tournament.Components }; } else + { InternalChild = video = new VideoSprite(stream) { RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, Clock = new FramedClock(manualClock = new ManualClock()) }; + } } public bool Loop diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index e05d96e098..47f2bed77a 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -60,6 +60,7 @@ namespace osu.Game.Tournament.IPC const string file_ipc_channel_filename = "ipc-channel.txt"; if (Storage.Exists(file_ipc_filename)) + { scheduled = Scheduler.AddDelayed(delegate { try @@ -134,6 +135,7 @@ namespace osu.Game.Tournament.IPC // file might be in use. } }, 250, true); + } } catch (Exception e) { diff --git a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs index b036350879..2c515edda7 100644 --- a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs @@ -266,12 +266,14 @@ namespace osu.Game.Tournament.Screens.Editors drawableContainer.Clear(); if (Model.BeatmapInfo != null) + { drawableContainer.Child = new TournamentBeatmapPanel(Model.BeatmapInfo, Model.Mods) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Width = 300 }; + } } } } diff --git a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs index 83a41a662f..66e68a0f37 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs @@ -81,8 +81,10 @@ namespace osu.Game.Tournament.Screens.Ladder LadderInfo.Matches.ItemsRemoved += matches => { foreach (var p in matches) - foreach (var d in MatchesContainer.Where(d => d.Match == p)) - d.Expire(); + { + foreach (var d in MatchesContainer.Where(d => d.Match == p)) + d.Expire(); + } layout.Invalidate(); }; diff --git a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs index c901a5c7ef..47c923ff30 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs @@ -164,6 +164,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro if (team != null) { foreach (var p in team.Players) + { players.Add(new OsuSpriteText { Text = p.Username, @@ -172,6 +173,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro Anchor = left ? Anchor.CentreRight : Anchor.CentreLeft, Origin = left ? Anchor.CentreRight : Anchor.CentreLeft, }); + } } } diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 3b7718c61d..d34ea9648d 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -164,15 +164,17 @@ namespace osu.Game.Tournament // link matches to rounds foreach (var round in ladder.Rounds) - foreach (var id in round.Matches) { - var found = ladder.Matches.FirstOrDefault(p => p.ID == id); - - if (found != null) + foreach (var id in round.Matches) { - found.Round.Value = round; - if (round.StartDate.Value > found.Date.Value) - found.Date.Value = round.StartDate.Value; + var found = ladder.Matches.FirstOrDefault(p => p.ID == id); + + if (found != null) + { + found.Round.Value = round; + if (round.StartDate.Value > found.Date.Value) + found.Date.Value = round.StartDate.Value; + } } } @@ -192,15 +194,19 @@ namespace osu.Game.Tournament bool addedInfo = false; foreach (var t in ladder.Teams) - foreach (var p in t.Players) - if (string.IsNullOrEmpty(p.Username)) + { + foreach (var p in t.Players) { - var req = new GetUserRequest(p.Id); - req.Perform(API); - p.Username = req.Result.Username; + if (string.IsNullOrEmpty(p.Username)) + { + var req = new GetUserRequest(p.Id); + req.Perform(API); + p.Username = req.Result.Username; - addedInfo = true; + addedInfo = true; + } } + } return addedInfo; } @@ -213,15 +219,19 @@ namespace osu.Game.Tournament bool addedInfo = false; foreach (var r in ladder.Rounds) - foreach (var b in r.Beatmaps) - if (b.BeatmapInfo == null && b.ID > 0) + { + foreach (var b in r.Beatmaps) { - var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID }); - req.Perform(API); - b.BeatmapInfo = req.Result?.ToBeatmap(RulesetStore); + if (b.BeatmapInfo == null && b.ID > 0) + { + var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID }); + req.Perform(API); + b.BeatmapInfo = req.Result?.ToBeatmap(RulesetStore); - addedInfo = true; + addedInfo = true; + } } + } return addedInfo; } diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 5dbd67d304..a86fa43f3c 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -144,16 +144,16 @@ namespace osu.Game.Beatmaps.Formats var endTime = split.Length > 3 ? double.Parse(split[3], CultureInfo.InvariantCulture) : double.MaxValue; var groupNumber = split.Length > 4 ? int.Parse(split[4]) : 0; timelineGroup = storyboardSprite?.AddTrigger(triggerName, startTime, endTime, groupNumber); - } break; + } case "L": { var startTime = double.Parse(split[1], CultureInfo.InvariantCulture); var loopCount = int.Parse(split[2]); timelineGroup = storyboardSprite?.AddLoop(startTime, loopCount); - } break; + } default: { @@ -172,7 +172,7 @@ namespace osu.Game.Beatmaps.Formats var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue); } - break; + break; case "S": { @@ -180,7 +180,7 @@ namespace osu.Game.Beatmaps.Formats var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startValue), new Vector2(endValue)); } - break; + break; case "V": { @@ -190,7 +190,7 @@ namespace osu.Game.Beatmaps.Formats var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY; timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY)); } - break; + break; case "R": { @@ -198,7 +198,7 @@ namespace osu.Game.Beatmaps.Formats var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; timelineGroup?.Rotation.Add(easing, startTime, endTime, MathHelper.RadiansToDegrees(startValue), MathHelper.RadiansToDegrees(endValue)); } - break; + break; case "M": { @@ -209,7 +209,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.X.Add(easing, startTime, endTime, startX, endX); timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY); } - break; + break; case "MX": { @@ -217,7 +217,7 @@ namespace osu.Game.Beatmaps.Formats var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue); } - break; + break; case "MY": { @@ -225,7 +225,7 @@ namespace osu.Game.Beatmaps.Formats var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue); } - break; + break; case "C": { @@ -239,7 +239,7 @@ namespace osu.Game.Beatmaps.Formats new Color4(startRed / 255f, startGreen / 255f, startBlue / 255f, 1), new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1)); } - break; + break; case "P": { @@ -260,13 +260,13 @@ namespace osu.Game.Beatmaps.Formats break; } } - break; + break; default: throw new InvalidDataException($@"Unknown command type: {commandType}"); } } - break; + break; } } } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 3fc33e9f52..7c69a992dd 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -133,8 +133,10 @@ namespace osu.Game.Beatmaps obj.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty); foreach (var mod in mods.OfType()) - foreach (var obj in converted.HitObjects) - mod.ApplyToHitObject(obj); + { + foreach (var obj in converted.HitObjects) + mod.ApplyToHitObject(obj); + } processor?.PostProcess(); diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 9fed8e03ac..a262b76125 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -265,8 +265,10 @@ namespace osu.Game.Database // for now, concatenate all .osu files in the set to create a unique hash. MemoryStream hashable = new MemoryStream(); foreach (string file in reader.Filenames.Where(f => HashableFileTypes.Any(f.EndsWith))) + { using (Stream s = reader.GetStream(file)) s.CopyTo(hashable); + } return hashable.Length > 0 ? hashable.ComputeSHA2Hash() : null; } @@ -485,12 +487,16 @@ namespace osu.Game.Database // import files to manager foreach (string file in reader.Filenames) + { using (Stream s = reader.GetStream(file)) + { fileInfos.Add(new TFileModel { Filename = FileSafety.PathStandardise(file.Substring(prefix.Length)), FileInfo = files.Add(s) }); + } + } return fileInfos; } @@ -651,8 +657,10 @@ namespace osu.Game.Database private void handleEvent(Action a) { if (delayingEvents) + { lock (queuedEvents) queuedEvents.Add(a); + } else a.Invoke(); } diff --git a/osu.Game/Graphics/Containers/ShakeContainer.cs b/osu.Game/Graphics/Containers/ShakeContainer.cs index e5a6bcc28e..dca9df1e98 100644 --- a/osu.Game/Graphics/Containers/ShakeContainer.cs +++ b/osu.Game/Graphics/Containers/ShakeContainer.cs @@ -43,9 +43,11 @@ namespace osu.Game.Graphics.Containers // if we don't have enough time for the second shake, skip it. if (!maximumLength.HasValue || maximumLength >= ShakeDuration * 4) + { sequence = sequence .MoveToX(shake_amount, ShakeDuration, Easing.InOutSine).Then() .MoveToX(-shake_amount, ShakeDuration, Easing.InOutSine).Then(); + } sequence.MoveToX(0, ShakeDuration / 2, Easing.InSine); } diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index c55d14456b..5d1bdc62e9 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -51,8 +51,10 @@ namespace osu.Game.Graphics.UserInterface }); if (isEnumType && AddEnumEntriesAutomatically) + { foreach (var val in (T[])Enum.GetValues(typeof(T))) AddItem(val); + } } [BackgroundDependencyLoader] diff --git a/osu.Game/Graphics/UserInterface/ScoreCounter.cs b/osu.Game/Graphics/UserInterface/ScoreCounter.cs index 63062cdc9d..ee96b78844 100644 --- a/osu.Game/Graphics/UserInterface/ScoreCounter.cs +++ b/osu.Game/Graphics/UserInterface/ScoreCounter.cs @@ -44,8 +44,10 @@ namespace osu.Game.Graphics.UserInterface { string format = new string('0', (int)LeadingZeroes); if (UseCommaSeparator) + { for (int i = format.Length - 3; i > 0; i -= 3) format = format.Insert(i, @","); + } return ((long)count).ToString(format); } diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index caddb1ae0d..b1c794a856 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -47,6 +47,7 @@ namespace osu.Game.Input foreach (var insertable in group.Skip(count).Take(aimCount - count)) // insert any defaults which are missing. + { usage.Context.DatabasedKeyBinding.Add(new DatabasedKeyBinding { KeyCombination = insertable.KeyCombination, @@ -54,6 +55,7 @@ namespace osu.Game.Input RulesetID = rulesetId, Variant = variant }); + } } } } diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 83de0635fb..d214743b30 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -75,8 +75,10 @@ namespace osu.Game.Online.Leaderboards int i = 0; foreach (var s in scrollFlow.Children) + { using (s.BeginDelayedSequence(i++ * 50, true)) s.Show(); + } scrollContainer.ScrollTo(0f, false); }, (showScoresCancellationSource = new CancellationTokenSource()).Token)); @@ -342,13 +344,17 @@ namespace osu.Game.Online.Leaderboards else { if (bottomY - fadeBottom > 0 && FadeBottom) + { c.Colour = ColourInfo.GradientVertical( Color4.White.Opacity(Math.Min(1 - (topY - fadeBottom) / LeaderboardScore.HEIGHT, 1)), Color4.White.Opacity(Math.Min(1 - (bottomY - fadeBottom) / LeaderboardScore.HEIGHT, 1))); + } else if (FadeTop) + { c.Colour = ColourInfo.GradientVertical( Color4.White.Opacity(Math.Min(1 - (fadeTop - topY) / LeaderboardScore.HEIGHT, 1)), Color4.White.Opacity(Math.Min(1 - (fadeTop - bottomY) / LeaderboardScore.HEIGHT, 1))); + } } } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 1f823e6eba..7735030bbb 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -402,8 +402,10 @@ namespace osu.Game nextBeatmap.Track.Completed += currentTrackCompleted; using (var oldBeatmap = beatmap.OldValue) + { if (oldBeatmap?.Track != null) oldBeatmap.Track.Completed -= currentTrackCompleted; + } nextBeatmap?.LoadBeatmapAsync(); } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 194a439b06..87321030a9 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -292,8 +292,10 @@ namespace osu.Game var extension = Path.GetExtension(paths.First())?.ToLowerInvariant(); foreach (var importer in fileImporters) + { if (importer.HandledExtensions.Contains(extension)) await importer.Import(paths); + } } public string[] HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions).ToArray(); diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs index d8488b21ab..5a3ce6291e 100644 --- a/osu.Game/Overlays/Changelog/ChangelogBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs @@ -130,6 +130,7 @@ namespace osu.Game.Overlays.Changelog }); if (entry.GithubUser.UserId != null) + { title.AddUserLink(new User { Username = entry.GithubUser.OsuUsername, @@ -139,18 +140,23 @@ namespace osu.Game.Overlays.Changelog t.Font = fontMedium; t.Colour = entryColour; }); + } else if (entry.GithubUser.GithubUrl != null) + { title.AddLink(entry.GithubUser.DisplayName, entry.GithubUser.GithubUrl, t => { t.Font = fontMedium; t.Colour = entryColour; }); + } else + { title.AddText(entry.GithubUser.DisplayName, t => { t.Font = fontSmall; t.Colour = entryColour; }); + } ChangelogEntries.Add(titleContainer); diff --git a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs index adcd33fb48..3297b00322 100644 --- a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs @@ -68,11 +68,13 @@ namespace osu.Game.Overlays.Changelog } if (build != null) + { Children = new Drawable[] { new ChangelogBuildWithNavigation(build) { SelectBuild = SelectBuild }, new Comments(build) }; + } } public class ChangelogBuildWithNavigation : ChangelogBuild diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index abc1b7233d..560123eb55 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -162,10 +162,12 @@ namespace osu.Game.Overlays.Comments foreach (var c in response.Comments) { if (c.IsTopLevel) + { page.Add(new DrawableComment(c) { ShowDeleted = { BindTarget = ShowDeleted } }); + } } LoadComponentAsync(page, loaded => diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index 3ffc3f332b..c1c5113c5e 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -149,8 +149,10 @@ namespace osu.Game.Overlays.Direct icons.Add(new GroupedDifficultyIcon(SetInfo.Beatmaps.FindAll(b => b.Ruleset.Equals(ruleset)), ruleset, this is DirectListPanel ? Color4.White : colours.Gray5)); } else + { foreach (var b in SetInfo.Beatmaps.OrderBy(beatmap => beatmap.StarDifficulty)) icons.Add(new DifficultyIcon(b)); + } return icons; } diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 58892cd0dd..8252020e9b 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -194,8 +194,10 @@ namespace osu.Game.Overlays.Mods start = Mods.Length - 1; for (int i = start; i < Mods.Length && i >= 0; i += direction) + { if (SelectAt(i)) return; + } Deselect(); } diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index dedd397fa5..5c2ab8e18c 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -112,6 +112,7 @@ namespace osu.Game.Overlays.Mods if (selected == null) continue; foreach (var type in modTypes) + { if (type.IsInstanceOfType(selected)) { if (immediate) @@ -119,6 +120,7 @@ namespace osu.Game.Overlays.Mods else Scheduler.AddDelayed(button.Deselect, delay += 50); } + } } } diff --git a/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs b/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs index 95b7d9b19d..d47caf409b 100644 --- a/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs +++ b/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs @@ -81,8 +81,10 @@ namespace osu.Game.Rulesets.Difficulty.Utils yield return array[i]; if (Count == capacity) + { for (int i = 0; i < marker; ++i) yield return array[i]; + } } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 1d9b66f88d..af1a2fb63a 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -113,8 +113,10 @@ namespace osu.Game.Rulesets.Objects.Drawables if (samples.Length > 0) { if (HitObject.SampleControlPoint == null) + { throw new ArgumentNullException(nameof(HitObject.SampleControlPoint), $"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}." + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}."); + } samples = samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).ToArray(); foreach (var s in samples) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index e990938291..64f01952b1 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -75,8 +75,10 @@ namespace osu.Game.Rulesets.Objects.Legacy int pointCount = 1; foreach (var t in pointSplit) + { if (t.Length > 1) pointCount++; + } var points = new Vector2[pointCount]; diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index bc9571c85d..7763b0eaaf 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -185,8 +185,10 @@ namespace osu.Game.Rulesets.Objects ReadOnlySpan cpSpan = ControlPoints.Slice(start, end - start); foreach (Vector2 t in calculateSubpath(cpSpan)) + { if (calculatedPath.Count == 0 || calculatedPath.Last() != t) calculatedPath.Add(t); + } start = end; } diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 23988ff0ff..7d13afe9e5 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -69,8 +69,10 @@ namespace osu.Game.Rulesets //add any other modes foreach (var r in instances.Where(r => r.LegacyID == null)) + { if (context.RulesetInfo.FirstOrDefault(ri => ri.InstantiationInfo == r.RulesetInfo.InstantiationInfo) == null) context.RulesetInfo.Add(r.RulesetInfo); + } context.SaveChanges(); diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index e005eea831..d1749d33c0 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -436,8 +436,10 @@ namespace osu.Game.Rulesets.UI return h.HitWindows; foreach (var n in h.NestedHitObjects) + { if (h.HitWindows.WindowFor(HitResult.Miss) > 0) return n.HitWindows; + } } return null; diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index f2e7f51b52..ca4983e3d7 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -130,9 +130,13 @@ namespace osu.Game.Rulesets.UI base.Update(); if (beatmap != null) + { foreach (var mod in mods) + { if (mod is IUpdatableByPlayfield updatable) updatable.Update(this); + } + } } /// diff --git a/osu.Game/Screens/Multi/Components/BeatmapTitle.cs b/osu.Game/Screens/Multi/Components/BeatmapTitle.cs index f79cac7649..b41b2d073e 100644 --- a/osu.Game/Screens/Multi/Components/BeatmapTitle.cs +++ b/osu.Game/Screens/Multi/Components/BeatmapTitle.cs @@ -57,11 +57,13 @@ namespace osu.Game.Screens.Multi.Components var beatmap = CurrentItem.Value?.Beatmap; if (beatmap == null) + { textFlow.AddText("No beatmap selected", s => { s.Font = s.Font.With(size: TextSize); s.Colour = colours.PinkLight; }); + } else { textFlow.AddLink(new[] diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index a05937801c..968b83e68c 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -100,9 +100,11 @@ namespace osu.Game.Screens.Play.HUD if (text.Alpha > 0 || button.Progress.Value > 0 || button.IsHovered) Alpha = 1; else + { Alpha = Interpolation.ValueAt( MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 200), Alpha, MathHelper.Clamp(1 - positionalAdjust, 0.04f, 1), 0, 200, Easing.OutQuint); + } } private class Button : HoldToConfirmContainer, IKeyBindingHandler diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 31cdff5fb9..835867fe62 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -181,8 +181,11 @@ namespace osu.Game.Screens.Play this.FadeIn(500, Easing.OutExpo); if (!IsHovered && !IsDragged) + { using (BeginDelayedSequence(1000)) scheduledHide = Schedule(Hide); + } + break; case Visibility.Hidden: diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 6c3c9d20f3..afd6211dec 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -44,10 +44,14 @@ namespace osu.Game.Screens.Select.Carousel criteria.Artist.Matches(Beatmap.Metadata.ArtistUnicode); if (match) + { foreach (var criteriaTerm in criteria.SearchTerms) + { match &= Beatmap.Metadata.SearchableTerms.Any(term => term.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0) || Beatmap.Version.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0; + } + } Filtered.Value = !match; } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 409ea4bbbe..78ff150f08 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -251,11 +251,13 @@ namespace osu.Game.Screens.Select { // if we have no beatmaps but osu-stable is found, let's prompt the user to import. if (!beatmaps.GetAllUsableBeatmapSetsEnumerable().Any() && beatmaps.StableInstallationAvailable) + { dialogOverlay.Push(new ImportFromStablePopup(() => { Task.Run(beatmaps.ImportFromStableAsync).ContinueWith(_ => scores.ImportFromStableAsync(), TaskContinuationOptions.OnlyOnRanToCompletion); Task.Run(skins.ImportFromStableAsync); })); + } }); } } @@ -333,11 +335,13 @@ namespace osu.Game.Screens.Select if (this.IsCurrentScreen() && !Carousel.SelectBeatmap(e.NewValue?.BeatmapInfo, false)) // If selecting new beatmap without bypassing filters failed, there's possibly a ruleset mismatch + { if (e.NewValue?.BeatmapInfo?.Ruleset != null && !e.NewValue.BeatmapInfo.Ruleset.Equals(decoupledRuleset.Value)) { Ruleset.Value = e.NewValue.BeatmapInfo.Ruleset; Carousel.SelectBeatmap(e.NewValue.BeatmapInfo); } + } } // We need to keep track of the last selected beatmap ignoring debounce to play the correct selection sounds. diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index fea15458e4..593b80c012 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -36,8 +36,10 @@ namespace osu.Game.Skinning { Stream stream = storage?.GetStream(filename); if (stream != null) + { using (LineBufferedReader reader = new LineBufferedReader(stream)) Configuration = new LegacySkinDecoder().Decode(reader); + } else Configuration = new DefaultSkinConfiguration(); diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index c5582af836..c758b699ed 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -22,17 +22,19 @@ namespace osu.Game.Skinning if (animatable) { - for (int i = 0;; i++) + for (int i = 0; true; i++) { if ((texture = getFrameTexture(i)) == null) break; if (animation == null) + { animation = new TextureAnimation { DefaultFrameLength = default_frame_time, Repeat = looping }; + } animation.AddFrame(texture); } @@ -42,10 +44,12 @@ namespace osu.Game.Skinning return animation; if ((texture = source.GetTexture(componentName)) != null) + { return new Sprite { Texture = texture }; + } return null; } diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index bdf8be773b..6d23f22515 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -81,9 +81,13 @@ namespace osu.Game.Skinning var ch = skin.GetSample(s); if (ch == null && allowFallback) + { foreach (var lookup in s.LookupNames) + { if ((ch = samples.Get($"Gameplay/{lookup}")) != null) break; + } + } if (ch != null) { @@ -91,8 +95,10 @@ namespace osu.Game.Skinning ch.Volume.Value = s.Volume / 100.0; if (adjustments != null) + { foreach (var adjustment in adjustments) ch.AddAdjustment(adjustment.property, adjustment.bindable); + } } return ch; @@ -104,8 +110,10 @@ namespace osu.Game.Skinning base.Dispose(isDisposing); if (channels != null) + { foreach (var c in channels) c.Dispose(); + } } } } diff --git a/osu.Game/Storyboards/CommandTimelineGroup.cs b/osu.Game/Storyboards/CommandTimelineGroup.cs index 461ee762e9..364c971874 100644 --- a/osu.Game/Storyboards/CommandTimelineGroup.cs +++ b/osu.Game/Storyboards/CommandTimelineGroup.cs @@ -65,6 +65,7 @@ namespace osu.Game.Storyboards public virtual IEnumerable.TypedCommand> GetCommands(CommandTimelineSelector timelineSelector, double offset = 0) { if (offset != 0) + { return timelineSelector(this).Commands.Select(command => new CommandTimeline.TypedCommand { @@ -74,6 +75,7 @@ namespace osu.Game.Storyboards StartValue = command.StartValue, EndValue = command.EndValue, }); + } return timelineSelector(this).Commands; } diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 37c3ff495f..96c7ceea8d 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -106,8 +106,11 @@ namespace osu.Game.Storyboards foreach (var loop in loops) commands = commands.Concat(loop.GetCommands(timelineSelector)); if (triggeredGroups != null) + { foreach (var pair in triggeredGroups) commands = commands.Concat(pair.Item1.GetCommands(timelineSelector, pair.Item2)); + } + return commands; } diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index e99b5fc5fb..b144de35c5 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -72,11 +72,15 @@ namespace osu.Game.Tests.Beatmaps break; if (objectCounter >= ourMapping.Objects.Count) + { Assert.Fail($"The conversion did not generate a hitobject, but should have, for hitobject at time: {expectedMapping.StartTime}:\n" + $"Expected: {JsonConvert.SerializeObject(expectedMapping.Objects[objectCounter])}\n"); + } else if (objectCounter >= expectedMapping.Objects.Count) + { Assert.Fail($"The conversion generated a hitobject, but should not have, for hitobject at time: {ourMapping.StartTime}:\n" + $"Received: {JsonConvert.SerializeObject(ourMapping.Objects[objectCounter])}\n"); + } else if (!expectedMapping.Objects[objectCounter].Equals(ourMapping.Objects[objectCounter])) { Assert.Fail($"The conversion generated differing hitobjects for object at time: {expectedMapping.StartTime}:\n" diff --git a/osu.Game/Tests/Visual/OsuGridTestScene.cs b/osu.Game/Tests/Visual/OsuGridTestScene.cs index c09f4d6218..33ede9453e 100644 --- a/osu.Game/Tests/Visual/OsuGridTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGridTestScene.cs @@ -38,8 +38,10 @@ namespace osu.Game.Tests.Visual cells = new Drawable[rows, cols]; for (int r = 0; r < rows; r++) - for (int c = 0; c < cols; c++) - cells[r, c] = new Container { RelativeSizeAxes = Axes.Both }; + { + for (int c = 0; c < cols; c++) + cells[r, c] = new Container { RelativeSizeAxes = Axes.Both }; + } testContainer.Content = cells.ToJagged(); } diff --git a/osu.Game/Users/UserCoverBackground.cs b/osu.Game/Users/UserCoverBackground.cs index e583acac9f..a45fd85901 100644 --- a/osu.Game/Users/UserCoverBackground.cs +++ b/osu.Game/Users/UserCoverBackground.cs @@ -46,6 +46,7 @@ namespace osu.Game.Users }; } else + { InternalChild = new Sprite { RelativeSizeAxes = Axes.Both, @@ -54,6 +55,7 @@ namespace osu.Game.Users Anchor = Anchor.Centre, Origin = Anchor.Centre }; + } } protected override void LoadComplete() From d8482448dc91ecf93e63789075fc287e0b2c7258 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 11 Nov 2019 19:53:41 +0800 Subject: [PATCH 2227/2815] Save iOS projects with BOM to avoid VS continuously changing them. --- .../osu.Game.Rulesets.Catch.Tests.iOS.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.iOS.csproj | 2 +- .../osu.Game.Rulesets.Osu.Tests.iOS.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.iOS.csproj | 2 +- osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj | 2 +- osu.iOS/osu.iOS.csproj | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj index 708779986c..be6044bbd0 100644 --- a/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj +++ b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj @@ -1,4 +1,4 @@ - + Debug diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj b/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj index 52e558931b..88ad484bc1 100644 --- a/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj +++ b/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj @@ -1,4 +1,4 @@ - + Debug diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj index cacd035078..545abcec6c 100644 --- a/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj +++ b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj @@ -1,4 +1,4 @@ - + Debug diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj b/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj index c20bd4fe02..8ee640cd99 100644 --- a/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj @@ -1,4 +1,4 @@ - + Debug diff --git a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj index 583d188295..ca68369ebb 100644 --- a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj +++ b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj @@ -1,4 +1,4 @@ - + Debug diff --git a/osu.iOS/osu.iOS.csproj b/osu.iOS/osu.iOS.csproj index 378ac231c2..d60a3475e7 100644 --- a/osu.iOS/osu.iOS.csproj +++ b/osu.iOS/osu.iOS.csproj @@ -1,4 +1,4 @@ - + Debug From e9b8cbb5165a65db0c779cc8ef4b97a580d8d3cc Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 11 Nov 2019 20:05:36 +0800 Subject: [PATCH 2228/2815] Apply other styles. --- .../Beatmaps/CatchBeatmapProcessor.cs | 1 + osu.Game.Tests/Skins/LegacySkinDecoderTest.cs | 1 + .../TestSceneTournamentMatchChatDisplay.cs | 31 ++++++++++--------- .../Formats/LegacyStoryboardDecoder.cs | 22 +++++++------ osu.Game/Database/ArchiveModelManager.cs | 1 + .../Graphics/UserInterface/ScoreCounter.cs | 1 + osu.Game/Input/KeyBindingStore.cs | 2 +- osu.Game/Online/Chat/ChannelManager.cs | 2 +- .../Objects/Legacy/ConvertHitObjectParser.cs | 1 + osu.Game/Screens/Select/SongSelect.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 1 + osu.Game/Storyboards/StoryboardSprite.cs | 1 + osu.Game/Tests/Visual/OsuGridTestScene.cs | 1 + osu.iOS/AppDelegate.cs | 1 - 14 files changed, 39 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index 5d0c6116d7..58bf811fac 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -195,6 +195,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps { if (currentObject is Fruit) objectWithDroplets.Add(currentObject); + if (currentObject is JuiceStream) { foreach (var currentJuiceElement in currentObject.NestedHitObjects) diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs index ea9d51a9b8..085f502517 100644 --- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs +++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs @@ -25,6 +25,7 @@ namespace osu.Game.Tests.Skins var comboColors = decoder.Decode(stream).ComboColours; List expectedColors; + if (hasColours) { expectedColors = new List diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentMatchChatDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentMatchChatDisplay.cs index d77a0b75a5..9905e17824 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentMatchChatDisplay.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentMatchChatDisplay.cs @@ -102,21 +102,22 @@ namespace osu.Game.Tournament.Tests.Components Content = "Okay okay, calm down guys. Let's do this!" })); - AddStep("multiple messages", () => testChannel.AddNewMessages(new Message(nextMessageId()) - { - Sender = admin, - Content = "I spam you!" - }, - new Message(nextMessageId()) - { - Sender = admin, - Content = "I spam you!!!1" - }, - new Message(nextMessageId()) - { - Sender = admin, - Content = "I spam you!1!1" - })); + AddStep("multiple messages", () => testChannel.AddNewMessages( + new Message(nextMessageId()) + { + Sender = admin, + Content = "I spam you!" + }, + new Message(nextMessageId()) + { + Sender = admin, + Content = "I spam you!!!1" + }, + new Message(nextMessageId()) + { + Sender = admin, + Content = "I spam you!1!1" + })); AddStep("change channel to 2", () => chatDisplay.Channel.Value = testChannel2); diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index a86fa43f3c..e3320f62ac 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -171,16 +171,16 @@ namespace osu.Game.Beatmaps.Formats var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue); + break; } - break; case "S": { var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startValue), new Vector2(endValue)); + break; } - break; case "V": { @@ -189,16 +189,16 @@ namespace osu.Game.Beatmaps.Formats var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX; var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY; timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY)); + break; } - break; case "R": { var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; timelineGroup?.Rotation.Add(easing, startTime, endTime, MathHelper.RadiansToDegrees(startValue), MathHelper.RadiansToDegrees(endValue)); + break; } - break; case "M": { @@ -208,24 +208,24 @@ namespace osu.Game.Beatmaps.Formats var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY; timelineGroup?.X.Add(easing, startTime, endTime, startX, endX); timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY); + break; } - break; case "MX": { var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue); + break; } - break; case "MY": { var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue); + break; } - break; case "C": { @@ -238,8 +238,8 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.Colour.Add(easing, startTime, endTime, new Color4(startRed / 255f, startGreen / 255f, startBlue / 255f, 1), new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1)); + break; } - break; case "P": { @@ -259,14 +259,16 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.FlipV.Add(easing, startTime, endTime, true, startTime == endTime); break; } + + break; } - break; default: throw new InvalidDataException($@"Unknown command type: {commandType}"); } + + break; } - break; } } } diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index a262b76125..8fa4eaf267 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -264,6 +264,7 @@ namespace osu.Game.Database { // for now, concatenate all .osu files in the set to create a unique hash. MemoryStream hashable = new MemoryStream(); + foreach (string file in reader.Filenames.Where(f => HashableFileTypes.Any(f.EndsWith))) { using (Stream s = reader.GetStream(file)) diff --git a/osu.Game/Graphics/UserInterface/ScoreCounter.cs b/osu.Game/Graphics/UserInterface/ScoreCounter.cs index ee96b78844..e291401670 100644 --- a/osu.Game/Graphics/UserInterface/ScoreCounter.cs +++ b/osu.Game/Graphics/UserInterface/ScoreCounter.cs @@ -43,6 +43,7 @@ namespace osu.Game.Graphics.UserInterface protected override string FormatCount(double count) { string format = new string('0', (int)LeadingZeroes); + if (UseCommaSeparator) { for (int i = format.Length - 3; i > 0; i -= 3) diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index b1c794a856..74b3134964 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -46,8 +46,8 @@ namespace osu.Game.Input continue; foreach (var insertable in group.Skip(count).Take(aimCount - count)) - // insert any defaults which are missing. { + // insert any defaults which are missing. usage.Context.DatabasedKeyBinding.Add(new DatabasedKeyBinding { KeyCombination = insertable.KeyCombination, diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 4f6066cab1..1d8c5609d9 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -220,7 +220,7 @@ namespace osu.Game.Online.Chat break; } - var channel = availableChannels.Where(c => c.Name == content || c.Name == $"#{content}").FirstOrDefault(); + var channel = availableChannels.FirstOrDefault(c => c.Name == content || c.Name == $"#{content}"); if (channel == null) { diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 64f01952b1..4049e40013 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -74,6 +74,7 @@ namespace osu.Game.Rulesets.Objects.Legacy string[] pointSplit = split[5].Split('|'); int pointCount = 1; + foreach (var t in pointSplit) { if (t.Length > 1) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 78ff150f08..375b994169 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -334,8 +334,8 @@ namespace osu.Game.Screens.Select if (e.NewValue is DummyWorkingBeatmap) return; if (this.IsCurrentScreen() && !Carousel.SelectBeatmap(e.NewValue?.BeatmapInfo, false)) - // If selecting new beatmap without bypassing filters failed, there's possibly a ruleset mismatch { + // If selecting new beatmap without bypassing filters failed, there's possibly a ruleset mismatch if (e.NewValue?.BeatmapInfo?.Ruleset != null && !e.NewValue.BeatmapInfo.Ruleset.Equals(decoupledRuleset.Value)) { Ruleset.Value = e.NewValue.BeatmapInfo.Ruleset; diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 593b80c012..67a83f19e2 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -35,6 +35,7 @@ namespace osu.Game.Skinning : base(skin) { Stream stream = storage?.GetStream(filename); + if (stream != null) { using (LineBufferedReader reader = new LineBufferedReader(stream)) diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 96c7ceea8d..d5e69fd103 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -105,6 +105,7 @@ namespace osu.Game.Storyboards var commands = TimelineGroup.GetCommands(timelineSelector); foreach (var loop in loops) commands = commands.Concat(loop.GetCommands(timelineSelector)); + if (triggeredGroups != null) { foreach (var pair in triggeredGroups) diff --git a/osu.Game/Tests/Visual/OsuGridTestScene.cs b/osu.Game/Tests/Visual/OsuGridTestScene.cs index 33ede9453e..48f85be6ba 100644 --- a/osu.Game/Tests/Visual/OsuGridTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGridTestScene.cs @@ -37,6 +37,7 @@ namespace osu.Game.Tests.Visual Add(testContainer = new GridContainer { RelativeSizeAxes = Axes.Both }); cells = new Drawable[rows, cols]; + for (int r = 0; r < rows; r++) { for (int c = 0; c < cols; c++) diff --git a/osu.iOS/AppDelegate.cs b/osu.iOS/AppDelegate.cs index 164a182ebe..14e3627752 100644 --- a/osu.iOS/AppDelegate.cs +++ b/osu.iOS/AppDelegate.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using Foundation; using osu.Framework.iOS; -using osu.Framework.Threading; using UIKit; namespace osu.iOS From d3858622676274c0c30d731099311a9bca1401d2 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 11 Nov 2019 20:32:32 +0800 Subject: [PATCH 2229/2815] Add dotnet format check. --- .config/dotnet-tools.json | 6 ++++++ build/build.cake | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 1b70c949eb..6ba6ae82c8 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -7,6 +7,12 @@ "commands": [ "dotnet-cake" ] + }, + "dotnet-format": { + "version": "3.1.37601", + "commands": [ + "dotnet-format" + ] } } } \ No newline at end of file diff --git a/build/build.cake b/build/build.cake index 389ff4829d..274e57ef4e 100644 --- a/build/build.cake +++ b/build/build.cake @@ -11,6 +11,7 @@ var target = Argument("target", "Build"); var configuration = Argument("configuration", "Release"); var rootDirectory = new DirectoryPath(".."); +var sln = rootDirectory.CombineWithFilePath("osu.sln"); var desktopBuilds = rootDirectory.CombineWithFilePath("build/Desktop.proj"); var desktopSlnf = rootDirectory.CombineWithFilePath("osu.Desktop.slnf"); @@ -60,8 +61,12 @@ Task("CodeFileSanity") }); }); +Task("DotnetFormat") + .Does(() => DotNetCoreTool(sln.FullPath, "format", "--dry-run --check")); + Task("Build") .IsDependentOn("CodeFileSanity") + .IsDependentOn("DotnetFormat") .IsDependentOn("InspectCode") .IsDependentOn("Test"); From 1ef645a7105977268c17c5fe438341be1f59ab5a Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 11 Nov 2019 21:02:48 +0800 Subject: [PATCH 2230/2815] Turn off some more not applied styles. --- .editorconfig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.editorconfig b/.editorconfig index 8f9d0ca9b0..2c000d3881 100644 --- a/.editorconfig +++ b/.editorconfig @@ -140,7 +140,7 @@ dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning dotnet_style_prefer_auto_properties = true:silent dotnet_style_prefer_conditional_expression_over_assignment = true:silent dotnet_style_prefer_conditional_expression_over_return = true:silent -dotnet_style_prefer_compound_assignment = true:warning +dotnet_style_prefer_compound_assignment = true:silent #Style - null/type checks dotnet_style_coalesce_expression = true:warning @@ -153,17 +153,17 @@ csharp_style_conditional_delegate_call = true:suggestion #Style - unused dotnet_code_quality_unused_parameters = non_public:silent csharp_style_unused_value_expression_statement_preference = discard_variable:silent -csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:silent #Style - variable declaration -csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_inlined_variable_declaration = true:silent csharp_style_deconstructed_variable_declaration = true:silent #Style - other C# 7.x features csharp_style_expression_bodied_local_functions = true:silent dotnet_style_prefer_inferred_tuple_names = true:warning csharp_prefer_simple_default_expression = true:warning -csharp_style_pattern_local_over_anonymous_function = true:warning +csharp_style_pattern_local_over_anonymous_function = true:silent dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent #Supressing roslyn built-in analyzers From 3655f881806ee7b13cd6e22074439d7d07405885 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 11 Nov 2019 17:56:57 +0300 Subject: [PATCH 2231/2815] Merge dependency --- .../Online/TestSceneLeaderboardModSelector.cs | 77 ++++++++ .../BeatmapSet/LeaderboardModSelector.cs | 165 ++++++++++++++++++ osu.Game/Rulesets/UI/ModIcon.cs | 19 +- 3 files changed, 259 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs create mode 100644 osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs new file mode 100644 index 0000000000..799528220b --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs @@ -0,0 +1,77 @@ +// 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.Overlays.BeatmapSet; +using System; +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Taiko; +using osu.Game.Rulesets.Catch; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Bindables; +using osu.Game.Rulesets; +using osu.Framework.Extensions.IEnumerableExtensions; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneLeaderboardModSelector : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(LeaderboardModSelector), + }; + + public TestSceneLeaderboardModSelector() + { + LeaderboardModSelector modSelector; + FillFlowContainer selectedMods; + Bindable ruleset = new Bindable(); + + Add(selectedMods = new FillFlowContainer + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + }); + + Add(modSelector = new LeaderboardModSelector + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Ruleset = { BindTarget = ruleset } + }); + + modSelector.SelectedMods.ItemsAdded += mods => + { + mods.ForEach(mod => selectedMods.Add(new SpriteText + { + Text = mod.Acronym, + })); + }; + + modSelector.SelectedMods.ItemsRemoved += mods => + { + mods.ForEach(mod => + { + foreach (var selected in selectedMods) + { + if (selected.Text == mod.Acronym) + { + selectedMods.Remove(selected); + break; + } + } + }); + }; + + AddStep("osu ruleset", () => ruleset.Value = new OsuRuleset().RulesetInfo); + AddStep("mania ruleset", () => ruleset.Value = new ManiaRuleset().RulesetInfo); + AddStep("taiko ruleset", () => ruleset.Value = new TaikoRuleset().RulesetInfo); + AddStep("catch ruleset", () => ruleset.Value = new CatchRuleset().RulesetInfo); + AddStep("Deselect all", () => modSelector.DeselectAll()); + AddStep("null ruleset", () => ruleset.Value = null); + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs new file mode 100644 index 0000000000..75c24cb710 --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -0,0 +1,165 @@ +// 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.Containers; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Mods; +using osu.Framework.Bindables; +using osu.Game.Rulesets; +using osuTK; +using osu.Game.Rulesets.UI; +using osu.Framework.Input.Events; +using osu.Game.Graphics.UserInterface; +using osuTK.Graphics; +using System; +using System.Linq; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics.Sprites; + +namespace osu.Game.Overlays.BeatmapSet +{ + public class LeaderboardModSelector : CompositeDrawable + { + public readonly BindableList SelectedMods = new BindableList(); + public readonly Bindable Ruleset = new Bindable(); + + private readonly FillFlowContainer modsContainer; + + public LeaderboardModSelector() + { + AutoSizeAxes = Axes.Both; + InternalChild = modsContainer = new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Full, + Spacing = new Vector2(4), + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Ruleset.BindValueChanged(onRulesetChanged, true); + } + + private void onRulesetChanged(ValueChangedEvent ruleset) + { + SelectedMods.Clear(); + modsContainer.Clear(); + + if (ruleset.NewValue == null) + return; + + modsContainer.Add(new ModButton(new NoMod())); + + ruleset.NewValue.CreateInstance().GetAllMods().ForEach(mod => + { + if (mod.Ranked) + modsContainer.Add(new ModButton(mod)); + }); + + modsContainer.ForEach(button => button.OnSelectionChanged += selectionChanged); + } + + private void selectionChanged(Mod mod, bool selected) + { + if (selected) + SelectedMods.Add(mod); + else + SelectedMods.Remove(mod); + + if (!SelectedMods.Any() && !IsHovered) + highlightAll(); + } + + protected override bool OnHover(HoverEvent e) + { + if (!SelectedMods.Any()) + modsContainer.ForEach(button => + { + if (!button.IsHovered) + button.Highlighted.Value = false; + }); + + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + + if (!SelectedMods.Any()) + highlightAll(); + } + + public void DeselectAll() => modsContainer.ForEach(mod => mod.Selected.Value = false); + + private void highlightAll() => modsContainer.ForEach(mod => mod.Highlighted.Value = true); + + private class ModButton : ModIcon + { + private const float mod_scale = 0.4f; + private const int duration = 200; + + public readonly BindableBool Selected = new BindableBool(); + public Action OnSelectionChanged; + + public ModButton(Mod mod) + : base(mod) + { + Scale = new Vector2(mod_scale); + Highlighted.Value = true; + Add(new HoverClickSounds()); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Selected.BindValueChanged(selected => + { + updateState(); + OnSelectionChanged?.Invoke(Mod, selected.NewValue); + }); + } + + protected override bool OnClick(ClickEvent e) + { + Selected.Value = !Selected.Value; + return base.OnClick(e); + } + + protected override bool OnHover(HoverEvent e) + { + updateState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + updateState(); + } + + private void updateState() => Highlighted.Value = IsHovered || Selected.Value; + + protected override void OnHighlightedChanged(ValueChangedEvent highlighted) => + this.FadeColour(highlighted.NewValue ? Color4.White : Color4.Gray, duration, Easing.OutQuint); + } + + private class NoMod : Mod + { + public override string Name => "NoMod"; + + public override string Acronym => "NM"; + + public override double ScoreMultiplier => 1; + + public override IconUsage Icon => FontAwesome.Solid.Ban; + + public override ModType Type => ModType.System; + } + } +} diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 88a2338b94..19211e0c80 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -34,9 +34,11 @@ namespace osu.Game.Rulesets.UI public virtual string TooltipText { get; } + protected Mod Mod { get; private set; } + public ModIcon(Mod mod) { - if (mod == null) throw new ArgumentNullException(nameof(mod)); + Mod = mod ?? throw new ArgumentNullException(nameof(mod)); type = mod.Type; @@ -98,13 +100,26 @@ namespace osu.Game.Rulesets.UI backgroundColour = colours.Pink; highlightedColour = colours.PinkLight; break; + + case ModType.System: + backgroundColour = colours.Gray6; + highlightedColour = colours.Gray7; + modIcon.Colour = colours.Yellow; + break; } + + background.Colour = backgroundColour; } protected override void LoadComplete() { base.LoadComplete(); - Highlighted.BindValueChanged(highlighted => background.Colour = highlighted.NewValue ? highlightedColour : backgroundColour, true); + Highlighted.BindValueChanged(OnHighlightedChanged, true); + } + + protected virtual void OnHighlightedChanged(ValueChangedEvent highlighted) + { + background.Colour = highlighted.NewValue ? highlightedColour : backgroundColour; } } } From 32b2f5e330e70fe7ef910bf28dec1f84255e93c1 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 11 Nov 2019 18:10:25 +0300 Subject: [PATCH 2232/2815] Use Bindable for ScoresContainer --- .../BeatmapSet/Scores/ScoresContainer.cs | 31 +++++++------------ osu.Game/Overlays/BeatmapSetOverlay.cs | 6 ++-- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 4bbcd8d631..580e863a0c 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -13,6 +13,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Framework.Bindables; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -20,6 +21,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { private const int spacing = 15; + public readonly Bindable Beatmap = new Bindable(); + private readonly Box background; private readonly ScoreTable scoreTable; private readonly FillFlowContainer topScoresContainer; @@ -30,22 +33,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private GetScoresRequest getScoresRequest; - private BeatmapInfo beatmap; - - public BeatmapInfo Beatmap - { - get => beatmap; - set - { - if (beatmap == value) - return; - - beatmap = value; - - getScores(beatmap); - } - } - protected APILegacyScores Scores { set @@ -125,18 +112,24 @@ namespace osu.Game.Overlays.BeatmapSet.Scores background.Colour = colours.Gray2; } - private void getScores(BeatmapInfo beatmap) + protected override void LoadComplete() + { + base.LoadComplete(); + Beatmap.BindValueChanged(getScores, true); + } + + private void getScores(ValueChangedEvent beatmap) { getScoresRequest?.Cancel(); getScoresRequest = null; Scores = null; - if (beatmap?.OnlineBeatmapID.HasValue != true || beatmap.Status <= BeatmapSetOnlineStatus.Pending) + if (beatmap.NewValue?.OnlineBeatmapID.HasValue != true || beatmap.NewValue.Status <= BeatmapSetOnlineStatus.Pending) return; loadingAnimation.Show(); - getScoresRequest = new GetScoresRequest(beatmap, beatmap.Ruleset); + getScoresRequest = new GetScoresRequest(beatmap.NewValue, beatmap.NewValue.Ruleset); getScoresRequest.Success += scores => { loadingAnimation.Hide(); diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index c20e6368d8..65d68e4c8c 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -59,7 +59,10 @@ namespace osu.Game.Overlays { Header = new Header(), info = new Info(), - scoreContainer = new ScoresContainer(), + scoreContainer = new ScoresContainer + { + Beatmap = { BindTarget = Header.Picker.Beatmap } + } }, }, }, @@ -71,7 +74,6 @@ namespace osu.Game.Overlays Header.Picker.Beatmap.ValueChanged += b => { info.Beatmap = b.NewValue; - scoreContainer.Beatmap = b.NewValue; scroll.ScrollToStart(); }; From e690a8a301d6c9e451b6723d215be23d9fdc4d14 Mon Sep 17 00:00:00 2001 From: recapitalverb <41869184+recapitalverb@users.noreply.github.com> Date: Mon, 11 Nov 2019 22:15:19 +0700 Subject: [PATCH 2233/2815] Fix capitalisation in README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 67eb3254e1..bc5beeb83d 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,9 @@ Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh ## Requirements - A desktop platform with the [.NET Core SDK 3.0](https://www.microsoft.com/net/learn/get-started) or higher installed. -- When running on linux, please have a system-wide ffmpeg installation available to support video decoding. +- When running on Linux, please have a system-wide ffmpeg installation available to support video decoding. - When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs. -- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [Jetbrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/). +- When working with the codebase, we recommend using an IDE with IntelliSense and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [Jetbrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/). ## Running osu! From 00b92297375b3062337d303cdb9f6885e440fc71 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 11 Nov 2019 18:21:07 +0300 Subject: [PATCH 2234/2815] Add mod filter to beatmap overlay --- .../BeatmapSet/Scores/ScoresContainer.cs | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 580e863a0c..acf0f08956 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -14,6 +14,7 @@ using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Framework.Bindables; +using osu.Game.Rulesets; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -22,11 +23,14 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private const int spacing = 15; public readonly Bindable Beatmap = new Bindable(); + private readonly Bindable ruleset = new Bindable(); private readonly Box background; private readonly ScoreTable scoreTable; private readonly FillFlowContainer topScoresContainer; private readonly LoadingAnimation loadingAnimation; + private readonly FillFlowContainer modFilter; + private readonly LeaderboardModSelector modSelector; [Resolved] private IAPIProvider api { get; set; } @@ -84,6 +88,22 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Margin = new MarginPadding { Vertical = spacing }, Children = new Drawable[] { + modFilter = new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, spacing), + Children = new Drawable[] + { + new LeaderboardScopeSelector(), + modSelector = new LeaderboardModSelector + { + Ruleset = { BindTarget = ruleset } + } + } + }, topScoresContainer = new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -115,21 +135,33 @@ namespace osu.Game.Overlays.BeatmapSet.Scores protected override void LoadComplete() { base.LoadComplete(); - Beatmap.BindValueChanged(getScores, true); + Beatmap.BindValueChanged(onBeatmapChanged, true); } - private void getScores(ValueChangedEvent beatmap) + private void onBeatmapChanged(ValueChangedEvent beatmap) + { + var beatmapRuleset = beatmap.NewValue?.Ruleset; + + if (modSelector.Ruleset.Value?.Equals(beatmapRuleset) ?? false) + modSelector.DeselectAll(); + else + ruleset.Value = beatmapRuleset; + + getScores(beatmap.NewValue); + } + + private void getScores(BeatmapInfo beatmap) { getScoresRequest?.Cancel(); getScoresRequest = null; Scores = null; - if (beatmap.NewValue?.OnlineBeatmapID.HasValue != true || beatmap.NewValue.Status <= BeatmapSetOnlineStatus.Pending) + if (beatmap?.OnlineBeatmapID.HasValue != true || beatmap.Status <= BeatmapSetOnlineStatus.Pending) return; loadingAnimation.Show(); - getScoresRequest = new GetScoresRequest(beatmap.NewValue, beatmap.NewValue.Ruleset); + getScoresRequest = new GetScoresRequest(beatmap, beatmap.Ruleset); getScoresRequest.Success += scores => { loadingAnimation.Hide(); From 2cfd54ca0d577da1872f74b92c83596a6001e1d1 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 11 Nov 2019 18:27:48 +0300 Subject: [PATCH 2235/2815] Handle scope changing --- .../BeatmapSet/Scores/ScoresContainer.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index acf0f08956..8058d0fc0f 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -15,6 +15,7 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Framework.Bindables; using osu.Game.Rulesets; +using osu.Game.Screens.Select.Leaderboards; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -24,6 +25,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores public readonly Bindable Beatmap = new Bindable(); private readonly Bindable ruleset = new Bindable(); + private readonly Bindable scope = new Bindable(); private readonly Box background; private readonly ScoreTable scoreTable; @@ -97,7 +99,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Spacing = new Vector2(0, spacing), Children = new Drawable[] { - new LeaderboardScopeSelector(), + new LeaderboardScopeSelector + { + Current = { BindTarget = scope } + }, modSelector = new LeaderboardModSelector { Ruleset = { BindTarget = ruleset } @@ -135,6 +140,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores protected override void LoadComplete() { base.LoadComplete(); + scope.BindValueChanged(_ => getScores()); Beatmap.BindValueChanged(onBeatmapChanged, true); } @@ -147,21 +153,21 @@ namespace osu.Game.Overlays.BeatmapSet.Scores else ruleset.Value = beatmapRuleset; - getScores(beatmap.NewValue); + getScores(); } - private void getScores(BeatmapInfo beatmap) + private void getScores() { getScoresRequest?.Cancel(); getScoresRequest = null; Scores = null; - if (beatmap?.OnlineBeatmapID.HasValue != true || beatmap.Status <= BeatmapSetOnlineStatus.Pending) + if (Beatmap.Value?.OnlineBeatmapID.HasValue != true || Beatmap.Value.Status <= BeatmapSetOnlineStatus.Pending) return; loadingAnimation.Show(); - getScoresRequest = new GetScoresRequest(beatmap, beatmap.Ruleset); + getScoresRequest = new GetScoresRequest(Beatmap.Value, Beatmap.Value.Ruleset, scope.Value); getScoresRequest.Success += scores => { loadingAnimation.Hide(); From 31191dadf1c9e487174bbc36a21199f697e721e2 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 11 Nov 2019 18:31:42 +0300 Subject: [PATCH 2236/2815] Handle mods change --- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 8058d0fc0f..5c1f1ed7e4 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -141,6 +141,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { base.LoadComplete(); scope.BindValueChanged(_ => getScores()); + + modSelector.SelectedMods.ItemsAdded += _ => getScores(); + modSelector.SelectedMods.ItemsRemoved += _ => getScores(); + Beatmap.BindValueChanged(onBeatmapChanged, true); } @@ -167,7 +171,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores return; loadingAnimation.Show(); - getScoresRequest = new GetScoresRequest(Beatmap.Value, Beatmap.Value.Ruleset, scope.Value); + getScoresRequest = new GetScoresRequest(Beatmap.Value, Beatmap.Value.Ruleset, scope.Value, modSelector.SelectedMods); getScoresRequest.Success += scores => { loadingAnimation.Hide(); From 851b414e9874fc5c6bb1e024b8eda2db3ddf94db Mon Sep 17 00:00:00 2001 From: recapitalverb <41869184+recapitalverb@users.noreply.github.com> Date: Mon, 11 Nov 2019 22:37:15 +0700 Subject: [PATCH 2237/2815] Update README.md Co-Authored-By: Joseph Madamba --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bc5beeb83d..0ee1bedae4 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh - A desktop platform with the [.NET Core SDK 3.0](https://www.microsoft.com/net/learn/get-started) or higher installed. - When running on Linux, please have a system-wide ffmpeg installation available to support video decoding. - When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs. -- When working with the codebase, we recommend using an IDE with IntelliSense and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [Jetbrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/). +- When working with the codebase, we recommend using an IDE with IntelliSense and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/). ## Running osu! From 5f5d130d1a7f2d7022424ce771ec1cc59795ed62 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 11 Nov 2019 18:40:57 +0300 Subject: [PATCH 2238/2815] Update mod filter visibility on user change --- .../BeatmapSet/Scores/ScoresContainer.cs | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 5c1f1ed7e4..1dd224e737 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -16,6 +16,7 @@ using osu.Game.Online.API.Requests; using osu.Framework.Bindables; using osu.Game.Rulesets; using osu.Game.Screens.Select.Leaderboards; +using osu.Game.Users; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -145,7 +146,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores modSelector.SelectedMods.ItemsAdded += _ => getScores(); modSelector.SelectedMods.ItemsRemoved += _ => getScores(); - Beatmap.BindValueChanged(onBeatmapChanged, true); + Beatmap.BindValueChanged(onBeatmapChanged); + api.LocalUser.BindValueChanged(onUserChanged, true); } private void onBeatmapChanged(ValueChangedEvent beatmap) @@ -160,6 +162,18 @@ namespace osu.Game.Overlays.BeatmapSet.Scores getScores(); } + private void onUserChanged(ValueChangedEvent user) + { + scope.Value = BeatmapLeaderboardScope.Global; + modSelector.DeselectAll(); + updateModFilterVisibility(); + } + + private void updateModFilterVisibility() + { + modFilter.FadeTo(api.IsLoggedIn && api.LocalUser.Value.IsSupporter && !hasNoLeaderboard ? 1 : 0); + } + private void getScores() { getScoresRequest?.Cancel(); @@ -167,7 +181,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Scores = null; - if (Beatmap.Value?.OnlineBeatmapID.HasValue != true || Beatmap.Value.Status <= BeatmapSetOnlineStatus.Pending) + updateModFilterVisibility(); + + if (hasNoLeaderboard) return; loadingAnimation.Show(); @@ -179,5 +195,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }; api.Queue(getScoresRequest); } + + private bool hasNoLeaderboard => Beatmap.Value?.OnlineBeatmapID.HasValue != true || Beatmap.Value.Status <= BeatmapSetOnlineStatus.Pending; } } From c5b64e26a306885caf7f10f2fdab073aa7cd005c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 11 Nov 2019 18:51:29 +0300 Subject: [PATCH 2239/2815] Layout adjustment --- .../BeatmapSet/Scores/ScoresContainer.cs | 47 +++++++++++++------ 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 1dd224e737..2f30cde5af 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -110,25 +110,44 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } } }, - topScoresContainer = new FillFlowContainer + new Container { - RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), - }, - scoreTable = new ScoreTable - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + Children = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, spacing), + Children = new Drawable[] + { + topScoresContainer = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + }, + scoreTable = new ScoreTable + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + } + } + }, + loadingAnimation = new LoadingAnimation + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Alpha = 0, + }, + } } } }, - loadingAnimation = new LoadingAnimation - { - Alpha = 0, - Margin = new MarginPadding(20), - }, }; } From 810cfab870dc042ec48e21361cc11dec7097cd1e Mon Sep 17 00:00:00 2001 From: recapitalverb Date: Mon, 11 Nov 2019 22:53:16 +0700 Subject: [PATCH 2240/2815] Fix capitalisation and grammar in README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0ee1bedae4..b8c6451c75 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,9 @@ Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh ## Requirements - A desktop platform with the [.NET Core SDK 3.0](https://www.microsoft.com/net/learn/get-started) or higher installed. -- When running on Linux, please have a system-wide ffmpeg installation available to support video decoding. +- When running on Linux, please have a system-wide FFmpeg installation available to support video decoding. - When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs. -- When working with the codebase, we recommend using an IDE with IntelliSense and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/). +- When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/). ## Running osu! @@ -68,7 +68,7 @@ dotnet run --project osu.Desktop If you are not interested in debugging osu!, you can add `-c Release` to gain performance. In this case, you must replace `Debug` with `Release` in any commands mentioned in this document. -If the build fails, try to restore nuget packages with `dotnet restore`. +If the build fails, try to restore NuGet packages with `dotnet restore`. ### Testing with resource/framework modifications @@ -76,11 +76,11 @@ Sometimes it may be necessary to cross-test changes in [osu-resources](https://g ### Code analysis -Code analysis can be run with `powershell ./build.ps1` or `build.sh`. This is currently only supported under windows due to [resharper cli shortcomings](https://youtrack.jetbrains.com/issue/RSRP-410004). Alternatively, you can install resharper or use rider to get inline support in your IDE of choice. +Code analysis can be run with `powershell ./build.ps1` or `build.sh`. This is currently only supported under Windows due to [ReSharper CLI shortcomings](https://youtrack.jetbrains.com/issue/RSRP-410004). Alternatively, you can install ReSharper or use Rider to get inline support in your IDE of choice. ## Contributing -We welcome all contributions, but keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention on having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time, to ensure no effort is wasted. +We welcome all contributions, but keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention of having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time, to ensure no effort is wasted. If you're unsure of what you can help with, check out the [list of open issues](https://github.com/ppy/osu/issues) (especially those with the ["good first issue"](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22) label). From 32cc7b3d4d69926c49071c85c1113c6ef1625423 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 11 Nov 2019 19:04:50 +0300 Subject: [PATCH 2241/2815] CI fix --- osu.Game/Overlays/BeatmapSetOverlay.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 65d68e4c8c..50fb2782d4 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -37,7 +37,6 @@ namespace osu.Game.Overlays { OsuScrollContainer scroll; Info info; - ScoresContainer scoreContainer; Children = new Drawable[] { @@ -59,7 +58,7 @@ namespace osu.Game.Overlays { Header = new Header(), info = new Info(), - scoreContainer = new ScoresContainer + new ScoresContainer { Beatmap = { BindTarget = Header.Picker.Beatmap } } From 0578f91a764733afafded1fb6b78c1e7990b81b6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 11 Nov 2019 19:06:46 +0300 Subject: [PATCH 2242/2815] Small logic adjustments --- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 2f30cde5af..73253546bb 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -161,6 +161,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { base.LoadComplete(); scope.BindValueChanged(_ => getScores()); + ruleset.BindValueChanged(_ => getScores()); modSelector.SelectedMods.ItemsAdded += _ => getScores(); modSelector.SelectedMods.ItemsRemoved += _ => getScores(); @@ -173,12 +174,12 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { var beatmapRuleset = beatmap.NewValue?.Ruleset; - if (modSelector.Ruleset.Value?.Equals(beatmapRuleset) ?? false) + if (ruleset.Value?.Equals(beatmapRuleset) ?? false) modSelector.DeselectAll(); else ruleset.Value = beatmapRuleset; - getScores(); + scope.Value = BeatmapLeaderboardScope.Global; } private void onUserChanged(ValueChangedEvent user) From 9efcc6addde56b5080ea2ab6b34c8c7aa6b1e647 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 11 Nov 2019 08:53:05 -0800 Subject: [PATCH 2243/2815] Fix remaining proper nouns --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b8c6451c75..a078265d6c 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ Before starting, please make sure you are familiar with the [development and tes Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured; with any libraries we are using; with any processes involved with contributing, *please* bring it up. We welcome all feedback so we can make contributing to this project as pain-free as possible. -For those interested, we love to reward quality contributions via [bounties](https://docs.google.com/spreadsheets/d/1jNXfj_S3Pb5PErA-czDdC9DUu4IgUbe1Lt8E7CYUJuE/view?&rm=minimal#gid=523803337), paid out via paypal or osu! supporter tags. Don't hesitate to [request a bounty](https://docs.google.com/forms/d/e/1FAIpQLSet_8iFAgPMG526pBZ2Kic6HSh7XPM3fE8xPcnWNkMzINDdYg/viewform) for your work on this project. +For those interested, we love to reward quality contributions via [bounties](https://docs.google.com/spreadsheets/d/1jNXfj_S3Pb5PErA-czDdC9DUu4IgUbe1Lt8E7CYUJuE/view?&rm=minimal#gid=523803337), paid out via PayPal or osu!supporter tags. Don't hesitate to [request a bounty](https://docs.google.com/forms/d/e/1FAIpQLSet_8iFAgPMG526pBZ2Kic6HSh7XPM3fE8xPcnWNkMzINDdYg/viewform) for your work on this project. ## Licence From 8402fb1490c32a920a2b3ae9a0df122a563560de Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Nov 2019 10:02:42 +0900 Subject: [PATCH 2244/2815] Move to const and add some xmldoc for future visitors --- osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index e262fa3c60..3437af8c1e 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -16,6 +16,12 @@ namespace osu.Game.Rulesets.Osu.Edit { public class DrawableOsuEditRuleset : DrawableOsuRuleset { + /// + /// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay. + /// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points. + /// + private const double editor_hit_object_fade_out_extension = 500; + public DrawableOsuEditRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { @@ -35,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Edit return; using (hitObject.BeginAbsoluteSequence(existing.StartTime)) - hitObject.FadeOut(500).Expire(); + hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire(); break; } } From 5bb65d0716ad1f0520b152e3ab14701b1f998fd8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Nov 2019 10:18:25 +0900 Subject: [PATCH 2245/2815] Rename button class --- .../TestSceneStatefulMenuItem.cs | 8 +++--- .../Graphics/UserInterface/TernaryState.cs | 2 +- ...ateMenuItem.cs => TernaryStateMenuItem.cs} | 26 +++++++++---------- 3 files changed, 18 insertions(+), 18 deletions(-) rename osu.Game/Graphics/UserInterface/{ThreeStateMenuItem.cs => TernaryStateMenuItem.cs} (69%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs index 1eff30d15e..2ada5b927b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.UserInterface { typeof(OsuMenu), typeof(StatefulMenuItem), - typeof(ThreeStateMenuItem), + typeof(TernaryStateMenuItem), typeof(DrawableStatefulMenuItem), }; @@ -39,9 +39,9 @@ namespace osu.Game.Tests.Visual.UserInterface Origin = Anchor.Centre, Items = new[] { - new ThreeStateMenuItem("First"), - new ThreeStateMenuItem("Second") { State = { BindTarget = state } }, - new ThreeStateMenuItem("Third") { State = { Value = TernaryState.True } }, + new TernaryStateMenuItem("First"), + new TernaryStateMenuItem("Second") { State = { BindTarget = state } }, + new TernaryStateMenuItem("Third") { State = { Value = TernaryState.True } }, } }; }); diff --git a/osu.Game/Graphics/UserInterface/TernaryState.cs b/osu.Game/Graphics/UserInterface/TernaryState.cs index 784122e35c..d4de28044f 100644 --- a/osu.Game/Graphics/UserInterface/TernaryState.cs +++ b/osu.Game/Graphics/UserInterface/TernaryState.cs @@ -15,7 +15,7 @@ namespace osu.Game.Graphics.UserInterface /// /// The current state is a combination of and . - /// The state becomes if the is pressed. + /// The state becomes if the is pressed. /// Indeterminate, diff --git a/osu.Game/Graphics/UserInterface/ThreeStateMenuItem.cs b/osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs similarity index 69% rename from osu.Game/Graphics/UserInterface/ThreeStateMenuItem.cs rename to osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs index c5b9edf3c4..2d9e2106d4 100644 --- a/osu.Game/Graphics/UserInterface/ThreeStateMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs @@ -9,37 +9,37 @@ namespace osu.Game.Graphics.UserInterface /// /// An with three possible states. /// - public class ThreeStateMenuItem : StatefulMenuItem + public class TernaryStateMenuItem : StatefulMenuItem { /// - /// Creates a new . + /// Creates a new . /// /// The text to display. - /// The type of action which this performs. - public ThreeStateMenuItem(string text, MenuItemType type = MenuItemType.Standard) + /// The type of action which this performs. + public TernaryStateMenuItem(string text, MenuItemType type = MenuItemType.Standard) : this(text, type, null) { } /// - /// Creates a new . + /// Creates a new . /// /// The text to display. - /// The type of action which this performs. - /// A delegate to be invoked when this is pressed. - public ThreeStateMenuItem(string text, MenuItemType type, Action action) + /// The type of action which this performs. + /// A delegate to be invoked when this is pressed. + public TernaryStateMenuItem(string text, MenuItemType type, Action action) : this(text, getNextState, type, action) { } /// - /// Creates a new . + /// Creates a new . /// /// The text to display. - /// A function that mutates a state to another state after this is pressed. - /// The type of action which this performs. - /// A delegate to be invoked when this is pressed. - protected ThreeStateMenuItem(string text, Func changeStateFunc, MenuItemType type, Action action) + /// A function that mutates a state to another state after this is pressed. + /// The type of action which this performs. + /// A delegate to be invoked when this is pressed. + protected TernaryStateMenuItem(string text, Func changeStateFunc, MenuItemType type, Action action) : base(text, changeStateFunc, type, action) { } From a43b0ee01bc9b92db30ff4084c11c09b3547dd26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Nov 2019 10:45:46 +0900 Subject: [PATCH 2246/2815] Apply naming and styling changes --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 ++ .../Edit/Compose/Components/SelectionHandler.cs | 16 ++++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index f3399af2de..c6f5a075e0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -203,12 +203,14 @@ namespace osu.Game.Rulesets.Osu.Objects var sampleList = new List(); if (firstSample != null) + { sampleList.Add(new HitSampleInfo { Bank = firstSample.Bank, Volume = firstSample.Volume, Name = @"slidertick", }); + } foreach (var tick in NestedHitObjects.OfType()) tick.Samples = sampleList; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 7b61640c89..942643d116 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -241,36 +241,36 @@ namespace osu.Game.Screens.Edit.Compose.Components private MenuItem createHitSampleMenuItem(string name, string sampleName) { - return new ThreeStateMenuItem(name, MenuItemType.Standard, setHitSampleState) + return new TernaryStateMenuItem(name, MenuItemType.Standard, setHitSampleState) { State = { Value = getHitSampleState() } }; - void setHitSampleState(ThreeStates state) + void setHitSampleState(TernaryState state) { switch (state) { - case ThreeStates.Disabled: + case TernaryState.False: RemoveHitSample(sampleName); break; - case ThreeStates.Enabled: + case TernaryState.True: AddHitSample(sampleName); break; } } - ThreeStates getHitSampleState() + TernaryState getHitSampleState() { int countExisting = SelectedHitObjects.Count(h => h.Samples.Any(s => s.Name == sampleName)); if (countExisting == 0) - return ThreeStates.Disabled; + return TernaryState.False; if (countExisting < SelectedHitObjects.Count()) - return ThreeStates.Indeterminate; + return TernaryState.Indeterminate; - return ThreeStates.Enabled; + return TernaryState.True; } } From 2f8768a4b10af5290f94c80cba54184b2957b5d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Nov 2019 11:04:49 +0900 Subject: [PATCH 2247/2815] Move LabelledDropdown local to usage --- osu.Game.Tournament/Screens/SetupScreen.cs | 21 ++++++++++++++ .../UserInterfaceV2/LabelledDropdown.cs | 29 ------------------- 2 files changed, 21 insertions(+), 29 deletions(-) delete mode 100644 osu.Game/Graphics/UserInterfaceV2/LabelledDropdown.cs diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index b511d572da..8e1481d87c 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -2,6 +2,7 @@ // 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; @@ -100,6 +101,26 @@ namespace osu.Game.Tournament.Screens }; } + public class LabelledDropdown : LabelledComponent, T> + { + public LabelledDropdown() + : base(true) + { + } + + public IEnumerable Items + { + get => Component.Items; + set => Component.Items = value; + } + + protected override OsuDropdown CreateComponent() => new OsuDropdown + { + RelativeSizeAxes = Axes.X, + Width = 0.5f, + }; + } + private class ActionableInfo : LabelledDrawable { private OsuButton button; diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledDropdown.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledDropdown.cs deleted file mode 100644 index 7e50fa7bc6..0000000000 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledDropdown.cs +++ /dev/null @@ -1,29 +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 System.Collections.Generic; -using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Graphics.UserInterfaceV2 -{ - public class LabelledDropdown : LabelledComponent, T> - { - public LabelledDropdown() - : base(true) - { - } - - public IEnumerable Items - { - get => Component.Items; - set => Component.Items = value; - } - - protected override OsuDropdown CreateComponent() => new OsuDropdown - { - RelativeSizeAxes = Axes.X, - Width = 0.5f, - }; - } -} From 9f1d490ac9876457e7299ed758da3858f893b105 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 12 Nov 2019 12:18:24 +0900 Subject: [PATCH 2248/2815] Only handle selection input on blueprints --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 942643d116..258304d775 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -90,6 +90,8 @@ namespace osu.Game.Screens.Edit.Compose.Components public bool OnReleased(PlatformAction action) => action.ActionMethod == PlatformActionMethod.Delete; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => selectedBlueprints.Any(b => b.IsHovered); + #endregion #region Selection Handling From 3f8928ca253660e6ae7a686e5c6f0d3faad60075 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 12 Nov 2019 13:41:54 +0900 Subject: [PATCH 2249/2815] Suppress warnings --- osu.Game/Rulesets/Configuration/IRulesetConfigManager.cs | 3 ++- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 ++ osu.Game/Rulesets/RulesetConfigCache.cs | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Configuration/IRulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/IRulesetConfigManager.cs index 179cd5e2dc..5a3ad5e786 100644 --- a/osu.Game/Rulesets/Configuration/IRulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/IRulesetConfigManager.cs @@ -1,11 +1,12 @@ // 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; namespace osu.Game.Rulesets.Configuration { - public interface IRulesetConfigManager : ITrackableConfigManager + public interface IRulesetConfigManager : ITrackableConfigManager, IDisposable { } } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 818571bb23..35228e9ad1 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -149,8 +149,10 @@ namespace osu.Game.Rulesets.Objects.Drawables return; if (HitObject.SampleControlPoint == null) + { throw new ArgumentNullException(nameof(HitObject.SampleControlPoint), $"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}." + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}."); + } samples = samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).ToArray(); foreach (var s in samples) diff --git a/osu.Game/Rulesets/RulesetConfigCache.cs b/osu.Game/Rulesets/RulesetConfigCache.cs index cdcd2666cf..d42428638c 100644 --- a/osu.Game/Rulesets/RulesetConfigCache.cs +++ b/osu.Game/Rulesets/RulesetConfigCache.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets // ensures any potential database operations are finalised before game destruction. foreach (var c in configCache.Values) - (c as IDisposable)?.Dispose(); + c?.Dispose(); } } } From b4525c1f6e95d61a56ca33b5a09cbeba38d60ab5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Nov 2019 14:42:30 +0900 Subject: [PATCH 2250/2815] Fix right clicking to select not showing context menu --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 258304d775..3a7c85d36c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -90,8 +90,6 @@ namespace osu.Game.Screens.Edit.Compose.Components public bool OnReleased(PlatformAction action) => action.ActionMethod == PlatformActionMethod.Delete; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => selectedBlueprints.Any(b => b.IsHovered); - #endregion #region Selection Handling @@ -223,7 +221,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { get { - if (!SelectedBlueprints.Any()) + if (!selectedBlueprints.Any(b => b.IsHovered)) return Array.Empty(); return new MenuItem[] From a69a4643c950526a0683120799c06f7051f478ba Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 12 Nov 2019 08:45:21 +0300 Subject: [PATCH 2251/2815] Simplify LINQ expressions --- .../Overlays/BeatmapSet/LeaderboardModSelector.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index 75c24cb710..a7128ca157 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -53,12 +53,7 @@ namespace osu.Game.Overlays.BeatmapSet return; modsContainer.Add(new ModButton(new NoMod())); - - ruleset.NewValue.CreateInstance().GetAllMods().ForEach(mod => - { - if (mod.Ranked) - modsContainer.Add(new ModButton(mod)); - }); + modsContainer.AddRange(ruleset.NewValue.CreateInstance().GetAllMods().Where(m => m.Ranked).Select(m => new ModButton(m))); modsContainer.ForEach(button => button.OnSelectionChanged += selectionChanged); } @@ -77,11 +72,7 @@ namespace osu.Game.Overlays.BeatmapSet protected override bool OnHover(HoverEvent e) { if (!SelectedMods.Any()) - modsContainer.ForEach(button => - { - if (!button.IsHovered) - button.Highlighted.Value = false; - }); + modsContainer.Children.Where(button => !button.IsHovered).ForEach(button => button.Highlighted.Value = false); return base.OnHover(e); } From 461f76926f6e23015eaec30ae5344f3ea9085861 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 12 Nov 2019 13:32:31 +0900 Subject: [PATCH 2252/2815] Add right-click menu to support control point addition --- .../Sliders/SliderSelectionBlueprint.cs | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 25362820a3..a77350714f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -1,8 +1,13 @@ // 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.Cursor; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -10,10 +15,11 @@ using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osuTK; +using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { - public class SliderSelectionBlueprint : OsuSelectionBlueprint + public class SliderSelectionBlueprint : OsuSelectionBlueprint, IHasContextMenu { protected readonly SliderBodyPiece BodyPiece; protected readonly SliderCircleSelectionBlueprint HeadBlueprint; @@ -44,6 +50,48 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders BodyPiece.UpdateFrom(HitObject); } + private Vector2 rightClickPosition; + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (e.Button == MouseButton.Right) + rightClickPosition = e.MouseDownPosition; + + return false; + } + + private void addControlPoint() + { + Vector2 position = rightClickPosition - HitObject.Position; + + var controlPoints = new Vector2[HitObject.Path.ControlPoints.Length + 1]; + HitObject.Path.ControlPoints.CopyTo(controlPoints); + + // Find the index at which the point will be inserted, by increasing x-coordinates + int insertionIndex = Array.FindIndex(controlPoints, 0, controlPoints.Length - 1, c => c.X >= position.X); + + // If no index was found, it should be inserted at the end + if (insertionIndex == -1) + insertionIndex = controlPoints.Length - 1; + + // Move the control points from the insertion index onwards to make room for the insertion + Array.Copy(controlPoints, insertionIndex, controlPoints, insertionIndex + 1, controlPoints.Length - insertionIndex - 1); + + if (insertionIndex == 0) + { + // Special case for a new first control point being added - the entire slider moves + HitObject.Position += position; + + // The first control point is always at (0, 0), but all other control points need to be re-referenced + for (int i = 1; i < controlPoints.Length; i++) + controlPoints[i] -= position; + } + else + controlPoints[insertionIndex] = position; + + onNewControlPoints(controlPoints); + } + private void onNewControlPoints(Vector2[] controlPoints) { var unsnappedPath = new SliderPath(controlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, controlPoints); @@ -54,6 +102,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders UpdateHitObject(); } + public MenuItem[] ContextMenuItems => new MenuItem[] + { + new OsuMenuItem("Add control point", MenuItemType.Standard, addControlPoint), + }; + public override Vector2 SelectionPoint => HeadBlueprint.SelectionPoint; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos); From 10fd5ef5a7137a66f037e0c49fdf19173986e0e8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 12 Nov 2019 13:38:42 +0900 Subject: [PATCH 2253/2815] Merge context menus --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 5 ++--- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 6 ++++++ .../Screens/Edit/Compose/Components/SelectionHandler.cs | 7 ++++++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index a77350714f..e87145fdc3 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -4,7 +4,6 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; @@ -19,7 +18,7 @@ using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { - public class SliderSelectionBlueprint : OsuSelectionBlueprint, IHasContextMenu + public class SliderSelectionBlueprint : OsuSelectionBlueprint { protected readonly SliderBodyPiece BodyPiece; protected readonly SliderCircleSelectionBlueprint HeadBlueprint; @@ -102,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders UpdateHitObject(); } - public MenuItem[] ContextMenuItems => new MenuItem[] + public override MenuItem[] ContextMenuItems => new MenuItem[] { new OsuMenuItem("Add control point", MenuItemType.Standard, addControlPoint), }; diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 2923411ce1..bf99f83e0b 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -104,6 +105,11 @@ namespace osu.Game.Rulesets.Edit public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos); + /// + /// The s to be displayed in the context menu for this . + /// + public virtual MenuItem[] ContextMenuItems => Array.Empty(); + /// /// The screen-space point that causes this to be selected. /// diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 3a7c85d36c..d5bbeeb907 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -224,7 +224,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (!selectedBlueprints.Any(b => b.IsHovered)) return Array.Empty(); - return new MenuItem[] + var items = new List { new OsuMenuItem("Sound") { @@ -236,6 +236,11 @@ namespace osu.Game.Screens.Edit.Compose.Components } } }; + + if (selectedBlueprints.Count == 1) + items.AddRange(selectedBlueprints[0].ContextMenuItems); + + return items.ToArray(); } } From 13b11996e0f625cec223e18ebecf8a83a2898bd0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 12 Nov 2019 14:37:07 +0900 Subject: [PATCH 2254/2815] Improve closest segment algorithm --- .../Sliders/SliderSelectionBlueprint.cs | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index e87145fdc3..9da684025f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -66,27 +66,33 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders var controlPoints = new Vector2[HitObject.Path.ControlPoints.Length + 1]; HitObject.Path.ControlPoints.CopyTo(controlPoints); - // Find the index at which the point will be inserted, by increasing x-coordinates - int insertionIndex = Array.FindIndex(controlPoints, 0, controlPoints.Length - 1, c => c.X >= position.X); + int insertionIndex = 0; + float minDistance = float.MaxValue; - // If no index was found, it should be inserted at the end - if (insertionIndex == -1) - insertionIndex = controlPoints.Length - 1; + for (int i = 0; i < controlPoints.Length - 2; i++) + { + Vector2 p1 = controlPoints[i]; + Vector2 p2 = controlPoints[i + 1]; + + if (p1 == p2) + continue; + + Vector2 dir = p2 - p1; + float projLength = MathHelper.Clamp(Vector2.Dot(position - p1, dir) / dir.LengthSquared, 0, 1); + Vector2 proj = p1 + projLength * dir; + + float dist = Vector2.Distance(position, proj); + + if (dist < minDistance) + { + insertionIndex = i + 1; + minDistance = dist; + } + } // Move the control points from the insertion index onwards to make room for the insertion Array.Copy(controlPoints, insertionIndex, controlPoints, insertionIndex + 1, controlPoints.Length - insertionIndex - 1); - - if (insertionIndex == 0) - { - // Special case for a new first control point being added - the entire slider moves - HitObject.Position += position; - - // The first control point is always at (0, 0), but all other control points need to be re-referenced - for (int i = 1; i < controlPoints.Length; i++) - controlPoints[i] -= position; - } - else - controlPoints[insertionIndex] = position; + controlPoints[insertionIndex] = position; onNewControlPoints(controlPoints); } From 316dcae6145594ec2cc83ab2aed1b3c7fb1ba6c7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 12 Nov 2019 14:41:10 +0900 Subject: [PATCH 2255/2815] Use squared distance --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 9da684025f..0f058dc088 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -78,10 +78,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders continue; Vector2 dir = p2 - p1; + float projLength = MathHelper.Clamp(Vector2.Dot(position - p1, dir) / dir.LengthSquared, 0, 1); Vector2 proj = p1 + projLength * dir; - float dist = Vector2.Distance(position, proj); + float dist = Vector2.DistanceSquared(position, proj); if (dist < minDistance) { From 407ca41ba454f1bd644ecb3fc1f5eabf9f1fe564 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 12 Nov 2019 14:44:11 +0900 Subject: [PATCH 2256/2815] Simplify using existing tools --- .../Blueprints/Sliders/SliderSelectionBlueprint.cs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 0f058dc088..34dc7ddc24 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; @@ -71,18 +72,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders for (int i = 0; i < controlPoints.Length - 2; i++) { - Vector2 p1 = controlPoints[i]; - Vector2 p2 = controlPoints[i + 1]; - - if (p1 == p2) - continue; - - Vector2 dir = p2 - p1; - - float projLength = MathHelper.Clamp(Vector2.Dot(position - p1, dir) / dir.LengthSquared, 0, 1); - Vector2 proj = p1 + projLength * dir; - - float dist = Vector2.DistanceSquared(position, proj); + float dist = new Line(controlPoints[i], controlPoints[i + 1]).DistanceToPoint(position); if (dist < minDistance) { From 93d8cd38ca4652f5082fdfbd3d87120e144bbc37 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 12 Nov 2019 15:00:57 +0900 Subject: [PATCH 2257/2815] Implement addition via ctrl+click --- .../Sliders/SliderSelectionBlueprint.cs | 45 ++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 34dc7ddc24..226afd68b1 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; @@ -54,15 +55,47 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected override bool OnMouseDown(MouseDownEvent e) { - if (e.Button == MouseButton.Right) - rightClickPosition = e.MouseDownPosition; + switch (e.Button) + { + case MouseButton.Right: + rightClickPosition = e.MouseDownPosition; + return false; // Allow right click to be handled by context menu + + case MouseButton.Left when e.ControlPressed: + placementControlPointIndex = addControlPoint(e.MousePosition); + return true; // Stop input from being handled and modifying the selection + } return false; } - private void addControlPoint() + private int? placementControlPointIndex; + + protected override bool OnDragStart(DragStartEvent e) => placementControlPointIndex != null; + + protected override bool OnDrag(DragEvent e) { - Vector2 position = rightClickPosition - HitObject.Position; + Debug.Assert(placementControlPointIndex != null); + + Vector2 position = e.MousePosition - HitObject.Position; + + var controlPoints = HitObject.Path.ControlPoints.ToArray(); + controlPoints[placementControlPointIndex.Value] = position; + + onNewControlPoints(controlPoints); + + return true; + } + + protected override bool OnDragEnd(DragEndEvent e) + { + placementControlPointIndex = null; + return true; + } + + private int addControlPoint(Vector2 position) + { + position -= HitObject.Position; var controlPoints = new Vector2[HitObject.Path.ControlPoints.Length + 1]; HitObject.Path.ControlPoints.CopyTo(controlPoints); @@ -86,6 +119,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders controlPoints[insertionIndex] = position; onNewControlPoints(controlPoints); + + return insertionIndex; } private void onNewControlPoints(Vector2[] controlPoints) @@ -100,7 +135,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders public override MenuItem[] ContextMenuItems => new MenuItem[] { - new OsuMenuItem("Add control point", MenuItemType.Standard, addControlPoint), + new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)), }; public override Vector2 SelectionPoint => HeadBlueprint.SelectionPoint; From 25eb9642904dfe8e640e85c2430c290de04e6462 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Nov 2019 15:03:58 +0900 Subject: [PATCH 2258/2815] Simplify overlay adding logic --- osu.Game/OsuGame.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index fa97044c95..e2ece8422c 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -593,14 +593,14 @@ namespace osu.Game loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add, true); loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add, true); - var login = loadComponentSingleFile(new LoginOverlay + loadComponentSingleFile(new LoginOverlay { GetToolbarHeight = () => ToolbarOffset, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, }, rightFloatingOverlayContent.Add, true); - var nowPlaying = loadComponentSingleFile(new NowPlayingOverlay + loadComponentSingleFile(new NowPlayingOverlay { GetToolbarHeight = () => ToolbarOffset, Anchor = Anchor.TopRight, @@ -615,10 +615,10 @@ namespace osu.Game Add(externalLinkOpener = new ExternalLinkOpener()); + // side overlays which cancel each other. var singleDisplaySideOverlays = new OverlayContainer[] { Settings, notifications }; - overlays.AddRange(singleDisplaySideOverlays); - foreach (var overlay in singleDisplaySideOverlays) + foreach (var overlay in new OverlayContainer[] { Settings, notifications }) { overlay.State.ValueChanged += state => { @@ -630,7 +630,6 @@ namespace osu.Game // eventually informational overlays should be displayed in a stack, but for now let's only allow one to stay open at a time. var informationalOverlays = new OverlayContainer[] { beatmapSetOverlay, userProfile }; - overlays.AddRange(informationalOverlays); foreach (var overlay in informationalOverlays) { @@ -644,7 +643,6 @@ namespace osu.Game // ensure only one of these overlays are open at once. var singleDisplayOverlays = new OverlayContainer[] { chatOverlay, social, direct, changelogOverlay }; - overlays.AddRange(singleDisplayOverlays); foreach (var overlay in singleDisplayOverlays) { @@ -659,8 +657,6 @@ namespace osu.Game }; } - overlays.AddRange(new OverlayContainer[] { login, nowPlaying }); - OverlayActivationMode.ValueChanged += mode => { if (mode.NewValue != OverlayActivation.All) CloseAllOverlays(); @@ -747,6 +743,9 @@ namespace osu.Game if (cache) dependencies.Cache(d); + if (d is OverlayContainer overlay) + overlays.Add(overlay); + // schedule is here to ensure that all component loads are done after LoadComplete is run (and thus all dependencies are cached). // with some better organisation of LoadComplete to do construction and dependency caching in one step, followed by calls to loadComponentSingleFile, // we could avoid the need for scheduling altogether. From 35351d7f7c34cb8f0395aa0992f245b7dfc52ddb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Nov 2019 15:04:51 +0900 Subject: [PATCH 2259/2815] Use variable instead of duplicated list --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e2ece8422c..f6b8d5cfbb 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -618,7 +618,7 @@ namespace osu.Game // side overlays which cancel each other. var singleDisplaySideOverlays = new OverlayContainer[] { Settings, notifications }; - foreach (var overlay in new OverlayContainer[] { Settings, notifications }) + foreach (var overlay in singleDisplaySideOverlays) { overlay.State.ValueChanged += state => { From a0884fe9d42a0815f642eb7203246b6bf608d838 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 12 Nov 2019 15:07:54 +0900 Subject: [PATCH 2260/2815] Fix being able to add while not selected --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 226afd68b1..820d6c92d7 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders rightClickPosition = e.MouseDownPosition; return false; // Allow right click to be handled by context menu - case MouseButton.Left when e.ControlPressed: + case MouseButton.Left when e.ControlPressed && IsSelected: placementControlPointIndex = addControlPoint(e.MousePosition); return true; // Stop input from being handled and modifying the selection } From 47be20fa3773c1fed6ba2e521d9a71b4c93a3582 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Nov 2019 15:13:47 +0900 Subject: [PATCH 2261/2815] Private set on track for safety --- osu.Game/Audio/PreviewTrack.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Audio/PreviewTrack.cs b/osu.Game/Audio/PreviewTrack.cs index b39d2bc8f8..5df656e1e0 100644 --- a/osu.Game/Audio/PreviewTrack.cs +++ b/osu.Game/Audio/PreviewTrack.cs @@ -24,7 +24,7 @@ namespace osu.Game.Audio /// public event Action Started; - protected Track Track; + protected Track Track { get; private set; } private bool hasStarted; From 46d02d9077e75619b535975778118175fa704601 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Nov 2019 17:37:01 +0900 Subject: [PATCH 2262/2815] Update framework --- 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 6c40dc4812..6fab2e7868 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -53,6 +53,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index baa1c14071..af60da3e70 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -21,7 +21,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 0184e45c15..8124357312 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -73,6 +73,6 @@ - + From 58df6930b279791e6cad547718d4182bd91aa22c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Nov 2019 19:34:20 +0900 Subject: [PATCH 2263/2815] Get error message from server --- osu.Game/Online/API/APIRequest.cs | 23 +++++++++++++++++++ .../BeatmapSet/Buttons/FavouriteButton.cs | 13 ++++------- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 4f613d5c3c..ea0d50511f 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using Newtonsoft.Json; using osu.Framework.IO.Network; using osu.Framework.Logging; @@ -112,6 +113,22 @@ namespace osu.Game.Online.API cancelled = true; WebRequest?.Abort(); + string responseString = WebRequest?.ResponseString; + + if (!string.IsNullOrEmpty(responseString)) + { + try + { + // attempt to decode a displayable error string. + var error = JsonConvert.DeserializeObject(responseString); + if (error != null) + e = new Exception(error.ErrorMessage, e); + } + catch + { + } + } + Logger.Log($@"Failing request {this} ({e})", LoggingTarget.Network); pendingFailure = () => Failure?.Invoke(e); checkAndScheduleFailure(); @@ -129,6 +146,12 @@ namespace osu.Game.Online.API pendingFailure = null; return true; } + + private class DisplayableError + { + [JsonProperty("error")] + public string ErrorMessage; + } } public delegate void APIFailureHandler(Exception e); diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index e2bc1ee008..f059e06214 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs @@ -71,15 +71,12 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons request.Success += () => favourited.Value = !favourited.Value; request.Failure += exception => { - if (exception.Message == "UnprocessableEntity") + notifications.Post(new SimpleNotification { - notifications.Post(new SimpleNotification - { - Text = @"You have too many favourited beatmaps! Please unfavourite some before trying again.", - Icon = FontAwesome.Solid.Times, - }); - loading.Hide(); - } + Text = exception.Message, + Icon = FontAwesome.Solid.Times, + }); + loading.Hide(); }; api.Queue(request); }; From bbeab6fa7650c07441dddb5bc648f58033634378 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 12 Nov 2019 17:45:42 +0800 Subject: [PATCH 2264/2815] Use auto property. --- .editorconfig | 2 +- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 8 +------- .../Objects/Drawables/Pieces/TaikoPiece.cs | 16 ++-------------- .../Gameplay/TestSceneSkinnableDrawable.cs | 6 ++---- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 6 ++---- osu.Game/OsuGameBase.cs | 12 +++++------- osu.Game/Overlays/BeatmapSet/BasicStats.cs | 5 ++--- osu.Game/Overlays/Chat/ChatLine.cs | 9 ++++----- .../KeyBinding/VariantBindingsSubsection.cs | 5 ++--- osu.Game/Overlays/VolumeOverlay.cs | 7 +++---- osu.Game/Skinning/SkinnableSprite.cs | 6 ++---- .../Storyboards/Drawables/DrawableStoryboard.cs | 5 ++--- osu.sln.DotSettings | 4 +++- 13 files changed, 31 insertions(+), 60 deletions(-) diff --git a/.editorconfig b/.editorconfig index 2c000d3881..34217e6206 100644 --- a/.editorconfig +++ b/.editorconfig @@ -137,7 +137,7 @@ csharp_style_expression_bodied_properties = true:silent dotnet_style_object_initializer = true:warning dotnet_style_collection_initializer = true:warning dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning -dotnet_style_prefer_auto_properties = true:silent +dotnet_style_prefer_auto_properties = true:warning dotnet_style_prefer_conditional_expression_over_assignment = true:silent dotnet_style_prefer_conditional_expression_over_return = true:silent dotnet_style_prefer_compound_assignment = true:silent diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 80a3af0aa0..33780427b6 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -116,13 +116,7 @@ namespace osu.Game.Rulesets.Catch.Objects public double Duration => EndTime - StartTime; - private SliderPath path; - - public SliderPath Path - { - get => path; - set => path = value; - } + public SliderPath Path { get; set; } public double Distance => Path.Distance; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs index 773e3ae907..8067054f8f 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs @@ -10,27 +10,15 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces { public class TaikoPiece : BeatSyncedContainer, IHasAccentColour { - private Color4 accentColour; - /// /// The colour of the inner circle and outer glows. /// - public virtual Color4 AccentColour - { - get => accentColour; - set => accentColour = value; - } - - private bool kiaiMode; + public virtual Color4 AccentColour { get; set; } /// /// Whether Kiai mode effects are enabled for this circle piece. /// - public virtual bool KiaiMode - { - get => kiaiMode; - set => kiaiMode = value; - } + public virtual bool KiaiMode { get; set; } public TaikoPiece() { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index 8beb107269..ec94053679 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -335,16 +335,14 @@ namespace osu.Game.Tests.Visual.Gameplay private class TestSkinComponent : ISkinComponent { - private readonly string name; - public TestSkinComponent(string name) { - this.name = name; + LookupName = name; } public string ComponentGroup => string.Empty; - public string LookupName => name; + public string LookupName { get; } } } } diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 623db07938..6ac5219282 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -288,17 +288,15 @@ namespace osu.Game.Online.Leaderboards private class ScoreComponentLabel : Container, IHasTooltip { private const float icon_size = 20; - - private readonly string name; private readonly FillFlowContainer content; public override bool Contains(Vector2 screenSpacePos) => content.Contains(screenSpacePos); - public string TooltipText => name; + public string TooltipText { get; } public ScoreComponentLabel(LeaderboardScoreStatistic statistic) { - name = statistic.Name; + TooltipText = statistic.Name; AutoSizeAxes = Axes.Both; Child = content = new FillFlowContainer diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 4a432bf74e..0845a33639 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -74,8 +74,6 @@ namespace osu.Game protected Storage Storage { get; set; } - private Bindable beatmap; // cached via load() method - [Cached] [Cached(typeof(IBindable))] protected readonly Bindable Ruleset = new Bindable(); @@ -85,7 +83,7 @@ namespace osu.Game [Cached(Type = typeof(IBindable>))] protected readonly Bindable> Mods = new Bindable>(Array.Empty()); - protected Bindable Beatmap => beatmap; + protected Bindable Beatmap { get; private set; } // cached via load() method private Bindable fpsDisplayVisible; @@ -201,16 +199,16 @@ namespace osu.Game // this adds a global reduction of track volume for the time being. Audio.Tracks.AddAdjustment(AdjustableProperty.Volume, new BindableDouble(0.8)); - beatmap = new NonNullableBindable(defaultBeatmap); - beatmap.BindValueChanged(b => ScheduleAfterChildren(() => + Beatmap = new NonNullableBindable(defaultBeatmap); + Beatmap.BindValueChanged(b => ScheduleAfterChildren(() => { // compare to last beatmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo) if (b.OldValue?.TrackLoaded == true && b.OldValue?.Track != b.NewValue?.Track) b.OldValue.RecycleTrack(); })); - dependencies.CacheAs>(beatmap); - dependencies.CacheAs(beatmap); + dependencies.CacheAs>(Beatmap); + dependencies.CacheAs(Beatmap); FileStore.Cleanup(); diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs index 5b10c4e0bb..7092b860a0 100644 --- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs +++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs @@ -91,10 +91,9 @@ namespace osu.Game.Overlays.BeatmapSet private class Statistic : Container, IHasTooltip { - private readonly string name; private readonly OsuSpriteText value; - public string TooltipText => name; + public string TooltipText { get; } public string Value { @@ -104,7 +103,7 @@ namespace osu.Game.Overlays.BeatmapSet public Statistic(IconUsage icon, string name) { - this.name = name; + TooltipText = name; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index db378bde73..8abde8a24f 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -58,9 +58,8 @@ namespace osu.Game.Overlays.Chat private Message message; private OsuSpriteText username; - private LinkFlowContainer contentFlow; - public LinkFlowContainer ContentFlow => contentFlow; + public LinkFlowContainer ContentFlow { get; private set; } public Message Message { @@ -164,7 +163,7 @@ namespace osu.Game.Overlays.Chat Padding = new MarginPadding { Left = MessagePadding + HorizontalPadding }, Children = new Drawable[] { - contentFlow = new LinkFlowContainer(t => + ContentFlow = new LinkFlowContainer(t => { t.Shadow = false; @@ -206,8 +205,8 @@ namespace osu.Game.Overlays.Chat // remove non-existent channels from the link list message.Links.RemoveAll(link => link.Action == LinkAction.OpenChannel && chatManager?.AvailableChannels.Any(c => c.Name == link.Argument) != true); - contentFlow.Clear(); - contentFlow.AddLinks(message.DisplayContent, message.Links); + ContentFlow.Clear(); + ContentFlow.AddLinks(message.DisplayContent, message.Links); } private class MessageSender : OsuClickableContainer, IHasContextMenu diff --git a/osu.Game/Overlays/KeyBinding/VariantBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/VariantBindingsSubsection.cs index 07af657686..861d59c8f4 100644 --- a/osu.Game/Overlays/KeyBinding/VariantBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/VariantBindingsSubsection.cs @@ -7,8 +7,7 @@ namespace osu.Game.Overlays.KeyBinding { public class VariantBindingsSubsection : KeyBindingsSubsection { - protected override string Header => variantName; - private readonly string variantName; + protected override string Header { get; } public VariantBindingsSubsection(RulesetInfo ruleset, int variant) : base(variant) @@ -17,7 +16,7 @@ namespace osu.Game.Overlays.KeyBinding var rulesetInstance = ruleset.CreateInstance(); - variantName = rulesetInstance.GetVariantName(variant); + Header = rulesetInstance.GetVariantName(variant); Defaults = rulesetInstance.GetDefaultKeyBindings(variant); } } diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index ca7665eba5..b484921cce 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -30,8 +30,7 @@ namespace osu.Game.Overlays private readonly BindableDouble muteAdjustment = new BindableDouble(); - private readonly Bindable isMuted = new Bindable(); - public Bindable IsMuted => isMuted; + public Bindable IsMuted { get; } = new Bindable(); [BackgroundDependencyLoader] private void load(AudioManager audio, OsuColour colours) @@ -66,7 +65,7 @@ namespace osu.Game.Overlays muteButton = new MuteButton { Margin = new MarginPadding { Top = 100 }, - Current = { BindTarget = isMuted } + Current = { BindTarget = IsMuted } } } }, @@ -76,7 +75,7 @@ namespace osu.Game.Overlays volumeMeterEffect.Bindable.BindTo(audio.VolumeSample); volumeMeterMusic.Bindable.BindTo(audio.VolumeTrack); - isMuted.BindValueChanged(muted => + IsMuted.BindValueChanged(muted => { if (muted.NewValue) audio.AddAdjustment(AdjustableProperty.Volume, muteAdjustment); diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs index 4b78493e97..e225bfc490 100644 --- a/osu.Game/Skinning/SkinnableSprite.cs +++ b/osu.Game/Skinning/SkinnableSprite.cs @@ -28,14 +28,12 @@ namespace osu.Game.Skinning private class SpriteComponent : ISkinComponent { - private readonly string textureName; - public SpriteComponent(string textureName) { - this.textureName = textureName; + LookupName = textureName; } - public string LookupName => textureName; + public string LookupName { get; } } } } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index 2b27a56844..7a84ac009a 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -16,8 +16,7 @@ namespace osu.Game.Storyboards.Drawables { public Storyboard Storyboard { get; private set; } - private readonly Container content; - protected override Container Content => content; + protected override Container Content { get; } protected override Vector2 DrawScale => new Vector2(Parent.DrawHeight / 480); @@ -49,7 +48,7 @@ namespace osu.Game.Storyboards.Drawables Anchor = Anchor.Centre; Origin = Anchor.Centre; - AddInternal(content = new Container + AddInternal(Content = new Container { Size = new Vector2(640, 480), Anchor = Anchor.Centre, diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 44c5c05bc0..63d055702b 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -63,7 +63,9 @@ WARNING WARNING WARNING - HINT + WARNING + WARNING + WARNING WARNING WARNING HINT From 4b75e0bf6a5824668ee78921bf047a1f5ead940f Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 12 Nov 2019 17:50:50 +0800 Subject: [PATCH 2265/2815] Redundant assignment. --- .editorconfig | 2 +- .../Legacy/HitObjectPatternGenerator.cs | 24 +++++++++---------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/.editorconfig b/.editorconfig index 34217e6206..05927526d5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -153,7 +153,7 @@ csharp_style_conditional_delegate_call = true:suggestion #Style - unused dotnet_code_quality_unused_parameters = non_public:silent csharp_style_unused_value_expression_statement_preference = discard_variable:silent -csharp_style_unused_value_assignment_preference = discard_variable:silent +csharp_style_unused_value_assignment_preference = discard_variable:suggestion #Style - variable declaration csharp_style_inlined_variable_declaration = true:silent diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs index ada960a78d..be3c43699d 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs @@ -168,43 +168,43 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy } if (convertType.HasFlag(PatternType.KeepSingle)) - return pattern = generateRandomNotes(1); + return generateRandomNotes(1); if (convertType.HasFlag(PatternType.Mirror)) { if (ConversionDifficulty > 6.5) - return pattern = generateRandomPatternWithMirrored(0.12, 0.38, 0.12); + return generateRandomPatternWithMirrored(0.12, 0.38, 0.12); if (ConversionDifficulty > 4) - return pattern = generateRandomPatternWithMirrored(0.12, 0.17, 0); + return generateRandomPatternWithMirrored(0.12, 0.17, 0); - return pattern = generateRandomPatternWithMirrored(0.12, 0, 0); + return generateRandomPatternWithMirrored(0.12, 0, 0); } if (ConversionDifficulty > 6.5) { if (convertType.HasFlag(PatternType.LowProbability)) - return pattern = generateRandomPattern(0.78, 0.42, 0, 0); + return generateRandomPattern(0.78, 0.42, 0, 0); - return pattern = generateRandomPattern(1, 0.62, 0, 0); + return generateRandomPattern(1, 0.62, 0, 0); } if (ConversionDifficulty > 4) { if (convertType.HasFlag(PatternType.LowProbability)) - return pattern = generateRandomPattern(0.35, 0.08, 0, 0); + return generateRandomPattern(0.35, 0.08, 0, 0); - return pattern = generateRandomPattern(0.52, 0.15, 0, 0); + return generateRandomPattern(0.52, 0.15, 0, 0); } if (ConversionDifficulty > 2) { if (convertType.HasFlag(PatternType.LowProbability)) - return pattern = generateRandomPattern(0.18, 0, 0, 0); + return generateRandomPattern(0.18, 0, 0, 0); - return pattern = generateRandomPattern(0.45, 0, 0, 0); + return generateRandomPattern(0.45, 0, 0, 0); } - return pattern = generateRandomPattern(0, 0, 0, 0); + return generateRandomPattern(0, 0, 0, 0); } finally { @@ -384,8 +384,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// The amount of notes to be generated. The note to be added to the centre column will NOT be part of this count. private int getRandomNoteCountMirrored(double centreProbability, double p2, double p3, out bool addToCentre) { - addToCentre = false; - switch (TotalColumns) { case 2: From 7d7b9e36b25ea355c22b258e4fbee768b3274844 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 12 Nov 2019 17:56:38 +0800 Subject: [PATCH 2266/2815] Use compound assignment. --- .editorconfig | 2 +- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 2 +- osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs | 2 +- osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs | 2 +- osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 2 +- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 2 +- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 2 +- osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs | 2 +- osu.Game/Graphics/UserInterface/PercentageCounter.cs | 2 +- osu.Game/Graphics/UserInterface/ScoreCounter.cs | 2 +- osu.Game/Graphics/UserInterface/SimpleComboCounter.cs | 2 +- osu.Game/Screens/Play/HUD/ComboCounter.cs | 2 +- osu.Game/Screens/Play/HUD/ComboResultCounter.cs | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.editorconfig b/.editorconfig index 05927526d5..35cbfe769f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -140,7 +140,7 @@ dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning dotnet_style_prefer_auto_properties = true:warning dotnet_style_prefer_conditional_expression_over_assignment = true:silent dotnet_style_prefer_conditional_expression_over_return = true:silent -dotnet_style_prefer_compound_assignment = true:silent +dotnet_style_prefer_compound_assignment = true:warning #Style - null/type checks dotnet_style_coalesce_expression = true:warning diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 56c8b33e02..435c5ac463 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -198,7 +198,7 @@ namespace osu.Game.Rulesets.Catch.UI var additive = createCatcherSprite(); additive.Anchor = Anchor; - additive.OriginPosition = additive.OriginPosition + new Vector2(DrawWidth / 2, 0); // also temporary to align sprite correctly. + additive.OriginPosition += new Vector2(DrawWidth / 2, 0); // also temporary to align sprite correctly. additive.Position = Position; additive.Scale = Scale; additive.Colour = HyperDashing ? Color4.Red : Color4.White; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 6c5bb304bf..c3a8cbd274 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps if (TargetColumns >= 10) { - TargetColumns = TargetColumns / 2; + TargetColumns /= 2; Dual = true; } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 9cdf045b5b..90dbe6ec8f 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.Edit // Flip the vertical coordinate space when scrolling downwards if (scrollingInfo.Direction.Value == ScrollingDirection.Down) - targetPosition = targetPosition - referenceParent.DrawHeight; + targetPosition -= referenceParent.DrawHeight; float movementDelta = targetPosition - reference.DrawableObject.Position.Y; diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 5ab07416a6..08f6049782 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Mania.UI foreach (var stage in stages) { - sum = sum + stage.Columns.Count; + sum += stage.Columns.Count; if (sum > column) return stage; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index eacac7ae6a..fa6c5c4d9c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing if (progress % 2 >= 1) progress = 1 - progress % 1; else - progress = progress % 1; + progress %= 1; // ReSharper disable once PossibleInvalidOperationException (bugged in current r# version) var diff = slider.StackedPosition + slider.Path.PositionAt(progress) - slider.LazyEndPosition.Value; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 6962736157..8565506c75 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components newControlPoints[i] = newControlPoints[i] - first; // The slider's position defines the position of the first control point, and all further control points are relative to that point - slider.Position = slider.Position + first; + slider.Position += first; // Since pieces are re-used, they will not point to the deleted control points while remaining selected foreach (var piece in Pieces) diff --git a/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs b/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs index f613ce5f46..d1f7a1e8d3 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tournament.Screens.Ladder { var newScale = MathHelper.Clamp(scale + e.ScrollDelta.Y / 15 * scale, min_scale, max_scale); - this.MoveTo(target = target - e.MousePosition * (newScale - scale), 2000, Easing.OutQuint); + this.MoveTo(target -= e.MousePosition * (newScale - scale), 2000, Easing.OutQuint); this.ScaleTo(scale = newScale, 2000, Easing.OutQuint); return true; diff --git a/osu.Game/Graphics/UserInterface/PercentageCounter.cs b/osu.Game/Graphics/UserInterface/PercentageCounter.cs index 8254bdda7c..064c663d59 100644 --- a/osu.Game/Graphics/UserInterface/PercentageCounter.cs +++ b/osu.Game/Graphics/UserInterface/PercentageCounter.cs @@ -41,7 +41,7 @@ namespace osu.Game.Graphics.UserInterface public override void Increment(double amount) { - Current.Value = Current.Value + amount; + Current.Value += amount; } } } diff --git a/osu.Game/Graphics/UserInterface/ScoreCounter.cs b/osu.Game/Graphics/UserInterface/ScoreCounter.cs index e291401670..24d8009f40 100644 --- a/osu.Game/Graphics/UserInterface/ScoreCounter.cs +++ b/osu.Game/Graphics/UserInterface/ScoreCounter.cs @@ -55,7 +55,7 @@ namespace osu.Game.Graphics.UserInterface public override void Increment(double amount) { - Current.Value = Current.Value + amount; + Current.Value += amount; } } } diff --git a/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs b/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs index 4717401c75..af03cbb63e 100644 --- a/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs +++ b/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs @@ -33,7 +33,7 @@ namespace osu.Game.Graphics.UserInterface public override void Increment(int amount) { - Current.Value = Current.Value + amount; + Current.Value += amount; } } } diff --git a/osu.Game/Screens/Play/HUD/ComboCounter.cs b/osu.Game/Screens/Play/HUD/ComboCounter.cs index 5ac3dac5f7..ea50a4a578 100644 --- a/osu.Game/Screens/Play/HUD/ComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ComboCounter.cs @@ -114,7 +114,7 @@ namespace osu.Game.Screens.Play.HUD /// public void Increment(int amount = 1) { - Current.Value = Current.Value + amount; + Current.Value += amount; } /// diff --git a/osu.Game/Screens/Play/HUD/ComboResultCounter.cs b/osu.Game/Screens/Play/HUD/ComboResultCounter.cs index 3f6b1e29e6..7ae8bc0ddf 100644 --- a/osu.Game/Screens/Play/HUD/ComboResultCounter.cs +++ b/osu.Game/Screens/Play/HUD/ComboResultCounter.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Play.HUD public override void Increment(long amount) { - Current.Value = Current.Value + amount; + Current.Value += amount; } } } From e5e8e70704936cf24e86f10c758590a63bd489c1 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 12 Nov 2019 18:16:51 +0800 Subject: [PATCH 2267/2815] Use pattern matching. --- .editorconfig | 4 +- .../Beatmaps/CatchBeatmapConverter.cs | 73 +++---- .../Beatmaps/ManiaBeatmapConverter.cs | 72 +++--- .../Legacy/DistanceObjectPatternGenerator.cs | 14 +- .../TestSceneSlider.cs | 33 ++- .../Beatmaps/OsuBeatmapConverter.cs | 81 ++++--- osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs | 19 +- .../Beatmaps/TaikoBeatmapConverter.cs | 206 +++++++++--------- .../Replays/TaikoAutoGenerator.cs | 121 +++++----- .../Components/ScrollingTeamContainer.cs | 53 ++--- .../Sections/General/LoginSettings.cs | 6 +- .../UI/Scrolling/DrawableScrollingRuleset.cs | 8 +- .../Backgrounds/BackgroundScreenCustom.cs | 6 +- 13 files changed, 348 insertions(+), 348 deletions(-) diff --git a/.editorconfig b/.editorconfig index 35cbfe769f..b70f17887e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -145,8 +145,8 @@ dotnet_style_prefer_compound_assignment = true:warning #Style - null/type checks dotnet_style_coalesce_expression = true:warning dotnet_style_null_propagation = true:warning -csharp_style_pattern_matching_over_is_with_cast_check = true:silent -csharp_style_pattern_matching_over_as_with_null_check = true:silent +csharp_style_pattern_matching_over_is_with_cast_check = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:warning csharp_style_throw_expression = true:silent csharp_style_conditional_delegate_call = true:suggestion diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 0d9a663b9f..b5497ea89f 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -8,6 +8,7 @@ using System; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects; +using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Rulesets.Catch.Beatmaps { @@ -22,48 +23,44 @@ namespace osu.Game.Rulesets.Catch.Beatmaps protected override IEnumerable ConvertHitObject(HitObject obj, IBeatmap beatmap) { - var curveData = obj as IHasCurve; var positionData = obj as IHasXPosition; var comboData = obj as IHasCombo; - var endTime = obj as IHasEndTime; - var legacyOffset = obj as IHasLegacyLastTickOffset; - if (curveData != null) + switch (obj) { - yield return new JuiceStream - { - StartTime = obj.StartTime, - Samples = obj.Samples, - Path = curveData.Path, - NodeSamples = curveData.NodeSamples, - RepeatCount = curveData.RepeatCount, - X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH, - NewCombo = comboData?.NewCombo ?? false, - ComboOffset = comboData?.ComboOffset ?? 0, - LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset ?? 0 - }; - } - else if (endTime != null) - { - yield return new BananaShower - { - StartTime = obj.StartTime, - Samples = obj.Samples, - Duration = endTime.Duration, - NewCombo = comboData?.NewCombo ?? false, - ComboOffset = comboData?.ComboOffset ?? 0, - }; - } - else - { - yield return new Fruit - { - StartTime = obj.StartTime, - Samples = obj.Samples, - NewCombo = comboData?.NewCombo ?? false, - ComboOffset = comboData?.ComboOffset ?? 0, - X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH - }; + case IHasCurve curveData: + return new JuiceStream + { + StartTime = obj.StartTime, + Samples = obj.Samples, + Path = curveData.Path, + NodeSamples = curveData.NodeSamples, + RepeatCount = curveData.RepeatCount, + X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH, + NewCombo = comboData?.NewCombo ?? false, + ComboOffset = comboData?.ComboOffset ?? 0, + LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0 + }.Yield(); + + case IHasEndTime endTime: + return new BananaShower + { + StartTime = obj.StartTime, + Samples = obj.Samples, + Duration = endTime.Duration, + NewCombo = comboData?.NewCombo ?? false, + ComboOffset = comboData?.ComboOffset ?? 0, + }.Yield(); + + default: + return new Fruit + { + StartTime = obj.StartTime, + Samples = obj.Samples, + NewCombo = comboData?.NewCombo ?? false, + ComboOffset = comboData?.ComboOffset ?? 0, + X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH + }.Yield(); } } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index c3a8cbd274..6e3d5761ac 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -156,37 +156,44 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// The hit objects generated. private IEnumerable generateConverted(HitObject original, IBeatmap originalBeatmap) { - var endTimeData = original as IHasEndTime; - var distanceData = original as IHasDistance; - var positionData = original as IHasPosition; - Patterns.PatternGenerator conversion = null; - if (distanceData != null) + switch (original) { - var generator = new DistanceObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap); - conversion = generator; - - for (double time = original.StartTime; !Precision.DefinitelyBigger(time, generator.EndTime); time += generator.SegmentDuration) + case IHasDistance _: { - recordNote(time, positionData?.Position ?? Vector2.Zero); - computeDensity(time); + var generator = new DistanceObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap); + conversion = generator; + + var positionData = original as IHasPosition; + + for (double time = original.StartTime; !Precision.DefinitelyBigger(time, generator.EndTime); time += generator.SegmentDuration) + { + recordNote(time, positionData?.Position ?? Vector2.Zero); + computeDensity(time); + } + + break; } - } - else if (endTimeData != null) - { - conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, originalBeatmap); - recordNote(endTimeData.EndTime, new Vector2(256, 192)); - computeDensity(endTimeData.EndTime); - } - else if (positionData != null) - { - computeDensity(original.StartTime); + case IHasEndTime endTimeData: + { + conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, originalBeatmap); - conversion = new HitObjectPatternGenerator(Random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair, originalBeatmap); + recordNote(endTimeData.EndTime, new Vector2(256, 192)); + computeDensity(endTimeData.EndTime); + break; + } - recordNote(original.StartTime, positionData.Position); + case IHasPosition positionData: + { + computeDensity(original.StartTime); + + conversion = new HitObjectPatternGenerator(Random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair, originalBeatmap); + + recordNote(original.StartTime, positionData.Position); + break; + } } if (conversion == null) @@ -219,14 +226,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps private Pattern generate() { - var endTimeData = HitObject as IHasEndTime; var positionData = HitObject as IHasXPosition; int column = GetColumn(positionData?.X ?? 0); var pattern = new Pattern(); - if (endTimeData != null) + if (HitObject is IHasEndTime endTimeData) { pattern.Add(new HoldNote { @@ -237,7 +243,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps Tail = { Samples = sampleInfoListAt(endTimeData.EndTime) }, }); } - else if (positionData != null) + else if (HitObject is IHasXPosition) { pattern.Add(new Note { @@ -257,15 +263,15 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// private IList sampleInfoListAt(double time) { - var curveData = HitObject as IHasCurve; + if (HitObject is IHasCurve curveData) + { + double segmentTime = (curveData.EndTime - HitObject.StartTime) / curveData.SpanCount(); - if (curveData == null) - return HitObject.Samples; + int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); + return curveData.NodeSamples[index]; + } - double segmentTime = (curveData.EndTime - HitObject.StartTime) / curveData.SpanCount(); - - int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); - return curveData.NodeSamples[index]; + return HitObject.Samples; } } } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 6297a68e08..4e98b08377 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -474,15 +474,15 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// private IList sampleInfoListAt(double time) { - var curveData = HitObject as IHasCurve; + if (HitObject is IHasCurve curveData) + { + double segmentTime = (EndTime - HitObject.StartTime) / spanCount; - if (curveData == null) - return HitObject.Samples; + int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); + return curveData.NodeSamples[index]; + } - double segmentTime = (EndTime - HitObject.StartTime) / spanCount; - - int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); - return curveData.NodeSamples[index]; + return HitObject.Samples; } /// diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index 5c656bf594..8e8fbf8976 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -379,26 +379,25 @@ namespace osu.Game.Rulesets.Osu.Tests private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) { - var osuObject = judgedObject as DrawableOsuHitObject; - if (osuObject == null) - return; - - OsuSpriteText text; - Add(text = new OsuSpriteText + if (judgedObject is DrawableOsuHitObject osuObject) { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = result.IsHit ? "Hit!" : "Miss!", - Colour = result.IsHit ? Color4.Green : Color4.Red, - Font = OsuFont.GetFont(size: 30), - Position = osuObject.HitObject.StackedEndPosition + judgementOffsetDirection * new Vector2(0, 45) - }); + OsuSpriteText text; + Add(text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = result.IsHit ? "Hit!" : "Miss!", + Colour = result.IsHit ? Color4.Green : Color4.Red, + Font = OsuFont.GetFont(size: 30), + Position = osuObject.HitObject.StackedEndPosition + judgementOffsetDirection * new Vector2(0, 45) + }); - text.Delay(150) - .Then().FadeOut(200) - .Then().Expire(); + text.Delay(150) + .Then().FadeOut(200) + .Then().Expire(); - judgementOffsetDirection *= -1; + judgementOffsetDirection *= -1; + } } } } diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs index 6a41e93c35..2296030f81 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using osu.Game.Rulesets.Objects.Types; using System; using osu.Game.Rulesets.Osu.UI; +using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Rulesets.Osu.Beatmaps { @@ -23,52 +24,48 @@ namespace osu.Game.Rulesets.Osu.Beatmaps protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap) { - var curveData = original as IHasCurve; - var endTimeData = original as IHasEndTime; var positionData = original as IHasPosition; var comboData = original as IHasCombo; - var legacyOffset = original as IHasLegacyLastTickOffset; - if (curveData != null) + switch (original) { - yield return new Slider - { - StartTime = original.StartTime, - Samples = original.Samples, - Path = curveData.Path, - NodeSamples = curveData.NodeSamples, - RepeatCount = curveData.RepeatCount, - Position = positionData?.Position ?? Vector2.Zero, - NewCombo = comboData?.NewCombo ?? false, - ComboOffset = comboData?.ComboOffset ?? 0, - LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset, - // prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance. - // this results in more (or less) ticks being generated in ().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); + slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); - slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); - slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); + var newControlPoints = new Vector2[slider.Path.ControlPoints.Length]; + for (int i = 0; i < slider.Path.ControlPoints.Length; i++) + newControlPoints[i] = new Vector2(slider.Path.ControlPoints[i].X, -slider.Path.ControlPoints[i].Y); - var newControlPoints = new Vector2[slider.Path.ControlPoints.Length]; - for (int i = 0; i < slider.Path.ControlPoints.Length; i++) - newControlPoints[i] = new Vector2(slider.Path.ControlPoints[i].X, -slider.Path.ControlPoints[i].Y); - - slider.Path = new SliderPath(slider.Path.Type, newControlPoints, slider.Path.ExpectedDistance); + slider.Path = new SliderPath(slider.Path.Type, newControlPoints, slider.Path.ExpectedDistance); + } } } } diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 180e0d8309..10cc861b7e 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -73,127 +73,133 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps protected override IEnumerable ConvertHitObject(HitObject obj, IBeatmap beatmap) { - var distanceData = obj as IHasDistance; - var repeatsData = obj as IHasRepeats; - var endTimeData = obj as IHasEndTime; - var curveData = obj as IHasCurve; - // Old osu! used hit sounding to determine various hit type information IList samples = obj.Samples; bool strong = samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH); - if (distanceData != null) + switch (obj) { - // Number of spans of the object - one for the initial length and for each repeat - int spans = repeatsData?.SpanCount() ?? 1; - - TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime); - DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime); - - double speedAdjustment = difficultyPoint.SpeedMultiplier; - double speedAdjustedBeatLength = timingPoint.BeatLength / speedAdjustment; - - // The true distance, accounting for any repeats. This ends up being the drum roll distance later - double distance = distanceData.Distance * spans * legacy_velocity_multiplier; - - // The velocity of the taiko hit object - calculated as the velocity of a drum roll - double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / speedAdjustedBeatLength; - // The duration of the taiko hit object - double taikoDuration = distance / taikoVelocity; - - // The velocity of the osu! hit object - calculated as the velocity of a slider - double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / speedAdjustedBeatLength; - // The duration of the osu! hit object - double osuDuration = distance / osuVelocity; - - // osu-stable always uses the speed-adjusted beatlength to determine the velocities, but - // only uses it for tick rate if beatmap version < 8 - if (beatmap.BeatmapInfo.BeatmapVersion >= 8) - speedAdjustedBeatLength *= speedAdjustment; - - // If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat - double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate, taikoDuration / spans); - - if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) + case IHasDistance distanceData: { - List> allSamples = curveData != null ? curveData.NodeSamples : new List>(new[] { samples }); + // Number of spans of the object - one for the initial length and for each repeat + int spans = (obj as IHasRepeats)?.SpanCount() ?? 1; - int i = 0; + TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime); + DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime); - for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing) + double speedAdjustment = difficultyPoint.SpeedMultiplier; + double speedAdjustedBeatLength = timingPoint.BeatLength / speedAdjustment; + + // The true distance, accounting for any repeats. This ends up being the drum roll distance later + double distance = distanceData.Distance * spans * legacy_velocity_multiplier; + + // The velocity of the taiko hit object - calculated as the velocity of a drum roll + double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / speedAdjustedBeatLength; + // The duration of the taiko hit object + double taikoDuration = distance / taikoVelocity; + + // The velocity of the osu! hit object - calculated as the velocity of a slider + double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / speedAdjustedBeatLength; + // The duration of the osu! hit object + double osuDuration = distance / osuVelocity; + + // osu-stable always uses the speed-adjusted beatlength to determine the velocities, but + // only uses it for tick rate if beatmap version < 8 + if (beatmap.BeatmapInfo.BeatmapVersion >= 8) + speedAdjustedBeatLength *= speedAdjustment; + + // If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat + double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate, taikoDuration / spans); + + if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) { - IList currentSamples = allSamples[i]; - bool isRim = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE); - strong = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_FINISH); + List> allSamples = obj is IHasCurve curveData ? curveData.NodeSamples : new List>(new[] { samples }); - if (isRim) - { - yield return new RimHit - { - StartTime = j, - Samples = currentSamples, - IsStrong = strong - }; - } - else - { - yield return new CentreHit - { - StartTime = j, - Samples = currentSamples, - IsStrong = strong - }; - } + int i = 0; - i = (i + 1) % allSamples.Count; + for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing) + { + IList currentSamples = allSamples[i]; + bool isRim = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE); + strong = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_FINISH); + + if (isRim) + { + yield return new RimHit + { + StartTime = j, + Samples = currentSamples, + IsStrong = strong + }; + } + else + { + yield return new CentreHit + { + StartTime = j, + Samples = currentSamples, + IsStrong = strong + }; + } + + i = (i + 1) % allSamples.Count; + } } - } - else - { - yield return new DrumRoll + else { - StartTime = obj.StartTime, - Samples = obj.Samples, - IsStrong = strong, - Duration = taikoDuration, - TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4 - }; - } - } - else if (endTimeData != null) - { - double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier; + yield return new DrumRoll + { + StartTime = obj.StartTime, + Samples = obj.Samples, + IsStrong = strong, + Duration = taikoDuration, + TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4 + }; + } - yield return new Swell - { - StartTime = obj.StartTime, - Samples = obj.Samples, - Duration = endTimeData.Duration, - RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier) - }; - } - else - { - bool isRim = samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE); - - if (isRim) - { - yield return new RimHit - { - StartTime = obj.StartTime, - Samples = obj.Samples, - IsStrong = strong - }; + break; } - else + + case IHasEndTime endTimeData: { - yield return new CentreHit + double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier; + + yield return new Swell { StartTime = obj.StartTime, Samples = obj.Samples, - IsStrong = strong + Duration = endTimeData.Duration, + RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier) }; + + break; + } + + default: + { + bool isRim = samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE); + + if (isRim) + { + yield return new RimHit + { + StartTime = obj.StartTime, + Samples = obj.Samples, + IsStrong = strong + }; + } + else + { + yield return new CentreHit + { + StartTime = obj.StartTime, + Samples = obj.Samples, + IsStrong = strong + }; + } + + break; } } } diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index 299679b2c1..e61953aeb8 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -43,76 +43,83 @@ namespace osu.Game.Rulesets.Taiko.Replays IHasEndTime endTimeData = h as IHasEndTime; double endTime = endTimeData?.EndTime ?? h.StartTime; - Swell swell = h as Swell; - DrumRoll drumRoll = h as DrumRoll; - Hit hit = h as Hit; - - if (swell != null) + switch (h) { - int d = 0; - int count = 0; - int req = swell.RequiredHits; - double hitRate = Math.Min(swell_hit_speed, swell.Duration / req); - - for (double j = h.StartTime; j < endTime; j += hitRate) + case Swell swell: { - TaikoAction action; + int d = 0; + int count = 0; + int req = swell.RequiredHits; + double hitRate = Math.Min(swell_hit_speed, swell.Duration / req); - switch (d) + for (double j = h.StartTime; j < endTime; j += hitRate) { - default: - case 0: - action = TaikoAction.LeftCentre; - break; + TaikoAction action; - case 1: - action = TaikoAction.LeftRim; - break; + switch (d) + { + default: + case 0: + action = TaikoAction.LeftCentre; + break; - case 2: - action = TaikoAction.RightCentre; - break; + case 1: + action = TaikoAction.LeftRim; + break; - case 3: - action = TaikoAction.RightRim; + case 2: + action = TaikoAction.RightCentre; + break; + + case 3: + action = TaikoAction.RightRim; + break; + } + + Frames.Add(new TaikoReplayFrame(j, action)); + d = (d + 1) % 4; + if (++count == req) break; } - Frames.Add(new TaikoReplayFrame(j, action)); - d = (d + 1) % 4; - if (++count == req) - break; - } - } - else if (drumRoll != null) - { - foreach (var tick in drumRoll.NestedHitObjects.OfType()) - { - Frames.Add(new TaikoReplayFrame(tick.StartTime, hitButton ? TaikoAction.LeftCentre : TaikoAction.RightCentre)); - hitButton = !hitButton; - } - } - else if (hit != null) - { - TaikoAction[] actions; - - if (hit is CentreHit) - { - actions = h.IsStrong - ? new[] { TaikoAction.LeftCentre, TaikoAction.RightCentre } - : new[] { hitButton ? TaikoAction.LeftCentre : TaikoAction.RightCentre }; - } - else - { - actions = h.IsStrong - ? new[] { TaikoAction.LeftRim, TaikoAction.RightRim } - : new[] { hitButton ? TaikoAction.LeftRim : TaikoAction.RightRim }; + break; } - Frames.Add(new TaikoReplayFrame(h.StartTime, actions)); + case DrumRoll drumRoll: + { + foreach (var tick in drumRoll.NestedHitObjects.OfType()) + { + Frames.Add(new TaikoReplayFrame(tick.StartTime, hitButton ? TaikoAction.LeftCentre : TaikoAction.RightCentre)); + hitButton = !hitButton; + } + + break; + } + + case Hit hit: + { + TaikoAction[] actions; + + if (hit is CentreHit) + { + actions = h.IsStrong + ? new[] { TaikoAction.LeftCentre, TaikoAction.RightCentre } + : new[] { hitButton ? TaikoAction.LeftCentre : TaikoAction.RightCentre }; + } + else + { + actions = h.IsStrong + ? new[] { TaikoAction.LeftRim, TaikoAction.RightRim } + : new[] { hitButton ? TaikoAction.LeftRim : TaikoAction.RightRim }; + } + + Frames.Add(new TaikoReplayFrame(h.StartTime, actions)); + break; + } + + default: + throw new InvalidOperationException("Unknown hit object type."); } - else - throw new InvalidOperationException("Unknown hit object type."); var nextHitObject = GetNextObject(i); // Get the next object that requires pressing the same button diff --git a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs index b147d680f0..fff73fcf70 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs @@ -125,22 +125,20 @@ namespace osu.Game.Tournament.Screens.Drawings.Components foreach (var c in Children) { - var stc = c as ScrollingTeam; - - if (stc == null) - continue; - - if (closest == null) + if (c is ScrollingTeam stc) { - closest = stc; - continue; + if (closest == null) + { + closest = stc; + continue; + } + + float o = Math.Abs(c.Position.X + c.DrawWidth / 2f - DrawWidth / 2f); + float lastOffset = Math.Abs(closest.Position.X + closest.DrawWidth / 2f - DrawWidth / 2f); + + if (o < lastOffset) + closest = stc; } - - float o = Math.Abs(c.Position.X + c.DrawWidth / 2f - DrawWidth / 2f); - float lastOffset = Math.Abs(closest.Position.X + closest.DrawWidth / 2f - DrawWidth / 2f); - - if (o < lastOffset) - closest = stc; } Trace.Assert(closest != null, "closest != null"); @@ -203,15 +201,13 @@ namespace osu.Game.Tournament.Screens.Drawings.Components foreach (var c in Children) { - ScrollingTeam st = c as ScrollingTeam; - - if (st == null) - continue; - - if (st.Team == team) + if (c is ScrollingTeam st) { - st.FadeOut(200); - st.Expire(); + if (st.Team == team) + { + st.FadeOut(200); + st.Expire(); + } } } } @@ -295,14 +291,13 @@ namespace osu.Game.Tournament.Screens.Drawings.Components { foreach (var c in Children) { - ScrollingTeam st = c as ScrollingTeam; - if (st == null) - continue; - - if (st.Selected) + if (c is ScrollingTeam st) { - st.Selected = false; - RemoveTeam(st.Team); + if (st.Selected) + { + st.Selected = false; + RemoveTeam(st.Team); + } } } } diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index a8bbccb168..d739035d46 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -297,10 +297,8 @@ namespace osu.Game.Overlays.Settings.Sections.General { set { - var h = Header as UserDropdownHeader; - if (h == null) return; - - h.StatusColour = value; + if (Header is UserDropdownHeader h) + h.StatusColour = value; } } diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index f178c01fd6..31a0e3cb4e 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -148,13 +148,9 @@ namespace osu.Game.Rulesets.UI.Scrolling // Generate the timing points, making non-timing changes use the previous timing change and vice-versa var timingChanges = allPoints.Select(c => { - var timingPoint = c as TimingControlPoint; - var difficultyPoint = c as DifficultyControlPoint; - - if (timingPoint != null) + if (c is TimingControlPoint timingPoint) lastTimingPoint = timingPoint; - - if (difficultyPoint != null) + else if (c is DifficultyControlPoint difficultyPoint) lastDifficultyPoint = difficultyPoint; return new MultiplierControlPoint(c.Time) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenCustom.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenCustom.cs index 0cb41bc562..49c7934ed9 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenCustom.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenCustom.cs @@ -17,10 +17,10 @@ namespace osu.Game.Screens.Backgrounds public override bool Equals(BackgroundScreen other) { - var backgroundScreenCustom = other as BackgroundScreenCustom; - if (backgroundScreenCustom == null) return false; + if (other is BackgroundScreenCustom backgroundScreenCustom) + return base.Equals(other) && textureName == backgroundScreenCustom.textureName; - return base.Equals(other) && textureName == backgroundScreenCustom.textureName; + return false; } } } From 0d81b96c5f5dee2e2e8d9ae475f0ec3daea977c5 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 12 Nov 2019 18:19:48 +0800 Subject: [PATCH 2268/2815] Use deconstruction declaration. --- .editorconfig | 2 +- osu.Game.Tournament/Components/SongBar.cs | 6 +++--- osu.Game/Skinning/SkinnableSound.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.editorconfig b/.editorconfig index b70f17887e..e6bef25e93 100644 --- a/.editorconfig +++ b/.editorconfig @@ -157,7 +157,7 @@ csharp_style_unused_value_assignment_preference = discard_variable:suggestion #Style - variable declaration csharp_style_inlined_variable_declaration = true:silent -csharp_style_deconstructed_variable_declaration = true:silent +csharp_style_deconstructed_variable_declaration = true:warning #Style - other C# 7.x features csharp_style_expression_bodied_local_functions = true:silent diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index 7005c068ae..38094695fc 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -230,7 +230,7 @@ namespace osu.Game.Tournament.Components for (var i = 0; i < tuples.Length; i++) { - var tuple = tuples[i]; + var (heading, content) = tuples[i]; if (i > 0) { @@ -241,9 +241,9 @@ namespace osu.Game.Tournament.Components }); } - AddText(new OsuSpriteText { Text = tuple.heading }, s => cp(s, OsuColour.Gray(0.33f))); + AddText(new OsuSpriteText { Text = heading }, s => cp(s, OsuColour.Gray(0.33f))); AddText(" ", s => cp(s, OsuColour.Gray(0.33f))); - AddText(new OsuSpriteText { Text = tuple.content }, s => cp(s, OsuColour.Gray(0.5f))); + AddText(new OsuSpriteText { Text = content }, s => cp(s, OsuColour.Gray(0.5f))); } } } diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 6d23f22515..fc6afd0b27 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -96,8 +96,8 @@ namespace osu.Game.Skinning if (adjustments != null) { - foreach (var adjustment in adjustments) - ch.AddAdjustment(adjustment.property, adjustment.bindable); + foreach (var (property, bindable) in adjustments) + ch.AddAdjustment(property, bindable); } } From 31cc0d13da17951a324455b6923d964558c288a8 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 12 Nov 2019 18:22:35 +0800 Subject: [PATCH 2269/2815] Use 'out var'. --- .editorconfig | 2 +- .../Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs | 3 +-- osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs | 4 +--- osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs | 4 +--- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 4 +--- osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs | 4 +--- osu.Game/IO/Legacy/SerializationReader.cs | 4 +--- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 3 +-- osu.Game/Storyboards/Storyboard.cs | 3 +-- 9 files changed, 9 insertions(+), 22 deletions(-) diff --git a/.editorconfig b/.editorconfig index e6bef25e93..588661b59d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -156,7 +156,7 @@ csharp_style_unused_value_expression_statement_preference = discard_variable:sil csharp_style_unused_value_assignment_preference = discard_variable:suggestion #Style - variable declaration -csharp_style_inlined_variable_declaration = true:silent +csharp_style_inlined_variable_declaration = true:warning csharp_style_deconstructed_variable_declaration = true:warning #Style - other C# 7.x features diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs index be3c43699d..3b7a24726e 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs @@ -303,8 +303,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy var pattern = new Pattern(); - bool addToCentre; - int noteCount = getRandomNoteCountMirrored(centreProbability, p2, p3, out addToCentre); + int noteCount = getRandomNoteCountMirrored(centreProbability, p2, p3, out var addToCentre); int columnLimit = (TotalColumns % 2 == 0 ? TotalColumns : TotalColumns - 1) / 2; int nextColumn = GetRandomColumn(upperBound: columnLimit); diff --git a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs index 2c515edda7..7119533743 100644 --- a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs @@ -225,9 +225,7 @@ namespace osu.Game.Tournament.Screens.Editors beatmapId.Value = Model.ID.ToString(); beatmapId.BindValueChanged(idString => { - int parsed; - - int.TryParse(idString.NewValue, out parsed); + int.TryParse(idString.NewValue, out var parsed); Model.ID = parsed; diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index 11c2732d62..494dd73edd 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -267,9 +267,7 @@ namespace osu.Game.Tournament.Screens.Editors userId.Value = user.Id.ToString(); userId.BindValueChanged(idString => { - long parsed; - - long.TryParse(idString.NewValue, out parsed); + long.TryParse(idString.NewValue, out var parsed); user.Id = parsed; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index aeb5df46f8..838b1c2f07 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -293,9 +293,7 @@ namespace osu.Game.Beatmaps.Formats { string[] split = line.Split(','); - EventType type; - - if (!Enum.TryParse(split[0], out type)) + if (!Enum.TryParse(split[0], out EventType type)) throw new InvalidDataException($@"Unknown event type: {split[0]}"); switch (type) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index e3320f62ac..f94ab3f27b 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -83,9 +83,7 @@ namespace osu.Game.Beatmaps.Formats { storyboardSprite = null; - EventType type; - - if (!Enum.TryParse(split[0], out type)) + if (!Enum.TryParse(split[0], out EventType type)) throw new InvalidDataException($@"Unknown event type: {split[0]}"); switch (type) diff --git a/osu.Game/IO/Legacy/SerializationReader.cs b/osu.Game/IO/Legacy/SerializationReader.cs index 7a84c11930..82b2c4be32 100644 --- a/osu.Game/IO/Legacy/SerializationReader.cs +++ b/osu.Game/IO/Legacy/SerializationReader.cs @@ -226,9 +226,7 @@ namespace osu.Game.IO.Legacy public override Type BindToType(string assemblyName, string typeName) { - Type typeToDeserialize; - - if (cache.TryGetValue(assemblyName + typeName, out typeToDeserialize)) + if (cache.TryGetValue(assemblyName + typeName, out var typeToDeserialize)) return typeToDeserialize; List tmpTypes = new List(); diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 6c35b261d4..5e7d0d050a 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -177,8 +177,7 @@ namespace osu.Game.Rulesets.Objects.Legacy if (i >= adds.Length) break; - int sound; - int.TryParse(adds[i], out sound); + int.TryParse(adds[i], out var sound); nodeSoundTypes[i] = (LegacySoundType)sound; } } diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 3d988c5fe3..a76eedfc78 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -27,8 +27,7 @@ namespace osu.Game.Storyboards public StoryboardLayer GetLayer(string name) { - StoryboardLayer layer; - if (!layers.TryGetValue(name, out layer)) + if (!layers.TryGetValue(name, out var layer)) layers[name] = layer = new StoryboardLayer(name, layers.Values.Min(l => l.Depth) - 1); return layer; From 8a1b70513c05be230fd593225f5d70623bc0e554 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 12 Nov 2019 18:24:19 +0800 Subject: [PATCH 2270/2815] No this. qualification. --- .editorconfig | 6 ++++++ osu.Game/Beatmaps/ControlPoints/ControlPoint.cs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index 588661b59d..abaa003115 100644 --- a/.editorconfig +++ b/.editorconfig @@ -111,6 +111,12 @@ csharp_preserve_single_line_statements = true #Roslyn language styles +#Style - this. qualification +dotnet_style_qualification_for_field = false:warning +dotnet_style_qualification_for_property = false:warning +dotnet_style_qualification_for_method = false:warning +dotnet_style_qualification_for_event = false:warning + #Style - type names dotnet_style_predefined_type_for_locals_parameters_members = true:silent dotnet_style_predefined_type_for_member_access = true:silent diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index 0861e00d8d..7351187ab9 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -14,7 +14,7 @@ namespace osu.Game.Beatmaps.ControlPoints private ControlPointGroup controlPointGroup; - public void AttachGroup(ControlPointGroup pointGroup) => this.controlPointGroup = pointGroup; + public void AttachGroup(ControlPointGroup pointGroup) => controlPointGroup = pointGroup; public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time); From ca52d09e815b776e9e021ae138049b88481063b5 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 12 Nov 2019 18:26:42 +0800 Subject: [PATCH 2271/2815] Enforce expression body for typical simple ones. --- .editorconfig | 10 +++--- .../API/Requests/Responses/CommentBundle.cs | 13 +++---- .../BeatmapSet/Scores/ScoresContainer.cs | 35 +++++++++---------- osu.Game/Users/User.cs | 2 +- 4 files changed, 27 insertions(+), 33 deletions(-) diff --git a/.editorconfig b/.editorconfig index abaa003115..a555a3d2bc 100644 --- a/.editorconfig +++ b/.editorconfig @@ -132,12 +132,13 @@ csharp_preferred_modifier_order = public,private,protected,internal,new,abstract # Skipped because roslyn cannot separate +-*/ with << >> #Style - expression bodies -csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_accessors = true:warning csharp_style_expression_bodied_constructors = false:none -csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_indexers = true:warning csharp_style_expression_bodied_methods = true:silent -csharp_style_expression_bodied_operators = true:silent -csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_operators = true:warning +csharp_style_expression_bodied_properties = true:warning +csharp_style_expression_bodied_local_functions = true:silent #Style - expression preferences dotnet_style_object_initializer = true:warning @@ -166,7 +167,6 @@ csharp_style_inlined_variable_declaration = true:warning csharp_style_deconstructed_variable_declaration = true:warning #Style - other C# 7.x features -csharp_style_expression_bodied_local_functions = true:silent dotnet_style_prefer_inferred_tuple_names = true:warning csharp_prefer_simple_default_expression = true:warning csharp_style_pattern_local_over_anonymous_function = true:silent diff --git a/osu.Game/Online/API/Requests/Responses/CommentBundle.cs b/osu.Game/Online/API/Requests/Responses/CommentBundle.cs index 7db3126ade..8db5d8d6ad 100644 --- a/osu.Game/Online/API/Requests/Responses/CommentBundle.cs +++ b/osu.Game/Online/API/Requests/Responses/CommentBundle.cs @@ -50,17 +50,14 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"user_votes")] private List userVotes { - set + set => value.ForEach(v => { - value.ForEach(v => + Comments.ForEach(c => { - Comments.ForEach(c => - { - if (v == c.Id) - c.IsVoted = true; - }); + if (v == c.Id) + c.IsVoted = true; }); - } + }); } private List users; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 4bbcd8d631..80de8f8230 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -48,31 +48,28 @@ namespace osu.Game.Overlays.BeatmapSet.Scores protected APILegacyScores Scores { - set + set => Schedule(() => { - Schedule(() => + topScoresContainer.Clear(); + + if (value?.Scores.Any() != true) { - topScoresContainer.Clear(); + scoreTable.Scores = null; + scoreTable.Hide(); + return; + } - if (value?.Scores.Any() != true) - { - scoreTable.Scores = null; - scoreTable.Hide(); - return; - } + scoreTable.Scores = value.Scores; + scoreTable.Show(); - scoreTable.Scores = value.Scores; - scoreTable.Show(); + var topScore = value.Scores.First(); + var userScore = value.UserScore; - var topScore = value.Scores.First(); - var userScore = value.UserScore; + topScoresContainer.Add(new DrawableTopScore(topScore)); - topScoresContainer.Add(new DrawableTopScore(topScore)); - - if (userScore != null && userScore.Score.OnlineScoreID != topScore.OnlineScoreID) - topScoresContainer.Add(new DrawableTopScore(userScore.Score, userScore.Position)); - }); - } + if (userScore != null && userScore.Score.OnlineScoreID != topScore.OnlineScoreID) + topScoresContainer.Add(new DrawableTopScore(userScore.Score, userScore.Position)); + }); } public ScoresContainer() diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 1cb395fd75..b15789f324 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -129,7 +129,7 @@ namespace osu.Game.Users [JsonProperty] private string[] playstyle { - set { PlayStyles = value?.Select(str => Enum.Parse(typeof(PlayStyle), str, true)).Cast().ToArray(); } + set => PlayStyles = value?.Select(str => Enum.Parse(typeof(PlayStyle), str, true)).Cast().ToArray(); } public PlayStyle[] PlayStyles; From 205eda8566baf7566d5b315112e0d33ce47cd5e0 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 12 Nov 2019 18:30:25 +0800 Subject: [PATCH 2272/2815] Use readonly field when not reflected. --- .editorconfig | 3 +-- osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.editorconfig b/.editorconfig index a555a3d2bc..92d06da4f3 100644 --- a/.editorconfig +++ b/.editorconfig @@ -158,6 +158,7 @@ csharp_style_throw_expression = true:silent csharp_style_conditional_delegate_call = true:suggestion #Style - unused +dotnet_style_readonly_field = true:silent dotnet_code_quality_unused_parameters = non_public:silent csharp_style_unused_value_expression_statement_preference = discard_variable:silent csharp_style_unused_value_assignment_preference = discard_variable:suggestion @@ -175,8 +176,6 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent #Supressing roslyn built-in analyzers # Suppress: EC112 -#Field can be readonly -dotnet_diagnostic.IDE0044.severity = silent #Private method is unused dotnet_diagnostic.IDE0051.severity = silent #Private member is unused diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 9869ddde41..7b2913b817 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -87,7 +87,7 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.IsNull(filterCriteria.BPM.Max); } - private static object[] lengthQueryExamples = + private static readonly object[] length_query_examples = { new object[] { "6ms", TimeSpan.FromMilliseconds(6), TimeSpan.FromMilliseconds(1) }, new object[] { "23s", TimeSpan.FromSeconds(23), TimeSpan.FromSeconds(1) }, @@ -97,7 +97,7 @@ namespace osu.Game.Tests.NonVisual.Filtering }; [Test] - [TestCaseSource(nameof(lengthQueryExamples))] + [TestCaseSource(nameof(length_query_examples))] public void TestApplyLengthQueries(string lengthQuery, TimeSpan expectedLength, TimeSpan scale) { string query = $"length={lengthQuery} time"; From 64fc5007fc447dd14c54f7c1985ff2c83fce87f0 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 12 Nov 2019 18:33:24 +0800 Subject: [PATCH 2273/2815] Use language primitive types. --- .editorconfig | 4 ++-- osu.Desktop/OsuGameDesktop.cs | 2 +- osu.Game.Tournament/IPC/FileBasedIPC.cs | 2 +- osu.Game/Tests/Visual/OsuTestScene.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.editorconfig b/.editorconfig index 92d06da4f3..2c68b0ca09 100644 --- a/.editorconfig +++ b/.editorconfig @@ -118,8 +118,8 @@ dotnet_style_qualification_for_method = false:warning dotnet_style_qualification_for_event = false:warning #Style - type names -dotnet_style_predefined_type_for_locals_parameters_members = true:silent -dotnet_style_predefined_type_for_member_access = true:silent +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +dotnet_style_predefined_type_for_member_access = true:warning csharp_style_var_when_type_is_apparent = true:none csharp_style_var_for_built_in_types = true:none csharp_style_var_elsewhere = true:silent diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 7725ee6451..26a730b442 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -119,7 +119,7 @@ namespace osu.Desktop try { using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu")) - stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(String.Empty).ToString().Split('"')[1].Replace("osu!.exe", ""); + stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString().Split('"')[1].Replace("osu!.exe", ""); if (checkExists(stableInstallPath)) return stableInstallPath; diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 47f2bed77a..8ec8a21280 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -180,7 +180,7 @@ namespace osu.Game.Tournament.IPC try { using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu")) - stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(String.Empty).ToString().Split('"')[1].Replace("osu!.exe", ""); + stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString().Split('"')[1].Replace("osu!.exe", ""); if (checkExists(stableInstallPath)) return stableInstallPath; diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 345fff90aa..e350b40c25 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -218,7 +218,7 @@ namespace osu.Game.Tests.Visual public IEnumerable GetAvailableResources() => throw new NotImplementedException(); - public Track GetVirtual(double length = Double.PositiveInfinity) + public Track GetVirtual(double length = double.PositiveInfinity) { var track = new TrackVirtualManual(referenceClock) { Length = length }; AddItem(track); From 2270f33c33ac80f9489545f016fa3085af126df9 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 11 Nov 2019 21:20:18 +0800 Subject: [PATCH 2274/2815] Enable C# 8. --- Directory.Build.props | 2 +- osu.Android.props | 3 ++- osu.iOS.props | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index b4baa2833e..cb0d0f05b0 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ - 7.3 + 8.0 $(MSBuildThisFileDirectory)app.manifest diff --git a/osu.Android.props b/osu.Android.props index 6fab2e7868..74f899e81d 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -1,5 +1,6 @@ - + + 8.0 bin\$(Configuration) 4 2.0 diff --git a/osu.iOS.props b/osu.iOS.props index 8124357312..d529b5f4e2 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -1,5 +1,6 @@ + 8.0 {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} Resources PackageReference From dcfa98414f1a20b05a63fd9460c1b0937978c67a Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 12 Nov 2019 18:35:08 +0800 Subject: [PATCH 2275/2815] Use ??=. --- .../Beatmaps/Patterns/Legacy/PatternGenerator.cs | 6 +++--- osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs | 2 +- .../Objects/Drawables/DrawableOsuHitObject.cs | 2 +- osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs | 2 +- osu.Game/Beatmaps/WorkingBeatmap.cs | 4 ++-- osu.Game/Rulesets/Objects/SliderPath.cs | 2 +- osu.Game/Skinning/SkinManager.cs | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index fba52dfc32..f225173326 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -148,9 +148,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy protected int FindAvailableColumn(int initialColumn, int? lowerBound = null, int? upperBound = null, Func nextColumn = null, [InstantHandle] Func validation = null, params Pattern[] patterns) { - lowerBound = lowerBound ?? RandomStart; - upperBound = upperBound ?? TotalColumns; - nextColumn = nextColumn ?? (_ => GetRandomColumn(lowerBound, upperBound)); + lowerBound ??= RandomStart; + upperBound ??= TotalColumns; + nextColumn ??= (_ => GetRandomColumn(lowerBound, upperBound)); // Check for the initial column if (isValid(initialColumn)) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index 84a7bfc53e..64f353c4d9 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Tests private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null) { - positionOffset = positionOffset ?? Vector2.Zero; + positionOffset ??= Vector2.Zero; var circle = new HitCircle { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index c46343c73c..a677cb6a72 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt; private OsuInputManager osuActionInputManager; - internal OsuInputManager OsuActionInputManager => osuActionInputManager ?? (osuActionInputManager = GetContainingInputManager() as OsuInputManager); + internal OsuInputManager OsuActionInputManager => osuActionInputManager ??= GetContainingInputManager() as OsuInputManager; protected virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength); diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index b879b92f01..4924842e81 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -84,7 +84,7 @@ namespace osu.Game.Beatmaps { try { - return (trackStore ?? (trackStore = AudioManager.GetTrackStore(store))).Get(getPathForFile(Metadata.AudioFile)); + return (trackStore ??= AudioManager.GetTrackStore(store)).Get(getPathForFile(Metadata.AudioFile)); } catch { diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 7c69a992dd..44d6d33cef 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -150,7 +150,7 @@ namespace osu.Game.Beatmaps public bool BeatmapLoaded => beatmapLoadTask?.IsCompleted ?? false; - public Task LoadBeatmapAsync() => (beatmapLoadTask ?? (beatmapLoadTask = Task.Factory.StartNew(() => + public Task LoadBeatmapAsync() => beatmapLoadTask ??= Task.Factory.StartNew(() => { // Todo: Handle cancellation during beatmap parsing var b = GetBeatmap() ?? new Beatmap(); @@ -162,7 +162,7 @@ namespace osu.Game.Beatmaps b.BeatmapInfo = BeatmapInfo; return b; - }, beatmapCancellation.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default))); + }, beatmapCancellation.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); public IBeatmap Beatmap { diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 7763b0eaaf..61c27d0fba 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -129,7 +129,7 @@ namespace osu.Game.Rulesets.Objects isInitialised = true; - controlPoints = controlPoints ?? Array.Empty(); + controlPoints ??= Array.Empty(); calculatedPath = new List(); cumulativeLength = new List(); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index aa3b3981c2..3d469ab6e1 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -96,7 +96,7 @@ namespace osu.Game.Skinning else { model.Name = model.Name.Replace(".osk", ""); - model.Creator = model.Creator ?? "Unknown"; + model.Creator ??= "Unknown"; } } From 144812669dd1e861c108bf8747a777c6ab853265 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 12 Nov 2019 18:37:20 +0800 Subject: [PATCH 2276/2815] Use static local functions. --- .editorconfig | 3 +++ osu.Desktop/OsuGameDesktop.cs | 2 +- .../Objects/Drawable/DrawableFruit.cs | 2 +- .../Patterns/Legacy/DistanceObjectPatternGenerator.cs | 2 +- osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs | 9 +++++---- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 2 +- .../Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 6 +++--- .../Online/TestSceneBeatmapSetOverlaySuccessRate.cs | 2 +- osu.Game.Tournament/Components/SongBar.cs | 2 +- osu.Game.Tournament/IPC/FileBasedIPC.cs | 2 +- .../Screens/Ladder/Components/ProgressionPath.cs | 2 +- osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs | 2 +- .../Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 2 +- 14 files changed, 22 insertions(+), 18 deletions(-) diff --git a/.editorconfig b/.editorconfig index 2c68b0ca09..8c2dde00aa 100644 --- a/.editorconfig +++ b/.editorconfig @@ -173,6 +173,9 @@ csharp_prefer_simple_default_expression = true:warning csharp_style_pattern_local_over_anonymous_function = true:silent dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent +#Style _ C# 8 features +csharp_prefer_static_local_function = true:warning + #Supressing roslyn built-in analyzers # Suppress: EC112 diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 26a730b442..66e7bb381c 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -112,7 +112,7 @@ namespace osu.Desktop { protected override string LocateBasePath() { - bool checkExists(string p) => Directory.Exists(Path.Combine(p, "Songs")); + static bool checkExists(string p) => Directory.Exists(Path.Combine(p, "Songs")); string stableInstallPath; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs index 1af77b75fc..a6da682aee 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs @@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable const float small_pulp = large_pulp_3 / 2; - Vector2 positionAt(float angle, float distance) => new Vector2( + static Vector2 positionAt(float angle, float distance) => new Vector2( distance * (float)Math.Sin(angle * Math.PI / 180), distance * (float)Math.Cos(angle * Math.PI / 180)); diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 4e98b08377..0cbf5cf51c 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -364,7 +364,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy break; } - bool isDoubleSample(HitSampleInfo sample) => sample.Name == HitSampleInfo.HIT_CLAP || sample.Name == HitSampleInfo.HIT_FINISH; + static bool isDoubleSample(HitSampleInfo sample) => sample.Name == HitSampleInfo.HIT_CLAP || sample.Name == HitSampleInfo.HIT_FINISH; bool canGenerateTwoNotes = !convertType.HasFlag(PatternType.LowProbability); canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample); diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs index e9fdf924c3..0c55194250 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Tests break; } - ConvertValue createConvertValue(OsuHitObject obj) => new ConvertValue + static ConvertValue createConvertValue(OsuHitObject obj) => new ConvertValue { StartTime = obj.StartTime, EndTime = (obj as IHasEndTime)?.EndTime ?? obj.StartTime, diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index 8e8fbf8976..88c9324d4c 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -148,9 +148,9 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("repeat samples updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType().All(assertSamples)); AddAssert("tail has no samples", () => ((Slider)slider.HitObject).TailCircle.Samples.Count == 0); - bool assertTickSamples(SliderTick tick) => tick.Samples.Single().Name == "slidertick"; + static bool assertTickSamples(SliderTick tick) => tick.Samples.Single().Name == "slidertick"; - bool assertSamples(HitObject hitObject) + static bool assertSamples(HitObject hitObject) { return hitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP) && hitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_WHISTLE); @@ -183,8 +183,9 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("repeat samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType().All(assertSamples)); AddAssert("tail has no samples", () => ((Slider)slider.HitObject).TailCircle.Samples.Count == 0); - bool assertTickSamples(SliderTick tick) => tick.Samples.Single().Name == "slidertick"; - bool assertSamples(HitObject hitObject) => hitObject.Samples.All(s => s.Name != HitSampleInfo.HIT_CLAP && s.Name != HitSampleInfo.HIT_WHISTLE); + static bool assertTickSamples(SliderTick tick) => tick.Samples.Single().Name == "slidertick"; + + static bool assertSamples(HitObject hitObject) => hitObject.Samples.All(s => s.Name != HitSampleInfo.HIT_CLAP && s.Name != HitSampleInfo.HIT_WHISTLE); } private Drawable testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 32c9e913c6..7f53340fb8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override void ApplyToDrawableHitObjects(IEnumerable drawables) { - void adjustFadeIn(OsuHitObject h) => h.TimeFadeIn = h.TimePreempt * fade_in_duration_multiplier; + static void adjustFadeIn(OsuHitObject h) => h.TimeFadeIn = h.TimePreempt * fade_in_duration_multiplier; foreach (var d in drawables.OfType()) { diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 2ecc516919..26e70f19e4 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -413,7 +413,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual("soft-hitnormal8", getTestableSampleInfo(hitObjects[4]).LookupNames.First()); } - HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); + static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); } [Test] @@ -431,7 +431,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual("normal-hitnormal3", getTestableSampleInfo(hitObjects[2]).LookupNames.First()); } - HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); + static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); } [Test] @@ -451,7 +451,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(70, getTestableSampleInfo(hitObjects[3]).Volume); } - HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); + static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); } [Test] diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs index 05f5c117e4..80fad44593 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("set second set", () => successRate.Beatmap = secondBeatmap); AddAssert("ratings set", () => successRate.Graph.Metrics == secondBeatmap.Metrics); - BeatmapInfo createBeatmap() => new BeatmapInfo + static BeatmapInfo createBeatmap() => new BeatmapInfo { Metrics = new BeatmapMetrics { diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index 38094695fc..61618aedc0 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -222,7 +222,7 @@ namespace osu.Game.Tournament.Components Margin = new MarginPadding { Horizontal = 15, Vertical = 1 }; AutoSizeAxes = Axes.Both; - void cp(SpriteText s, Color4 colour) + static void cp(SpriteText s, Color4 colour) { s.Colour = colour; s.Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 15); diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 8ec8a21280..b19f2bedf0 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -152,7 +152,7 @@ namespace osu.Game.Tournament.IPC { protected override string LocateBasePath() { - bool checkExists(string p) + static bool checkExists(string p) { return File.Exists(Path.Combine(p, "ipc.txt")); } diff --git a/osu.Game.Tournament/Screens/Ladder/Components/ProgressionPath.cs b/osu.Game.Tournament/Screens/Ladder/Components/ProgressionPath.cs index 34e0dc770f..84a329085a 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/ProgressionPath.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/ProgressionPath.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components { base.LoadComplete(); - Vector2 getCenteredVector(Vector2 top, Vector2 bottom) => new Vector2(top.X, top.Y + (bottom.Y - top.Y) / 2); + static Vector2 getCenteredVector(Vector2 top, Vector2 bottom) => new Vector2(top.X, top.Y + (bottom.Y - top.Y) / 2); var q1 = Source.ScreenSpaceDrawQuad; var q2 = Destination.ScreenSpaceDrawQuad; diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index 7a5fc2cd06..c3875716b8 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -120,7 +120,7 @@ namespace osu.Game.Tournament.Screens.MapPool pickColour = colour; pickType = choiceType; - Color4 setColour(bool active) => active ? Color4.White : Color4.Gray; + static Color4 setColour(bool active) => active ? Color4.White : Color4.Gray; buttonRedBan.Colour = setColour(pickColour == TeamColour.Red && pickType == ChoiceType.Ban); buttonBlueBan.Colour = setColour(pickColour == TeamColour.Blue && pickType == ChoiceType.Ban); diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 5e7d0d050a..5348ff1f02 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Objects.Legacy } // osu-stable special-cased colinear perfect curves to a CurveType.Linear - bool isLinear(Vector2[] p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) - (p[1].X - p[0].X) * (p[2].Y - p[0].Y)); + static bool isLinear(Vector2[] p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) - (p[1].X - p[0].X) * (p[2].Y - p[0].Y)); if (points.Length == 3 && pathType == PathType.PerfectCurve && isLinear(points)) pathType = PathType.Linear; From 5f3e9a791c6e44747ba3e9ff1e33cb50ced91425 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 12 Nov 2019 18:45:19 +0800 Subject: [PATCH 2277/2815] Disable suggestions not applicable for this PR. --- .editorconfig | 6 +++++- osu.sln.DotSettings | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.editorconfig b/.editorconfig index 8c2dde00aa..3e7325e15b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -173,8 +173,12 @@ csharp_prefer_simple_default_expression = true:warning csharp_style_pattern_local_over_anonymous_function = true:silent dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent -#Style _ C# 8 features +#Style - C# 8 features csharp_prefer_static_local_function = true:warning +csharp_prefer_simple_using_statement = true:silent +csharp_style_prefer_index_operator = false:none +csharp_style_prefer_range_operator = false:none +csharp_style_prefer_switch_expression = false:none #Supressing roslyn built-in analyzers # Suppress: EC112 diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 63d055702b..b08d314dbe 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -70,6 +70,7 @@ WARNING HINT WARNING + HINT WARNING DO_NOT_SHOW WARNING @@ -118,6 +119,7 @@ HINT HINT HINT + DO_NOT_SHOW HINT HINT WARNING @@ -132,7 +134,6 @@ DO_NOT_SHOW DO_NOT_SHOW WARNING - WARNING WARNING WARNING @@ -211,12 +212,13 @@ HINT HINT HINT - HINT HINT + DO_NOT_SHOW WARNING WARNING WARNING + DO_NOT_SHOW WARNING True From d60493a961dc44185702e5d101476fdd915f4752 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 12 Nov 2019 20:03:21 +0800 Subject: [PATCH 2278/2815] Use discards. --- osu.Game.Tests/Beatmaps/Formats/ParsingTest.cs | 2 +- osu.Game/Configuration/DatabasedConfigManager.cs | 2 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- osu.sln.DotSettings | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/ParsingTest.cs b/osu.Game.Tests/Beatmaps/Formats/ParsingTest.cs index b3863bcf44..669acc3202 100644 --- a/osu.Game.Tests/Beatmaps/Formats/ParsingTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/ParsingTest.cs @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Beatmaps.Formats { try { - var _ = int.Parse(input); + _ = int.Parse(input); } catch (Exception e) { diff --git a/osu.Game/Configuration/DatabasedConfigManager.cs b/osu.Game/Configuration/DatabasedConfigManager.cs index 02382cfd2b..1ef4c2527a 100644 --- a/osu.Game/Configuration/DatabasedConfigManager.cs +++ b/osu.Game/Configuration/DatabasedConfigManager.cs @@ -36,7 +36,7 @@ namespace osu.Game.Configuration protected override void PerformLoad() { databasedSettings = settings.Query(ruleset?.ID, variant); - legacySettingsExist = databasedSettings.Any(s => int.TryParse(s.Key, out var _)); + legacySettingsExist = databasedSettings.Any(s => int.TryParse(s.Key, out _)); } protected override bool PerformSave() diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c3436ffd45..b58753bdbb 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -79,7 +79,7 @@ namespace osu.Game.Screens.Select newRoot.Filter(activeCriteria); // preload drawables as the ctor overhead is quite high currently. - var _ = newRoot.Drawables; + _ = newRoot.Drawables; root = newRoot; if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet)) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index b08d314dbe..9b400de390 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -17,6 +17,7 @@ WARNING WARNING HINT + DO_NOT_SHOW HINT HINT HINT From 42a98c5d87124ac18f6ec6228430ea88aec0db89 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 12 Nov 2019 20:07:01 +0800 Subject: [PATCH 2279/2815] Use constants. --- osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs | 2 +- osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs | 2 +- osu.Game/Overlays/Dialog/PopupDialog.cs | 4 ++-- osu.Game/Overlays/SearchableList/SearchableListOverlay.cs | 2 +- osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs | 2 +- osu.Game/Screens/Select/FooterButton.cs | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs index 621728830a..505d2d6f89 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Chat.Selection { public class ChannelSelectionOverlay : WaveOverlayContainer { - public static readonly float WIDTH_PADDING = 170; + public const float WIDTH_PADDING = 170; private const float transition_duration = 500; diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs index 8b88d81b88..a14765ee88 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs @@ -14,7 +14,7 @@ namespace osu.Game.Overlays.Chat.Tabs { public class ChannelTabControl : OsuTabControl { - public static readonly float SHEAR_WIDTH = 10; + public const float SHEAR_WIDTH = 10; public Action OnRequestLeave; diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index cff887865a..ee88ff258a 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -21,8 +21,8 @@ namespace osu.Game.Overlays.Dialog { public abstract class PopupDialog : VisibilityContainer { - public static readonly float ENTER_DURATION = 500; - public static readonly float EXIT_DURATION = 200; + public const float ENTER_DURATION = 500; + public const float EXIT_DURATION = 200; private readonly Vector2 ringSize = new Vector2(100f); private readonly Vector2 ringMinifiedSize = new Vector2(20f); diff --git a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs index 177f731f12..fb0c1d9808 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.SearchableList { public abstract class SearchableListOverlay : FullscreenOverlay { - public static readonly float WIDTH_PADDING = 80; + public const float WIDTH_PADDING = 80; } public abstract class SearchableListOverlay : SearchableListOverlay diff --git a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs index bba72c7ee1..433e8ee398 100644 --- a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs +++ b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Select { public class BeatmapDetailAreaTabControl : Container { - public static readonly float HEIGHT = 24; + public const float HEIGHT = 24; private readonly OsuTabControlCheckbox modsCheckbox; private readonly OsuTabControl tabs; private readonly Container tabsContainer; diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index c1478aa4ce..b77da36748 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Select { public class FooterButton : OsuClickableContainer { - public static readonly float SHEAR_WIDTH = 7.5f; + public const float SHEAR_WIDTH = 7.5f; protected static readonly Vector2 SHEAR = new Vector2(SHEAR_WIDTH / Footer.HEIGHT, 0); From f3f5094c2686f7964c1613fc0b0f7799f4b3c7df Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 12 Nov 2019 20:12:11 +0800 Subject: [PATCH 2280/2815] Convert delegate to local function. --- .editorconfig | 2 +- osu.Game/Screens/Menu/Disclaimer.cs | 3 +-- .../Screens/Multi/Ranking/Pages/RoomLeaderboardPage.cs | 8 ++++---- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.editorconfig b/.editorconfig index 3e7325e15b..bfa071acd8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -170,7 +170,7 @@ csharp_style_deconstructed_variable_declaration = true:warning #Style - other C# 7.x features dotnet_style_prefer_inferred_tuple_names = true:warning csharp_prefer_simple_default_expression = true:warning -csharp_style_pattern_local_over_anonymous_function = true:silent +csharp_style_pattern_local_over_anonymous_function = true:warning dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent #Style - C# 8 features diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 17f999d519..bcab73715b 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.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.Allocation; using osu.Framework.Bindables; @@ -93,7 +92,7 @@ namespace osu.Game.Screens.Menu textFlow.AddParagraph("Things may not work as expected", t => t.Font = t.Font.With(size: 20)); textFlow.NewParagraph(); - Action format = t => t.Font = OsuFont.GetFont(size: 15, weight: FontWeight.SemiBold); + static void format(SpriteText t) => t.Font = OsuFont.GetFont(size: 15, weight: FontWeight.SemiBold); textFlow.AddParagraph("Detailed bug reports are welcomed via github issues.", format); textFlow.NewParagraph(); diff --git a/osu.Game/Screens/Multi/Ranking/Pages/RoomLeaderboardPage.cs b/osu.Game/Screens/Multi/Ranking/Pages/RoomLeaderboardPage.cs index d20b021fc6..e389611caf 100644 --- a/osu.Game/Screens/Multi/Ranking/Pages/RoomLeaderboardPage.cs +++ b/osu.Game/Screens/Multi/Ranking/Pages/RoomLeaderboardPage.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 Microsoft.EntityFrameworkCore.Internal; using osu.Framework.Allocation; @@ -77,12 +76,13 @@ namespace osu.Game.Screens.Multi.Ranking.Pages private void scoresLoaded(IEnumerable scores) { - Action gray = s => s.Colour = colours.GrayC; - Action white = s => + void gray(SpriteText s) => s.Colour = colours.GrayC; + + void white(SpriteText s) { s.Font = s.Font.With(size: s.Font.Size * 1.4f); s.Colour = colours.GrayF; - }; + } rankText.AddText(name + "\n", white); rankText.AddText("You are placed ", gray); From 2d46eed8aee992b136a5a20972cc109928948650 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 12 Nov 2019 20:15:55 +0800 Subject: [PATCH 2281/2815] Turn on missing rules. --- .editorconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.editorconfig b/.editorconfig index bfa071acd8..b5333ad8e7 100644 --- a/.editorconfig +++ b/.editorconfig @@ -155,13 +155,13 @@ dotnet_style_null_propagation = true:warning csharp_style_pattern_matching_over_is_with_cast_check = true:warning csharp_style_pattern_matching_over_as_with_null_check = true:warning csharp_style_throw_expression = true:silent -csharp_style_conditional_delegate_call = true:suggestion +csharp_style_conditional_delegate_call = true:warning #Style - unused dotnet_style_readonly_field = true:silent dotnet_code_quality_unused_parameters = non_public:silent csharp_style_unused_value_expression_statement_preference = discard_variable:silent -csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:warning #Style - variable declaration csharp_style_inlined_variable_declaration = true:warning From 61464c5c89723cbfe06dc975e760d207c2d8dc8b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Nov 2019 21:27:15 +0900 Subject: [PATCH 2282/2815] Fix potential nullref in unrelated test --- osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index c8c36789c4..5652b8d2bd 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -19,7 +19,7 @@ namespace osu.Game.Online.API.Requests public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset, BeatmapSearchCategory searchCategory = BeatmapSearchCategory.Any, DirectSortCriteria sortCriteria = DirectSortCriteria.Ranked, SortDirection direction = SortDirection.Descending) { - this.query = System.Uri.EscapeDataString(query); + this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query); this.ruleset = ruleset; this.searchCategory = searchCategory; this.sortCriteria = sortCriteria; From 1349289c0eae4e15db47ec3a807ef658cb7d7b59 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 12 Nov 2019 20:32:41 +0800 Subject: [PATCH 2283/2815] Remove SharpRaven package and install Sentry. --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index af60da3e70..a64334c4cc 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -22,9 +22,9 @@ + - From 5110ae82a1cd76a970ef11bfebf7559dea0c9514 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Nov 2019 21:38:08 +0900 Subject: [PATCH 2284/2815] Tidy up implementation --- .../BeatmapSet/Buttons/FavouriteButton.cs | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index f059e06214..33df1fa485 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs @@ -1,6 +1,7 @@ -// 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.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -20,7 +21,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons { public readonly Bindable BeatmapSet = new Bindable(); - private readonly Bindable favourited = new Bindable(); + private readonly BindableBool favourited = new BindableBool(); private PostBeatmapFavouriteRequest request; private DimmedLoadingLayer loading; @@ -44,40 +45,45 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons loading = new DimmedLoadingLayer(), }); + favourited.ValueChanged += favourited => icon.Icon = favourited.NewValue ? FontAwesome.Solid.Heart : FontAwesome.Regular.Heart; + BeatmapSet.BindValueChanged(setInfo => { - if (setInfo.NewValue?.OnlineInfo?.HasFavourited == null) - return; - - favourited.Value = setInfo.NewValue.OnlineInfo.HasFavourited; - }); - - favourited.ValueChanged += favourited => - { - loading.Hide(); - - icon.Icon = favourited.NewValue ? FontAwesome.Solid.Heart : FontAwesome.Regular.Heart; - }; + Enabled.Value = BeatmapSet.Value?.OnlineBeatmapSetID > 0; + favourited.Value = setInfo.NewValue?.OnlineInfo?.HasFavourited ?? false; + }, true); Action = () => { if (loading.State.Value == Visibility.Visible) return; + // guaranteed by disabled state abvove. + Debug.Assert(BeatmapSet.Value.OnlineBeatmapSetID != null); + loading.Show(); request?.Cancel(); - request = new PostBeatmapFavouriteRequest(BeatmapSet.Value?.OnlineBeatmapSetID ?? 0, favourited.Value ? BeatmapFavouriteAction.UnFavourite : BeatmapFavouriteAction.Favourite); - request.Success += () => favourited.Value = !favourited.Value; - request.Failure += exception => + + request = new PostBeatmapFavouriteRequest(BeatmapSet.Value.OnlineBeatmapSetID.Value, favourited.Value ? BeatmapFavouriteAction.UnFavourite : BeatmapFavouriteAction.Favourite); + + request.Success += () => + { + favourited.Toggle(); + loading.Hide(); + }; + + request.Failure += e => { notifications.Post(new SimpleNotification { - Text = exception.Message, + Text = e.Message, Icon = FontAwesome.Solid.Times, }); + loading.Hide(); }; + api.Queue(request); }; } From da819261e0063b6f3a98ed5c28821696c662dcdf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Nov 2019 21:38:16 +0900 Subject: [PATCH 2285/2815] Shrink and darken loading layer to better suit button --- osu.Game/Graphics/UserInterface/DimmedLoadingLayer.cs | 7 ++++--- osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/DimmedLoadingLayer.cs b/osu.Game/Graphics/UserInterface/DimmedLoadingLayer.cs index b7d2222f33..f7138827cc 100644 --- a/osu.Game/Graphics/UserInterface/DimmedLoadingLayer.cs +++ b/osu.Game/Graphics/UserInterface/DimmedLoadingLayer.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Extensions.Color4Extensions; +using osuTK; namespace osu.Game.Graphics.UserInterface { @@ -15,7 +16,7 @@ namespace osu.Game.Graphics.UserInterface private readonly LoadingAnimation loading; - public DimmedLoadingLayer() + public DimmedLoadingLayer(float dimAmount = 0.5f, float iconScale = 1f) { RelativeSizeAxes = Axes.Both; Children = new Drawable[] @@ -23,9 +24,9 @@ namespace osu.Game.Graphics.UserInterface new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.5f), + Colour = Color4.Black.Opacity(dimAmount), }, - loading = new LoadingAnimation(), + loading = new LoadingAnimation { Scale = new Vector2(iconScale) }, }; } diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index 33df1fa485..cfb34e4adf 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.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.Diagnostics; @@ -42,7 +42,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons Size = new Vector2(18), Shadow = false, }, - loading = new DimmedLoadingLayer(), + loading = new DimmedLoadingLayer(0.8f, 0.5f), }); favourited.ValueChanged += favourited => icon.Icon = favourited.NewValue ? FontAwesome.Solid.Heart : FontAwesome.Regular.Heart; From de732c6c2447e6f1b231cd2d843fa74413819539 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 12 Nov 2019 21:12:38 +0800 Subject: [PATCH 2286/2815] Change code to use Sentry client. --- osu.Game.Tests/Visual/TestSceneOsuGame.cs | 2 +- osu.Game/OsuGame.cs | 4 ++-- .../Utils/{RavenLogger.cs => SentryLogger.cs} | 22 +++++++++---------- 3 files changed, 14 insertions(+), 14 deletions(-) rename osu.Game/Utils/{RavenLogger.cs => SentryLogger.cs} (84%) diff --git a/osu.Game.Tests/Visual/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/TestSceneOsuGame.cs index 36cd49d839..e495b2a95a 100644 --- a/osu.Game.Tests/Visual/TestSceneOsuGame.cs +++ b/osu.Game.Tests/Visual/TestSceneOsuGame.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual private IReadOnlyList requiredGameDependencies => new[] { typeof(OsuGame), - typeof(RavenLogger), + typeof(SentryLogger), typeof(OsuLogo), typeof(IdleTracker), typeof(OnScreenDisplay), diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 328c964976..541050a30f 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -71,7 +71,7 @@ namespace osu.Game [Cached] private readonly ScreenshotManager screenshotManager = new ScreenshotManager(); - protected RavenLogger RavenLogger; + protected SentryLogger RavenLogger; public virtual Storage GetStorageForStableInstall() => null; @@ -110,7 +110,7 @@ namespace osu.Game forwardLoggedErrorsToNotifications(); - RavenLogger = new RavenLogger(this); + RavenLogger = new SentryLogger(this); } private void updateBlockingOverlayFade() => diff --git a/osu.Game/Utils/RavenLogger.cs b/osu.Game/Utils/SentryLogger.cs similarity index 84% rename from osu.Game/Utils/RavenLogger.cs rename to osu.Game/Utils/SentryLogger.cs index 16178e63bd..9629e830bd 100644 --- a/osu.Game/Utils/RavenLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -7,26 +7,26 @@ using System.IO; using System.Net; using System.Threading.Tasks; using osu.Framework.Logging; -using SharpRaven; -using SharpRaven.Data; +using Sentry; namespace osu.Game.Utils { /// /// Report errors to sentry. /// - public class RavenLogger : IDisposable + public class SentryLogger : IDisposable { - private readonly RavenClient raven = new RavenClient("https://5e342cd55f294edebdc9ad604d28bbd3@sentry.io/1255255"); - private readonly List tasks = new List(); - public RavenLogger(OsuGame game) + public SentryLogger(OsuGame game) { - raven.Release = game.Version; - if (!game.IsDeployedBuild) return; + SentrySdk.Init(new SentryOptions + { + Dsn = new Dsn("https://5e342cd55f294edebdc9ad604d28bbd3@sentry.io/1255255"), + Release = game.Version + }); Exception lastException = null; Logger.NewEntry += entry => @@ -46,10 +46,10 @@ namespace osu.Game.Utils return; lastException = exception; - queuePendingTask(raven.CaptureAsync(new SentryEvent(exception) { Message = entry.Message })); + SentrySdk.CaptureEvent(new SentryEvent(exception) { Message = entry.Message }); } else - raven.AddTrail(new Breadcrumb(entry.Target.ToString(), BreadcrumbType.Navigation) { Message = entry.Message }); + SentrySdk.AddBreadcrumb(category: entry.Target.ToString(), type: "navigation", message: entry.Message); }; } @@ -93,7 +93,7 @@ namespace osu.Game.Utils #region Disposal - ~RavenLogger() + ~SentryLogger() { Dispose(false); } From e01a50e4b332a0ea70f2dead860f75faf266611a Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 12 Nov 2019 21:39:38 +0800 Subject: [PATCH 2287/2815] Correct disposal. --- osu.Game/Utils/SentryLogger.cs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index 9629e830bd..256c52489b 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -16,13 +16,13 @@ namespace osu.Game.Utils /// public class SentryLogger : IDisposable { - private readonly List tasks = new List(); + private IDisposable sentry; public SentryLogger(OsuGame game) { if (!game.IsDeployedBuild) return; - SentrySdk.Init(new SentryOptions + sentry = SentrySdk.Init(new SentryOptions { Dsn = new Dsn("https://5e342cd55f294edebdc9ad604d28bbd3@sentry.io/1255255"), Release = game.Version @@ -81,16 +81,6 @@ namespace osu.Game.Utils return true; } - private void queuePendingTask(Task task) - { - lock (tasks) tasks.Add(task); - task.ContinueWith(_ => - { - lock (tasks) - tasks.Remove(task); - }); - } - #region Disposal ~SentryLogger() @@ -112,7 +102,8 @@ namespace osu.Game.Utils return; isDisposed = true; - lock (tasks) Task.WaitAll(tasks.ToArray(), 5000); + sentry.Dispose(); + sentry = null; } #endregion From 8276428efc0ff891a935acb69220b4f8e4bb7478 Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Tue, 12 Nov 2019 20:48:26 +0700 Subject: [PATCH 2288/2815] Add IsPresent check when dialog popping out --- osu.Game/Overlays/Dialog/PopupDialog.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index cff887865a..13eb677263 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -241,7 +241,7 @@ namespace osu.Game.Overlays.Dialog protected override void PopOut() { - if (!actionInvoked) + if (!actionInvoked && content.IsPresent) // In the case a user did not choose an action before a hide was triggered, press the last button. // This is presumed to always be a sane default "cancel" action. buttonsContainer.Last().Click(); From 70a02d27a99e86f4308f62aac12dbe10f75fa5bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Nov 2019 22:53:01 +0900 Subject: [PATCH 2289/2815] Update solution paths --- fastlane/Fastfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 7adf42a1eb..28a83fbbae 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -49,12 +49,12 @@ desc 'Deploy to play store' desc 'Compile the project' lane :build do |options| nuget_restore( - project_path: 'osu.Android.sln' + project_path: 'osu.sln' ) souyuz( build_configuration: 'Release', - solution_path: 'osu.Android.sln', + solution_path: 'osu.sln', platform: "android", output_path: "osu.Android/bin/Release/", keystore_path: options[:keystore_path], @@ -70,7 +70,7 @@ desc 'Deploy to play store' android_build = split.join('') app_version( - solution_path: 'osu.Android.sln', + solution_path: 'osu.sln', version: options[:version], build: android_build, ) @@ -106,7 +106,7 @@ platform :ios do desc 'Compile the project' lane :build do nuget_restore( - project_path: 'osu.iOS.sln' + project_path: 'osu.sln' ) souyuz( From 6ae6603dede8c6fb0c5e9b1671a1e65e5330ccd8 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 12 Nov 2019 22:08:16 +0800 Subject: [PATCH 2290/2815] Cleanup. --- osu.Game/OsuGame.cs | 8 ++++---- osu.Game/Utils/SentryLogger.cs | 4 +--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 541050a30f..55768baa7e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -71,7 +71,7 @@ namespace osu.Game [Cached] private readonly ScreenshotManager screenshotManager = new ScreenshotManager(); - protected SentryLogger RavenLogger; + protected SentryLogger SentryLogger; public virtual Storage GetStorageForStableInstall() => null; @@ -110,7 +110,7 @@ namespace osu.Game forwardLoggedErrorsToNotifications(); - RavenLogger = new SentryLogger(this); + SentryLogger = new SentryLogger(this); } private void updateBlockingOverlayFade() => @@ -166,7 +166,7 @@ namespace osu.Game dependencies.CacheAs(this); - dependencies.Cache(RavenLogger); + dependencies.Cache(SentryLogger); dependencies.Cache(osuLogo = new OsuLogo { Alpha = 0 }); @@ -486,7 +486,7 @@ namespace osu.Game protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - RavenLogger.Dispose(); + SentryLogger.Dispose(); } protected override void LoadComplete() diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index 256c52489b..5badd2546e 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -2,10 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.IO; using System.Net; -using System.Threading.Tasks; using osu.Framework.Logging; using Sentry; @@ -102,7 +100,7 @@ namespace osu.Game.Utils return; isDisposed = true; - sentry.Dispose(); + sentry?.Dispose(); sentry = null; } From 8a1276db3aa05c9188490f17e745156b28c1430e Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 12 Nov 2019 22:16:48 +0800 Subject: [PATCH 2291/2815] Change to use instance API of sentry. --- osu.Game/Utils/SentryLogger.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index 5badd2546e..7e879741d6 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -14,17 +14,21 @@ namespace osu.Game.Utils /// public class SentryLogger : IDisposable { - private IDisposable sentry; + private SentryClient sentry; + private Scope sentryScope; public SentryLogger(OsuGame game) { if (!game.IsDeployedBuild) return; - sentry = SentrySdk.Init(new SentryOptions + var options = new SentryOptions { Dsn = new Dsn("https://5e342cd55f294edebdc9ad604d28bbd3@sentry.io/1255255"), Release = game.Version - }); + }; + sentry = new SentryClient(options); + sentryScope = new Scope(options); + Exception lastException = null; Logger.NewEntry += entry => @@ -44,10 +48,10 @@ namespace osu.Game.Utils return; lastException = exception; - SentrySdk.CaptureEvent(new SentryEvent(exception) { Message = entry.Message }); + sentry.CaptureEvent(new SentryEvent(exception) { Message = entry.Message }, sentryScope); } else - SentrySdk.AddBreadcrumb(category: entry.Target.ToString(), type: "navigation", message: entry.Message); + sentryScope.AddBreadcrumb(DateTimeOffset.Now, entry.Message, entry.Target.ToString(), "navigation"); }; } @@ -102,6 +106,7 @@ namespace osu.Game.Utils isDisposed = true; sentry?.Dispose(); sentry = null; + sentryScope = null; } #endregion From dfd5c8807515bf6f5aec86610ff116e439b99d50 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 13 Nov 2019 00:00:08 +0300 Subject: [PATCH 2292/2815] Typo fix --- osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index cfb34e4adf..4c2a8652f3 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs @@ -58,7 +58,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons if (loading.State.Value == Visibility.Visible) return; - // guaranteed by disabled state abvove. + // guaranteed by disabled state above. Debug.Assert(BeatmapSet.Value.OnlineBeatmapSetID != null); loading.Show(); From 3903e59f2fecca4c63de60897a66279763973936 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 13 Nov 2019 00:01:13 +0300 Subject: [PATCH 2293/2815] Add null check for notification overlay --- osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index 4c2a8652f3..163251d798 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs @@ -75,7 +75,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons request.Failure += e => { - notifications.Post(new SimpleNotification + notifications?.Post(new SimpleNotification { Text = e.Message, Icon = FontAwesome.Solid.Times, From 27f721eec232a056e208d9ffcbbac1b8459db063 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 13 Nov 2019 00:24:13 +0300 Subject: [PATCH 2294/2815] Use = instead of += for OnSelectionChanged Action --- osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index a7128ca157..09c388a3b0 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -55,7 +55,7 @@ namespace osu.Game.Overlays.BeatmapSet modsContainer.Add(new ModButton(new NoMod())); modsContainer.AddRange(ruleset.NewValue.CreateInstance().GetAllMods().Where(m => m.Ranked).Select(m => new ModButton(m))); - modsContainer.ForEach(button => button.OnSelectionChanged += selectionChanged); + modsContainer.ForEach(button => button.OnSelectionChanged = selectionChanged); } private void selectionChanged(Mod mod, bool selected) From 99ec84ed38643c504378ede2a10894cd0ae5f350 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 13 Nov 2019 00:26:19 +0300 Subject: [PATCH 2295/2815] Update dependency --- .../Overlays/BeatmapSet/LeaderboardModSelector.cs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index 75c24cb710..bd639881e7 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -54,13 +54,9 @@ namespace osu.Game.Overlays.BeatmapSet modsContainer.Add(new ModButton(new NoMod())); - ruleset.NewValue.CreateInstance().GetAllMods().ForEach(mod => - { - if (mod.Ranked) - modsContainer.Add(new ModButton(mod)); - }); + modsContainer.AddRange(ruleset.NewValue.CreateInstance().GetAllMods().Where(m => m.Ranked).Select(m => new ModButton(m))); - modsContainer.ForEach(button => button.OnSelectionChanged += selectionChanged); + modsContainer.ForEach(button => button.OnSelectionChanged = selectionChanged); } private void selectionChanged(Mod mod, bool selected) @@ -77,11 +73,7 @@ namespace osu.Game.Overlays.BeatmapSet protected override bool OnHover(HoverEvent e) { if (!SelectedMods.Any()) - modsContainer.ForEach(button => - { - if (!button.IsHovered) - button.Highlighted.Value = false; - }); + modsContainer.Children.Where(button => !button.IsHovered).ForEach(button => button.Highlighted.Value = false); return base.OnHover(e); } From 5f609b48da9619025998a867882527b273e19905 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 13 Nov 2019 00:29:28 +0300 Subject: [PATCH 2296/2815] Use local bindable for api.LocalUser --- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 73253546bb..bc5daa39a0 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -27,6 +27,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores public readonly Bindable Beatmap = new Bindable(); private readonly Bindable ruleset = new Bindable(); private readonly Bindable scope = new Bindable(); + private readonly Bindable user = new Bindable(); private readonly Box background; private readonly ScoreTable scoreTable; @@ -155,6 +156,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private void load(OsuColour colours) { background.Colour = colours.Gray2; + + user.BindTo(api.LocalUser); } protected override void LoadComplete() @@ -167,7 +170,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores modSelector.SelectedMods.ItemsRemoved += _ => getScores(); Beatmap.BindValueChanged(onBeatmapChanged); - api.LocalUser.BindValueChanged(onUserChanged, true); + user.BindValueChanged(onUserChanged, true); } private void onBeatmapChanged(ValueChangedEvent beatmap) From 2b8a105ec5cab7296966efab2cc87fcc2d01347e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Nov 2019 11:49:36 +0900 Subject: [PATCH 2297/2815] Fix osu!direct test fail Unsure how this occurred only now; may warrant further investigation. --- osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index c8c36789c4..5652b8d2bd 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -19,7 +19,7 @@ namespace osu.Game.Online.API.Requests public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset, BeatmapSearchCategory searchCategory = BeatmapSearchCategory.Any, DirectSortCriteria sortCriteria = DirectSortCriteria.Ranked, SortDirection direction = SortDirection.Descending) { - this.query = System.Uri.EscapeDataString(query); + this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query); this.ruleset = ruleset; this.searchCategory = searchCategory; this.sortCriteria = sortCriteria; From 3c5008f94c20a968e6248dd0e5dcad74c649722d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Nov 2019 11:59:03 +0900 Subject: [PATCH 2298/2815] Add test --- .../Visual/Online/TestSceneFavouriteButton.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneFavouriteButton.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneFavouriteButton.cs b/osu.Game.Tests/Visual/Online/TestSceneFavouriteButton.cs new file mode 100644 index 0000000000..ae21245b59 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneFavouriteButton.cs @@ -0,0 +1,46 @@ +// 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.Testing; +using osu.Game.Beatmaps; +using osu.Game.Overlays.BeatmapSet.Buttons; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneFavouriteButton : OsuTestScene + { + private FavouriteButton favourite; + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create button", () => Child = favourite = new FavouriteButton()); + } + + [Test] + public void TestLoggedOutIn() + { + AddStep("set valid beatmap", () => favourite.BeatmapSet.Value = new BeatmapSetInfo { OnlineBeatmapSetID = 88 }); + AddStep("log out", () => API.Logout()); + checkEnabled(false); + AddStep("log in", () => API.Login("test", "test")); + checkEnabled(true); + } + + [Test] + public void TestBeatmapChange() + { + AddStep("log in", () => API.Login("test", "test")); + AddStep("set valid beatmap", () => favourite.BeatmapSet.Value = new BeatmapSetInfo { OnlineBeatmapSetID = 88 }); + checkEnabled(true); + AddStep("set invalid beatmap", () => favourite.BeatmapSet.Value = new BeatmapSetInfo()); + checkEnabled(true); + } + + private void checkEnabled(bool expected) + { + AddAssert("is " + (expected ? "enabled" : "disabled"), () => favourite.Enabled.Value == expected); + } + } +} From f18263aa70dc215af08e6b008c74a5be7a700d39 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Nov 2019 12:09:18 +0900 Subject: [PATCH 2299/2815] Add tests --- .../Visual/Online/TestSceneFavouriteButton.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneFavouriteButton.cs b/osu.Game.Tests/Visual/Online/TestSceneFavouriteButton.cs index ae21245b59..8e2ee4e28d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneFavouriteButton.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneFavouriteButton.cs @@ -2,9 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Overlays.BeatmapSet.Buttons; +using osuTK; namespace osu.Game.Tests.Visual.Online { @@ -15,7 +17,13 @@ namespace osu.Game.Tests.Visual.Online [SetUpSteps] public void SetUpSteps() { - AddStep("create button", () => Child = favourite = new FavouriteButton()); + AddStep("create button", () => Child = favourite = new FavouriteButton + { + RelativeSizeAxes = Axes.None, + Size = new Vector2(50), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); } [Test] @@ -35,7 +43,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("set valid beatmap", () => favourite.BeatmapSet.Value = new BeatmapSetInfo { OnlineBeatmapSetID = 88 }); checkEnabled(true); AddStep("set invalid beatmap", () => favourite.BeatmapSet.Value = new BeatmapSetInfo()); - checkEnabled(true); + checkEnabled(false); } private void checkEnabled(bool expected) From c751328665b1c5e1ff2b51f7aa359570518d6f8d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Nov 2019 12:13:33 +0900 Subject: [PATCH 2300/2815] Disable button when not logged in --- .../BeatmapSet/Buttons/FavouriteButton.cs | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index 163251d798..af0987d183 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Notifications; +using osu.Game.Users; using osuTK; namespace osu.Game.Overlays.BeatmapSet.Buttons @@ -26,12 +27,23 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons private PostBeatmapFavouriteRequest request; private DimmedLoadingLayer loading; - public string TooltipText => (favourited.Value ? "Unfavourite" : "Favourite") + " this beatmapset"; + private readonly Bindable localUser = new Bindable(); + + public string TooltipText + { + get + { + if (!Enabled.Value) return string.Empty; + + return (favourited.Value ? "Unfavourite" : "Favourite") + " this beatmapset"; + } + } [BackgroundDependencyLoader(true)] private void load(IAPIProvider api, NotificationOverlay notifications) { SpriteIcon icon; + AddRange(new Drawable[] { icon = new SpriteIcon @@ -45,14 +57,6 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons loading = new DimmedLoadingLayer(0.8f, 0.5f), }); - favourited.ValueChanged += favourited => icon.Icon = favourited.NewValue ? FontAwesome.Solid.Heart : FontAwesome.Regular.Heart; - - BeatmapSet.BindValueChanged(setInfo => - { - Enabled.Value = BeatmapSet.Value?.OnlineBeatmapSetID > 0; - favourited.Value = setInfo.NewValue?.OnlineInfo?.HasFavourited ?? false; - }, true); - Action = () => { if (loading.State.Value == Visibility.Visible) @@ -86,8 +90,22 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons api.Queue(request); }; + + favourited.ValueChanged += favourited => icon.Icon = favourited.NewValue ? FontAwesome.Solid.Heart : FontAwesome.Regular.Heart; + + localUser.BindTo(api.LocalUser); + localUser.BindValueChanged(_ => updateEnabled()); + + // must be run after setting the Action to ensure correct enabled state (setting an Action forces a button to be enabled). + BeatmapSet.BindValueChanged(setInfo => + { + updateEnabled(); + favourited.Value = setInfo.NewValue?.OnlineInfo?.HasFavourited ?? false; + }, true); } + private void updateEnabled() => Enabled.Value = !(localUser.Value is GuestUser) && BeatmapSet.Value?.OnlineBeatmapSetID > 0; + protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); From 4c0bca5c070da01dfdd6800e93113e58c1bebed6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 12 Nov 2019 18:35:28 +0900 Subject: [PATCH 2301/2815] Add ability to delete control points via right-click option --- .../Components/PathControlPointPiece.cs | 10 +- .../Components/PathControlPointVisualiser.cs | 102 +++++++++++------- 2 files changed, 69 insertions(+), 43 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 155e814596..70b4842089 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -14,6 +14,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Objects; using osuTK; using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { @@ -129,6 +130,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components protected override bool OnMouseDown(MouseDownEvent e) { + if (e.Button != MouseButton.Left) + return false; + if (RequestSelection != null) { RequestSelection.Invoke(Index); @@ -138,11 +142,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return false; } - protected override bool OnMouseUp(MouseUpEvent e) => RequestSelection != null; + protected override bool OnMouseUp(MouseUpEvent e) => e.Button == MouseButton.Left && RequestSelection != null; - protected override bool OnClick(ClickEvent e) => RequestSelection != null; + protected override bool OnClick(ClickEvent e) => e.Button == MouseButton.Left && RequestSelection != null; - protected override bool OnDragStart(DragStartEvent e) => true; + protected override bool OnDragStart(DragStartEvent e) => e.Button == MouseButton.Left; protected override bool OnDrag(DragEvent e) { diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 6962736157..f3fbb1f93f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -3,19 +3,23 @@ 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.Cursor; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose; using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { - public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler + public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler, IHasContextMenu { public Action ControlPointsChanged; @@ -73,6 +77,19 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return false; } + public bool OnPressed(PlatformAction action) + { + switch (action.ActionMethod) + { + case PlatformActionMethod.Delete: + return deleteSelected(); + } + + return false; + } + + public bool OnReleased(PlatformAction action) => action.ActionMethod == PlatformActionMethod.Delete; + private void selectPiece(int index) { if (inputManager.CurrentState.Keyboard.ControlPressed) @@ -84,50 +101,55 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } } - public bool OnPressed(PlatformAction action) + private bool deleteSelected() { - switch (action.ActionMethod) + var newControlPoints = new List(); + + foreach (var piece in Pieces) { - case PlatformActionMethod.Delete: - var newControlPoints = new List(); - - foreach (var piece in Pieces) - { - if (!piece.IsSelected.Value) - newControlPoints.Add(slider.Path.ControlPoints[piece.Index]); - } - - // Ensure that there are any points to be deleted - if (newControlPoints.Count == slider.Path.ControlPoints.Length) - return false; - - // If there are 0 remaining control points, treat the slider as being deleted - if (newControlPoints.Count == 0) - { - placementHandler?.Delete(slider); - return true; - } - - // Make control points relative - Vector2 first = newControlPoints[0]; - for (int i = 0; i < newControlPoints.Count; i++) - newControlPoints[i] = newControlPoints[i] - first; - - // The slider's position defines the position of the first control point, and all further control points are relative to that point - slider.Position = slider.Position + first; - - // Since pieces are re-used, they will not point to the deleted control points while remaining selected - foreach (var piece in Pieces) - piece.IsSelected.Value = false; - - ControlPointsChanged?.Invoke(newControlPoints.ToArray()); - - return true; + if (!piece.IsSelected.Value) + newControlPoints.Add(slider.Path.ControlPoints[piece.Index]); } - return false; + // Ensure that there are any points to be deleted + if (newControlPoints.Count == slider.Path.ControlPoints.Length) + return false; + + // If there are 0 remaining control points, treat the slider as being deleted + if (newControlPoints.Count == 0) + { + placementHandler?.Delete(slider); + return true; + } + + // Make control points relative + Vector2 first = newControlPoints[0]; + for (int i = 0; i < newControlPoints.Count; i++) + newControlPoints[i] = newControlPoints[i] - first; + + // The slider's position defines the position of the first control point, and all further control points are relative to that point + slider.Position = slider.Position + first; + + // Since pieces are re-used, they will not point to the deleted control points while remaining selected + foreach (var piece in Pieces) + piece.IsSelected.Value = false; + + ControlPointsChanged?.Invoke(newControlPoints.ToArray()); + return true; } - public bool OnReleased(PlatformAction action) => action.ActionMethod == PlatformActionMethod.Delete; + public MenuItem[] ContextMenuItems + { + get + { + if (!Pieces.Any(p => p.IsSelected.Value)) + return Array.Empty(); + + return new MenuItem[] + { + new OsuMenuItem("Delete control points", MenuItemType.Destructive, () => deleteSelected()) + }; + } + } } } From 9c52d239b44c06a2bdfeb9336e531fed5a1c9eb5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 13 Nov 2019 16:56:48 +0900 Subject: [PATCH 2302/2815] Return null to allow passthrough --- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index f3fbb1f93f..fd14e6dc23 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components get { if (!Pieces.Any(p => p.IsSelected.Value)) - return Array.Empty(); + return null; return new MenuItem[] { From 76ab0ecd3c0b5e27bf87750981bf30ed83cb2a79 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 13 Nov 2019 17:21:48 +0900 Subject: [PATCH 2303/2815] Disallow deselections with right clicks --- .../Edit/Compose/Components/BlueprintContainer.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 2be4ca684a..195bc663f1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -313,14 +313,15 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Attempts to select any hovered blueprints. /// /// The input event that triggered this selection. - private void beginClickSelection(UIEvent e) + private void beginClickSelection(MouseButtonEvent e) { Debug.Assert(!clickSelectionBegan); - // If a select blueprint is already hovered, disallow changes in selection. - // Exception is made when holding control, as deselection should still be allowed. - if (!e.CurrentState.Keyboard.ControlPressed && - selectionHandler.SelectedBlueprints.Any(s => s.IsHovered)) + // Deselections are only allowed for control + left clicks + bool allowDeselection = e.ControlPressed && e.Button == MouseButton.Left; + + // Todo: This is probably incorrectly disallowing multiple selections on stacked objects + if (!allowDeselection && selectionHandler.SelectedBlueprints.Any(s => s.IsHovered)) return; foreach (SelectionBlueprint blueprint in selectionBlueprints.AliveBlueprints) From c56503ee88c2969c90df8a5679774f870371782e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 13 Nov 2019 17:28:18 +0900 Subject: [PATCH 2304/2815] Select single control point on right click --- .../Components/PathControlPointPiece.cs | 20 ++++++++++++------- .../Components/PathControlPointVisualiser.cs | 5 +++-- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 70b4842089..0ccf020300 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { public class PathControlPointPiece : BlueprintPiece { - public Action RequestSelection; + public Action RequestSelection; public Action ControlPointsChanged; public readonly BindableBool IsSelected = new BindableBool(); @@ -130,21 +130,27 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components protected override bool OnMouseDown(MouseDownEvent e) { - if (e.Button != MouseButton.Left) + if (RequestSelection == null) return false; - if (RequestSelection != null) + switch (e.Button) { - RequestSelection.Invoke(Index); - return true; + case MouseButton.Left: + RequestSelection.Invoke(Index, e); + return true; + + case MouseButton.Right: + if (!IsSelected.Value) + RequestSelection.Invoke(Index, e); + return false; // Allow context menu to show } return false; } - protected override bool OnMouseUp(MouseUpEvent e) => e.Button == MouseButton.Left && RequestSelection != null; + protected override bool OnMouseUp(MouseUpEvent e) => RequestSelection != null; - protected override bool OnClick(ClickEvent e) => e.Button == MouseButton.Left && RequestSelection != null; + protected override bool OnClick(ClickEvent e) => RequestSelection != null; protected override bool OnDragStart(DragStartEvent e) => e.Button == MouseButton.Left; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index fd14e6dc23..97c756417c 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -16,6 +16,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose; using osuTK; +using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { @@ -90,9 +91,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public bool OnReleased(PlatformAction action) => action.ActionMethod == PlatformActionMethod.Delete; - private void selectPiece(int index) + private void selectPiece(int index, MouseButtonEvent e) { - if (inputManager.CurrentState.Keyboard.ControlPressed) + if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed) Pieces[index].IsSelected.Toggle(); else { From d835def4abfc5d52d22d604e35f7f596e3c54a48 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 13 Nov 2019 17:36:46 +0900 Subject: [PATCH 2305/2815] Add point count to the menu item text --- .../Sliders/Components/PathControlPointVisualiser.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 97c756417c..8d598f1ab3 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Humanizer; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -143,12 +144,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { get { - if (!Pieces.Any(p => p.IsSelected.Value)) + int selectedPoints = Pieces.Count(p => p.IsSelected.Value); + + if (selectedPoints == 0) return null; return new MenuItem[] { - new OsuMenuItem("Delete control points", MenuItemType.Destructive, () => deleteSelected()) + new OsuMenuItem($"Delete {"control point".ToQuantity(selectedPoints)}", MenuItemType.Destructive, () => deleteSelected()) }; } } From 031b686ee96386d8d7bd5b00d4f5a336037ea874 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 13 Nov 2019 17:38:34 +0900 Subject: [PATCH 2306/2815] Only accept input while hovered --- .../Sliders/Components/PathControlPointVisualiser.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 8d598f1ab3..2bcce99c89 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -144,6 +144,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { get { + if (!Pieces.Any(p => p.IsHovered)) + return null; + int selectedPoints = Pieces.Count(p => p.IsSelected.Value); if (selectedPoints == 0) From bca1be0bfa69c0c6c6e5bb1a756eb573f1e0d0e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Nov 2019 18:54:33 +0900 Subject: [PATCH 2307/2815] Add failing test --- .../SongSelect/TestScenePlaySongSelect.cs | 53 +++++++++++++------ osu.Game/Screens/Select/SongSelect.cs | 4 +- 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 794d135b06..2d1a786bb9 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -57,23 +57,6 @@ namespace osu.Game.Tests.Visual.SongSelect typeof(DrawableCarouselBeatmapSet), }; - private class TestSongSelect : PlaySongSelect - { - public Action StartRequested; - - public new Bindable Ruleset => base.Ruleset; - - public WorkingBeatmap CurrentBeatmap => Beatmap.Value; - public WorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap; - public new BeatmapCarousel Carousel => base.Carousel; - - protected override bool OnStart() - { - StartRequested?.Invoke(); - return base.OnStart(); - } - } - private TestSongSelect songSelect; [BackgroundDependencyLoader] @@ -101,6 +84,17 @@ namespace osu.Game.Tests.Visual.SongSelect manager?.Delete(manager.GetAllUsableBeatmapSets()); }); + [Test] + public void TestSingleFilterOnEnter() + { + addRulesetImportStep(0); + addRulesetImportStep(0); + + createSongSelect(); + + AddAssert("filter count is 1", () => songSelect.FilterCount == 1); + } + [Test] public void TestAudioResuming() { @@ -357,5 +351,30 @@ namespace osu.Game.Tests.Visual.SongSelect base.Dispose(isDisposing); rulesets?.Dispose(); } + + private class TestSongSelect : PlaySongSelect + { + public Action StartRequested; + + public new Bindable Ruleset => base.Ruleset; + + public WorkingBeatmap CurrentBeatmap => Beatmap.Value; + public WorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap; + public new BeatmapCarousel Carousel => base.Carousel; + + protected override bool OnStart() + { + StartRequested?.Invoke(); + return base.OnStart(); + } + + public int FilterCount; + + protected override void ApplyFilterToCarousel(FilterCriteria criteria) + { + FilterCount++; + base.ApplyFilterToCarousel(criteria); + } + } } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 375b994169..6810f77b93 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -165,7 +165,7 @@ namespace osu.Game.Screens.Select { RelativeSizeAxes = Axes.X, Height = FilterControl.HEIGHT, - FilterChanged = c => Carousel.Filter(c), + FilterChanged = ApplyFilterToCarousel, Background = { Width = 2 }, }, } @@ -217,6 +217,8 @@ namespace osu.Game.Screens.Select BeatmapDetails.Leaderboard.ScoreSelected += score => this.Push(new SoloResults(score)); } + protected virtual void ApplyFilterToCarousel(FilterCriteria criteria) => Carousel.Filter(criteria); + [BackgroundDependencyLoader(true)] private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins, ScoreManager scores) { From 280c1a0eb42b16954c4ffd98257bfb7e92ec0440 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Nov 2019 18:00:45 +0900 Subject: [PATCH 2308/2815] Fix carousel filtering twice on startup due to unpopulated ruleset --- osu.Game/Screens/Select/SongSelect.cs | 37 +++++++++++++++------------ 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 6810f77b93..269fb1b683 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -46,40 +46,40 @@ namespace osu.Game.Screens.Select protected const float BACKGROUND_BLUR = 20; private const float left_area_padding = 20; - public readonly FilterControl FilterControl; + public FilterControl FilterControl; protected virtual bool ShowFooter => true; /// /// Can be null if is false. /// - protected readonly BeatmapOptionsOverlay BeatmapOptions; + protected BeatmapOptionsOverlay BeatmapOptions; /// /// Can be null if is false. /// - protected readonly Footer Footer; + protected Footer Footer; /// /// Contains any panel which is triggered by a footer button. /// Helps keep them located beneath the footer itself. /// - protected readonly Container FooterPanels; + protected Container FooterPanels; protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(); - protected readonly BeatmapCarousel Carousel; - private readonly BeatmapInfoWedge beatmapInfoWedge; + protected BeatmapCarousel Carousel; + private BeatmapInfoWedge beatmapInfoWedge; private DialogOverlay dialogOverlay; private BeatmapManager beatmaps; - protected readonly ModSelectOverlay ModSelect; + protected ModSelectOverlay ModSelect; protected SampleChannel SampleConfirm; private SampleChannel sampleChangeDifficulty; private SampleChannel sampleChangeBeatmap; - protected readonly BeatmapDetailArea BeatmapDetails; + protected BeatmapDetailArea BeatmapDetails; private readonly Bindable decoupledRuleset = new Bindable(); @@ -90,8 +90,14 @@ namespace osu.Game.Screens.Select [Cached(Type = typeof(IBindable>))] private readonly Bindable> mods = new Bindable>(Array.Empty()); // Bound to the game's mods, but is not reset on exiting - protected SongSelect() + protected virtual void ApplyFilterToCarousel(FilterCriteria criteria) => Carousel.Filter(criteria); + + [BackgroundDependencyLoader(true)] + private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins, ScoreManager scores) { + // transfer initial value so filter is in a good state (it uses our re-cached bindables). + transferRulesetValue(); + AddRangeInternal(new Drawable[] { new ParallaxContainer @@ -215,13 +221,7 @@ namespace osu.Game.Screens.Select } BeatmapDetails.Leaderboard.ScoreSelected += score => this.Push(new SoloResults(score)); - } - protected virtual void ApplyFilterToCarousel(FilterCriteria criteria) => Carousel.Filter(criteria); - - [BackgroundDependencyLoader(true)] - private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins, ScoreManager scores) - { if (Footer != null) { Footer.AddButton(new FooterButtonMods { Current = mods }, ModSelect); @@ -640,7 +640,7 @@ namespace osu.Game.Screens.Select return; // manual binding to parent ruleset to allow for delayed load in the incoming direction. - rulesetNoDebounce = decoupledRuleset.Value = Ruleset.Value; + transferRulesetValue(); Ruleset.ValueChanged += r => updateSelectedRuleset(r.NewValue); decoupledRuleset.ValueChanged += r => Ruleset.Value = r.NewValue; @@ -652,6 +652,11 @@ namespace osu.Game.Screens.Select boundLocalBindables = true; } + private void transferRulesetValue() + { + rulesetNoDebounce = decoupledRuleset.Value = Ruleset.Value; + } + private void delete(BeatmapSetInfo beatmap) { if (beatmap == null || beatmap.ID <= 0) return; From 2cd156f3d4d005e445e7015de7af4171d716f013 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Nov 2019 19:09:03 +0900 Subject: [PATCH 2309/2815] Clean up carousel tests --- .../SongSelect/TestSceneBeatmapCarousel.cs | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index aa63bc1cf6..783bd8ae39 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -10,6 +10,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets; @@ -51,11 +52,6 @@ namespace osu.Game.Tests.Visual.SongSelect private void load(RulesetStore rulesets) { this.rulesets = rulesets; - - Add(carousel = new TestBeatmapCarousel - { - RelativeSizeAxes = Axes.Both, - }); } /// @@ -375,6 +371,8 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestSelectingFilteredRuleset() { + createCarousel(); + var testMixed = createTestBeatmapSet(set_count + 1); AddStep("add mixed ruleset beatmapset", () => { @@ -429,6 +427,8 @@ namespace osu.Game.Tests.Visual.SongSelect private void loadBeatmaps(List beatmapSets = null) { + createCarousel(); + if (beatmapSets == null) { beatmapSets = new List(); @@ -448,6 +448,17 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("Wait for load", () => changed); } + private void createCarousel(Container target = null) + { + AddStep($"Create carousel", () => + { + (target ?? this).Child = carousel = new TestBeatmapCarousel + { + RelativeSizeAxes = Axes.Both, + }; + }); + } + private void ensureRandomFetchSuccess() => AddAssert("ensure prev random fetch worked", () => selectedSets.Peek() == carousel.SelectedBeatmapSet); From 110c155fa0daa22ebbb54e89f7e22ee4c07cee1b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Nov 2019 19:42:05 +0900 Subject: [PATCH 2310/2815] Fix background loading twice when entering song select --- osu.Game/Screens/Select/SongSelect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 375b994169..b814253f25 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -66,7 +66,7 @@ namespace osu.Game.Screens.Select /// protected readonly Container FooterPanels; - protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(); + protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value); protected readonly BeatmapCarousel Carousel; private readonly BeatmapInfoWedge beatmapInfoWedge; From 2fe068174ab434634a29c124ff15910cd3906c2e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Nov 2019 19:42:33 +0900 Subject: [PATCH 2311/2815] Fix unnecessary string interpolation --- 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 783bd8ae39..14077c8e53 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -450,7 +450,7 @@ namespace osu.Game.Tests.Visual.SongSelect private void createCarousel(Container target = null) { - AddStep($"Create carousel", () => + AddStep("Create carousel", () => { (target ?? this).Child = carousel = new TestBeatmapCarousel { From 8f7fd9993d787c9be72fb088acd57e57a22c49d5 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 13 Nov 2019 14:24:22 +0300 Subject: [PATCH 2312/2815] Fix preview track manager potentially not updating --- osu.Game/Audio/PreviewTrackManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index 72b33c4073..ede84e18fb 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -24,6 +24,8 @@ namespace osu.Game.Audio private TrackManagerPreviewTrack current; + public override bool IsPresent => Scheduler.HasPendingTasks; + [BackgroundDependencyLoader] private void load(AudioManager audio) { From 6715b25ddd767c1a1d2b8891033b827765ce2ac7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 13 Nov 2019 16:04:15 +0300 Subject: [PATCH 2313/2815] Apply suggestions --- osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index 09c388a3b0..b400141abf 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -118,14 +118,14 @@ namespace osu.Game.Overlays.BeatmapSet protected override bool OnClick(ClickEvent e) { - Selected.Value = !Selected.Value; - return base.OnClick(e); + Selected.Toggle(); + return true; } protected override bool OnHover(HoverEvent e) { updateState(); - return base.OnHover(e); + return false; } protected override void OnHoverLost(HoverLostEvent e) From 2dbee5da79e027b3903acb8fd280b6fa9aa85333 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 13 Nov 2019 16:06:37 +0300 Subject: [PATCH 2314/2815] Update dependency --- osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index bd639881e7..b400141abf 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -53,7 +53,6 @@ namespace osu.Game.Overlays.BeatmapSet return; modsContainer.Add(new ModButton(new NoMod())); - modsContainer.AddRange(ruleset.NewValue.CreateInstance().GetAllMods().Where(m => m.Ranked).Select(m => new ModButton(m))); modsContainer.ForEach(button => button.OnSelectionChanged = selectionChanged); @@ -119,14 +118,14 @@ namespace osu.Game.Overlays.BeatmapSet protected override bool OnClick(ClickEvent e) { - Selected.Value = !Selected.Value; - return base.OnClick(e); + Selected.Toggle(); + return true; } protected override bool OnHover(HoverEvent e) { updateState(); - return base.OnHover(e); + return false; } protected override void OnHoverLost(HoverLostEvent e) From 47cbd516f392b8d824d4cd8782bce0ecb88e0e44 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 13 Nov 2019 16:11:13 +0300 Subject: [PATCH 2315/2815] Revert "Fix preview track manager potentially not updating" This reverts commit 8f7fd9993d787c9be72fb088acd57e57a22c49d5. --- osu.Game/Audio/PreviewTrackManager.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index ede84e18fb..72b33c4073 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -24,8 +24,6 @@ namespace osu.Game.Audio private TrackManagerPreviewTrack current; - public override bool IsPresent => Scheduler.HasPendingTasks; - [BackgroundDependencyLoader] private void load(AudioManager audio) { From 172e777416e551c2a9ea177fb4e6cf6838d2c71b Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 13 Nov 2019 16:16:06 +0300 Subject: [PATCH 2316/2815] Fix preview track manager permanently muting game tracks --- osu.Game/Audio/PreviewTrackManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index 72b33c4073..201671849a 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -80,7 +80,6 @@ namespace osu.Game.Audio return; current.Stop(); - current = null; } /// From 4e90daf212d718d3a64611d1e153bcca292fce53 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 13 Nov 2019 16:39:33 +0300 Subject: [PATCH 2317/2815] Implement NoScoresPlaceholder --- .../BeatmapSet/Scores/NoScoresPlaceholder.cs | 50 +++++++++++++++++++ .../BeatmapSet/Scores/ScoresContainer.cs | 13 +++++ 2 files changed, 63 insertions(+) create mode 100644 osu.Game/Overlays/BeatmapSet/Scores/NoScoresPlaceholder.cs diff --git a/osu.Game/Overlays/BeatmapSet/Scores/NoScoresPlaceholder.cs b/osu.Game/Overlays/BeatmapSet/Scores/NoScoresPlaceholder.cs new file mode 100644 index 0000000000..b34e54b301 --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/Scores/NoScoresPlaceholder.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 osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Framework.Bindables; +using osu.Game.Screens.Select.Leaderboards; +using osu.Framework.Graphics.Sprites; + +namespace osu.Game.Overlays.BeatmapSet.Scores +{ + public class NoScoresPlaceholder : Container + { + public readonly Bindable Scope = new Bindable(); + + private readonly SpriteText text; + + public NoScoresPlaceholder() + { + AutoSizeAxes = Axes.Both; + Child = text = new SpriteText + { + Font = OsuFont.GetFont(), + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Scope.BindValueChanged(scope => text.Text = getText(scope.NewValue), true); + } + + private string getText(BeatmapLeaderboardScope scope) + { + switch (scope) + { + default: + case BeatmapLeaderboardScope.Global: + return @"No scores yet. Maybe should try setting some?"; + + case BeatmapLeaderboardScope.Friend: + return @"None of your friends has set a score on this map yet!"; + + case BeatmapLeaderboardScope.Country: + return @"No one from your country has set a score on this map yet!"; + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index bc5daa39a0..62ec9ca609 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -35,6 +35,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly LoadingAnimation loadingAnimation; private readonly FillFlowContainer modFilter; private readonly LeaderboardModSelector modSelector; + private readonly NoScoresPlaceholder noScoresPlaceholder; [Resolved] private IAPIProvider api { get; set; } @@ -125,6 +126,13 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Spacing = new Vector2(0, spacing), Children = new Drawable[] { + noScoresPlaceholder = new NoScoresPlaceholder + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Alpha = 0, + Scope = { BindTarget = scope } + }, topScoresContainer = new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -204,6 +212,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Scores = null; + noScoresPlaceholder.Hide(); + updateModFilterVisibility(); if (hasNoLeaderboard) @@ -215,6 +225,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { loadingAnimation.Hide(); Scores = scores; + + if (!scores.Scores.Any()) + noScoresPlaceholder.Show(); }; api.Queue(getScoresRequest); } From 3f6140db6dbe88f0412ca7e08c8206a1657c2b1f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 13 Nov 2019 17:18:58 +0300 Subject: [PATCH 2318/2815] Improve loading animation --- .../BeatmapSet/Scores/NoScoresPlaceholder.cs | 20 +++----- .../BeatmapSet/Scores/ScoresContainer.cs | 50 +++++++++++-------- 2 files changed, 37 insertions(+), 33 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/NoScoresPlaceholder.cs b/osu.Game/Overlays/BeatmapSet/Scores/NoScoresPlaceholder.cs index b34e54b301..4241ea3e54 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/NoScoresPlaceholder.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/NoScoresPlaceholder.cs @@ -4,7 +4,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; -using osu.Framework.Bindables; using osu.Game.Screens.Select.Leaderboards; using osu.Framework.Graphics.Sprites; @@ -12,8 +11,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { public class NoScoresPlaceholder : Container { - public readonly Bindable Scope = new Bindable(); - private readonly SpriteText text; public NoScoresPlaceholder() @@ -25,25 +22,22 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }; } - protected override void LoadComplete() - { - base.LoadComplete(); - Scope.BindValueChanged(scope => text.Text = getText(scope.NewValue), true); - } - - private string getText(BeatmapLeaderboardScope scope) + public void UpdateText(BeatmapLeaderboardScope scope) { switch (scope) { default: case BeatmapLeaderboardScope.Global: - return @"No scores yet. Maybe should try setting some?"; + text.Text = @"No scores yet. Maybe should try setting some?"; + return; case BeatmapLeaderboardScope.Friend: - return @"None of your friends has set a score on this map yet!"; + text.Text = @"None of your friends has set a score on this map yet!"; + return; case BeatmapLeaderboardScope.Country: - return @"No one from your country has set a score on this map yet!"; + text.Text = @"No one from your country has set a score on this map yet!"; + return; } } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 62ec9ca609..c74c4e3df0 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -23,6 +23,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores public class ScoresContainer : CompositeDrawable { private const int spacing = 15; + private const int duration = 200; public readonly Bindable Beatmap = new Bindable(); private readonly Bindable ruleset = new Bindable(); @@ -32,7 +33,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly Box background; private readonly ScoreTable scoreTable; private readonly FillFlowContainer topScoresContainer; - private readonly LoadingAnimation loadingAnimation; + private readonly DimmedLoadingLayer loading; private readonly FillFlowContainer modFilter; private readonly LeaderboardModSelector modSelector; private readonly NoScoresPlaceholder noScoresPlaceholder; @@ -89,7 +90,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores AutoSizeAxes = Axes.Y, Width = 0.95f, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, spacing), Margin = new MarginPadding { Vertical = spacing }, Children = new Drawable[] { @@ -116,8 +116,17 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Vertical = spacing }, Children = new Drawable[] { + noScoresPlaceholder = new NoScoresPlaceholder + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Alpha = 0, + AlwaysPresent = true, + Margin = new MarginPadding { Vertical = 10 } + }, new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -126,13 +135,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Spacing = new Vector2(0, spacing), Children = new Drawable[] { - noScoresPlaceholder = new NoScoresPlaceholder - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Alpha = 0, - Scope = { BindTarget = scope } - }, topScoresContainer = new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -147,12 +149,16 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } } }, - loadingAnimation = new LoadingAnimation + new Container { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Alpha = 0, - }, + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 10, + Child = loading = new DimmedLoadingLayer(iconScale: 0.8f) + { + Alpha = 0, + }, + } } } } @@ -210,24 +216,28 @@ namespace osu.Game.Overlays.BeatmapSet.Scores getScoresRequest?.Cancel(); getScoresRequest = null; - Scores = null; - - noScoresPlaceholder.Hide(); + noScoresPlaceholder.FadeOut(duration, Easing.OutQuint); updateModFilterVisibility(); if (hasNoLeaderboard) + { + Scores = null; return; + } - loadingAnimation.Show(); + loading.Show(); getScoresRequest = new GetScoresRequest(Beatmap.Value, Beatmap.Value.Ruleset, scope.Value, modSelector.SelectedMods); getScoresRequest.Success += scores => { - loadingAnimation.Hide(); + loading.Hide(); Scores = scores; if (!scores.Scores.Any()) - noScoresPlaceholder.Show(); + { + noScoresPlaceholder.UpdateText(scope.Value); + noScoresPlaceholder.FadeIn(duration, Easing.OutQuint); + } }; api.Queue(getScoresRequest); } From 8005ee73af2f77b9cb12f00cdeb07e40b05c9e77 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 13 Nov 2019 17:23:46 +0300 Subject: [PATCH 2319/2815] Trim whitespace --- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index c74c4e3df0..bc950a0c0c 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -116,7 +116,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Vertical = spacing }, + Margin = new MarginPadding { Vertical = spacing }, Children = new Drawable[] { noScoresPlaceholder = new NoScoresPlaceholder From f90a9db2b226bfc929430333898c7dc1e5195a7c Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 13 Nov 2019 22:29:44 +0800 Subject: [PATCH 2320/2815] Add BannedApiAnalyzer --- CodeAnalysis/BannedSymbols.txt | 4 ++++ Directory.Build.props | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 CodeAnalysis/BannedSymbols.txt diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt new file mode 100644 index 0000000000..9fb86485d2 --- /dev/null +++ b/CodeAnalysis/BannedSymbols.txt @@ -0,0 +1,4 @@ +M:System.Object.Equals(System.Object,System.Object)~System.Boolean;Don't use object.Equals. Use IEquatable or EqualityComparer.Default instead. +M:System.Object.Equals(System.Object)~System.Boolean;Don't use object.Equals. Use IEquatable or EqualityComparer.Default instead. +M:System.ValueType.Equals(System.Object)~System.Boolean;Don't use object.Equals(Fallbacks to ValueType). Use IEquatable or EqualityComparer.Default instead. +T:System.IComparable;Don't use non-generic IComparable. Use generic version instead. diff --git a/Directory.Build.props b/Directory.Build.props index b4baa2833e..9735c78913 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -14,6 +14,10 @@ + + + + From d1853ea55bfc0ddd04c7785ebb246a9865877389 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 13 Nov 2019 17:29:50 +0300 Subject: [PATCH 2321/2815] Fix incorrect formatting for switch/case --- .../BeatmapSet/Scores/NoScoresPlaceholder.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/NoScoresPlaceholder.cs b/osu.Game/Overlays/BeatmapSet/Scores/NoScoresPlaceholder.cs index 4241ea3e54..aefff5d567 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/NoScoresPlaceholder.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/NoScoresPlaceholder.cs @@ -28,16 +28,16 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { default: case BeatmapLeaderboardScope.Global: - text.Text = @"No scores yet. Maybe should try setting some?"; - return; + text.Text = @"No scores yet. Maybe should try setting some?"; + return; case BeatmapLeaderboardScope.Friend: - text.Text = @"None of your friends has set a score on this map yet!"; - return; + text.Text = @"None of your friends has set a score on this map yet!"; + return; case BeatmapLeaderboardScope.Country: - text.Text = @"No one from your country has set a score on this map yet!"; - return; + text.Text = @"No one from your country has set a score on this map yet!"; + return; } } } From 87d40cf8d019756114e91708e082fc34feb30878 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 13 Nov 2019 22:35:50 +0800 Subject: [PATCH 2322/2815] Resolve equals usages. --- osu.Game/Online/Leaderboards/Leaderboard.cs | 2 +- osu.Game/Overlays/DirectOverlay.cs | 2 +- osu.Game/Overlays/SocialOverlay.cs | 2 +- osu.Game/Screens/Menu/MenuSideFlashes.cs | 2 +- osu.Game/Screens/Play/KeyCounterAction.cs | 6 ++++-- osu.Game/Screens/Select/SongSelect.cs | 2 +- 6 files changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index d214743b30..94c50185da 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -101,7 +101,7 @@ namespace osu.Game.Online.Leaderboards get => scope; set { - if (value.Equals(scope)) + if (EqualityComparer.Default.Equals(value, scope)) return; scope = value; diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index 7dcf76e41f..494bfcb2cc 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -47,7 +47,7 @@ namespace osu.Game.Overlays get => beatmapSets; set { - if (beatmapSets?.Equals(value) ?? false) return; + if (beatmapSets?.SequenceEqual(value) ?? false) return; beatmapSets = value?.ToList(); diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index 6f468bbeb7..5987a5f138 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays get => users; set { - if (users?.Equals(value) ?? false) + if (users?.SequenceEqual(value) ?? false) return; users = value?.ToList(); diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs index 55a6a33e89..3a88cda4ef 100644 --- a/osu.Game/Screens/Menu/MenuSideFlashes.cs +++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs @@ -102,7 +102,7 @@ namespace osu.Game.Screens.Menu private void flash(Drawable d, double beatLength, bool kiai, TrackAmplitudes amplitudes) { - d.FadeTo(Math.Max(0, ((d.Equals(leftBox) ? amplitudes.LeftChannel : amplitudes.RightChannel) - amplitude_dead_zone) / (kiai ? kiai_multiplier : alpha_multiplier)), box_fade_in_time) + d.FadeTo(Math.Max(0, ((ReferenceEquals(d, leftBox) ? amplitudes.LeftChannel : amplitudes.RightChannel) - amplitude_dead_zone) / (kiai ? kiai_multiplier : alpha_multiplier)), box_fade_in_time) .Then() .FadeOut(beatLength, Easing.In); } diff --git a/osu.Game/Screens/Play/KeyCounterAction.cs b/osu.Game/Screens/Play/KeyCounterAction.cs index f60ad7aa5a..33d675358c 100644 --- a/osu.Game/Screens/Play/KeyCounterAction.cs +++ b/osu.Game/Screens/Play/KeyCounterAction.cs @@ -1,6 +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.Collections.Generic; + namespace osu.Game.Screens.Play { public class KeyCounterAction : KeyCounter @@ -16,7 +18,7 @@ namespace osu.Game.Screens.Play public bool OnPressed(T action, bool forwards) { - if (!action.Equals(Action)) + if (!EqualityComparer.Default.Equals(action, Action)) return false; IsLit = true; @@ -27,7 +29,7 @@ namespace osu.Game.Screens.Play public bool OnReleased(T action, bool forwards) { - if (!action.Equals(Action)) + if (!EqualityComparer.Default.Equals(action, Action)) return false; IsLit = false; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 375b994169..fb057fcf18 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -405,7 +405,7 @@ namespace osu.Game.Screens.Select // We may be arriving here due to another component changing the bindable Beatmap. // In these cases, the other component has already loaded the beatmap, so we don't need to do so again. - if (!Equals(beatmap, Beatmap.Value.BeatmapInfo)) + if (!beatmap.Equals(Beatmap.Value.BeatmapInfo)) { Logger.Log($"beatmap changed from \"{Beatmap.Value.BeatmapInfo}\" to \"{beatmap}\""); From 633c497602db249d5113eb2e35a725f1a53fff3d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 13 Nov 2019 17:36:08 +0300 Subject: [PATCH 2323/2815] Hide content if beatmap has no leaderboard --- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index bc950a0c0c..32e5314fba 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -37,6 +37,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly FillFlowContainer modFilter; private readonly LeaderboardModSelector modSelector; private readonly NoScoresPlaceholder noScoresPlaceholder; + private readonly FillFlowContainer content; [Resolved] private IAPIProvider api { get; set; } @@ -82,7 +83,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { RelativeSizeAxes = Axes.Both, }, - new FillFlowContainer + content = new FillFlowContainer { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -223,9 +224,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (hasNoLeaderboard) { Scores = null; + content.Hide(); return; } + content.Show(); loading.Show(); getScoresRequest = new GetScoresRequest(Beatmap.Value, Beatmap.Value.Ruleset, scope.Value, modSelector.SelectedMods); getScoresRequest.Success += scores => From 2d3fadc1a8d21ed1895db93542bec2b597a50645 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 14 Nov 2019 00:38:37 +0300 Subject: [PATCH 2324/2815] Don't update mod filter visibility on every beatmap change --- .../Overlays/BeatmapSet/Scores/ScoresContainer.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 32e5314fba..3e3f823368 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -204,12 +204,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { scope.Value = BeatmapLeaderboardScope.Global; modSelector.DeselectAll(); - updateModFilterVisibility(); - } - - private void updateModFilterVisibility() - { - modFilter.FadeTo(api.IsLoggedIn && api.LocalUser.Value.IsSupporter && !hasNoLeaderboard ? 1 : 0); + modFilter.FadeTo(api.IsLoggedIn && api.LocalUser.Value.IsSupporter ? 1 : 0); } private void getScores() @@ -219,9 +214,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores noScoresPlaceholder.FadeOut(duration, Easing.OutQuint); - updateModFilterVisibility(); - - if (hasNoLeaderboard) + if (Beatmap.Value?.OnlineBeatmapID.HasValue != true || Beatmap.Value.Status <= BeatmapSetOnlineStatus.Pending) { Scores = null; content.Hide(); @@ -244,7 +237,5 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }; api.Queue(getScoresRequest); } - - private bool hasNoLeaderboard => Beatmap.Value?.OnlineBeatmapID.HasValue != true || Beatmap.Value.Status <= BeatmapSetOnlineStatus.Pending; } } From a580b9079a7995718fb7a1083882a9288a339ea7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2019 08:28:41 +0900 Subject: [PATCH 2325/2815] Reword comment --- osu.Game/Screens/Select/SongSelect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 269fb1b683..1f16b134c4 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -95,7 +95,7 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader(true)] private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins, ScoreManager scores) { - // transfer initial value so filter is in a good state (it uses our re-cached bindables). + // initial value transfer is requried for FilterControl (it uses our re-cached bindables in its asynd loac for the initial filter). transferRulesetValue(); AddRangeInternal(new Drawable[] From d8a5750e5d1f3462bf7edc4d7d48ba5f35341fd1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2019 08:38:01 +0900 Subject: [PATCH 2326/2815] Fix typo 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/SongSelect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 1f16b134c4..b595003f0a 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -95,7 +95,7 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader(true)] private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins, ScoreManager scores) { - // initial value transfer is requried for FilterControl (it uses our re-cached bindables in its asynd loac for the initial filter). + // initial value transfer is required for FilterControl (it uses our re-cached bindables in its async load for the initial filter). transferRulesetValue(); AddRangeInternal(new Drawable[] From 1109d201c3b41c5fb134b075965c064bc78f0d6d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2019 13:28:13 +0900 Subject: [PATCH 2327/2815] Add failing test --- .../Visual/SongSelect/TestScenePlaySongSelect.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 794d135b06..d45b1bdba2 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -242,6 +242,22 @@ namespace osu.Game.Tests.Visual.SongSelect void onRulesetChange(ValueChangedEvent e) => rulesetChangeIndex = actionIndex++; } + [Test] + public void TestModsRetainedBetweenSongSelect() + { + AddAssert("empty mods", () => !Mods.Value.Any()); + + createSongSelect(); + + addRulesetImportStep(0); + + changeMods(new OsuModHardRock()); + + createSongSelect(); + + AddAssert("mods retained", () => Mods.Value.Any()); + } + [Test] public void TestStartAfterUnMatchingFilterDoesNotStart() { From c15f909d8383ccae1a670a6b0e673cb576b4bfcf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2019 13:29:06 +0900 Subject: [PATCH 2328/2815] Remove local bindable at song select, along with misplaced reset logic --- osu.Game/Screens/Select/SongSelect.cs | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 375b994169..00f2cc7e5b 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -30,7 +30,6 @@ using osuTK; using osuTK.Graphics; using osuTK.Input; using System; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using osu.Framework.Graphics.Sprites; @@ -86,10 +85,6 @@ namespace osu.Game.Screens.Select [Resolved(canBeNull: true)] private MusicController music { get; set; } - [Cached] - [Cached(Type = typeof(IBindable>))] - private readonly Bindable> mods = new Bindable>(Array.Empty()); // Bound to the game's mods, but is not reset on exiting - protected SongSelect() { AddRangeInternal(new Drawable[] @@ -222,7 +217,7 @@ namespace osu.Game.Screens.Select { if (Footer != null) { - Footer.AddButton(new FooterButtonMods { Current = mods }, ModSelect); + Footer.AddButton(new FooterButtonMods { Current = Mods }, ModSelect); Footer.AddButton(new FooterButtonRandom { Action = triggerRandom }); Footer.AddButton(new FooterButtonOptions(), BeatmapOptions); @@ -262,13 +257,6 @@ namespace osu.Game.Screens.Select } } - protected override void LoadComplete() - { - base.LoadComplete(); - - mods.BindTo(Mods); - } - private DependencyContainer dependencies; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -390,7 +378,7 @@ namespace osu.Game.Screens.Select { Logger.Log($"ruleset changed from \"{decoupledRuleset.Value}\" to \"{ruleset}\""); - mods.Value = Array.Empty(); + Mods.Value = Array.Empty(); decoupledRuleset.Value = ruleset; // force a filter before attempting to change the beatmap. @@ -538,9 +526,6 @@ namespace osu.Game.Screens.Select if (Beatmap.Value.Track != null) Beatmap.Value.Track.Looping = false; - mods.UnbindAll(); - Mods.Value = Array.Empty(); - return false; } From bf567e6df56fad7ef637a03b274cad2a1afa10fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2019 14:19:55 +0900 Subject: [PATCH 2329/2815] Make settings textboxes commit on focus lost --- osu.Game/Overlays/Settings/SettingsTextBox.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Settings/SettingsTextBox.cs b/osu.Game/Overlays/Settings/SettingsTextBox.cs index 0f257c2bfb..5e700a1d6b 100644 --- a/osu.Game/Overlays/Settings/SettingsTextBox.cs +++ b/osu.Game/Overlays/Settings/SettingsTextBox.cs @@ -12,6 +12,7 @@ namespace osu.Game.Overlays.Settings { Margin = new MarginPadding { Top = 5 }, RelativeSizeAxes = Axes.X, + CommitOnFocusLost = true, }; } } From 677717875e924aa1450954346ad04091489c2b0b Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 14 Nov 2019 17:35:02 +0800 Subject: [PATCH 2330/2815] Change a case to EqualityComparer.Default to handle null properly. --- osu.Game/Screens/Select/SongSelect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 8c92f57b68..12ae395cea 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -405,7 +405,7 @@ namespace osu.Game.Screens.Select // We may be arriving here due to another component changing the bindable Beatmap. // In these cases, the other component has already loaded the beatmap, so we don't need to do so again. - if (!beatmap.Equals(Beatmap.Value.BeatmapInfo)) + if (!EqualityComparer.Default.Equals(beatmap, Beatmap.Value.BeatmapInfo)) { Logger.Log($"beatmap changed from \"{Beatmap.Value.BeatmapInfo}\" to \"{beatmap}\""); From 81033e1fdf4fccf8e5f5947d79c4660477ed3822 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2019 18:52:07 +0900 Subject: [PATCH 2331/2815] Add extra logging --- osu.Game/Beatmaps/BeatmapManager.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 6e485f642a..7a616ed8f2 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -129,9 +129,12 @@ namespace osu.Game.Beatmaps { var beatmapIds = beatmapSet.Beatmaps.Where(b => b.OnlineBeatmapID.HasValue).Select(b => b.OnlineBeatmapID).ToList(); + LogForModel(beatmapSet, "Validating online IDs..."); + // ensure all IDs are unique if (beatmapIds.GroupBy(b => b).Any(g => g.Count() > 1)) { + LogForModel(beatmapSet, "Found non-unique IDs, resetting..."); resetIds(); return; } @@ -144,8 +147,12 @@ namespace osu.Game.Beatmaps // reset the import ids (to force a re-fetch) *unless* they match the candidate CheckForExisting set. // we can ignore the case where the new ids are contained by the CheckForExisting set as it will either be used (import skipped) or deleted. var existing = CheckForExisting(beatmapSet); + if (existing == null || existingBeatmaps.Any(b => !existing.Beatmaps.Contains(b))) + { + LogForModel(beatmapSet, "Found existing import with IDs already, resetting..."); resetIds(); + } } void resetIds() => beatmapSet.Beatmaps.ForEach(b => b.OnlineBeatmapID = null); From 1bc0eae2a6fa08edf390f47a4f40cc80f71af3f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2019 18:53:22 +0900 Subject: [PATCH 2332/2815] Fix beatmap online retrieval response running incorrectly scheduled --- osu.Game/Beatmaps/BeatmapManager.cs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 7a616ed8f2..80d41c5267 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -387,22 +387,21 @@ namespace osu.Game.Beatmaps var req = new GetBeatmapRequest(beatmap); - req.Success += res => - { - LogForModel(set, $"Online retrieval mapped {beatmap} to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}."); - - beatmap.Status = res.Status; - beatmap.BeatmapSet.Status = res.BeatmapSet.Status; - beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID; - beatmap.OnlineBeatmapID = res.OnlineBeatmapID; - }; - req.Failure += e => { LogForModel(set, $"Online retrieval failed for {beatmap} ({e.Message})"); }; try { // intentionally blocking to limit web request concurrency req.Perform(api); + + var res = req.Result; + + 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}."); } catch (Exception e) { From fbf81207d4a634bb4213d9794cdd72ed491c1ab5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2019 18:54:03 +0900 Subject: [PATCH 2333/2815] Don't assign server-fetched online id if it was assigned elsewhere --- osu.Game/Beatmaps/BeatmapManager.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 80d41c5267..7057bd46bb 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -399,7 +399,11 @@ namespace osu.Game.Beatmaps beatmap.Status = res.Status; beatmap.BeatmapSet.Status = res.BeatmapSet.Status; beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID; - beatmap.OnlineBeatmapID = res.OnlineBeatmapID; + + // note that this check only needs to be here if two identical hashed beatmaps exist int he same import. + // probably fine to leave it for safety. + if (set.Beatmaps.All(b => b.OnlineBeatmapID != res.OnlineBeatmapID)) + beatmap.OnlineBeatmapID = res.OnlineBeatmapID; LogForModel(set, $"Online retrieval mapped {beatmap} to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}."); } From 9f62ec869a6b8f6fed16e8cc6266df12e27e8e94 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2019 19:15:15 +0900 Subject: [PATCH 2334/2815] Add failing test --- .../Beatmaps/IO/ImportBeatmapTest.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 4e81954f50..4766411cbd 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -411,6 +411,48 @@ namespace osu.Game.Tests.Beatmaps.IO } } + [Test] + public async Task TestImportWithDuplicateHashes() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportNestedStructure))) + { + try + { + var osu = loadOsu(host); + + var temp = TestResources.GetTestBeatmapForImport(); + + string extractedFolder = $"{temp}_extracted"; + Directory.CreateDirectory(extractedFolder); + + try + { + using (var zip = ZipArchive.Open(temp)) + zip.WriteToDirectory(extractedFolder); + + using (var zip = ZipArchive.Create()) + { + zip.AddAllFromDirectory(extractedFolder); + zip.AddEntry("duplicate.osu", Directory.GetFiles(extractedFolder, "*.osu").First()); + zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate)); + } + + await osu.Dependencies.Get().Import(temp); + + ensureLoaded(osu); + } + finally + { + Directory.Delete(extractedFolder, true); + } + } + finally + { + host.Exit(); + } + } + } + [Test] public async Task TestImportNestedStructure() { From 12243aaa9aa32933e1c6ca3ff3a82629212a0851 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2019 19:02:11 +0900 Subject: [PATCH 2335/2815] Dedupe .osu files with same hash in same set --- osu.Game/Beatmaps/BeatmapManager.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 6e485f642a..524e4c7bf2 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -296,8 +296,13 @@ namespace osu.Game.Beatmaps var decoder = Decoder.GetDecoder(sr); IBeatmap beatmap = decoder.Decode(sr); + string hash = ms.ComputeSHA2Hash(); + + if (beatmapInfos.Any(b => b.Hash == hash)) + continue; + beatmap.BeatmapInfo.Path = file.Filename; - beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash(); + beatmap.BeatmapInfo.Hash = hash; beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash(); var ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID); From 4e79bbad6b030f712242e3d2221c39e072e1be6a Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 14 Nov 2019 14:19:42 +0300 Subject: [PATCH 2336/2815] Add inline comment --- osu.Game/Audio/PreviewTrackManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index 201671849a..56d507ef0e 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -80,6 +80,8 @@ namespace osu.Game.Audio return; current.Stop(); + // CurrentTrack must not change until the scheduled stopped event has been invoked. + // To ensure that this doesn't early-return on (CurrentTrack != track) check. } /// From d2ce0878bc5c99629ae29a9f6e833af21e869289 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 14 Nov 2019 14:20:29 +0300 Subject: [PATCH 2337/2815] Change 'current' accessibility to protected --- osu.Game/Audio/PreviewTrackManager.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index 56d507ef0e..d85df42d2b 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -22,7 +22,7 @@ namespace osu.Game.Audio private AudioManager audio; private PreviewTrackStore trackStore; - private TrackManagerPreviewTrack current; + protected TrackManagerPreviewTrack CurrentTrack; [BackgroundDependencyLoader] private void load(AudioManager audio) @@ -48,17 +48,17 @@ namespace osu.Game.Audio track.Started += () => Schedule(() => { - current?.Stop(); - current = track; + CurrentTrack?.Stop(); + CurrentTrack = track; audio.Tracks.AddAdjustment(AdjustableProperty.Volume, muteBindable); }); track.Stopped += () => Schedule(() => { - if (current != track) + if (CurrentTrack != track) return; - current = null; + CurrentTrack = null; audio.Tracks.RemoveAdjustment(AdjustableProperty.Volume, muteBindable); }); @@ -76,10 +76,10 @@ namespace osu.Game.Audio /// The which may be the owner of the . public void StopAnyPlaying(IPreviewTrackOwner source) { - if (current == null || current.Owner != source) + if (CurrentTrack == null || CurrentTrack.Owner != source) return; - current.Stop(); + CurrentTrack.Stop(); // CurrentTrack must not change until the scheduled stopped event has been invoked. // To ensure that this doesn't early-return on (CurrentTrack != track) check. } From 08e9813192f52d68cc0694f2c92e6661b0a9d742 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 14 Nov 2019 14:23:52 +0300 Subject: [PATCH 2338/2815] Add test ensuring correct changing behaviour --- .../TestScenePreviewTrackManager.cs | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs index 3a9fce90cd..0c5b485728 100644 --- a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs +++ b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs @@ -13,7 +13,7 @@ namespace osu.Game.Tests.Visual.Components { public class TestScenePreviewTrackManager : OsuTestScene, IPreviewTrackOwner { - private readonly PreviewTrackManager trackManager = new TestPreviewTrackManager(); + private readonly TestPreviewTrackManager trackManager = new TestPreviewTrackManager(); protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { @@ -108,6 +108,26 @@ namespace osu.Game.Tests.Visual.Components AddAssert("track stopped", () => !track.IsRunning); } + /// + /// Ensures that changes correctly. + /// + [Test] + public void TestCurrentTrackChanges() + { + PreviewTrack track = null; + TestTrackOwner owner = null; + + AddStep("get track", () => Add(owner = new TestTrackOwner(track = getTrack()))); + AddUntilStep("wait loaded", () => track.IsLoaded); + AddStep("start track", () => track.Start()); + AddAssert("current is track", () => trackManager.CurrentTrack == track); + AddStep("pause manager updates", () => trackManager.AllowUpdate = false); + AddStep("stop any playing", () => trackManager.StopAnyPlaying(owner)); + AddAssert("current not changed", () => trackManager.CurrentTrack == track); + AddStep("resume manager updates", () => trackManager.AllowUpdate = true); + AddAssert("current is null", () => trackManager.CurrentTrack == null); + } + private TestPreviewTrack getTrack() => (TestPreviewTrack)trackManager.Get(null); private TestPreviewTrack getOwnedTrack() @@ -144,8 +164,20 @@ namespace osu.Game.Tests.Visual.Components public class TestPreviewTrackManager : PreviewTrackManager { + public bool AllowUpdate = true; + + public new PreviewTrack CurrentTrack => base.CurrentTrack; + protected override TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackStore) => new TestPreviewTrack(beatmapSetInfo, trackStore); + public override bool UpdateSubTree() + { + if (!AllowUpdate) + return true; + + return base.UpdateSubTree(); + } + public class TestPreviewTrack : TrackManagerPreviewTrack { private readonly ITrackStore trackManager; From 0998afdbdb68eb3b5861b31a6370572e28d264e2 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 14 Nov 2019 19:23:19 +0800 Subject: [PATCH 2339/2815] Use ReferenceEquals instead of SequenceEqual. --- osu.Game/Overlays/DirectOverlay.cs | 2 +- osu.Game/Overlays/SocialOverlay.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index 494bfcb2cc..aedbd1b08b 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -47,7 +47,7 @@ namespace osu.Game.Overlays get => beatmapSets; set { - if (beatmapSets?.SequenceEqual(value) ?? false) return; + if (ReferenceEquals(beatmapSets, value)) return; beatmapSets = value?.ToList(); diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index 5987a5f138..da05cc7f9b 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays get => users; set { - if (users?.SequenceEqual(value) ?? false) + if (ReferenceEquals(users, value)) return; users = value?.ToList(); From 8ae869b788c70474726438935bfa78e59d65fd66 Mon Sep 17 00:00:00 2001 From: Gerard Dalmau Date: Thu, 14 Nov 2019 21:14:59 +0100 Subject: [PATCH 2340/2815] ADD version badge --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a078265d6c..65fb97eb5d 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,10 @@ # osu! -[![Build status](https://ci.appveyor.com/api/projects/status/u2p01nx7l6og8buh?svg=true)](https://ci.appveyor.com/project/peppy/osu) [![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu) [![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](https://discord.gg/ppy) +[![Build status](https://ci.appveyor.com/api/projects/status/u2p01nx7l6og8buh?svg=true)](https://ci.appveyor.com/project/peppy/osu) +[![GitHub release](https://img.shields.io/github/release/ppy/osu.svg)]() +[![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu) +[![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](https://discord.gg/ppy) Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commonly known by the codename "osu!lazer". Pew pew. From 8f02e57d1f799aadcbcfdc747e15a1be2a4e1f1a Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 15 Nov 2019 01:08:40 +0300 Subject: [PATCH 2341/2815] Add test ensuring correct muting behaviour --- .../TestScenePreviewTrackManager.cs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs index 0c5b485728..5dac40104a 100644 --- a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs +++ b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Containers; using osu.Game.Audio; @@ -15,6 +16,8 @@ namespace osu.Game.Tests.Visual.Components { private readonly TestPreviewTrackManager trackManager = new TestPreviewTrackManager(); + private AudioManager audio; + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); @@ -24,8 +27,10 @@ namespace osu.Game.Tests.Visual.Components } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { + this.audio = audio; + Add(trackManager); } @@ -128,6 +133,29 @@ namespace osu.Game.Tests.Visual.Components AddAssert("current is null", () => trackManager.CurrentTrack == null); } + /// + /// Ensures that mutes game-wide audio tracks correctly. + /// + [TestCase(false)] + [TestCase(true)] + public void TestEnsureMutingCorrectly(bool stopAnyPlaying) + { + PreviewTrack track = null; + TestTrackOwner owner = null; + + AddStep("get track", () => Add(owner = new TestTrackOwner(track = getTrack()))); + AddUntilStep("wait loaded", () => track.IsLoaded); + AddStep("start track", () => track.Start()); + AddAssert("game is muted", () => audio.Tracks.AggregateVolume.Value == 0); + + if (stopAnyPlaying) + AddStep("stop any playing", () => trackManager.StopAnyPlaying(owner)); + else + AddStep("stop track", () => track.Stop()); + + AddAssert("game not muted", () => audio.Tracks.AggregateVolume.Value != 0); + } + private TestPreviewTrack getTrack() => (TestPreviewTrack)trackManager.Get(null); private TestPreviewTrack getOwnedTrack() From 956fb9912d71168e8c579d698150a49deeefa78e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 Nov 2019 08:23:56 +0900 Subject: [PATCH 2342/2815] Hook up multiplayer search filter --- .../Screens/Multi/Lounge/Components/RoomsContainer.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index 99a6de0064..f5f43a52b1 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -24,6 +24,9 @@ namespace osu.Game.Screens.Multi.Lounge.Components private readonly FillFlowContainer roomFlow; public IReadOnlyList Rooms => roomFlow; + [Resolved] + private Bindable filter { get; set; } + [Resolved] private Bindable currentRoom { get; set; } @@ -57,6 +60,11 @@ namespace osu.Game.Screens.Multi.Lounge.Components addRooms(rooms); } + protected override void LoadComplete() + { + filter.BindValueChanged(f => Filter(f.NewValue), true); + } + private FilterCriteria currentFilter; public void Filter(FilterCriteria criteria) From 6d5484646297c146f5202cae667552416d5edac1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Nov 2019 10:18:47 +0900 Subject: [PATCH 2343/2815] Null online id on lookup failure --- osu.Game/Beatmaps/BeatmapManager.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 7057bd46bb..77fcf44a9b 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -387,7 +387,7 @@ namespace osu.Game.Beatmaps var req = new GetBeatmapRequest(beatmap); - req.Failure += e => { LogForModel(set, $"Online retrieval failed for {beatmap} ({e.Message})"); }; + req.Failure += fail; try { @@ -399,16 +399,18 @@ namespace osu.Game.Beatmaps beatmap.Status = res.Status; beatmap.BeatmapSet.Status = res.BeatmapSet.Status; beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID; - - // note that this check only needs to be here if two identical hashed beatmaps exist int he same import. - // probably fine to leave it for safety. - if (set.Beatmaps.All(b => b.OnlineBeatmapID != res.OnlineBeatmapID)) - beatmap.OnlineBeatmapID = res.OnlineBeatmapID; + beatmap.OnlineBeatmapID = res.OnlineBeatmapID; LogForModel(set, $"Online retrieval mapped {beatmap} to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}."); } catch (Exception e) { + fail(e); + } + + void fail(Exception e) + { + beatmap.OnlineBeatmapID = null; LogForModel(set, $"Online retrieval failed for {beatmap} ({e.Message})"); } } From f0b7b2e1c42d9a1fa5e0b257f5eac057922173c6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 Nov 2019 11:07:16 +0900 Subject: [PATCH 2344/2815] Fix broken test case --- osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index f5f43a52b1..4e9f645732 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components private readonly FillFlowContainer roomFlow; public IReadOnlyList Rooms => roomFlow; - [Resolved] + [Resolved(CanBeNull = true)] private Bindable filter { get; set; } [Resolved] @@ -62,7 +62,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components protected override void LoadComplete() { - filter.BindValueChanged(f => Filter(f.NewValue), true); + filter?.BindValueChanged(f => Filter(f.NewValue), true); } private FilterCriteria currentFilter; From 22c3be2c6f2ea039d11007778113464adc8884ed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Nov 2019 11:18:19 +0900 Subject: [PATCH 2345/2815] Clear count variables on carousel creation --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 14077c8e53..7ec76cdd2f 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -452,6 +452,9 @@ namespace osu.Game.Tests.Visual.SongSelect { AddStep("Create carousel", () => { + selectedSets.Clear(); + eagerSelectedIDs.Clear(); + (target ?? this).Child = carousel = new TestBeatmapCarousel { RelativeSizeAxes = Axes.Both, From e81dfcad4c62faf6844c1d20340c4026ed53b6f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Nov 2019 11:46:32 +0900 Subject: [PATCH 2346/2815] Fix remaining cases of pollution --- .../SongSelect/TestSceneBeatmapCarousel.cs | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 7ec76cdd2f..132b104afb 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -334,10 +334,19 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestHiding() { - BeatmapSetInfo hidingSet = createTestBeatmapSet(1); - hidingSet.Beatmaps[1].Hidden = true; + BeatmapSetInfo hidingSet = null; + List hiddenList = new List(); - loadBeatmaps(new List { hidingSet }); + AddStep("create hidden set", () => + { + hidingSet = createTestBeatmapSet(1); + hidingSet.Beatmaps[1].Hidden = true; + + hiddenList.Clear(); + hiddenList.Add(hidingSet); + }); + + loadBeatmaps(hiddenList); setSelected(1, 1); @@ -371,11 +380,14 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestSelectingFilteredRuleset() { + BeatmapSetInfo testMixed = null; + createCarousel(); - var testMixed = createTestBeatmapSet(set_count + 1); AddStep("add mixed ruleset beatmapset", () => { + testMixed = createTestBeatmapSet(set_count + 1); + for (int i = 0; i <= 2; i++) { testMixed.Beatmaps[i].Ruleset = rulesets.AvailableRulesets.ElementAt(i); From 15e85234e4d4b80df421bb1c9d8383b35c5aab10 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 15 Nov 2019 11:12:51 +0800 Subject: [PATCH 2347/2815] remove unecessary step --- .../Visual/UserInterface/TestSceneModCustomizationSettings.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs index ec5b3c1e16..83d77cccdb 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs @@ -34,7 +34,6 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("select mod", () => modSelect.SelectMod(testMod)); AddAssert("button enabled", () => modSelect.CustomizeButton.Enabled.Value); AddStep("open customization", () => modSelect.CustomizeButton.Click()); - AddAssert("controls exist", () => modSelect.GetControlSection(testMod) != null); AddStep("deselect mod", () => modSelect.SelectMod(testMod)); AddAssert("controls hidden", () => modSelect.ModSettingsContainer.Alpha == 0); } From 6d06b444bae4f8ccfbb7b00cd0ee16390e47d458 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Nov 2019 13:47:14 +0900 Subject: [PATCH 2348/2815] Allow screen to specify whether to apply audio rate adjustments from mods --- osu.Game/OsuGame.cs | 2 ++ osu.Game/Overlays/MusicController.cs | 27 ++++++++++++++++++++++++--- osu.Game/Screens/IOsuScreen.cs | 5 +++++ osu.Game/Screens/Menu/MainMenu.cs | 2 ++ osu.Game/Screens/OsuScreen.cs | 2 ++ osu.Game/Screens/StartupScreen.cs | 2 ++ 6 files changed, 37 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 328c964976..20e343ac0a 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -925,6 +925,8 @@ namespace osu.Game { OverlayActivationMode.Value = newOsuScreen.InitialOverlayActivationMode; + musicController.AllowRateAdjustments = newOsuScreen.AllowRateAdjustments; + if (newOsuScreen.HideOverlaysOnEnter) CloseAllOverlays(); else diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 9ec0364420..0db05ae6f7 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -47,7 +47,7 @@ namespace osu.Game.Overlays private OnScreenDisplay onScreenDisplay { get; set; } [BackgroundDependencyLoader] - private void load() + private void load(OsuGame game) { beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next())); beatmaps.ItemAdded += handleBeatmapAdded; @@ -233,6 +233,24 @@ namespace osu.Game.Overlays queuedDirection = null; } + private bool allowRateAdjustments; + + /// + /// Whether mod rate adjustments are allowed to be applied. + /// + public bool AllowRateAdjustments + { + get => allowRateAdjustments; + set + { + if (allowRateAdjustments == value) + return; + + allowRateAdjustments = value; + ResetTrackAdjustments(); + } + } + public void ResetTrackAdjustments() { var track = current?.Track; @@ -241,8 +259,11 @@ namespace osu.Game.Overlays track.ResetSpeedAdjustments(); - foreach (var mod in mods.Value.OfType()) - mod.ApplyToClock(track); + if (allowRateAdjustments) + { + foreach (var mod in mods.Value.OfType()) + mod.ApplyToClock(track); + } } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/IOsuScreen.cs b/osu.Game/Screens/IOsuScreen.cs index 9fc907c2a4..22fe0ad816 100644 --- a/osu.Game/Screens/IOsuScreen.cs +++ b/osu.Game/Screens/IOsuScreen.cs @@ -51,5 +51,10 @@ namespace osu.Game.Screens Bindable Beatmap { get; } Bindable Ruleset { get; } + + /// + /// Whether mod rate adjustments are allowed to be applied. + /// + bool AllowRateAdjustments { get; } } } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index c195ed6cb6..08338845e6 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -37,6 +37,8 @@ namespace osu.Game.Screens.Menu public override bool AllowExternalScreenChange => true; + public override bool AllowRateAdjustments => false; + private Screen songSelect; private MenuSideFlashes sideFlashes; diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 328631ff9c..94165fe4b7 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -91,6 +91,8 @@ namespace osu.Game.Screens public Bindable Ruleset { get; private set; } + public virtual bool AllowRateAdjustments => true; + public Bindable> Mods { get; private set; } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) diff --git a/osu.Game/Screens/StartupScreen.cs b/osu.Game/Screens/StartupScreen.cs index 797f185a37..c3e36c8e9d 100644 --- a/osu.Game/Screens/StartupScreen.cs +++ b/osu.Game/Screens/StartupScreen.cs @@ -16,6 +16,8 @@ namespace osu.Game.Screens public override bool CursorVisible => false; + public override bool AllowRateAdjustments => false; + public override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; } } From 2c3109980ab69e08eb0a25b1e86a57b5df5dcdb2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Nov 2019 13:51:39 +0900 Subject: [PATCH 2349/2815] Fix broken merge --- osu.Game/Screens/Select/SongSelect.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 862f8f9aa8..e3cd98454a 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -30,6 +30,7 @@ using osuTK; using osuTK.Graphics; using osuTK.Input; using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using osu.Framework.Graphics.Sprites; From c7d0b88854a66cfacab8ac91c2e885088a606ca3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Nov 2019 14:15:09 +0900 Subject: [PATCH 2350/2815] Update test scene --- .../Visual/Gameplay/{TestCaseLeadIn.cs => TestSceneLeadIn.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Tests/Visual/Gameplay/{TestCaseLeadIn.cs => TestSceneLeadIn.cs} (97%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs similarity index 97% rename from osu.Game.Tests/Visual/Gameplay/TestCaseLeadIn.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index 41cd80ed8c..3522c2265a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -15,7 +15,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Gameplay { - public class TestCaseLeadIn : RateAdjustedBeatmapTestScene + public class TestSceneLeadIn : RateAdjustedBeatmapTestScene { private Ruleset ruleset; From dbfec215e7acdf716f4c11c42f3ab8bd6c7711d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Nov 2019 14:47:28 +0900 Subject: [PATCH 2351/2815] Improve test quality --- .../Visual/Gameplay/TestSceneLeadIn.cs | 50 ++++++++----------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index 3522c2265a..addf7c3b28 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -1,61 +1,55 @@ // 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 NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps; -using osuTK.Graphics; namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneLeadIn : RateAdjustedBeatmapTestScene { - private Ruleset ruleset; - private LeadInPlayer player; - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - Add(new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Depth = int.MaxValue - }); - - ruleset = rulesets.AvailableRulesets.First().CreateInstance(); - } - [Test] public void TestShortLeadIn() { - AddStep("create player", () => loadPlayerWithBeatmap(new TestBeatmap(ruleset.RulesetInfo) { BeatmapInfo = { AudioLeadIn = 1000 } })); - AddUntilStep("correct lead-in", () => player.FirstFrameClockTime == 0); + loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo) + { + BeatmapInfo = { AudioLeadIn = 1000 } + }); + + AddUntilStep("player loaded", () => player.IsLoaded && player.Alpha == 1); + + AddAssert("correct lead-in", () => player.FirstFrameClockTime == 0); } [Test] public void TestLongLeadIn() { - AddStep("create player", () => loadPlayerWithBeatmap(new TestBeatmap(ruleset.RulesetInfo) { BeatmapInfo = { AudioLeadIn = 10000 } })); - AddUntilStep("correct lead-in", () => player.FirstFrameClockTime == player.GameplayStartTime - 10000); + loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo) + { + BeatmapInfo = { AudioLeadIn = 10000 } + }); + + AddAssert("correct lead-in", () => player.FirstFrameClockTime == player.GameplayStartTime - 10000); } private void loadPlayerWithBeatmap(IBeatmap beatmap) { - Beatmap.Value = new TestWorkingBeatmap(beatmap); + AddStep("create player", () => + { + Beatmap.Value = CreateWorkingBeatmap(beatmap); + LoadScreen(player = new LeadInPlayer()); + }); - LoadScreen(player = new LeadInPlayer()); + AddUntilStep("player loaded", () => player.IsLoaded && player.Alpha == 1); } - private class LeadInPlayer : Player + private class LeadInPlayer : TestPlayer { public LeadInPlayer() : base(false, false) From 813aebca1df0a46c4008b6cadcf5d536764cf086 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Nov 2019 14:51:01 +0900 Subject: [PATCH 2352/2815] Remove unintentional dependency --- osu.Game/Overlays/MusicController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 0db05ae6f7..5e0a67c2f7 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -47,7 +47,7 @@ namespace osu.Game.Overlays private OnScreenDisplay onScreenDisplay { get; set; } [BackgroundDependencyLoader] - private void load(OsuGame game) + private void load() { beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next())); beatmaps.ItemAdded += handleBeatmapAdded; From 1b4bcb81c895da963e7fbc655493864abbc3b96b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 Nov 2019 17:49:02 +0900 Subject: [PATCH 2353/2815] Fix filtering breaking on secondary filters --- .../Screens/Multi/Lounge/Components/DrawableRoom.cs | 10 ++++++++-- .../Screens/Multi/Lounge/Components/RoomsContainer.cs | 8 ++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs index 6ec8f2bfe5..f6cbe300f3 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs @@ -74,7 +74,9 @@ namespace osu.Game.Screens.Multi.Lounge.Components set { matchingFilter = value; - this.FadeTo(MatchingFilter ? 1 : 0, 200); + + if (IsLoaded) + this.FadeTo(MatchingFilter ? 1 : 0, 200); } } @@ -203,7 +205,11 @@ namespace osu.Game.Screens.Multi.Lounge.Components protected override void LoadComplete() { base.LoadComplete(); - this.FadeInFromZero(transition_duration); + + if (matchingFilter) + this.FadeInFromZero(transition_duration); + else + Alpha = 0; } private class RoomName : OsuSpriteText diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index 4e9f645732..77f4632bf1 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -65,8 +65,6 @@ namespace osu.Game.Screens.Multi.Lounge.Components filter?.BindValueChanged(f => Filter(f.NewValue), true); } - private FilterCriteria currentFilter; - public void Filter(FilterCriteria criteria) { roomFlow.Children.ForEach(r => @@ -82,15 +80,13 @@ namespace osu.Game.Screens.Multi.Lounge.Components { default: case SecondaryFilter.Public: - r.MatchingFilter = r.Room.Availability.Value == RoomAvailability.Public; + matchingFilter &= r.Room.Availability.Value == RoomAvailability.Public; break; } r.MatchingFilter = matchingFilter; } }); - - currentFilter = criteria; } private void addRooms(IEnumerable rooms) @@ -98,7 +94,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components foreach (var r in rooms) roomFlow.Add(new DrawableRoom(r) { Action = () => selectRoom(r) }); - Filter(currentFilter); + filter?.TriggerChange(); } private void removeRooms(IEnumerable rooms) From 1218d41b50ee6d1e52552c533c110448b312e63d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 15 Nov 2019 11:52:49 +0300 Subject: [PATCH 2354/2815] Make Ruleset a property --- .../BeatmapSet/LeaderboardModSelector.cs | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index b400141abf..660d957e1a 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -21,7 +21,34 @@ namespace osu.Game.Overlays.BeatmapSet public class LeaderboardModSelector : CompositeDrawable { public readonly BindableList SelectedMods = new BindableList(); - public readonly Bindable Ruleset = new Bindable(); + + private RulesetInfo ruleset; + + public RulesetInfo Ruleset + { + get => ruleset; + set + { + if (ruleset == value) + { + DeselectAll(); + return; + } + + ruleset = value; + + SelectedMods.Clear(); + modsContainer.Clear(); + + if (ruleset == null) + return; + + modsContainer.Add(new ModButton(new NoMod())); + modsContainer.AddRange(ruleset.CreateInstance().GetAllMods().Where(m => m.Ranked).Select(m => new ModButton(m))); + + modsContainer.ForEach(button => button.OnSelectionChanged = selectionChanged); + } + } private readonly FillFlowContainer modsContainer; @@ -38,26 +65,6 @@ namespace osu.Game.Overlays.BeatmapSet }; } - protected override void LoadComplete() - { - base.LoadComplete(); - Ruleset.BindValueChanged(onRulesetChanged, true); - } - - private void onRulesetChanged(ValueChangedEvent ruleset) - { - SelectedMods.Clear(); - modsContainer.Clear(); - - if (ruleset.NewValue == null) - return; - - modsContainer.Add(new ModButton(new NoMod())); - modsContainer.AddRange(ruleset.NewValue.CreateInstance().GetAllMods().Where(m => m.Ranked).Select(m => new ModButton(m))); - - modsContainer.ForEach(button => button.OnSelectionChanged = selectionChanged); - } - private void selectionChanged(Mod mod, bool selected) { if (selected) From 2592a0489b05fb4a7d5dfe112adc1802e5c27694 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 15 Nov 2019 11:57:40 +0300 Subject: [PATCH 2355/2815] Use existing ModNoMod --- .../Online/TestSceneLeaderboardModSelector.cs | 12 +++++------- .../BeatmapSet/LeaderboardModSelector.cs | 16 +--------------- osu.Game/Rulesets/Mods/ModNoMod.cs | 4 ++++ 3 files changed, 10 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs index 799528220b..94540eeb5b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs @@ -28,7 +28,6 @@ namespace osu.Game.Tests.Visual.Online { LeaderboardModSelector modSelector; FillFlowContainer selectedMods; - Bindable ruleset = new Bindable(); Add(selectedMods = new FillFlowContainer { @@ -40,7 +39,6 @@ namespace osu.Game.Tests.Visual.Online { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Ruleset = { BindTarget = ruleset } }); modSelector.SelectedMods.ItemsAdded += mods => @@ -66,12 +64,12 @@ namespace osu.Game.Tests.Visual.Online }); }; - AddStep("osu ruleset", () => ruleset.Value = new OsuRuleset().RulesetInfo); - AddStep("mania ruleset", () => ruleset.Value = new ManiaRuleset().RulesetInfo); - AddStep("taiko ruleset", () => ruleset.Value = new TaikoRuleset().RulesetInfo); - AddStep("catch ruleset", () => ruleset.Value = new CatchRuleset().RulesetInfo); + AddStep("osu ruleset", () => modSelector.Ruleset = new OsuRuleset().RulesetInfo); + AddStep("mania ruleset", () => modSelector.Ruleset = new ManiaRuleset().RulesetInfo); + AddStep("taiko ruleset", () => modSelector.Ruleset = new TaikoRuleset().RulesetInfo); + AddStep("catch ruleset", () => modSelector.Ruleset = new CatchRuleset().RulesetInfo); AddStep("Deselect all", () => modSelector.DeselectAll()); - AddStep("null ruleset", () => ruleset.Value = null); + AddStep("null ruleset", () => modSelector.Ruleset = null); } } } diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index 660d957e1a..c96840c959 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -14,7 +14,6 @@ using osuTK.Graphics; using System; using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.BeatmapSet { @@ -43,7 +42,7 @@ namespace osu.Game.Overlays.BeatmapSet if (ruleset == null) return; - modsContainer.Add(new ModButton(new NoMod())); + modsContainer.Add(new ModButton(new ModNoMod())); modsContainer.AddRange(ruleset.CreateInstance().GetAllMods().Where(m => m.Ranked).Select(m => new ModButton(m))); modsContainer.ForEach(button => button.OnSelectionChanged = selectionChanged); @@ -146,18 +145,5 @@ namespace osu.Game.Overlays.BeatmapSet protected override void OnHighlightedChanged(ValueChangedEvent highlighted) => this.FadeColour(highlighted.NewValue ? Color4.White : Color4.Gray, duration, Easing.OutQuint); } - - private class NoMod : Mod - { - public override string Name => "NoMod"; - - public override string Acronym => "NM"; - - public override double ScoreMultiplier => 1; - - public override IconUsage Icon => FontAwesome.Solid.Ban; - - public override ModType Type => ModType.System; - } } } diff --git a/osu.Game/Rulesets/Mods/ModNoMod.cs b/osu.Game/Rulesets/Mods/ModNoMod.cs index 0ddbd2a8cb..487985b2b3 100644 --- a/osu.Game/Rulesets/Mods/ModNoMod.cs +++ b/osu.Game/Rulesets/Mods/ModNoMod.cs @@ -1,6 +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 osu.Framework.Graphics.Sprites; + namespace osu.Game.Rulesets.Mods { /// @@ -11,5 +13,7 @@ namespace osu.Game.Rulesets.Mods public override string Name => "No Mod"; public override string Acronym => "NM"; public override double ScoreMultiplier => 1; + public override IconUsage Icon => FontAwesome.Solid.Ban; + public override ModType Type => ModType.System; } } From 10287eb66dfe31fb02a864e3343ce7373d2914bf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 Nov 2019 17:58:47 +0900 Subject: [PATCH 2356/2815] Add debounce logic in several places --- .../Multi/Lounge/Components/RoomsContainer.cs | 17 ++++++++++++++++- osu.Game/Screens/Multi/RoomManager.cs | 11 ++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index 77f4632bf1..3dde9452e4 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Threading; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osuTK; @@ -62,7 +63,10 @@ namespace osu.Game.Screens.Multi.Lounge.Components protected override void LoadComplete() { - filter?.BindValueChanged(f => Filter(f.NewValue), true); + filter?.BindValueChanged(f => scheduleFilter()); + + if (filter != null) + Filter(filter.Value); } public void Filter(FilterCriteria criteria) @@ -89,6 +93,17 @@ namespace osu.Game.Screens.Multi.Lounge.Components }); } + private ScheduledDelegate scheduledFilter; + + private void scheduleFilter() + { + if (filter == null) + return; + + scheduledFilter?.Cancel(); + scheduledFilter = Scheduler.AddDelayed(() => Filter(filter.Value), 200); + } + private void addRooms(IEnumerable rooms) { foreach (var r in rooms) diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs index cdaba85b9e..d6d3ec37ef 100644 --- a/osu.Game/Screens/Multi/RoomManager.cs +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; +using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Online; using osu.Game.Online.API; @@ -44,7 +45,7 @@ namespace osu.Game.Screens.Multi currentFilter.BindValueChanged(_ => { if (IsLoaded) - PollImmediately(); + schedulePoll(); }); } @@ -157,6 +158,14 @@ namespace osu.Game.Screens.Multi return tcs.Task; } + private ScheduledDelegate scheduledPoll; + + private void schedulePoll() + { + scheduledPoll?.Cancel(); + scheduledPoll = Scheduler.AddDelayed(PollImmediately, 200); + } + /// /// Updates a local with a remote copy. /// From 18f77008dbcafb9c69d1b09a25bf31fdb95c67b0 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 15 Nov 2019 12:04:01 +0300 Subject: [PATCH 2357/2815] Update dependency --- .../Online/TestSceneLeaderboardModSelector.cs | 14 ++--- .../BeatmapSet/LeaderboardModSelector.cs | 63 +++++++++---------- .../BeatmapSet/Scores/ScoresContainer.cs | 13 +--- osu.Game/Rulesets/Mods/ModNoMod.cs | 4 ++ 4 files changed, 39 insertions(+), 55 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs index 799528220b..c0c18c2583 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs @@ -11,8 +11,6 @@ using osu.Game.Rulesets.Taiko; using osu.Game.Rulesets.Catch; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Bindables; -using osu.Game.Rulesets; using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Tests.Visual.Online @@ -28,7 +26,6 @@ namespace osu.Game.Tests.Visual.Online { LeaderboardModSelector modSelector; FillFlowContainer selectedMods; - Bindable ruleset = new Bindable(); Add(selectedMods = new FillFlowContainer { @@ -40,7 +37,6 @@ namespace osu.Game.Tests.Visual.Online { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Ruleset = { BindTarget = ruleset } }); modSelector.SelectedMods.ItemsAdded += mods => @@ -66,12 +62,12 @@ namespace osu.Game.Tests.Visual.Online }); }; - AddStep("osu ruleset", () => ruleset.Value = new OsuRuleset().RulesetInfo); - AddStep("mania ruleset", () => ruleset.Value = new ManiaRuleset().RulesetInfo); - AddStep("taiko ruleset", () => ruleset.Value = new TaikoRuleset().RulesetInfo); - AddStep("catch ruleset", () => ruleset.Value = new CatchRuleset().RulesetInfo); + AddStep("osu ruleset", () => modSelector.Ruleset = new OsuRuleset().RulesetInfo); + AddStep("mania ruleset", () => modSelector.Ruleset = new ManiaRuleset().RulesetInfo); + AddStep("taiko ruleset", () => modSelector.Ruleset = new TaikoRuleset().RulesetInfo); + AddStep("catch ruleset", () => modSelector.Ruleset = new CatchRuleset().RulesetInfo); AddStep("Deselect all", () => modSelector.DeselectAll()); - AddStep("null ruleset", () => ruleset.Value = null); + AddStep("null ruleset", () => modSelector.Ruleset = null); } } } diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index b400141abf..c96840c959 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -14,14 +14,40 @@ using osuTK.Graphics; using System; using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.BeatmapSet { public class LeaderboardModSelector : CompositeDrawable { public readonly BindableList SelectedMods = new BindableList(); - public readonly Bindable Ruleset = new Bindable(); + + private RulesetInfo ruleset; + + public RulesetInfo Ruleset + { + get => ruleset; + set + { + if (ruleset == value) + { + DeselectAll(); + return; + } + + ruleset = value; + + SelectedMods.Clear(); + modsContainer.Clear(); + + if (ruleset == null) + return; + + modsContainer.Add(new ModButton(new ModNoMod())); + modsContainer.AddRange(ruleset.CreateInstance().GetAllMods().Where(m => m.Ranked).Select(m => new ModButton(m))); + + modsContainer.ForEach(button => button.OnSelectionChanged = selectionChanged); + } + } private readonly FillFlowContainer modsContainer; @@ -38,26 +64,6 @@ namespace osu.Game.Overlays.BeatmapSet }; } - protected override void LoadComplete() - { - base.LoadComplete(); - Ruleset.BindValueChanged(onRulesetChanged, true); - } - - private void onRulesetChanged(ValueChangedEvent ruleset) - { - SelectedMods.Clear(); - modsContainer.Clear(); - - if (ruleset.NewValue == null) - return; - - modsContainer.Add(new ModButton(new NoMod())); - modsContainer.AddRange(ruleset.NewValue.CreateInstance().GetAllMods().Where(m => m.Ranked).Select(m => new ModButton(m))); - - modsContainer.ForEach(button => button.OnSelectionChanged = selectionChanged); - } - private void selectionChanged(Mod mod, bool selected) { if (selected) @@ -139,18 +145,5 @@ namespace osu.Game.Overlays.BeatmapSet protected override void OnHighlightedChanged(ValueChangedEvent highlighted) => this.FadeColour(highlighted.NewValue ? Color4.White : Color4.Gray, duration, Easing.OutQuint); } - - private class NoMod : Mod - { - public override string Name => "NoMod"; - - public override string Acronym => "NM"; - - public override double ScoreMultiplier => 1; - - public override IconUsage Icon => FontAwesome.Solid.Ban; - - public override ModType Type => ModType.System; - } } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 3e3f823368..bdcf536e39 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -107,10 +107,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { Current = { BindTarget = scope } }, - modSelector = new LeaderboardModSelector - { - Ruleset = { BindTarget = ruleset } - } + modSelector = new LeaderboardModSelector() } }, new Container @@ -190,13 +187,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private void onBeatmapChanged(ValueChangedEvent beatmap) { - var beatmapRuleset = beatmap.NewValue?.Ruleset; - - if (ruleset.Value?.Equals(beatmapRuleset) ?? false) - modSelector.DeselectAll(); - else - ruleset.Value = beatmapRuleset; - + ruleset.Value = modSelector.Ruleset = beatmap.NewValue?.Ruleset; scope.Value = BeatmapLeaderboardScope.Global; } diff --git a/osu.Game/Rulesets/Mods/ModNoMod.cs b/osu.Game/Rulesets/Mods/ModNoMod.cs index 0ddbd2a8cb..487985b2b3 100644 --- a/osu.Game/Rulesets/Mods/ModNoMod.cs +++ b/osu.Game/Rulesets/Mods/ModNoMod.cs @@ -1,6 +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 osu.Framework.Graphics.Sprites; + namespace osu.Game.Rulesets.Mods { /// @@ -11,5 +13,7 @@ namespace osu.Game.Rulesets.Mods public override string Name => "No Mod"; public override string Acronym => "NM"; public override double ScoreMultiplier => 1; + public override IconUsage Icon => FontAwesome.Solid.Ban; + public override ModType Type => ModType.System; } } From e51fd00d588cd2b7488995b15f15b5533a8d759d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 15 Nov 2019 12:09:31 +0300 Subject: [PATCH 2358/2815] CI fix --- osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs | 2 -- osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs index 94540eeb5b..c0c18c2583 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs @@ -11,8 +11,6 @@ using osu.Game.Rulesets.Taiko; using osu.Game.Rulesets.Catch; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Bindables; -using osu.Game.Rulesets; using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Tests.Visual.Online diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index c96840c959..e62a754e92 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.BeatmapSet get => ruleset; set { - if (ruleset == value) + if (ruleset?.Equals(value) ?? false) { DeselectAll(); return; From f2862e95e0be0544027118cbf47cac3f00b99fa9 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 15 Nov 2019 12:15:51 +0300 Subject: [PATCH 2359/2815] CI fix --- osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index c96840c959..e62a754e92 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.BeatmapSet get => ruleset; set { - if (ruleset == value) + if (ruleset?.Equals(value) ?? false) { DeselectAll(); return; From e9e37fc821b1ce075d3cc2fba3ff92f5a260f8cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Nov 2019 18:23:35 +0900 Subject: [PATCH 2360/2815] Add private setter for FilterControl --- osu.Game/Screens/Select/SongSelect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 974120e658..089510513b 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Select protected const float BACKGROUND_BLUR = 20; private const float left_area_padding = 20; - public FilterControl FilterControl; + public FilterControl FilterControl { get; private set; } protected virtual bool ShowFooter => true; From 509440ea14bf0f6e9540953d1f8dbcb77328f420 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 16 Nov 2019 18:02:34 +0300 Subject: [PATCH 2361/2815] TotalCommentsCounter implementation --- .../Online/TestSceneTotalCommentsCounter.cs | 36 +++++++++ .../Overlays/Comments/TotalCommentsCounter.cs | 80 +++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneTotalCommentsCounter.cs create mode 100644 osu.Game/Overlays/Comments/TotalCommentsCounter.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneTotalCommentsCounter.cs b/osu.Game.Tests/Visual/Online/TestSceneTotalCommentsCounter.cs new file mode 100644 index 0000000000..4702d24125 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneTotalCommentsCounter.cs @@ -0,0 +1,36 @@ +// 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.Bindables; +using osu.Game.Overlays.Comments; +using osu.Framework.MathUtils; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneTotalCommentsCounter : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(TotalCommentsCounter), + }; + + public TestSceneTotalCommentsCounter() + { + var count = new BindableInt(); + + Add(new TotalCommentsCounter + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Current = { BindTarget = count } + }); + + AddStep(@"Set 100", () => count.Value = 100); + AddStep(@"Set 0", () => count.Value = 0); + AddStep(@"Set random", () => count.Value = RNG.Next(0, int.MaxValue)); + } + } +} diff --git a/osu.Game/Overlays/Comments/TotalCommentsCounter.cs b/osu.Game/Overlays/Comments/TotalCommentsCounter.cs new file mode 100644 index 0000000000..c71ca762e1 --- /dev/null +++ b/osu.Game/Overlays/Comments/TotalCommentsCounter.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.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; +using osuTK; +using osu.Framework.Allocation; +using osu.Framework.Bindables; + +namespace osu.Game.Overlays.Comments +{ + public class TotalCommentsCounter : CompositeDrawable + { + public readonly BindableInt Current = new BindableInt(); + + private readonly SpriteText counter; + + public TotalCommentsCounter() + { + RelativeSizeAxes = Axes.X; + Height = 50; + AddInternal(new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { Left = 50 }, + Spacing = new Vector2(5, 0), + Children = new Drawable[] + { + new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: 20, italics: true), + Text = @"Comments" + }, + new CircularContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.05f) + }, + counter = new SpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Horizontal = 10, Vertical = 5 }, + Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold) + } + }, + } + } + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + counter.Colour = colours.BlueLighter; + } + + protected override void LoadComplete() + { + Current.BindValueChanged(value => counter.Text = value.NewValue.ToString("N0"), true); + base.LoadComplete(); + } + } +} From 5727963f8642989bf87b915f48fe00053efd19d6 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 17 Nov 2019 20:43:34 +0800 Subject: [PATCH 2362/2815] Turn on xmldoc warning, and use appendive syntax for NoWarn. --- Directory.Build.props | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 9735c78913..838851b712 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -18,10 +18,14 @@ + + true + $(NoWarn);CS1591 + - NU1701 + $(NoWarn);NU1701 ppy Pty Ltd From f05b83d7d450c458077cafeaf012a8cb57283b00 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 17 Nov 2019 20:48:23 +0800 Subject: [PATCH 2363/2815] Use typeparamref. --- osu.Game/Beatmaps/Formats/Decoder.cs | 2 +- osu.Game/Database/ArchiveModelManager.cs | 12 ++++++------ .../DownloadableArchiveModelManager.cs | 12 ++++++------ osu.Game/Database/IModelDownloader.cs | 18 +++++++++--------- osu.Game/Database/IModelManager.cs | 2 +- .../Database/MutableDatabaseBackedStore.cs | 8 ++++---- .../Graphics/UserInterface/OsuTabControl.cs | 2 +- .../Bindings/DatabasedKeyBindingContainer.cs | 2 +- osu.Game/Online/DownloadTrackingComposite.cs | 4 ++-- osu.Game/Rulesets/UI/DrawableRuleset.cs | 4 ++-- 10 files changed, 33 insertions(+), 33 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs index 40c329eb7e..45122f6312 100644 --- a/osu.Game/Beatmaps/Formats/Decoder.cs +++ b/osu.Game/Beatmaps/Formats/Decoder.cs @@ -93,7 +93,7 @@ namespace osu.Game.Beatmaps.Formats /// /// Registers a fallback decoder instantiation function. /// The fallback will be returned if the first non-empty line of the decoded stream does not match any known magic. - /// Calling this method will overwrite any existing global fallback registration for type - use with caution. + /// Calling this method will overwrite any existing global fallback registration for type - use with caution. /// /// Type of object being decoded. /// A function that constructs the fallback. diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 8fa4eaf267..7cce2fb92f 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -54,13 +54,13 @@ namespace osu.Game.Database public Action PostNotification { protected get; set; } /// - /// Fired when a new becomes available in the database. + /// Fired when a new becomes available in the database. /// This is not guaranteed to run on the update thread. /// public event Action ItemAdded; /// - /// Fired when a is removed from the database. + /// Fired when a is removed from the database. /// This is not guaranteed to run on the update thread. /// public event Action ItemRemoved; @@ -95,7 +95,7 @@ namespace osu.Game.Database } /// - /// Import one or more items from filesystem . + /// Import one or more items from filesystem . /// This will post notifications tracking progress. /// /// One or more archive locations on disk. @@ -173,7 +173,7 @@ namespace osu.Game.Database } /// - /// Import one from the filesystem and delete the file on success. + /// Import one from the filesystem and delete the file on success. /// /// The archive location on disk. /// An optional cancellation token. @@ -275,7 +275,7 @@ namespace osu.Game.Database } /// - /// Import an item from a . + /// Import an item from a . /// /// The model to be imported. /// An optional archive to use for model population. @@ -589,7 +589,7 @@ namespace osu.Game.Database protected TModel CheckForExisting(TModel model) => model.Hash == null ? null : ModelStore.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash); /// - /// After an existing is found during an import process, the default behaviour is to restore the existing + /// After an existing is found during an import process, the default behaviour is to restore the existing /// item and skip the import. This method allows changing that behaviour. /// /// The existing model. diff --git a/osu.Game/Database/DownloadableArchiveModelManager.cs b/osu.Game/Database/DownloadableArchiveModelManager.cs index a81dff3475..0b7d63f469 100644 --- a/osu.Game/Database/DownloadableArchiveModelManager.cs +++ b/osu.Game/Database/DownloadableArchiveModelManager.cs @@ -41,17 +41,17 @@ namespace osu.Game.Database } /// - /// Creates the download request for this . + /// Creates the download request for this . /// - /// The to be downloaded. + /// The to be downloaded. /// Whether this download should be optimised for slow connections. Generally means extras are not included in the download bundle. /// The request object. protected abstract ArchiveDownloadRequest CreateDownloadRequest(TModel model, bool minimiseDownloadSize); /// - /// Begin a download for the requested . + /// Begin a download for the requested . /// - /// The to be downloaded. + /// The to be downloaded. /// Whether this download should be optimised for slow connections. Generally means extras are not included in the download bundle. /// Whether the download was started. public bool Download(TModel model, bool minimiseDownloadSize = false) @@ -131,9 +131,9 @@ namespace osu.Game.Database /// /// Performs implementation specific comparisons to determine whether a given model is present in the local store. /// - /// The whose existence needs to be checked. + /// The whose existence needs to be checked. /// The usable items present in the store. - /// Whether the exists. + /// Whether the exists. protected abstract bool CheckLocalAvailability(TModel model, IQueryable items); public ArchiveDownloadRequest GetExistingDownload(TModel model) => currentDownloads.Find(r => r.Model.Equals(model)); diff --git a/osu.Game/Database/IModelDownloader.cs b/osu.Game/Database/IModelDownloader.cs index f6f4b0aa42..17f1ccab06 100644 --- a/osu.Game/Database/IModelDownloader.cs +++ b/osu.Game/Database/IModelDownloader.cs @@ -14,34 +14,34 @@ namespace osu.Game.Database where TModel : class { /// - /// Fired when a download begins. + /// Fired when a download begins. /// event Action> DownloadBegan; /// - /// Fired when a download is interrupted, either due to user cancellation or failure. + /// Fired when a download is interrupted, either due to user cancellation or failure. /// event Action> DownloadFailed; /// - /// Checks whether a given is already available in the local store. + /// Checks whether a given is already available in the local store. /// - /// The whose existence needs to be checked. - /// Whether the exists. + /// The whose existence needs to be checked. + /// Whether the exists. bool IsAvailableLocally(TModel model); /// - /// Begin a download for the requested . + /// Begin a download for the requested . /// - /// The to be downloaded. + /// The to be downloaded. /// Whether this download should be optimised for slow connections. Generally means extras are not included in the download bundle.. /// Whether the download was started. bool Download(TModel model, bool minimiseDownloadSize); /// - /// Gets an existing download request if it exists. + /// Gets an existing download request if it exists. /// - /// The whose request is wanted. + /// The whose request is wanted. /// The object if it exists, otherwise null. ArchiveDownloadRequest GetExistingDownload(TModel model); } diff --git a/osu.Game/Database/IModelManager.cs b/osu.Game/Database/IModelManager.cs index 884814cb38..1bdbbb48e6 100644 --- a/osu.Game/Database/IModelManager.cs +++ b/osu.Game/Database/IModelManager.cs @@ -6,7 +6,7 @@ using System; namespace osu.Game.Database { /// - /// Represents a model manager that publishes events when s are added or removed. + /// Represents a model manager that publishes events when s are added or removed. /// /// The model type. public interface IModelManager diff --git a/osu.Game/Database/MutableDatabaseBackedStore.cs b/osu.Game/Database/MutableDatabaseBackedStore.cs index 39a48b5be6..4ca1eef989 100644 --- a/osu.Game/Database/MutableDatabaseBackedStore.cs +++ b/osu.Game/Database/MutableDatabaseBackedStore.cs @@ -30,7 +30,7 @@ namespace osu.Game.Database public IQueryable ConsumableItems => AddIncludesForConsumption(ContextFactory.Get().Set()); /// - /// Add a to the database. + /// Add a to the database. /// /// The item to add. public void Add(T item) @@ -45,7 +45,7 @@ namespace osu.Game.Database } /// - /// Update a in the database. + /// Update a in the database. /// /// The item to update. public void Update(T item) @@ -58,7 +58,7 @@ namespace osu.Game.Database } /// - /// Delete a from the database. + /// Delete a from the database. /// /// The item to delete. public bool Delete(T item) @@ -77,7 +77,7 @@ namespace osu.Game.Database } /// - /// Restore a from a deleted state. + /// Restore a from a deleted state. /// /// The item to undelete. public bool Undelete(T item) diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index 5d1bdc62e9..585a46f3e1 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -32,7 +32,7 @@ namespace osu.Game.Graphics.UserInterface protected virtual float StripHeight() => 1; /// - /// Whether entries should be automatically populated if is an type. + /// Whether entries should be automatically populated if is an type. /// protected virtual bool AddEnumEntriesAutomatically => true; diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index f34b8f14b0..ea274284ac 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -30,7 +30,7 @@ namespace osu.Game.Input.Bindings /// /// A reference to identify the current . Used to lookup mappings. Null for global mappings. /// An optional variant for the specified . Used when a ruleset has more than one possible keyboard layouts. - /// Specify how to deal with multiple matches of s and s. + /// Specify how to deal with multiple matches of s and s. public DatabasedKeyBindingContainer(RulesetInfo ruleset = null, int? variant = null, SimultaneousBindingMode simultaneousMode = SimultaneousBindingMode.None) : base(simultaneousMode) { diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs index 7bfdc7ff69..dcec17788a 100644 --- a/osu.Game/Online/DownloadTrackingComposite.cs +++ b/osu.Game/Online/DownloadTrackingComposite.cs @@ -11,7 +11,7 @@ using osu.Game.Online.API; namespace osu.Game.Online { /// - /// A component which tracks a through potential download/import/deletion. + /// A component which tracks a through potential download/import/deletion. /// public abstract class DownloadTrackingComposite : CompositeDrawable where TModel : class, IEquatable @@ -22,7 +22,7 @@ namespace osu.Game.Online private TModelManager manager; /// - /// Holds the current download state of the , whether is has already been downloaded, is in progress, or is not downloaded. + /// Holds the current download state of the , whether is has already been downloaded, is in progress, or is not downloaded. /// protected readonly Bindable State = new Bindable(); diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index d1749d33c0..aec0e58a10 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -246,9 +246,9 @@ namespace osu.Game.Rulesets.UI } /// - /// Creates and adds the visual representation of a to this . + /// Creates and adds the visual representation of a to this . /// - /// The to add the visual representation for. + /// The to add the visual representation for. private void addHitObject(TObject hitObject) { var drawableObject = CreateDrawableRepresentation(hitObject); From b1b234c6fbbdc55c7ceef31193350ea63baa437e Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 17 Nov 2019 20:49:36 +0800 Subject: [PATCH 2364/2815] Use paramref. --- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 2 +- osu.Game/Overlays/OnScreenDisplay.cs | 2 +- .../Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index c3e2b469ae..ce2783004c 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -218,7 +218,7 @@ namespace osu.Game.Beatmaps.ControlPoints } /// - /// Check whether should be added. + /// Check whether should be added. /// /// The time to find the timing control point at. /// A point to be added. diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs index a92320945e..e6708093c4 100644 --- a/osu.Game/Overlays/OnScreenDisplay.cs +++ b/osu.Game/Overlays/OnScreenDisplay.cs @@ -86,7 +86,7 @@ namespace osu.Game.Overlays /// The object that registered the to be tracked. /// The that is being tracked. /// If is null. - /// If is not being tracked from the same . + /// If is not being tracked from the same . public void StopTracking(object source, ITrackableConfigManager configManager) { if (configManager == null) throw new ArgumentNullException(nameof(configManager)); diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs index b7a5eedc22..5f053975c7 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms /// /// The point in time. /// The amount of visible time. - /// The time at which enters . + /// The time at which enters . double GetDisplayStartTime(double time, double timeRange); /// @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms /// The start time. /// The end time. /// The amount of visible time. - /// The absolute spatial length through . + /// The absolute spatial length through . /// The absolute spatial length. float GetLength(double startTime, double endTime, double timeRange, float scrollLength); @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms /// The time to compute the spatial position of. /// The current time. /// The amount of visible time. - /// The absolute spatial length through . + /// The absolute spatial length through . /// The absolute spatial position. float PositionAt(double time, double currentTime, double timeRange, float scrollLength); @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms /// The absolute spatial position. /// The current time. /// The amount of visible time. - /// The absolute spatial length through . + /// The absolute spatial length through . /// The time at which == . double TimeAt(float position, double currentTime, double timeRange, float scrollLength); From b04bca7db653c6e925313d567f813be8e898b252 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 17 Nov 2019 20:55:40 +0800 Subject: [PATCH 2365/2815] Reference elements in origin definition. --- .../Beatmaps/Patterns/Legacy/PatternGenerator.cs | 4 ++-- osu.Game/Graphics/Sprites/OsuSpriteText.cs | 4 ++-- osu.Game/Graphics/UserInterface/IconButton.cs | 4 ++-- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index fba52dfc32..149dafe449 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -139,7 +139,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// A function to retrieve the next column. If null, a randomisation scheme will be used. /// A function to perform additional validation checks to determine if a column is a valid candidate for a . /// The minimum column index. If null, is used. - /// The maximum column index. If null, is used. + /// The maximum column index. If null, is used. /// A list of patterns for which the validity of a column should be checked against. /// A column is not a valid candidate if a occupies the same column in any of the patterns. /// A column which has passed the check and for which there are no @@ -184,7 +184,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// Returns a random column index in the range [, ). /// /// The minimum column index. If null, is used. - /// The maximum column index. If null, is used. + /// The maximum column index. If null, is used. protected int GetRandomColumn(int? lowerBound = null, int? upperBound = null) => Random.Next(lowerBound ?? RandomStart, upperBound ?? TotalColumns); /// diff --git a/osu.Game/Graphics/Sprites/OsuSpriteText.cs b/osu.Game/Graphics/Sprites/OsuSpriteText.cs index ed771bb03f..bd1a7791e8 100644 --- a/osu.Game/Graphics/Sprites/OsuSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuSpriteText.cs @@ -19,7 +19,7 @@ namespace osu.Game.Graphics.Sprites public static class OsuSpriteTextTransformExtensions { /// - /// Sets to a new value after a duration. + /// Sets 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) @@ -27,7 +27,7 @@ namespace osu.Game.Graphics.Sprites => spriteText.TransformTo(nameof(OsuSpriteText.Text), newText, duration, easing); /// - /// Sets to a new value after a duration. + /// Sets 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) diff --git a/osu.Game/Graphics/UserInterface/IconButton.cs b/osu.Game/Graphics/UserInterface/IconButton.cs index 27427581fd..8600c5dcb1 100644 --- a/osu.Game/Graphics/UserInterface/IconButton.cs +++ b/osu.Game/Graphics/UserInterface/IconButton.cs @@ -16,7 +16,7 @@ namespace osu.Game.Graphics.UserInterface private Color4? iconColour; /// - /// The icon colour. This does not affect . + /// The icon colour. This does not affect . /// public Color4 IconColour { @@ -49,7 +49,7 @@ namespace osu.Game.Graphics.UserInterface } /// - /// The icon scale. This does not affect . + /// The icon scale. This does not affect . /// public Vector2 IconScale { diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c3436ffd45..fa0ca3d9b3 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -351,7 +351,7 @@ namespace osu.Game.Screens.Select /// /// Half the height of the visible content. /// - /// This is different from the height of , since + /// This is different from the height of .displayableContent, since /// the beatmap carousel bleeds into the and the /// /// From 4d1513cef60c2c0d74632ab08202a913dcbf652b Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 17 Nov 2019 21:01:56 +0800 Subject: [PATCH 2366/2815] Sadly, xmldoc doesn't support tuple elements. --- osu.Game/Beatmaps/BeatmapDifficulty.cs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs index 8727431e0e..c56fec67aa 100644 --- a/osu.Game/Beatmaps/BeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs @@ -56,10 +56,22 @@ namespace osu.Game.Beatmaps /// Maps a difficulty value [0, 10] to a two-piece linear range of values. /// /// The difficulty value to be mapped. - /// The values that define the two linear ranges. - /// Minimum of the resulting range which will be achieved by a difficulty value of 0. - /// Midpoint of the resulting range which will be achieved by a difficulty value of 5. - /// Maximum of the resulting range which will be achieved by a difficulty value of 10. + /// The values that define the two linear ranges. + /// + /// + /// od0 + /// Minimum of the resulting range which will be achieved by a difficulty value of 0. + /// + /// + /// od5 + /// Midpoint of the resulting range which will be achieved by a difficulty value of 5. + /// + /// + /// od10 + /// Maximum of the resulting range which will be achieved by a difficulty value of 10. + /// + /// + /// /// Value to which the difficulty value maps in the specified range. public static double DifficultyRange(double difficulty, (double od0, double od5, double od10) range) => DifficultyRange(difficulty, range.od0, range.od5, range.od10); From 53bc2dcab7a064ca0678497cf85c40a50c6e7a21 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 18 Nov 2019 15:29:18 +0800 Subject: [PATCH 2367/2815] Use costum displaying text for inherited references. --- .../Beatmaps/Patterns/Legacy/PatternGenerator.cs | 2 +- osu.Game/Graphics/Sprites/OsuSpriteText.cs | 4 ++-- osu.Game/Graphics/UserInterface/IconButton.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index 149dafe449..f989f22298 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -139,7 +139,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// A function to retrieve the next column. If null, a randomisation scheme will be used. /// A function to perform additional validation checks to determine if a column is a valid candidate for a . /// The minimum column index. If null, is used. - /// The maximum column index. If null, is used. + /// The maximum column index. If null, TotalColumns is used. /// A list of patterns for which the validity of a column should be checked against. /// A column is not a valid candidate if a occupies the same column in any of the patterns. /// A column which has passed the check and for which there are no diff --git a/osu.Game/Graphics/Sprites/OsuSpriteText.cs b/osu.Game/Graphics/Sprites/OsuSpriteText.cs index bd1a7791e8..cd988c347b 100644 --- a/osu.Game/Graphics/Sprites/OsuSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuSpriteText.cs @@ -19,7 +19,7 @@ namespace osu.Game.Graphics.Sprites public static class OsuSpriteTextTransformExtensions { /// - /// Sets to a new value after a duration. + /// 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) @@ -27,7 +27,7 @@ namespace osu.Game.Graphics.Sprites => spriteText.TransformTo(nameof(OsuSpriteText.Text), newText, duration, easing); /// - /// Sets to a new value after a duration. + /// 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) diff --git a/osu.Game/Graphics/UserInterface/IconButton.cs b/osu.Game/Graphics/UserInterface/IconButton.cs index 8600c5dcb1..d7e5666545 100644 --- a/osu.Game/Graphics/UserInterface/IconButton.cs +++ b/osu.Game/Graphics/UserInterface/IconButton.cs @@ -16,7 +16,7 @@ namespace osu.Game.Graphics.UserInterface private Color4? iconColour; /// - /// The icon colour. This does not affect . + /// The icon colour. This does not affect Colour. /// public Color4 IconColour { @@ -49,7 +49,7 @@ namespace osu.Game.Graphics.UserInterface } /// - /// The icon scale. This does not affect . + /// The icon scale. This does not affect Scale. /// public Vector2 IconScale { From 140278bf4c4753e749ccca2884479b6493b7e8bd Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 13 Nov 2019 18:15:16 +0800 Subject: [PATCH 2368/2815] Bump Xamarin.Android version to 10.0 --- osu.Android.props | 2 +- osu.Android/Properties/AndroidManifest.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 6fab2e7868..d06e04aea4 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ Off True Xamarin.Android.Net.AndroidClientHandler - v9.0 + v10.0 false true armeabi-v7a;x86;arm64-v8a diff --git a/osu.Android/Properties/AndroidManifest.xml b/osu.Android/Properties/AndroidManifest.xml index acd21f9587..770eaf2222 100644 --- a/osu.Android/Properties/AndroidManifest.xml +++ b/osu.Android/Properties/AndroidManifest.xml @@ -1,6 +1,6 @@  - + From 608785b99aecb959aeab0dbe470d1181890090de Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 13 Nov 2019 18:28:56 +0800 Subject: [PATCH 2369/2815] Update .Net Standard projects to 2.1 --- osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj | 2 +- osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj | 2 +- osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj | 2 +- osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj | 2 +- osu.Game.Tournament/osu.Game.Tournament.csproj | 2 +- osu.Game/osu.Game.csproj | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj index f24cf1def9..b19affbf9f 100644 --- a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj +++ b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj @@ -1,6 +1,6 @@  - netstandard2.0 + netstandard2.1 Library true catch the fruit. to the beat. diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj index 0af200d19b..07ef1022ae 100644 --- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj +++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj @@ -1,6 +1,6 @@  - netstandard2.0 + netstandard2.1 Library true smash the keys. to the beat. diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index fb3fe8808d..bffeaabb55 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -1,6 +1,6 @@  - netstandard2.0 + netstandard2.1 Library true click the circles. to the beat. diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj index 0a2b189c3a..ebed8c6d7c 100644 --- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj +++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj @@ -1,6 +1,6 @@  - netstandard2.0 + netstandard2.1 Library true bash the drum. to the beat. diff --git a/osu.Game.Tournament/osu.Game.Tournament.csproj b/osu.Game.Tournament/osu.Game.Tournament.csproj index f5306facaf..8e881fdd9c 100644 --- a/osu.Game.Tournament/osu.Game.Tournament.csproj +++ b/osu.Game.Tournament/osu.Game.Tournament.csproj @@ -1,6 +1,6 @@  - netstandard2.0 + netstandard2.1 Library true tools for tournaments. diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index af60da3e70..8e3858d0f1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -1,6 +1,6 @@  - netstandard2.0 + netstandard2.1 Library true From efbab5420667370d995e5b9f3a1315040ebff1bc Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 18 Nov 2019 18:48:29 +0800 Subject: [PATCH 2370/2815] Bump Android test projects version. --- .../Properties/AndroidManifest.xml | 2 +- .../Properties/AndroidManifest.xml | 2 +- .../Properties/AndroidManifest.xml | 2 +- .../Properties/AndroidManifest.xml | 2 +- osu.Game.Tests.Android/Properties/AndroidManifest.xml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml index db95e18f13..0fa3b7730d 100644 --- a/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml +++ b/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Mania.Tests.Android/Properties/AndroidManifest.xml index e6728c801d..de7935b2ef 100644 --- a/osu.Game.Rulesets.Mania.Tests.Android/Properties/AndroidManifest.xml +++ b/osu.Game.Rulesets.Mania.Tests.Android/Properties/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Osu.Tests.Android/Properties/AndroidManifest.xml index aad907b241..3ce17ccc27 100644 --- a/osu.Game.Rulesets.Osu.Tests.Android/Properties/AndroidManifest.xml +++ b/osu.Game.Rulesets.Osu.Tests.Android/Properties/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Taiko.Tests.Android/Properties/AndroidManifest.xml index cd4b74aa16..d9de0fde4e 100644 --- a/osu.Game.Rulesets.Taiko.Tests.Android/Properties/AndroidManifest.xml +++ b/osu.Game.Rulesets.Taiko.Tests.Android/Properties/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Tests.Android/Properties/AndroidManifest.xml index bb996dc5ca..4a63f0c357 100644 --- a/osu.Game.Tests.Android/Properties/AndroidManifest.xml +++ b/osu.Game.Tests.Android/Properties/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file From 465c8c26186aba9f98688fa048165046adbe66ad Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Tue, 19 Nov 2019 13:47:29 +0900 Subject: [PATCH 2371/2815] Re-add package references to osu.iOS.props --- osu.iOS.props | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.iOS.props b/osu.iOS.props index 8124357312..6965b16303 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -75,4 +75,17 @@ + + + + + + + + + + + + + From 08b8cedfdf4766b4d29c2e36bd4ed33277c3cd33 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 20 Nov 2019 01:15:40 +0300 Subject: [PATCH 2372/2815] Add setter to legacy skin configuration access --- osu.Game/Skinning/LegacySkin.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 35aca3aa7b..0ee41e654e 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -26,7 +26,11 @@ namespace osu.Game.Skinning [CanBeNull] protected IResourceStore Samples; - protected new LegacySkinConfiguration Configuration => (LegacySkinConfiguration)base.Configuration; + protected new LegacySkinConfiguration Configuration + { + get => base.Configuration as LegacySkinConfiguration; + set => base.Configuration = value; + } public LegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager) : this(skin, new LegacySkinResourceStore(skin, storage), audioManager, "skin.ini") @@ -41,10 +45,10 @@ namespace osu.Game.Skinning if (stream != null) { using (LineBufferedReader reader = new LineBufferedReader(stream)) - base.Configuration = new LegacySkinDecoder().Decode(reader); + Configuration = new LegacySkinDecoder().Decode(reader); } else - base.Configuration = new LegacySkinConfiguration { LegacyVersion = LegacySkinConfiguration.LATEST_VERSION }; + Configuration = new LegacySkinConfiguration { LegacyVersion = LegacySkinConfiguration.LATEST_VERSION }; if (storage != null) { From 76ed573c56793b267fdbc74832daafb46826044b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 Nov 2019 12:21:49 +0900 Subject: [PATCH 2373/2815] Fix crash when loading results after gameplay --- osu.Game/Scoring/ScoreInfo.cs | 5 ++++- osu.Game/Scoring/ScoreManager.cs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index d3c37bd4f4..ab344c7791 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -183,6 +183,9 @@ namespace osu.Game.Scoring public override string ToString() => $"{User} playing {Beatmap}"; - public bool Equals(ScoreInfo other) => other?.OnlineScoreID == OnlineScoreID; + public bool Equals(ScoreInfo other) => + other?.OnlineScoreID == OnlineScoreID + && other?.BeatmapInfoID == BeatmapInfoID + && other.Hash == Hash; } } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 8475158c78..3279af05b6 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -69,6 +69,6 @@ namespace osu.Game.Scoring protected override ArchiveDownloadRequest CreateDownloadRequest(ScoreInfo score, bool minimiseDownload) => new DownloadReplayRequest(score); - protected override bool CheckLocalAvailability(ScoreInfo model, IQueryable items) => items.Any(s => s.OnlineScoreID == model.OnlineScoreID && s.Files.Any()); + protected override bool CheckLocalAvailability(ScoreInfo model, IQueryable items) => items.Any(s => s.Equals(model) && s.Files.Any()); } } From 6288e6da566a29e5e61f1cc0e64fc4478838bf39 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 Nov 2019 13:42:54 +0900 Subject: [PATCH 2374/2815] Add null check --- osu.Game/Scoring/ScoreInfo.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index ab344c7791..f7bac82e74 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -184,8 +184,9 @@ namespace osu.Game.Scoring public override string ToString() => $"{User} playing {Beatmap}"; public bool Equals(ScoreInfo other) => - other?.OnlineScoreID == OnlineScoreID - && other?.BeatmapInfoID == BeatmapInfoID + other != null + && other.OnlineScoreID == OnlineScoreID + && other.BeatmapInfoID == BeatmapInfoID && other.Hash == Hash; } } From bcb1504110f732679752f43254f2bdf1f9b0fd0b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Nov 2019 15:40:35 +0900 Subject: [PATCH 2375/2815] Fix naming --- osu.Game/Skinning/LegacySkin.cs | 4 ++-- osu.Game/Skinning/LegacySkinConfiguration.cs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 0ee41e654e..7ffed18d7b 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -80,10 +80,10 @@ namespace osu.Game.Skinning case GlobalSkinColour colour: return SkinUtils.As(getCustomColour(colour.ToString())); - case LegacySkinConfigurations legacy: + case LegacySkinConfiguration.LegacySetting legacy: switch (legacy) { - case LegacySkinConfigurations.Version: + case LegacySkinConfiguration.LegacySetting.Version: if (Configuration.LegacyVersion is decimal version) return SkinUtils.As(new Bindable(version)); diff --git a/osu.Game/Skinning/LegacySkinConfiguration.cs b/osu.Game/Skinning/LegacySkinConfiguration.cs index 33c29cd47c..6a667e69f4 100644 --- a/osu.Game/Skinning/LegacySkinConfiguration.cs +++ b/osu.Game/Skinning/LegacySkinConfiguration.cs @@ -11,10 +11,10 @@ namespace osu.Game.Skinning /// Legacy version of this skin. Null if no version was set to allow fallback to a parent skin version. /// public decimal? LegacyVersion { get; internal set; } - } - public enum LegacySkinConfigurations - { - Version, + public enum LegacySetting + { + Version, + } } } From e0f59d8e24ea0c0f2f64dcda6fc8a6ddf5e72cf9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Nov 2019 15:43:00 +0900 Subject: [PATCH 2376/2815] Move method --- osu.Game/Screens/Select/SongSelect.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 089510513b..b5e62740f2 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -88,8 +88,6 @@ namespace osu.Game.Screens.Select [Resolved(canBeNull: true)] private MusicController music { get; set; } - protected virtual void ApplyFilterToCarousel(FilterCriteria criteria) => Carousel.Filter(criteria); - [BackgroundDependencyLoader(true)] private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins, ScoreManager scores) { @@ -262,6 +260,8 @@ namespace osu.Game.Screens.Select } } + protected virtual void ApplyFilterToCarousel(FilterCriteria criteria) => Carousel.Filter(criteria); + private DependencyContainer dependencies; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) From 5b416eb7ba35c261810368b2115079e375d17dd0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Nov 2019 17:24:43 +0900 Subject: [PATCH 2377/2815] Move initial filter to run on entering --- osu.Game/Screens/Select/SongSelect.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index b5e62740f2..a52edb70db 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -260,7 +260,11 @@ namespace osu.Game.Screens.Select } } - protected virtual void ApplyFilterToCarousel(FilterCriteria criteria) => Carousel.Filter(criteria); + protected virtual void ApplyFilterToCarousel(FilterCriteria criteria) + { + if (this.IsCurrentScreen()) + Carousel.Filter(criteria); + } private DependencyContainer dependencies; @@ -433,6 +437,8 @@ namespace osu.Game.Screens.Select { base.OnEntering(last); + Carousel.Filter(FilterControl.CreateCriteria(), false); + this.FadeInFromZero(250); FilterControl.Activate(); } From 3ba9f840fdbae2ba70b6f23edf0f12806ac23917 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Nov 2019 19:38:39 +0900 Subject: [PATCH 2378/2815] Fix song select not always scrolling to the correct location --- osu.Game/Screens/Select/BeatmapCarousel.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index fa0ca3d9b3..beffa5b33f 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -452,9 +452,6 @@ namespace osu.Game.Screens.Select if (!itemsCache.IsValid) updateItems(); - if (!scrollPositionCache.IsValid) - updateScrollPosition(); - // Remove all items that should no longer be on-screen scrollableContent.RemoveAll(p => p.Y < visibleUpperBound - p.DrawHeight || p.Y > visibleBottomBound || !p.IsPresent); @@ -519,6 +516,14 @@ namespace osu.Game.Screens.Select updateItem(p); } + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + if (!scrollPositionCache.IsValid) + updateScrollPosition(); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -637,8 +642,11 @@ namespace osu.Game.Screens.Select private void updateScrollPosition() { - if (scrollTarget != null) scroll.ScrollTo(scrollTarget.Value); - scrollPositionCache.Validate(); + if (scrollTarget != null) + { + scroll.ScrollTo(scrollTarget.Value); + scrollPositionCache.Validate(); + } } /// From 6cace0c5f5cba38be8c97a65368031a22330a8d8 Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Wed, 20 Nov 2019 12:43:14 +0100 Subject: [PATCH 2379/2815] Replace all Mathhelper.Clamp usages with Math.Clamp --- osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs | 5 ++--- .../Difficulty/CatchPerformanceCalculator.cs | 2 +- osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs | 2 +- osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs | 2 +- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 4 ++-- .../Beatmaps/Patterns/Legacy/PatternGenerator.cs | 6 +++--- osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs | 4 ++-- osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs | 4 ++-- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 3 ++- .../Objects/Drawables/DrawableRepeatPoint.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 3 ++- .../Objects/Drawables/DrawableSliderHead.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 3 ++- .../Objects/Drawables/Pieces/SnakingSliderBody.cs | 2 +- .../Objects/Drawables/DrawableDrumRoll.cs | 4 ++-- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs | 3 +-- .../UI/TaikoPlayfieldAdjustmentContainer.cs | 3 ++- osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs | 3 ++- osu.Game/Graphics/Containers/ParallaxContainer.cs | 3 ++- osu.Game/Graphics/UserInterface/Bar.cs | 4 ++-- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 4 ++-- osu.Game/Graphics/UserInterface/OsuTabControl.cs | 2 +- osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs | 2 +- osu.Game/Overlays/Music/PlaylistList.cs | 2 +- .../Overlays/Profile/Header/Components/SupporterIcon.cs | 4 ++-- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 3 +-- osu.Game/Rulesets/Objects/SliderEventGenerator.cs | 2 +- osu.Game/Rulesets/Objects/SliderPath.cs | 2 +- osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs | 2 +- .../UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs | 4 ++-- .../Edit/Components/Timelines/Summary/Parts/MarkerPart.cs | 3 ++- .../Edit/Compose/Components/CircularDistanceSnapGrid.cs | 2 +- .../Compose/Components/Timeline/ZoomableScrollContainer.cs | 4 ++-- osu.Game/Screens/Edit/EditorClock.cs | 3 +-- osu.Game/Screens/Menu/Button.cs | 2 +- osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 4 ++-- osu.Game/Screens/Play/SongProgressBar.cs | 2 +- osu.Game/Screens/Play/SquareGraph.cs | 2 +- osu.Game/Screens/ScreenWhiteBox.cs | 6 +++--- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- osu.Game/Screens/Select/Details/FailRetryGraph.cs | 4 ++-- osu.Game/Tests/Visual/OsuTestScene.cs | 3 +-- 42 files changed, 65 insertions(+), 63 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index 58bf811fac..db52fbac1b 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -8,7 +8,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects.Types; -using osuTK; using osu.Game.Rulesets.Catch.MathUtils; using osu.Game.Rulesets.Mods; @@ -78,7 +77,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps catchObject.XOffset = 0; if (catchObject is TinyDroplet) - catchObject.XOffset = MathHelper.Clamp(rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH, -catchObject.X, 1 - catchObject.X); + catchObject.XOffset = Math.Clamp(rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH, -catchObject.X, 1 - catchObject.X); else if (catchObject is Droplet) rng.Next(); // osu!stable retrieved a random droplet rotation } @@ -230,7 +229,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps else { currentObject.DistanceToHyperDash = distanceToHyper; - lastExcess = MathHelper.Clamp(distanceToHyper, 0, halfCatcherWidth); + lastExcess = Math.Clamp(distanceToHyper, 0, halfCatcherWidth); } lastDirection = thisDirection; diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index 5a640f6d1a..638fd3f603 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty return value; } - private float accuracy() => totalHits() == 0 ? 0 : MathHelper.Clamp((float)totalSuccessfulHits() / totalHits(), 0f, 1f); + private float accuracy() => totalHits() == 0 ? 0 : Math.Clamp((float)totalSuccessfulHits() / totalHits(), 0f, 1f); private int totalHits() => tinyTicksHit + ticksHit + fruitsHit + misses + tinyTicksMissed; private int totalSuccessfulHits() => tinyTicksHit + ticksHit + fruitsHit; private int totalComboHits() => misses + ticksHit + fruitsHit; diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index d146153294..800346280d 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills if (lastPlayerPosition == null) lastPlayerPosition = catchCurrent.LastNormalizedPosition; - float playerPosition = MathHelper.Clamp( + float playerPosition = Math.Clamp( lastPlayerPosition.Value, catchCurrent.NormalizedPosition - (normalized_hitobject_radius - absolute_player_positioning_error), catchCurrent.NormalizedPosition + (normalized_hitobject_radius - absolute_player_positioning_error) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs index 1af77b75fc..eae652573b 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs @@ -278,7 +278,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { base.Update(); - border.Alpha = (float)MathHelper.Clamp((HitObject.StartTime - Time.Current) / 500, 0, 1); + border.Alpha = (float)Math.Clamp((HitObject.StartTime - Time.Current) / 500, 0, 1); } private Color4 colourForRepresentation(FruitVisualRepresentation representation) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 56c8b33e02..d330add1c4 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -235,7 +235,7 @@ namespace osu.Game.Rulesets.Catch.UI fruit.Y -= RNG.NextSingle() * diff; } - fruit.X = MathHelper.Clamp(fruit.X, -CATCHER_SIZE / 2, CATCHER_SIZE / 2); + fruit.X = Math.Clamp(fruit.X, -CATCHER_SIZE / 2, CATCHER_SIZE / 2); caughtFruit.Add(fruit); } @@ -378,7 +378,7 @@ namespace osu.Game.Rulesets.Catch.UI double speed = BASE_SPEED * dashModifier * hyperDashModifier; Scale = new Vector2(Math.Abs(Scale.X) * direction, Scale.Y); - X = (float)MathHelper.Clamp(X + direction * Clock.ElapsedFrameTime * speed, 0, 1); + X = (float)Math.Clamp(X + direction * Clock.ElapsedFrameTime * speed, 0, 1); // Correct overshooting. if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) || diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index f989f22298..eadf60b10b 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -54,11 +54,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (allowSpecial && TotalColumns == 8) { const float local_x_divisor = 512f / 7; - return MathHelper.Clamp((int)Math.Floor(position / local_x_divisor), 0, 6) + 1; + return Math.Clamp((int)Math.Floor(position / local_x_divisor), 0, 6) + 1; } float localXDivisor = 512f / TotalColumns; - return MathHelper.Clamp((int)Math.Floor(position / localXDivisor), 0, TotalColumns - 1); + return Math.Clamp((int)Math.Floor(position / localXDivisor), 0, TotalColumns - 1); } /// @@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy drainTime = 10000; BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty; - conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)OriginalBeatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15; + conversionDifficulty = ((difficulty.DrainRate + Math.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)OriginalBeatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15; conversionDifficulty = Math.Min(conversionDifficulty.Value, 12); return conversionDifficulty.Value; diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 9cdf045b5b..53f7e30dfd 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.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.Timing; @@ -9,7 +10,6 @@ using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit.Compose.Components; -using osuTK; namespace osu.Game.Rulesets.Mania.Edit { @@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Mania.Edit maxColumn = obj.Column; } - columnDelta = MathHelper.Clamp(columnDelta, -minColumn, composer.TotalColumns - 1 - maxColumn); + columnDelta = Math.Clamp(columnDelta, -minColumn, composer.TotalColumns - 1 - maxColumn); foreach (var obj in SelectedHitObjects.OfType()) obj.Column += columnDelta; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index 1eb37f8119..63110b2797 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.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.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -13,7 +14,6 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; -using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Mods @@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Osu.Mods }; } - private float calculateGap(float value) => MathHelper.Clamp(value, 0, target_clamp) * targetBreakMultiplier; + private float calculateGap(float value) => Math.Clamp(value, 0, target_clamp) * targetBreakMultiplier; // lagrange polinominal for (0,0) (0.6,0.4) (1,1) should make a good curve private static float applyAdjustmentCurve(float value) => 0.6f * value * value + 0.4f * value; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 7fa3dbe07e..778c2f7d43 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.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 osu.Framework.Bindables; @@ -55,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Mods var destination = e.MousePosition; FlashlightPosition = Interpolation.ValueAt( - MathHelper.Clamp(Clock.ElapsedFrameTime, 0, follow_delay), position, destination, 0, follow_delay, Easing.Out); + Math.Clamp(Clock.ElapsedFrameTime, 0, follow_delay), position, destination, 0, follow_delay, Easing.Out); return base.OnMouseMove(e); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index 84d2a4af9b..122975d55e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables else { // If we're already snaking, interpolate to smooth out sharp curves (linear sliders, mainly). - Rotation = Interpolation.ValueAt(MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 100), Rotation, aimRotation, 0, 50, Easing.OutQuint); + Rotation = Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, 100), Rotation, aimRotation, 0, 50, Easing.OutQuint); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 433d29f2e4..69189758a6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.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 osuTK; using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; @@ -165,7 +166,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Tracking.Value = Ball.Tracking; - double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); + double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); Ball.UpdateProgress(completionProgress); Body.UpdateProgress(completionProgress); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index 66b6f0f9ac..a10c66d1df 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.Update(); - double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); + double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); //todo: we probably want to reconsider this before adding scoring, but it looks and feels nice. if (!IsHit) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index d1b9ee6cb4..1261d3d19a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.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.Graphics; using osu.Framework.Graphics.Containers; @@ -136,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables positionBindable.BindTo(HitObject.PositionBindable); } - public float Progress => MathHelper.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1); + public float Progress => Math.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1); protected override void CheckForResult(bool userTriggered, double timeOffset) { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs index 70a1bad4a3..f2150280b3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces var spanProgress = slider.ProgressAt(completionProgress); double start = 0; - double end = SnakingIn.Value ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / (slider.TimePreempt / 3), 0, 1) : 1; + double end = SnakingIn.Value ? Math.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / (slider.TimePreempt / 3), 0, 1) : 1; if (span >= slider.SpanCount() - 1) { diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index cc0d6829ba..338fd9e20f 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -1,12 +1,12 @@ // 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.MathUtils; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; -using osuTK; using osuTK.Graphics; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using osu.Framework.Graphics; @@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables else rollingHits--; - rollingHits = MathHelper.Clamp(rollingHits, 0, rolling_hits_for_engaged_colour); + rollingHits = Math.Clamp(rollingHits, 0, rolling_hits_for_engaged_colour); Color4 newColour = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_engaged_colour, colourIdle, colourEngaged, 0, 1); MainPiece.FadeAccent(newColour, 100); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 9c9dfc5f9e..fa39819199 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; -using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Objects; @@ -179,7 +178,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables var completion = (float)numHits / HitObject.RequiredHits; expandingRing - .FadeTo(expandingRing.Alpha + MathHelper.Clamp(completion / 16, 0.1f, 0.6f), 50) + .FadeTo(expandingRing.Alpha + Math.Clamp(completion / 16, 0.1f, 0.6f), 50) .Then() .FadeTo(completion / 8, 2000, Easing.OutQuint); diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs index 84464b199e..980f5ea340 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.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.Game.Rulesets.UI; using osuTK; @@ -22,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.UI { base.Update(); - float aspectAdjust = MathHelper.Clamp(Parent.ChildSize.X / Parent.ChildSize.Y, 0.4f, 4) / default_aspect; + float aspectAdjust = Math.Clamp(Parent.ChildSize.X / Parent.ChildSize.Y, 0.4f, 4) / default_aspect; Size = new Vector2(1, default_relative_height * aspectAdjust); } } diff --git a/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs b/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs index f613ce5f46..724612ebce 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.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.Containers; using osu.Framework.Graphics.Primitives; @@ -32,7 +33,7 @@ namespace osu.Game.Tournament.Screens.Ladder protected override bool OnScroll(ScrollEvent e) { - var newScale = MathHelper.Clamp(scale + e.ScrollDelta.Y / 15 * scale, min_scale, max_scale); + var newScale = Math.Clamp(scale + e.ScrollDelta.Y / 15 * scale, min_scale, max_scale); this.MoveTo(target = target - e.MousePosition * (newScale - scale), 2000, Easing.OutQuint); this.ScaleTo(scale = newScale, 2000, Easing.OutQuint); diff --git a/osu.Game/Graphics/Containers/ParallaxContainer.cs b/osu.Game/Graphics/Containers/ParallaxContainer.cs index 86f922e4b8..f8c21eabc2 100644 --- a/osu.Game/Graphics/Containers/ParallaxContainer.cs +++ b/osu.Game/Graphics/Containers/ParallaxContainer.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.Containers; using osu.Framework.Graphics; using osu.Framework.Input; @@ -71,7 +72,7 @@ namespace osu.Game.Graphics.Containers const float parallax_duration = 100; - double elapsed = MathHelper.Clamp(Clock.ElapsedFrameTime, 0, parallax_duration); + double elapsed = Math.Clamp(Clock.ElapsedFrameTime, 0, parallax_duration); content.Position = Interpolation.ValueAt(elapsed, content.Position, offset, 0, parallax_duration, Easing.OutQuint); content.Scale = Interpolation.ValueAt(elapsed, content.Scale, new Vector2(1 + System.Math.Abs(ParallaxAmount)), 0, 1000, Easing.OutQuint); diff --git a/osu.Game/Graphics/UserInterface/Bar.cs b/osu.Game/Graphics/UserInterface/Bar.cs index f8d5955503..0be928cf83 100644 --- a/osu.Game/Graphics/UserInterface/Bar.cs +++ b/osu.Game/Graphics/UserInterface/Bar.cs @@ -1,12 +1,12 @@ // 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 osuTK; using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using System; namespace osu.Game.Graphics.UserInterface { @@ -29,7 +29,7 @@ namespace osu.Game.Graphics.UserInterface get => length; set { - length = MathHelper.Clamp(value, 0, 1); + length = Math.Clamp(value, 0, 1); updateBarLength(); } } diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 11aba80d76..7412224f6c 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -175,9 +175,9 @@ namespace osu.Game.Graphics.UserInterface protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); - leftBox.Scale = new Vector2(MathHelper.Clamp( + leftBox.Scale = new Vector2(Math.Clamp( Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, DrawWidth), 1); - rightBox.Scale = new Vector2(MathHelper.Clamp( + rightBox.Scale = new Vector2(Math.Clamp( DrawWidth - Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, DrawWidth), 1); } diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index 585a46f3e1..064cba6adf 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -99,7 +99,7 @@ namespace osu.Game.Graphics.UserInterface // dont bother calculating if the strip is invisible if (strip.Colour.MaxAlpha > 0) - strip.Width = Interpolation.ValueAt(MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000), strip.Width, StripWidth(), 0, 500, Easing.OutQuint); + strip.Width = Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, 1000), strip.Width, StripWidth(), 0, 500, Easing.OutQuint); } public class OsuTabItem : TabItem, IHasAccentColour diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs index 8b88d81b88..583db460f4 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs @@ -99,7 +99,7 @@ namespace osu.Game.Overlays.Chat.Tabs private void tabCloseRequested(TabItem tab) { int totalTabs = TabContainer.Count - 1; // account for selectorTab - int currentIndex = MathHelper.Clamp(TabContainer.IndexOf(tab), 1, totalTabs); + int currentIndex = Math.Clamp(TabContainer.IndexOf(tab), 1, totalTabs); if (tab == SelectedTab && totalTabs > 1) // Select the tab after tab-to-be-removed's index, or the tab before if current == last diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index e3acd31626..83528298b1 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -217,7 +217,7 @@ namespace osu.Game.Overlays.Music break; } - dstIndex = MathHelper.Clamp(dstIndex, 0, items.Count - 1); + dstIndex = Math.Clamp(dstIndex, 0, items.Count - 1); if (srcIndex == dstIndex) return; diff --git a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs index fa60a37ddb..d581e2750c 100644 --- a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs +++ b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.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.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -8,7 +9,6 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; -using osuTK; namespace osu.Game.Overlays.Profile.Header.Components { @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { set { - int count = MathHelper.Clamp(value, 0, 3); + int count = Math.Clamp(value, 0, 3); if (count == 0) { diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 9edf57ad00..f217512965 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -9,7 +9,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; -using osuTK; namespace osu.Game.Rulesets.Mods { @@ -59,7 +58,7 @@ namespace osu.Game.Rulesets.Mods /// The amount of adjustment to apply (from 0..1). private void applyAdjustment(double amount) { - double adjust = 1 + (Math.Sign(FinalRateAdjustment) * MathHelper.Clamp(amount, 0, 1) * Math.Abs(FinalRateAdjustment)); + double adjust = 1 + (Math.Sign(FinalRateAdjustment) * Math.Clamp(amount, 0, 1) * Math.Abs(FinalRateAdjustment)); switch (clock) { diff --git a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs index 0d8796b4cb..1a671e8041 100644 --- a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs +++ b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Objects const double max_length = 100000; var length = Math.Min(max_length, totalDistance); - tickDistance = MathHelper.Clamp(tickDistance, 0, length); + tickDistance = Math.Clamp(tickDistance, 0, length); var minDistanceFromEnd = velocity * 10; diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 7763b0eaaf..e8ef16e825 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -246,7 +246,7 @@ namespace osu.Game.Rulesets.Objects private double progressToDistance(double progress) { - return MathHelper.Clamp(progress, 0, 1) * Distance; + return Math.Clamp(progress, 0, 1) * Distance; } private Vector2 interpolateVertices(int i, double d) diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index 4c011388fa..cb89653675 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Replays private int? currentFrameIndex; - private int nextFrameIndex => currentFrameIndex.HasValue ? MathHelper.Clamp(currentFrameIndex.Value + (currentDirection > 0 ? 1 : -1), 0, Frames.Count - 1) : 0; + private int nextFrameIndex => currentFrameIndex.HasValue ? Math.Clamp(currentFrameIndex.Value + (currentDirection > 0 ? 1 : -1), 0, Frames.Count - 1) : 0; protected FramedReplayInputHandler(Replay replay) { diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs index 5316585493..fe22a86fad 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs @@ -1,9 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Lists; using osu.Game.Rulesets.Timing; -using osuTK; namespace osu.Game.Rulesets.UI.Scrolling.Algorithms { @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms } } - i = MathHelper.Clamp(i, 0, controlPoints.Count - 1); + i = Math.Clamp(i, 0, controlPoints.Count - 1); return controlPoints[i].StartTime + (position - pos) * timeRange / controlPoints[i].Multiplier / scrollLength; } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs index 07d307f293..79ada40a89 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.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 osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -59,7 +60,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts if (Beatmap.Value == null) return; - float markerPos = MathHelper.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth); + float markerPos = Math.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth); adjustableClock.Seek(markerPos / DrawWidth * Beatmap.Value.Track.Length); }); } diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index 0f2bae6305..fc7d51db4b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -75,7 +75,7 @@ namespace osu.Game.Screens.Edit.Compose.Components float distance = direction.Length; float radius = DistanceSpacing; - int radialCount = MathHelper.Clamp((int)Math.Round(distance / radius), 1, MaxIntervals); + int radialCount = Math.Clamp((int)Math.Round(distance / radius), 1, MaxIntervals); Vector2 normalisedDirection = direction * new Vector2(1f / distance); Vector2 snappedPosition = CentrePosition + normalisedDirection * radialCount * radius; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index cffb6bedf3..54922fec5e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -84,7 +84,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline get => zoomTarget; set { - value = MathHelper.Clamp(value, MinZoom, MaxZoom); + value = Math.Clamp(value, MinZoom, MaxZoom); if (IsLoaded) setZoomTarget(value, ToSpaceOfOtherDrawable(new Vector2(DrawWidth / 2, 0), zoomedContent).X); @@ -117,7 +117,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void setZoomTarget(float newZoom, float focusPoint) { - zoomTarget = MathHelper.Clamp(newZoom, MinZoom, MaxZoom); + zoomTarget = Math.Clamp(newZoom, MinZoom, MaxZoom); transformZoomTo(zoomTarget, focusPoint, ZoomDuration, ZoomEasing); } diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index bd2db4ae2b..93a5f19121 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -7,7 +7,6 @@ using osu.Framework.MathUtils; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osuTK; namespace osu.Game.Screens.Edit { @@ -125,7 +124,7 @@ namespace osu.Game.Screens.Edit seekTime = nextTimingPoint.Time; // Ensure the sought point is within the boundaries - seekTime = MathHelper.Clamp(seekTime, 0, TrackLength); + seekTime = Math.Clamp(seekTime, 0, TrackLength); Seek(seekTime); } } diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs index ffeadb96c7..fac6b69e1f 100644 --- a/osu.Game/Screens/Menu/Button.cs +++ b/osu.Game/Screens/Menu/Button.cs @@ -236,7 +236,7 @@ namespace osu.Game.Screens.Menu protected override void Update() { - iconText.Alpha = MathHelper.Clamp((box.Scale.X - 0.5f) / 0.3f, 0, 1); + iconText.Alpha = Math.Clamp((box.Scale.X - 0.5f) / 0.3f, 0, 1); base.Update(); } diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 968b83e68c..640224c057 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -102,8 +102,8 @@ namespace osu.Game.Screens.Play.HUD else { Alpha = Interpolation.ValueAt( - MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 200), - Alpha, MathHelper.Clamp(1 - positionalAdjust, 0.04f, 1), 0, 200, Easing.OutQuint); + Math.Clamp(Clock.ElapsedFrameTime, 0, 200), + Alpha, Math.Clamp(1 - positionalAdjust, 0.04f, 1), 0, 200, Easing.OutQuint); } } diff --git a/osu.Game/Screens/Play/SongProgressBar.cs b/osu.Game/Screens/Play/SongProgressBar.cs index 33c7595b37..cdf495e257 100644 --- a/osu.Game/Screens/Play/SongProgressBar.cs +++ b/osu.Game/Screens/Play/SongProgressBar.cs @@ -116,7 +116,7 @@ namespace osu.Game.Screens.Play { base.Update(); - float newX = (float)Interpolation.Lerp(handleBase.X, NormalizedValue * UsableWidth, MathHelper.Clamp(Time.Elapsed / 40, 0, 1)); + float newX = (float)Interpolation.Lerp(handleBase.X, NormalizedValue * UsableWidth, Math.Clamp(Time.Elapsed / 40, 0, 1)); fill.Width = newX; handleBase.X = newX; diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index 05f6128ac2..715ba3c065 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -256,7 +256,7 @@ namespace osu.Game.Screens.Play { Color4 colour = State == ColumnState.Lit ? LitColour : DimmedColour; - int countFilled = (int)MathHelper.Clamp(filled * drawableRows.Count, 0, drawableRows.Count); + int countFilled = (int)Math.Clamp(filled * drawableRows.Count, 0, drawableRows.Count); for (int i = 0; i < drawableRows.Count; i++) drawableRows[i].Colour = i < countFilled ? colour : EmptyColour; diff --git a/osu.Game/Screens/ScreenWhiteBox.cs b/osu.Game/Screens/ScreenWhiteBox.cs index e4971221c4..3d8fd5dad7 100644 --- a/osu.Game/Screens/ScreenWhiteBox.cs +++ b/osu.Game/Screens/ScreenWhiteBox.cs @@ -87,9 +87,9 @@ namespace osu.Game.Screens private static Color4 getColourFor(object type) { int hash = type.GetHashCode(); - byte r = (byte)MathHelper.Clamp(((hash & 0xFF0000) >> 16) * 0.8f, 20, 255); - byte g = (byte)MathHelper.Clamp(((hash & 0x00FF00) >> 8) * 0.8f, 20, 255); - byte b = (byte)MathHelper.Clamp((hash & 0x0000FF) * 0.8f, 20, 255); + byte r = (byte)Math.Clamp(((hash & 0xFF0000) >> 16) * 0.8f, 20, 255); + byte g = (byte)Math.Clamp(((hash & 0x00FF00) >> 8) * 0.8f, 20, 255); + byte b = (byte)Math.Clamp((hash & 0x0000FF) * 0.8f, 20, 255); return new Color4(r, g, b, 255); } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index fa0ca3d9b3..c328eebca7 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -677,7 +677,7 @@ namespace osu.Game.Screens.Select // We are applying a multiplicative alpha (which is internally done by nesting an // additional container and setting that container's alpha) such that we can // layer transformations on top, with a similar reasoning to the previous comment. - p.SetMultiplicativeAlpha(MathHelper.Clamp(1.75f - 1.5f * dist, 0, 1)); + p.SetMultiplicativeAlpha(Math.Clamp(1.75f - 1.5f * dist, 0, 1)); } private class CarouselRoot : CarouselGroupEagerSelect diff --git a/osu.Game/Screens/Select/Details/FailRetryGraph.cs b/osu.Game/Screens/Select/Details/FailRetryGraph.cs index 34297d89a4..121f8efe5a 100644 --- a/osu.Game/Screens/Select/Details/FailRetryGraph.cs +++ b/osu.Game/Screens/Select/Details/FailRetryGraph.cs @@ -1,7 +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 osuTK; +using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -35,7 +35,7 @@ namespace osu.Game.Screens.Select.Details retryGraph.MaxValue = maxValue; failGraph.Values = fails.Select(f => (float)f); - retryGraph.Values = retries.Zip(fails, (retry, fail) => retry + MathHelper.Clamp(fail, 0, maxValue)); + retryGraph.Values = retries.Zip(fails, (retry, fail) => retry + Math.Clamp(fail, 0, maxValue)); } } diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 345fff90aa..13e08c7d22 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -22,7 +22,6 @@ using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Tests.Beatmaps; -using osuTK; namespace osu.Game.Tests.Visual { @@ -250,7 +249,7 @@ namespace osu.Game.Tests.Visual public override bool Seek(double seek) { - offset = MathHelper.Clamp(seek, 0, Length); + offset = Math.Clamp(seek, 0, Length); lastReferenceTime = null; return offset == seek; From b7d5e3689015b584c7464f312f33c7cfa87268d3 Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Wed, 20 Nov 2019 13:18:03 +0100 Subject: [PATCH 2380/2815] Remove unused Using directives --- osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs | 1 - osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs | 1 - .../Beatmaps/Patterns/Legacy/PatternGenerator.cs | 1 - osu.Game/Rulesets/Objects/SliderEventGenerator.cs | 1 - osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs | 1 - 5 files changed, 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index 638fd3f603..a7f0d358ed 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -10,7 +10,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; -using osuTK; namespace osu.Game.Rulesets.Catch.Difficulty { diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index 800346280d..7cd569035b 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -6,7 +6,6 @@ using osu.Game.Rulesets.Catch.Difficulty.Preprocessing; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; -using osuTK; namespace osu.Game.Rulesets.Catch.Difficulty.Skills { diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index eadf60b10b..b9984a8b90 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -7,7 +7,6 @@ using JetBrains.Annotations; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Objects; -using osuTK; namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { diff --git a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs index 1a671e8041..e9ee3833b7 100644 --- a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs +++ b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using osuTK; namespace osu.Game.Rulesets.Objects { diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index cb89653675..7e17396fde 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -7,7 +7,6 @@ using JetBrains.Annotations; using osu.Framework.Input.StateChanges; using osu.Game.Input.Handlers; using osu.Game.Replays; -using osuTK; namespace osu.Game.Rulesets.Replays { From da41b70d438108d09b058bdc05b8f6f93c8d1f19 Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Wed, 20 Nov 2019 13:19:49 +0100 Subject: [PATCH 2381/2815] .NET Standard 2.1 implements Math.Clamp , use it instead of MathHelper.Clamp from osuTK. --- osu.Android.props | 2 +- osu.Android/Properties/AndroidManifest.xml | 2 +- .../Properties/AndroidManifest.xml | 2 +- .../Beatmaps/CatchBeatmapProcessor.cs | 5 +- .../Difficulty/CatchPerformanceCalculator.cs | 3 +- .../Difficulty/Skills/Movement.cs | 3 +- .../Objects/Drawable/DrawableFruit.cs | 2 +- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 4 +- .../osu.Game.Rulesets.Catch.csproj | 2 +- .../Properties/AndroidManifest.xml | 2 +- .../Patterns/Legacy/PatternGenerator.cs | 7 ++- .../Edit/ManiaSelectionHandler.cs | 4 +- .../osu.Game.Rulesets.Mania.csproj | 2 +- .../Properties/AndroidManifest.xml | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs | 4 +- .../Mods/OsuModFlashlight.cs | 3 +- .../Objects/Drawables/DrawableRepeatPoint.cs | 2 +- .../Objects/Drawables/DrawableSlider.cs | 3 +- .../Objects/Drawables/DrawableSliderHead.cs | 2 +- .../Objects/Drawables/DrawableSpinner.cs | 3 +- .../Drawables/Pieces/SnakingSliderBody.cs | 2 +- .../osu.Game.Rulesets.Osu.csproj | 2 +- .../Properties/AndroidManifest.xml | 2 +- .../Objects/Drawables/DrawableDrumRoll.cs | 4 +- .../Objects/Drawables/DrawableSwell.cs | 3 +- .../UI/TaikoPlayfieldAdjustmentContainer.cs | 3 +- .../osu.Game.Rulesets.Taiko.csproj | 2 +- .../Properties/AndroidManifest.xml | 2 +- .../SongSelect/TestScenePlaySongSelect.cs | 53 +++++++++++++------ .../Screens/Ladder/LadderDragContainer.cs | 3 +- .../osu.Game.Tournament.csproj | 2 +- .../Graphics/Containers/ParallaxContainer.cs | 3 +- osu.Game/Graphics/UserInterface/Bar.cs | 4 +- .../Graphics/UserInterface/OsuSliderBar.cs | 4 +- .../Graphics/UserInterface/OsuTabControl.cs | 2 +- .../Overlays/Chat/Tabs/ChannelTabControl.cs | 2 +- osu.Game/Overlays/Music/PlaylistList.cs | 2 +- .../Header/Components/SupporterIcon.cs | 4 +- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 3 +- .../Rulesets/Objects/SliderEventGenerator.cs | 3 +- osu.Game/Rulesets/Objects/SliderPath.cs | 2 +- .../Replays/FramedReplayInputHandler.cs | 3 +- .../Algorithms/OverlappingScrollAlgorithm.cs | 4 +- osu.Game/Scoring/ScoreInfo.cs | 6 ++- osu.Game/Scoring/ScoreManager.cs | 2 +- .../Timelines/Summary/Parts/MarkerPart.cs | 3 +- .../Components/CircularDistanceSnapGrid.cs | 2 +- .../Timeline/ZoomableScrollContainer.cs | 4 +- osu.Game/Screens/Edit/EditorClock.cs | 3 +- osu.Game/Screens/Menu/Button.cs | 2 +- .../Screens/Play/HUD/HoldForMenuButton.cs | 4 +- osu.Game/Screens/Play/SongProgressBar.cs | 2 +- osu.Game/Screens/Play/SquareGraph.cs | 2 +- osu.Game/Screens/ScreenWhiteBox.cs | 6 +-- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- .../Screens/Select/Details/FailRetryGraph.cs | 4 +- osu.Game/Screens/Select/SongSelect.cs | 47 ++++++++++------ osu.Game/Tests/Visual/OsuTestScene.cs | 3 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 13 +++++ 60 files changed, 164 insertions(+), 116 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 6fab2e7868..d06e04aea4 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ Off True Xamarin.Android.Net.AndroidClientHandler - v9.0 + v10.0 false true armeabi-v7a;x86;arm64-v8a diff --git a/osu.Android/Properties/AndroidManifest.xml b/osu.Android/Properties/AndroidManifest.xml index acd21f9587..770eaf2222 100644 --- a/osu.Android/Properties/AndroidManifest.xml +++ b/osu.Android/Properties/AndroidManifest.xml @@ -1,6 +1,6 @@  - + diff --git a/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml index db95e18f13..0fa3b7730d 100644 --- a/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml +++ b/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index 58bf811fac..db52fbac1b 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -8,7 +8,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects.Types; -using osuTK; using osu.Game.Rulesets.Catch.MathUtils; using osu.Game.Rulesets.Mods; @@ -78,7 +77,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps catchObject.XOffset = 0; if (catchObject is TinyDroplet) - catchObject.XOffset = MathHelper.Clamp(rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH, -catchObject.X, 1 - catchObject.X); + catchObject.XOffset = Math.Clamp(rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH, -catchObject.X, 1 - catchObject.X); else if (catchObject is Droplet) rng.Next(); // osu!stable retrieved a random droplet rotation } @@ -230,7 +229,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps else { currentObject.DistanceToHyperDash = distanceToHyper; - lastExcess = MathHelper.Clamp(distanceToHyper, 0, halfCatcherWidth); + lastExcess = Math.Clamp(distanceToHyper, 0, halfCatcherWidth); } lastDirection = thisDirection; diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index 5a640f6d1a..a7f0d358ed 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -10,7 +10,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; -using osuTK; namespace osu.Game.Rulesets.Catch.Difficulty { @@ -96,7 +95,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty return value; } - private float accuracy() => totalHits() == 0 ? 0 : MathHelper.Clamp((float)totalSuccessfulHits() / totalHits(), 0f, 1f); + private float accuracy() => totalHits() == 0 ? 0 : Math.Clamp((float)totalSuccessfulHits() / totalHits(), 0f, 1f); private int totalHits() => tinyTicksHit + ticksHit + fruitsHit + misses + tinyTicksMissed; private int totalSuccessfulHits() => tinyTicksHit + ticksHit + fruitsHit; private int totalComboHits() => misses + ticksHit + fruitsHit; diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index d146153294..7cd569035b 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -6,7 +6,6 @@ using osu.Game.Rulesets.Catch.Difficulty.Preprocessing; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; -using osuTK; namespace osu.Game.Rulesets.Catch.Difficulty.Skills { @@ -31,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills if (lastPlayerPosition == null) lastPlayerPosition = catchCurrent.LastNormalizedPosition; - float playerPosition = MathHelper.Clamp( + float playerPosition = Math.Clamp( lastPlayerPosition.Value, catchCurrent.NormalizedPosition - (normalized_hitobject_radius - absolute_player_positioning_error), catchCurrent.NormalizedPosition + (normalized_hitobject_radius - absolute_player_positioning_error) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs index 1af77b75fc..eae652573b 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs @@ -278,7 +278,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { base.Update(); - border.Alpha = (float)MathHelper.Clamp((HitObject.StartTime - Time.Current) / 500, 0, 1); + border.Alpha = (float)Math.Clamp((HitObject.StartTime - Time.Current) / 500, 0, 1); } private Color4 colourForRepresentation(FruitVisualRepresentation representation) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 56c8b33e02..d330add1c4 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -235,7 +235,7 @@ namespace osu.Game.Rulesets.Catch.UI fruit.Y -= RNG.NextSingle() * diff; } - fruit.X = MathHelper.Clamp(fruit.X, -CATCHER_SIZE / 2, CATCHER_SIZE / 2); + fruit.X = Math.Clamp(fruit.X, -CATCHER_SIZE / 2, CATCHER_SIZE / 2); caughtFruit.Add(fruit); } @@ -378,7 +378,7 @@ namespace osu.Game.Rulesets.Catch.UI double speed = BASE_SPEED * dashModifier * hyperDashModifier; Scale = new Vector2(Math.Abs(Scale.X) * direction, Scale.Y); - X = (float)MathHelper.Clamp(X + direction * Clock.ElapsedFrameTime * speed, 0, 1); + X = (float)Math.Clamp(X + direction * Clock.ElapsedFrameTime * speed, 0, 1); // Correct overshooting. if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) || diff --git a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj index f24cf1def9..b19affbf9f 100644 --- a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj +++ b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj @@ -1,6 +1,6 @@  - netstandard2.0 + netstandard2.1 Library true catch the fruit. to the beat. diff --git a/osu.Game.Rulesets.Mania.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Mania.Tests.Android/Properties/AndroidManifest.xml index e6728c801d..de7935b2ef 100644 --- a/osu.Game.Rulesets.Mania.Tests.Android/Properties/AndroidManifest.xml +++ b/osu.Game.Rulesets.Mania.Tests.Android/Properties/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index f989f22298..b9984a8b90 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -7,7 +7,6 @@ using JetBrains.Annotations; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Objects; -using osuTK; namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { @@ -54,11 +53,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (allowSpecial && TotalColumns == 8) { const float local_x_divisor = 512f / 7; - return MathHelper.Clamp((int)Math.Floor(position / local_x_divisor), 0, 6) + 1; + return Math.Clamp((int)Math.Floor(position / local_x_divisor), 0, 6) + 1; } float localXDivisor = 512f / TotalColumns; - return MathHelper.Clamp((int)Math.Floor(position / localXDivisor), 0, TotalColumns - 1); + return Math.Clamp((int)Math.Floor(position / localXDivisor), 0, TotalColumns - 1); } /// @@ -113,7 +112,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy drainTime = 10000; BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty; - conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)OriginalBeatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15; + conversionDifficulty = ((difficulty.DrainRate + Math.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)OriginalBeatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15; conversionDifficulty = Math.Min(conversionDifficulty.Value, 12); return conversionDifficulty.Value; diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 9cdf045b5b..53f7e30dfd 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.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.Timing; @@ -9,7 +10,6 @@ using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit.Compose.Components; -using osuTK; namespace osu.Game.Rulesets.Mania.Edit { @@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Mania.Edit maxColumn = obj.Column; } - columnDelta = MathHelper.Clamp(columnDelta, -minColumn, composer.TotalColumns - 1 - maxColumn); + columnDelta = Math.Clamp(columnDelta, -minColumn, composer.TotalColumns - 1 - maxColumn); foreach (var obj in SelectedHitObjects.OfType()) obj.Column += columnDelta; diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj index 0af200d19b..07ef1022ae 100644 --- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj +++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj @@ -1,6 +1,6 @@  - netstandard2.0 + netstandard2.1 Library true smash the keys. to the beat. diff --git a/osu.Game.Rulesets.Osu.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Osu.Tests.Android/Properties/AndroidManifest.xml index aad907b241..3ce17ccc27 100644 --- a/osu.Game.Rulesets.Osu.Tests.Android/Properties/AndroidManifest.xml +++ b/osu.Game.Rulesets.Osu.Tests.Android/Properties/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index 1eb37f8119..63110b2797 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.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.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -13,7 +14,6 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; -using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Mods @@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Osu.Mods }; } - private float calculateGap(float value) => MathHelper.Clamp(value, 0, target_clamp) * targetBreakMultiplier; + private float calculateGap(float value) => Math.Clamp(value, 0, target_clamp) * targetBreakMultiplier; // lagrange polinominal for (0,0) (0.6,0.4) (1,1) should make a good curve private static float applyAdjustmentCurve(float value) => 0.6f * value * value + 0.4f * value; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 7fa3dbe07e..778c2f7d43 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.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 osu.Framework.Bindables; @@ -55,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Mods var destination = e.MousePosition; FlashlightPosition = Interpolation.ValueAt( - MathHelper.Clamp(Clock.ElapsedFrameTime, 0, follow_delay), position, destination, 0, follow_delay, Easing.Out); + Math.Clamp(Clock.ElapsedFrameTime, 0, follow_delay), position, destination, 0, follow_delay, Easing.Out); return base.OnMouseMove(e); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index 84d2a4af9b..122975d55e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables else { // If we're already snaking, interpolate to smooth out sharp curves (linear sliders, mainly). - Rotation = Interpolation.ValueAt(MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 100), Rotation, aimRotation, 0, 50, Easing.OutQuint); + Rotation = Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, 100), Rotation, aimRotation, 0, 50, Easing.OutQuint); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 433d29f2e4..69189758a6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.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 osuTK; using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; @@ -165,7 +166,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Tracking.Value = Ball.Tracking; - double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); + double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); Ball.UpdateProgress(completionProgress); Body.UpdateProgress(completionProgress); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index 66b6f0f9ac..a10c66d1df 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.Update(); - double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); + double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); //todo: we probably want to reconsider this before adding scoring, but it looks and feels nice. if (!IsHit) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index d1b9ee6cb4..1261d3d19a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.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.Graphics; using osu.Framework.Graphics.Containers; @@ -136,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables positionBindable.BindTo(HitObject.PositionBindable); } - public float Progress => MathHelper.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1); + public float Progress => Math.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1); protected override void CheckForResult(bool userTriggered, double timeOffset) { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs index 70a1bad4a3..f2150280b3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces var spanProgress = slider.ProgressAt(completionProgress); double start = 0; - double end = SnakingIn.Value ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / (slider.TimePreempt / 3), 0, 1) : 1; + double end = SnakingIn.Value ? Math.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / (slider.TimePreempt / 3), 0, 1) : 1; if (span >= slider.SpanCount() - 1) { diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index fb3fe8808d..bffeaabb55 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -1,6 +1,6 @@  - netstandard2.0 + netstandard2.1 Library true click the circles. to the beat. diff --git a/osu.Game.Rulesets.Taiko.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Taiko.Tests.Android/Properties/AndroidManifest.xml index cd4b74aa16..d9de0fde4e 100644 --- a/osu.Game.Rulesets.Taiko.Tests.Android/Properties/AndroidManifest.xml +++ b/osu.Game.Rulesets.Taiko.Tests.Android/Properties/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index cc0d6829ba..338fd9e20f 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -1,12 +1,12 @@ // 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.MathUtils; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; -using osuTK; using osuTK.Graphics; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using osu.Framework.Graphics; @@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables else rollingHits--; - rollingHits = MathHelper.Clamp(rollingHits, 0, rolling_hits_for_engaged_colour); + rollingHits = Math.Clamp(rollingHits, 0, rolling_hits_for_engaged_colour); Color4 newColour = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_engaged_colour, colourIdle, colourEngaged, 0, 1); MainPiece.FadeAccent(newColour, 100); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 9c9dfc5f9e..fa39819199 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; -using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Objects; @@ -179,7 +178,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables var completion = (float)numHits / HitObject.RequiredHits; expandingRing - .FadeTo(expandingRing.Alpha + MathHelper.Clamp(completion / 16, 0.1f, 0.6f), 50) + .FadeTo(expandingRing.Alpha + Math.Clamp(completion / 16, 0.1f, 0.6f), 50) .Then() .FadeTo(completion / 8, 2000, Easing.OutQuint); diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs index 84464b199e..980f5ea340 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.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.Game.Rulesets.UI; using osuTK; @@ -22,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.UI { base.Update(); - float aspectAdjust = MathHelper.Clamp(Parent.ChildSize.X / Parent.ChildSize.Y, 0.4f, 4) / default_aspect; + float aspectAdjust = Math.Clamp(Parent.ChildSize.X / Parent.ChildSize.Y, 0.4f, 4) / default_aspect; Size = new Vector2(1, default_relative_height * aspectAdjust); } } diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj index 0a2b189c3a..ebed8c6d7c 100644 --- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj +++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj @@ -1,6 +1,6 @@  - netstandard2.0 + netstandard2.1 Library true bash the drum. to the beat. diff --git a/osu.Game.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Tests.Android/Properties/AndroidManifest.xml index bb996dc5ca..4a63f0c357 100644 --- a/osu.Game.Tests.Android/Properties/AndroidManifest.xml +++ b/osu.Game.Tests.Android/Properties/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index d45b1bdba2..a4b8d1a24a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -57,23 +57,6 @@ namespace osu.Game.Tests.Visual.SongSelect typeof(DrawableCarouselBeatmapSet), }; - private class TestSongSelect : PlaySongSelect - { - public Action StartRequested; - - public new Bindable Ruleset => base.Ruleset; - - public WorkingBeatmap CurrentBeatmap => Beatmap.Value; - public WorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap; - public new BeatmapCarousel Carousel => base.Carousel; - - protected override bool OnStart() - { - StartRequested?.Invoke(); - return base.OnStart(); - } - } - private TestSongSelect songSelect; [BackgroundDependencyLoader] @@ -101,6 +84,17 @@ namespace osu.Game.Tests.Visual.SongSelect manager?.Delete(manager.GetAllUsableBeatmapSets()); }); + [Test] + public void TestSingleFilterOnEnter() + { + addRulesetImportStep(0); + addRulesetImportStep(0); + + createSongSelect(); + + AddAssert("filter count is 1", () => songSelect.FilterCount == 1); + } + [Test] public void TestAudioResuming() { @@ -373,5 +367,30 @@ namespace osu.Game.Tests.Visual.SongSelect base.Dispose(isDisposing); rulesets?.Dispose(); } + + private class TestSongSelect : PlaySongSelect + { + public Action StartRequested; + + public new Bindable Ruleset => base.Ruleset; + + public WorkingBeatmap CurrentBeatmap => Beatmap.Value; + public WorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap; + public new BeatmapCarousel Carousel => base.Carousel; + + protected override bool OnStart() + { + StartRequested?.Invoke(); + return base.OnStart(); + } + + public int FilterCount; + + protected override void ApplyFilterToCarousel(FilterCriteria criteria) + { + FilterCount++; + base.ApplyFilterToCarousel(criteria); + } + } } } diff --git a/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs b/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs index f613ce5f46..724612ebce 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.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.Containers; using osu.Framework.Graphics.Primitives; @@ -32,7 +33,7 @@ namespace osu.Game.Tournament.Screens.Ladder protected override bool OnScroll(ScrollEvent e) { - var newScale = MathHelper.Clamp(scale + e.ScrollDelta.Y / 15 * scale, min_scale, max_scale); + var newScale = Math.Clamp(scale + e.ScrollDelta.Y / 15 * scale, min_scale, max_scale); this.MoveTo(target = target - e.MousePosition * (newScale - scale), 2000, Easing.OutQuint); this.ScaleTo(scale = newScale, 2000, Easing.OutQuint); diff --git a/osu.Game.Tournament/osu.Game.Tournament.csproj b/osu.Game.Tournament/osu.Game.Tournament.csproj index f5306facaf..8e881fdd9c 100644 --- a/osu.Game.Tournament/osu.Game.Tournament.csproj +++ b/osu.Game.Tournament/osu.Game.Tournament.csproj @@ -1,6 +1,6 @@  - netstandard2.0 + netstandard2.1 Library true tools for tournaments. diff --git a/osu.Game/Graphics/Containers/ParallaxContainer.cs b/osu.Game/Graphics/Containers/ParallaxContainer.cs index 86f922e4b8..f8c21eabc2 100644 --- a/osu.Game/Graphics/Containers/ParallaxContainer.cs +++ b/osu.Game/Graphics/Containers/ParallaxContainer.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.Containers; using osu.Framework.Graphics; using osu.Framework.Input; @@ -71,7 +72,7 @@ namespace osu.Game.Graphics.Containers const float parallax_duration = 100; - double elapsed = MathHelper.Clamp(Clock.ElapsedFrameTime, 0, parallax_duration); + double elapsed = Math.Clamp(Clock.ElapsedFrameTime, 0, parallax_duration); content.Position = Interpolation.ValueAt(elapsed, content.Position, offset, 0, parallax_duration, Easing.OutQuint); content.Scale = Interpolation.ValueAt(elapsed, content.Scale, new Vector2(1 + System.Math.Abs(ParallaxAmount)), 0, 1000, Easing.OutQuint); diff --git a/osu.Game/Graphics/UserInterface/Bar.cs b/osu.Game/Graphics/UserInterface/Bar.cs index f8d5955503..0be928cf83 100644 --- a/osu.Game/Graphics/UserInterface/Bar.cs +++ b/osu.Game/Graphics/UserInterface/Bar.cs @@ -1,12 +1,12 @@ // 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 osuTK; using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using System; namespace osu.Game.Graphics.UserInterface { @@ -29,7 +29,7 @@ namespace osu.Game.Graphics.UserInterface get => length; set { - length = MathHelper.Clamp(value, 0, 1); + length = Math.Clamp(value, 0, 1); updateBarLength(); } } diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 11aba80d76..7412224f6c 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -175,9 +175,9 @@ namespace osu.Game.Graphics.UserInterface protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); - leftBox.Scale = new Vector2(MathHelper.Clamp( + leftBox.Scale = new Vector2(Math.Clamp( Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, DrawWidth), 1); - rightBox.Scale = new Vector2(MathHelper.Clamp( + rightBox.Scale = new Vector2(Math.Clamp( DrawWidth - Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, DrawWidth), 1); } diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index 585a46f3e1..064cba6adf 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -99,7 +99,7 @@ namespace osu.Game.Graphics.UserInterface // dont bother calculating if the strip is invisible if (strip.Colour.MaxAlpha > 0) - strip.Width = Interpolation.ValueAt(MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000), strip.Width, StripWidth(), 0, 500, Easing.OutQuint); + strip.Width = Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, 1000), strip.Width, StripWidth(), 0, 500, Easing.OutQuint); } public class OsuTabItem : TabItem, IHasAccentColour diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs index 8b88d81b88..583db460f4 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs @@ -99,7 +99,7 @@ namespace osu.Game.Overlays.Chat.Tabs private void tabCloseRequested(TabItem tab) { int totalTabs = TabContainer.Count - 1; // account for selectorTab - int currentIndex = MathHelper.Clamp(TabContainer.IndexOf(tab), 1, totalTabs); + int currentIndex = Math.Clamp(TabContainer.IndexOf(tab), 1, totalTabs); if (tab == SelectedTab && totalTabs > 1) // Select the tab after tab-to-be-removed's index, or the tab before if current == last diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index e3acd31626..83528298b1 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -217,7 +217,7 @@ namespace osu.Game.Overlays.Music break; } - dstIndex = MathHelper.Clamp(dstIndex, 0, items.Count - 1); + dstIndex = Math.Clamp(dstIndex, 0, items.Count - 1); if (srcIndex == dstIndex) return; diff --git a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs index fa60a37ddb..d581e2750c 100644 --- a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs +++ b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.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.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -8,7 +9,6 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; -using osuTK; namespace osu.Game.Overlays.Profile.Header.Components { @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { set { - int count = MathHelper.Clamp(value, 0, 3); + int count = Math.Clamp(value, 0, 3); if (count == 0) { diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 9edf57ad00..f217512965 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -9,7 +9,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; -using osuTK; namespace osu.Game.Rulesets.Mods { @@ -59,7 +58,7 @@ namespace osu.Game.Rulesets.Mods /// The amount of adjustment to apply (from 0..1). private void applyAdjustment(double amount) { - double adjust = 1 + (Math.Sign(FinalRateAdjustment) * MathHelper.Clamp(amount, 0, 1) * Math.Abs(FinalRateAdjustment)); + double adjust = 1 + (Math.Sign(FinalRateAdjustment) * Math.Clamp(amount, 0, 1) * Math.Abs(FinalRateAdjustment)); switch (clock) { diff --git a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs index 0d8796b4cb..e9ee3833b7 100644 --- a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs +++ b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using osuTK; namespace osu.Game.Rulesets.Objects { @@ -18,7 +17,7 @@ namespace osu.Game.Rulesets.Objects const double max_length = 100000; var length = Math.Min(max_length, totalDistance); - tickDistance = MathHelper.Clamp(tickDistance, 0, length); + tickDistance = Math.Clamp(tickDistance, 0, length); var minDistanceFromEnd = velocity * 10; diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 7763b0eaaf..e8ef16e825 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -246,7 +246,7 @@ namespace osu.Game.Rulesets.Objects private double progressToDistance(double progress) { - return MathHelper.Clamp(progress, 0, 1) * Distance; + return Math.Clamp(progress, 0, 1) * Distance; } private Vector2 interpolateVertices(int i, double d) diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index 4c011388fa..7e17396fde 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -7,7 +7,6 @@ using JetBrains.Annotations; using osu.Framework.Input.StateChanges; using osu.Game.Input.Handlers; using osu.Game.Replays; -using osuTK; namespace osu.Game.Rulesets.Replays { @@ -52,7 +51,7 @@ namespace osu.Game.Rulesets.Replays private int? currentFrameIndex; - private int nextFrameIndex => currentFrameIndex.HasValue ? MathHelper.Clamp(currentFrameIndex.Value + (currentDirection > 0 ? 1 : -1), 0, Frames.Count - 1) : 0; + private int nextFrameIndex => currentFrameIndex.HasValue ? Math.Clamp(currentFrameIndex.Value + (currentDirection > 0 ? 1 : -1), 0, Frames.Count - 1) : 0; protected FramedReplayInputHandler(Replay replay) { diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs index 5316585493..fe22a86fad 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs @@ -1,9 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Lists; using osu.Game.Rulesets.Timing; -using osuTK; namespace osu.Game.Rulesets.UI.Scrolling.Algorithms { @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms } } - i = MathHelper.Clamp(i, 0, controlPoints.Count - 1); + i = Math.Clamp(i, 0, controlPoints.Count - 1); return controlPoints[i].StartTime + (position - pos) * timeRange / controlPoints[i].Multiplier / scrollLength; } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index d3c37bd4f4..f7bac82e74 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -183,6 +183,10 @@ namespace osu.Game.Scoring public override string ToString() => $"{User} playing {Beatmap}"; - public bool Equals(ScoreInfo other) => other?.OnlineScoreID == OnlineScoreID; + public bool Equals(ScoreInfo other) => + other != null + && other.OnlineScoreID == OnlineScoreID + && other.BeatmapInfoID == BeatmapInfoID + && other.Hash == Hash; } } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 8475158c78..3279af05b6 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -69,6 +69,6 @@ namespace osu.Game.Scoring protected override ArchiveDownloadRequest CreateDownloadRequest(ScoreInfo score, bool minimiseDownload) => new DownloadReplayRequest(score); - protected override bool CheckLocalAvailability(ScoreInfo model, IQueryable items) => items.Any(s => s.OnlineScoreID == model.OnlineScoreID && s.Files.Any()); + protected override bool CheckLocalAvailability(ScoreInfo model, IQueryable items) => items.Any(s => s.Equals(model) && s.Files.Any()); } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs index 07d307f293..79ada40a89 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.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 osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -59,7 +60,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts if (Beatmap.Value == null) return; - float markerPos = MathHelper.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth); + float markerPos = Math.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth); adjustableClock.Seek(markerPos / DrawWidth * Beatmap.Value.Track.Length); }); } diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index 0f2bae6305..fc7d51db4b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -75,7 +75,7 @@ namespace osu.Game.Screens.Edit.Compose.Components float distance = direction.Length; float radius = DistanceSpacing; - int radialCount = MathHelper.Clamp((int)Math.Round(distance / radius), 1, MaxIntervals); + int radialCount = Math.Clamp((int)Math.Round(distance / radius), 1, MaxIntervals); Vector2 normalisedDirection = direction * new Vector2(1f / distance); Vector2 snappedPosition = CentrePosition + normalisedDirection * radialCount * radius; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index cffb6bedf3..54922fec5e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -84,7 +84,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline get => zoomTarget; set { - value = MathHelper.Clamp(value, MinZoom, MaxZoom); + value = Math.Clamp(value, MinZoom, MaxZoom); if (IsLoaded) setZoomTarget(value, ToSpaceOfOtherDrawable(new Vector2(DrawWidth / 2, 0), zoomedContent).X); @@ -117,7 +117,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void setZoomTarget(float newZoom, float focusPoint) { - zoomTarget = MathHelper.Clamp(newZoom, MinZoom, MaxZoom); + zoomTarget = Math.Clamp(newZoom, MinZoom, MaxZoom); transformZoomTo(zoomTarget, focusPoint, ZoomDuration, ZoomEasing); } diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index bd2db4ae2b..93a5f19121 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -7,7 +7,6 @@ using osu.Framework.MathUtils; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osuTK; namespace osu.Game.Screens.Edit { @@ -125,7 +124,7 @@ namespace osu.Game.Screens.Edit seekTime = nextTimingPoint.Time; // Ensure the sought point is within the boundaries - seekTime = MathHelper.Clamp(seekTime, 0, TrackLength); + seekTime = Math.Clamp(seekTime, 0, TrackLength); Seek(seekTime); } } diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs index ffeadb96c7..fac6b69e1f 100644 --- a/osu.Game/Screens/Menu/Button.cs +++ b/osu.Game/Screens/Menu/Button.cs @@ -236,7 +236,7 @@ namespace osu.Game.Screens.Menu protected override void Update() { - iconText.Alpha = MathHelper.Clamp((box.Scale.X - 0.5f) / 0.3f, 0, 1); + iconText.Alpha = Math.Clamp((box.Scale.X - 0.5f) / 0.3f, 0, 1); base.Update(); } diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 968b83e68c..640224c057 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -102,8 +102,8 @@ namespace osu.Game.Screens.Play.HUD else { Alpha = Interpolation.ValueAt( - MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 200), - Alpha, MathHelper.Clamp(1 - positionalAdjust, 0.04f, 1), 0, 200, Easing.OutQuint); + Math.Clamp(Clock.ElapsedFrameTime, 0, 200), + Alpha, Math.Clamp(1 - positionalAdjust, 0.04f, 1), 0, 200, Easing.OutQuint); } } diff --git a/osu.Game/Screens/Play/SongProgressBar.cs b/osu.Game/Screens/Play/SongProgressBar.cs index 33c7595b37..cdf495e257 100644 --- a/osu.Game/Screens/Play/SongProgressBar.cs +++ b/osu.Game/Screens/Play/SongProgressBar.cs @@ -116,7 +116,7 @@ namespace osu.Game.Screens.Play { base.Update(); - float newX = (float)Interpolation.Lerp(handleBase.X, NormalizedValue * UsableWidth, MathHelper.Clamp(Time.Elapsed / 40, 0, 1)); + float newX = (float)Interpolation.Lerp(handleBase.X, NormalizedValue * UsableWidth, Math.Clamp(Time.Elapsed / 40, 0, 1)); fill.Width = newX; handleBase.X = newX; diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index 05f6128ac2..715ba3c065 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -256,7 +256,7 @@ namespace osu.Game.Screens.Play { Color4 colour = State == ColumnState.Lit ? LitColour : DimmedColour; - int countFilled = (int)MathHelper.Clamp(filled * drawableRows.Count, 0, drawableRows.Count); + int countFilled = (int)Math.Clamp(filled * drawableRows.Count, 0, drawableRows.Count); for (int i = 0; i < drawableRows.Count; i++) drawableRows[i].Colour = i < countFilled ? colour : EmptyColour; diff --git a/osu.Game/Screens/ScreenWhiteBox.cs b/osu.Game/Screens/ScreenWhiteBox.cs index e4971221c4..3d8fd5dad7 100644 --- a/osu.Game/Screens/ScreenWhiteBox.cs +++ b/osu.Game/Screens/ScreenWhiteBox.cs @@ -87,9 +87,9 @@ namespace osu.Game.Screens private static Color4 getColourFor(object type) { int hash = type.GetHashCode(); - byte r = (byte)MathHelper.Clamp(((hash & 0xFF0000) >> 16) * 0.8f, 20, 255); - byte g = (byte)MathHelper.Clamp(((hash & 0x00FF00) >> 8) * 0.8f, 20, 255); - byte b = (byte)MathHelper.Clamp((hash & 0x0000FF) * 0.8f, 20, 255); + byte r = (byte)Math.Clamp(((hash & 0xFF0000) >> 16) * 0.8f, 20, 255); + byte g = (byte)Math.Clamp(((hash & 0x00FF00) >> 8) * 0.8f, 20, 255); + byte b = (byte)Math.Clamp((hash & 0x0000FF) * 0.8f, 20, 255); return new Color4(r, g, b, 255); } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index fa0ca3d9b3..c328eebca7 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -677,7 +677,7 @@ namespace osu.Game.Screens.Select // We are applying a multiplicative alpha (which is internally done by nesting an // additional container and setting that container's alpha) such that we can // layer transformations on top, with a similar reasoning to the previous comment. - p.SetMultiplicativeAlpha(MathHelper.Clamp(1.75f - 1.5f * dist, 0, 1)); + p.SetMultiplicativeAlpha(Math.Clamp(1.75f - 1.5f * dist, 0, 1)); } private class CarouselRoot : CarouselGroupEagerSelect diff --git a/osu.Game/Screens/Select/Details/FailRetryGraph.cs b/osu.Game/Screens/Select/Details/FailRetryGraph.cs index 34297d89a4..121f8efe5a 100644 --- a/osu.Game/Screens/Select/Details/FailRetryGraph.cs +++ b/osu.Game/Screens/Select/Details/FailRetryGraph.cs @@ -1,7 +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 osuTK; +using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -35,7 +35,7 @@ namespace osu.Game.Screens.Select.Details retryGraph.MaxValue = maxValue; failGraph.Values = fails.Select(f => (float)f); - retryGraph.Values = retries.Zip(fails, (retry, fail) => retry + MathHelper.Clamp(fail, 0, maxValue)); + retryGraph.Values = retries.Zip(fails, (retry, fail) => retry + Math.Clamp(fail, 0, maxValue)); } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index e3cd98454a..a52edb70db 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -46,48 +46,54 @@ namespace osu.Game.Screens.Select protected const float BACKGROUND_BLUR = 20; private const float left_area_padding = 20; - public readonly FilterControl FilterControl; + public FilterControl FilterControl { get; private set; } protected virtual bool ShowFooter => true; /// /// Can be null if is false. /// - protected readonly BeatmapOptionsOverlay BeatmapOptions; + protected BeatmapOptionsOverlay BeatmapOptions { get; private set; } /// /// Can be null if is false. /// - protected readonly Footer Footer; + protected Footer Footer { get; private set; } /// /// Contains any panel which is triggered by a footer button. /// Helps keep them located beneath the footer itself. /// - protected readonly Container FooterPanels; + protected Container FooterPanels { get; private set; } protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value); - protected readonly BeatmapCarousel Carousel; - private readonly BeatmapInfoWedge beatmapInfoWedge; + protected BeatmapCarousel Carousel { get; private set; } + + private BeatmapInfoWedge beatmapInfoWedge; private DialogOverlay dialogOverlay; private BeatmapManager beatmaps; - protected readonly ModSelectOverlay ModSelect; + protected ModSelectOverlay ModSelect { get; private set; } + + protected SampleChannel SampleConfirm { get; private set; } - protected SampleChannel SampleConfirm; private SampleChannel sampleChangeDifficulty; private SampleChannel sampleChangeBeatmap; - protected readonly BeatmapDetailArea BeatmapDetails; + protected BeatmapDetailArea BeatmapDetails { get; private set; } private readonly Bindable decoupledRuleset = new Bindable(); [Resolved(canBeNull: true)] private MusicController music { get; set; } - protected SongSelect() + [BackgroundDependencyLoader(true)] + private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins, ScoreManager scores) { + // initial value transfer is required for FilterControl (it uses our re-cached bindables in its async load for the initial filter). + transferRulesetValue(); + AddRangeInternal(new Drawable[] { new ParallaxContainer @@ -161,7 +167,7 @@ namespace osu.Game.Screens.Select { RelativeSizeAxes = Axes.X, Height = FilterControl.HEIGHT, - FilterChanged = c => Carousel.Filter(c), + FilterChanged = ApplyFilterToCarousel, Background = { Width = 2 }, }, } @@ -211,11 +217,7 @@ namespace osu.Game.Screens.Select } BeatmapDetails.Leaderboard.ScoreSelected += score => this.Push(new SoloResults(score)); - } - [BackgroundDependencyLoader(true)] - private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins, ScoreManager scores) - { if (Footer != null) { Footer.AddButton(new FooterButtonMods { Current = Mods }, ModSelect); @@ -258,6 +260,12 @@ namespace osu.Game.Screens.Select } } + protected virtual void ApplyFilterToCarousel(FilterCriteria criteria) + { + if (this.IsCurrentScreen()) + Carousel.Filter(criteria); + } + private DependencyContainer dependencies; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -429,6 +437,8 @@ namespace osu.Game.Screens.Select { base.OnEntering(last); + Carousel.Filter(FilterControl.CreateCriteria(), false); + this.FadeInFromZero(250); FilterControl.Activate(); } @@ -624,7 +634,7 @@ namespace osu.Game.Screens.Select return; // manual binding to parent ruleset to allow for delayed load in the incoming direction. - rulesetNoDebounce = decoupledRuleset.Value = Ruleset.Value; + transferRulesetValue(); Ruleset.ValueChanged += r => updateSelectedRuleset(r.NewValue); decoupledRuleset.ValueChanged += r => Ruleset.Value = r.NewValue; @@ -636,6 +646,11 @@ namespace osu.Game.Screens.Select boundLocalBindables = true; } + private void transferRulesetValue() + { + rulesetNoDebounce = decoupledRuleset.Value = Ruleset.Value; + } + private void delete(BeatmapSetInfo beatmap) { if (beatmap == null || beatmap.ID <= 0) return; diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 345fff90aa..13e08c7d22 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -22,7 +22,6 @@ using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Tests.Beatmaps; -using osuTK; namespace osu.Game.Tests.Visual { @@ -250,7 +249,7 @@ namespace osu.Game.Tests.Visual public override bool Seek(double seek) { - offset = MathHelper.Clamp(seek, 0, Length); + offset = Math.Clamp(seek, 0, Length); lastReferenceTime = null; return offset == seek; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index af60da3e70..8e3858d0f1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -1,6 +1,6 @@  - netstandard2.0 + netstandard2.1 Library true diff --git a/osu.iOS.props b/osu.iOS.props index 8124357312..6965b16303 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -75,4 +75,17 @@ + + + + + + + + + + + + + From 6cab517b2df2c8296d311a931ed21ddfc8f2c0ca Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Wed, 20 Nov 2019 13:19:49 +0100 Subject: [PATCH 2382/2815] .NET Standard 2.1 implements Math.Clamp , use it instead of MathHelper.Clamp from osuTK. --- osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs | 5 ++--- .../Difficulty/CatchPerformanceCalculator.cs | 3 +-- osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs | 3 +-- osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs | 2 +- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 4 ++-- .../Beatmaps/Patterns/Legacy/PatternGenerator.cs | 7 +++---- osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs | 4 ++-- osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs | 4 ++-- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 3 ++- .../Objects/Drawables/DrawableRepeatPoint.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 3 ++- .../Objects/Drawables/DrawableSliderHead.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 3 ++- .../Objects/Drawables/Pieces/SnakingSliderBody.cs | 2 +- .../Objects/Drawables/DrawableDrumRoll.cs | 4 ++-- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs | 3 +-- .../UI/TaikoPlayfieldAdjustmentContainer.cs | 3 ++- osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs | 3 ++- osu.Game/Graphics/Containers/ParallaxContainer.cs | 3 ++- osu.Game/Graphics/UserInterface/Bar.cs | 4 ++-- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 4 ++-- osu.Game/Graphics/UserInterface/OsuTabControl.cs | 2 +- osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs | 2 +- osu.Game/Overlays/Music/PlaylistList.cs | 2 +- .../Overlays/Profile/Header/Components/SupporterIcon.cs | 4 ++-- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 3 +-- osu.Game/Rulesets/Objects/SliderEventGenerator.cs | 3 +-- osu.Game/Rulesets/Objects/SliderPath.cs | 2 +- osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs | 3 +-- .../UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs | 4 ++-- .../Edit/Components/Timelines/Summary/Parts/MarkerPart.cs | 3 ++- .../Edit/Compose/Components/CircularDistanceSnapGrid.cs | 2 +- .../Compose/Components/Timeline/ZoomableScrollContainer.cs | 4 ++-- osu.Game/Screens/Edit/EditorClock.cs | 3 +-- osu.Game/Screens/Menu/Button.cs | 2 +- osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 4 ++-- osu.Game/Screens/Play/SongProgressBar.cs | 2 +- osu.Game/Screens/Play/SquareGraph.cs | 2 +- osu.Game/Screens/ScreenWhiteBox.cs | 6 +++--- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- osu.Game/Screens/Select/Details/FailRetryGraph.cs | 4 ++-- osu.Game/Tests/Visual/OsuTestScene.cs | 3 +-- 42 files changed, 65 insertions(+), 68 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index 58bf811fac..db52fbac1b 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -8,7 +8,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects.Types; -using osuTK; using osu.Game.Rulesets.Catch.MathUtils; using osu.Game.Rulesets.Mods; @@ -78,7 +77,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps catchObject.XOffset = 0; if (catchObject is TinyDroplet) - catchObject.XOffset = MathHelper.Clamp(rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH, -catchObject.X, 1 - catchObject.X); + catchObject.XOffset = Math.Clamp(rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH, -catchObject.X, 1 - catchObject.X); else if (catchObject is Droplet) rng.Next(); // osu!stable retrieved a random droplet rotation } @@ -230,7 +229,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps else { currentObject.DistanceToHyperDash = distanceToHyper; - lastExcess = MathHelper.Clamp(distanceToHyper, 0, halfCatcherWidth); + lastExcess = Math.Clamp(distanceToHyper, 0, halfCatcherWidth); } lastDirection = thisDirection; diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index 5a640f6d1a..a7f0d358ed 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -10,7 +10,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; -using osuTK; namespace osu.Game.Rulesets.Catch.Difficulty { @@ -96,7 +95,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty return value; } - private float accuracy() => totalHits() == 0 ? 0 : MathHelper.Clamp((float)totalSuccessfulHits() / totalHits(), 0f, 1f); + private float accuracy() => totalHits() == 0 ? 0 : Math.Clamp((float)totalSuccessfulHits() / totalHits(), 0f, 1f); private int totalHits() => tinyTicksHit + ticksHit + fruitsHit + misses + tinyTicksMissed; private int totalSuccessfulHits() => tinyTicksHit + ticksHit + fruitsHit; private int totalComboHits() => misses + ticksHit + fruitsHit; diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index d146153294..7cd569035b 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -6,7 +6,6 @@ using osu.Game.Rulesets.Catch.Difficulty.Preprocessing; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; -using osuTK; namespace osu.Game.Rulesets.Catch.Difficulty.Skills { @@ -31,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills if (lastPlayerPosition == null) lastPlayerPosition = catchCurrent.LastNormalizedPosition; - float playerPosition = MathHelper.Clamp( + float playerPosition = Math.Clamp( lastPlayerPosition.Value, catchCurrent.NormalizedPosition - (normalized_hitobject_radius - absolute_player_positioning_error), catchCurrent.NormalizedPosition + (normalized_hitobject_radius - absolute_player_positioning_error) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs index 1af77b75fc..eae652573b 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs @@ -278,7 +278,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { base.Update(); - border.Alpha = (float)MathHelper.Clamp((HitObject.StartTime - Time.Current) / 500, 0, 1); + border.Alpha = (float)Math.Clamp((HitObject.StartTime - Time.Current) / 500, 0, 1); } private Color4 colourForRepresentation(FruitVisualRepresentation representation) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 56c8b33e02..d330add1c4 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -235,7 +235,7 @@ namespace osu.Game.Rulesets.Catch.UI fruit.Y -= RNG.NextSingle() * diff; } - fruit.X = MathHelper.Clamp(fruit.X, -CATCHER_SIZE / 2, CATCHER_SIZE / 2); + fruit.X = Math.Clamp(fruit.X, -CATCHER_SIZE / 2, CATCHER_SIZE / 2); caughtFruit.Add(fruit); } @@ -378,7 +378,7 @@ namespace osu.Game.Rulesets.Catch.UI double speed = BASE_SPEED * dashModifier * hyperDashModifier; Scale = new Vector2(Math.Abs(Scale.X) * direction, Scale.Y); - X = (float)MathHelper.Clamp(X + direction * Clock.ElapsedFrameTime * speed, 0, 1); + X = (float)Math.Clamp(X + direction * Clock.ElapsedFrameTime * speed, 0, 1); // Correct overshooting. if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) || diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index f989f22298..b9984a8b90 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -7,7 +7,6 @@ using JetBrains.Annotations; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Objects; -using osuTK; namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { @@ -54,11 +53,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (allowSpecial && TotalColumns == 8) { const float local_x_divisor = 512f / 7; - return MathHelper.Clamp((int)Math.Floor(position / local_x_divisor), 0, 6) + 1; + return Math.Clamp((int)Math.Floor(position / local_x_divisor), 0, 6) + 1; } float localXDivisor = 512f / TotalColumns; - return MathHelper.Clamp((int)Math.Floor(position / localXDivisor), 0, TotalColumns - 1); + return Math.Clamp((int)Math.Floor(position / localXDivisor), 0, TotalColumns - 1); } /// @@ -113,7 +112,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy drainTime = 10000; BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty; - conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)OriginalBeatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15; + conversionDifficulty = ((difficulty.DrainRate + Math.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)OriginalBeatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15; conversionDifficulty = Math.Min(conversionDifficulty.Value, 12); return conversionDifficulty.Value; diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 9cdf045b5b..53f7e30dfd 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.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.Timing; @@ -9,7 +10,6 @@ using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit.Compose.Components; -using osuTK; namespace osu.Game.Rulesets.Mania.Edit { @@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Mania.Edit maxColumn = obj.Column; } - columnDelta = MathHelper.Clamp(columnDelta, -minColumn, composer.TotalColumns - 1 - maxColumn); + columnDelta = Math.Clamp(columnDelta, -minColumn, composer.TotalColumns - 1 - maxColumn); foreach (var obj in SelectedHitObjects.OfType()) obj.Column += columnDelta; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index 1eb37f8119..63110b2797 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.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.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -13,7 +14,6 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; -using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Mods @@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Osu.Mods }; } - private float calculateGap(float value) => MathHelper.Clamp(value, 0, target_clamp) * targetBreakMultiplier; + private float calculateGap(float value) => Math.Clamp(value, 0, target_clamp) * targetBreakMultiplier; // lagrange polinominal for (0,0) (0.6,0.4) (1,1) should make a good curve private static float applyAdjustmentCurve(float value) => 0.6f * value * value + 0.4f * value; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 7fa3dbe07e..778c2f7d43 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.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 osu.Framework.Bindables; @@ -55,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Mods var destination = e.MousePosition; FlashlightPosition = Interpolation.ValueAt( - MathHelper.Clamp(Clock.ElapsedFrameTime, 0, follow_delay), position, destination, 0, follow_delay, Easing.Out); + Math.Clamp(Clock.ElapsedFrameTime, 0, follow_delay), position, destination, 0, follow_delay, Easing.Out); return base.OnMouseMove(e); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index 84d2a4af9b..122975d55e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables else { // If we're already snaking, interpolate to smooth out sharp curves (linear sliders, mainly). - Rotation = Interpolation.ValueAt(MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 100), Rotation, aimRotation, 0, 50, Easing.OutQuint); + Rotation = Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, 100), Rotation, aimRotation, 0, 50, Easing.OutQuint); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 433d29f2e4..69189758a6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.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 osuTK; using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; @@ -165,7 +166,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Tracking.Value = Ball.Tracking; - double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); + double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); Ball.UpdateProgress(completionProgress); Body.UpdateProgress(completionProgress); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index 66b6f0f9ac..a10c66d1df 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.Update(); - double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); + double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); //todo: we probably want to reconsider this before adding scoring, but it looks and feels nice. if (!IsHit) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index d1b9ee6cb4..1261d3d19a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.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.Graphics; using osu.Framework.Graphics.Containers; @@ -136,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables positionBindable.BindTo(HitObject.PositionBindable); } - public float Progress => MathHelper.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1); + public float Progress => Math.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1); protected override void CheckForResult(bool userTriggered, double timeOffset) { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs index 70a1bad4a3..f2150280b3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces var spanProgress = slider.ProgressAt(completionProgress); double start = 0; - double end = SnakingIn.Value ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / (slider.TimePreempt / 3), 0, 1) : 1; + double end = SnakingIn.Value ? Math.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / (slider.TimePreempt / 3), 0, 1) : 1; if (span >= slider.SpanCount() - 1) { diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index cc0d6829ba..338fd9e20f 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -1,12 +1,12 @@ // 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.MathUtils; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; -using osuTK; using osuTK.Graphics; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using osu.Framework.Graphics; @@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables else rollingHits--; - rollingHits = MathHelper.Clamp(rollingHits, 0, rolling_hits_for_engaged_colour); + rollingHits = Math.Clamp(rollingHits, 0, rolling_hits_for_engaged_colour); Color4 newColour = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_engaged_colour, colourIdle, colourEngaged, 0, 1); MainPiece.FadeAccent(newColour, 100); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 9c9dfc5f9e..fa39819199 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; -using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Objects; @@ -179,7 +178,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables var completion = (float)numHits / HitObject.RequiredHits; expandingRing - .FadeTo(expandingRing.Alpha + MathHelper.Clamp(completion / 16, 0.1f, 0.6f), 50) + .FadeTo(expandingRing.Alpha + Math.Clamp(completion / 16, 0.1f, 0.6f), 50) .Then() .FadeTo(completion / 8, 2000, Easing.OutQuint); diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs index 84464b199e..980f5ea340 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.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.Game.Rulesets.UI; using osuTK; @@ -22,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.UI { base.Update(); - float aspectAdjust = MathHelper.Clamp(Parent.ChildSize.X / Parent.ChildSize.Y, 0.4f, 4) / default_aspect; + float aspectAdjust = Math.Clamp(Parent.ChildSize.X / Parent.ChildSize.Y, 0.4f, 4) / default_aspect; Size = new Vector2(1, default_relative_height * aspectAdjust); } } diff --git a/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs b/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs index f613ce5f46..724612ebce 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.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.Containers; using osu.Framework.Graphics.Primitives; @@ -32,7 +33,7 @@ namespace osu.Game.Tournament.Screens.Ladder protected override bool OnScroll(ScrollEvent e) { - var newScale = MathHelper.Clamp(scale + e.ScrollDelta.Y / 15 * scale, min_scale, max_scale); + var newScale = Math.Clamp(scale + e.ScrollDelta.Y / 15 * scale, min_scale, max_scale); this.MoveTo(target = target - e.MousePosition * (newScale - scale), 2000, Easing.OutQuint); this.ScaleTo(scale = newScale, 2000, Easing.OutQuint); diff --git a/osu.Game/Graphics/Containers/ParallaxContainer.cs b/osu.Game/Graphics/Containers/ParallaxContainer.cs index 86f922e4b8..f8c21eabc2 100644 --- a/osu.Game/Graphics/Containers/ParallaxContainer.cs +++ b/osu.Game/Graphics/Containers/ParallaxContainer.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.Containers; using osu.Framework.Graphics; using osu.Framework.Input; @@ -71,7 +72,7 @@ namespace osu.Game.Graphics.Containers const float parallax_duration = 100; - double elapsed = MathHelper.Clamp(Clock.ElapsedFrameTime, 0, parallax_duration); + double elapsed = Math.Clamp(Clock.ElapsedFrameTime, 0, parallax_duration); content.Position = Interpolation.ValueAt(elapsed, content.Position, offset, 0, parallax_duration, Easing.OutQuint); content.Scale = Interpolation.ValueAt(elapsed, content.Scale, new Vector2(1 + System.Math.Abs(ParallaxAmount)), 0, 1000, Easing.OutQuint); diff --git a/osu.Game/Graphics/UserInterface/Bar.cs b/osu.Game/Graphics/UserInterface/Bar.cs index f8d5955503..0be928cf83 100644 --- a/osu.Game/Graphics/UserInterface/Bar.cs +++ b/osu.Game/Graphics/UserInterface/Bar.cs @@ -1,12 +1,12 @@ // 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 osuTK; using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using System; namespace osu.Game.Graphics.UserInterface { @@ -29,7 +29,7 @@ namespace osu.Game.Graphics.UserInterface get => length; set { - length = MathHelper.Clamp(value, 0, 1); + length = Math.Clamp(value, 0, 1); updateBarLength(); } } diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 11aba80d76..7412224f6c 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -175,9 +175,9 @@ namespace osu.Game.Graphics.UserInterface protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); - leftBox.Scale = new Vector2(MathHelper.Clamp( + leftBox.Scale = new Vector2(Math.Clamp( Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, DrawWidth), 1); - rightBox.Scale = new Vector2(MathHelper.Clamp( + rightBox.Scale = new Vector2(Math.Clamp( DrawWidth - Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, DrawWidth), 1); } diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index 585a46f3e1..064cba6adf 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -99,7 +99,7 @@ namespace osu.Game.Graphics.UserInterface // dont bother calculating if the strip is invisible if (strip.Colour.MaxAlpha > 0) - strip.Width = Interpolation.ValueAt(MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000), strip.Width, StripWidth(), 0, 500, Easing.OutQuint); + strip.Width = Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, 1000), strip.Width, StripWidth(), 0, 500, Easing.OutQuint); } public class OsuTabItem : TabItem, IHasAccentColour diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs index 8b88d81b88..583db460f4 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs @@ -99,7 +99,7 @@ namespace osu.Game.Overlays.Chat.Tabs private void tabCloseRequested(TabItem tab) { int totalTabs = TabContainer.Count - 1; // account for selectorTab - int currentIndex = MathHelper.Clamp(TabContainer.IndexOf(tab), 1, totalTabs); + int currentIndex = Math.Clamp(TabContainer.IndexOf(tab), 1, totalTabs); if (tab == SelectedTab && totalTabs > 1) // Select the tab after tab-to-be-removed's index, or the tab before if current == last diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index e3acd31626..83528298b1 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -217,7 +217,7 @@ namespace osu.Game.Overlays.Music break; } - dstIndex = MathHelper.Clamp(dstIndex, 0, items.Count - 1); + dstIndex = Math.Clamp(dstIndex, 0, items.Count - 1); if (srcIndex == dstIndex) return; diff --git a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs index fa60a37ddb..d581e2750c 100644 --- a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs +++ b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.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.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -8,7 +9,6 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; -using osuTK; namespace osu.Game.Overlays.Profile.Header.Components { @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { set { - int count = MathHelper.Clamp(value, 0, 3); + int count = Math.Clamp(value, 0, 3); if (count == 0) { diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 9edf57ad00..f217512965 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -9,7 +9,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; -using osuTK; namespace osu.Game.Rulesets.Mods { @@ -59,7 +58,7 @@ namespace osu.Game.Rulesets.Mods /// The amount of adjustment to apply (from 0..1). private void applyAdjustment(double amount) { - double adjust = 1 + (Math.Sign(FinalRateAdjustment) * MathHelper.Clamp(amount, 0, 1) * Math.Abs(FinalRateAdjustment)); + double adjust = 1 + (Math.Sign(FinalRateAdjustment) * Math.Clamp(amount, 0, 1) * Math.Abs(FinalRateAdjustment)); switch (clock) { diff --git a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs index 0d8796b4cb..e9ee3833b7 100644 --- a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs +++ b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using osuTK; namespace osu.Game.Rulesets.Objects { @@ -18,7 +17,7 @@ namespace osu.Game.Rulesets.Objects const double max_length = 100000; var length = Math.Min(max_length, totalDistance); - tickDistance = MathHelper.Clamp(tickDistance, 0, length); + tickDistance = Math.Clamp(tickDistance, 0, length); var minDistanceFromEnd = velocity * 10; diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 7763b0eaaf..e8ef16e825 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -246,7 +246,7 @@ namespace osu.Game.Rulesets.Objects private double progressToDistance(double progress) { - return MathHelper.Clamp(progress, 0, 1) * Distance; + return Math.Clamp(progress, 0, 1) * Distance; } private Vector2 interpolateVertices(int i, double d) diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index 4c011388fa..7e17396fde 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -7,7 +7,6 @@ using JetBrains.Annotations; using osu.Framework.Input.StateChanges; using osu.Game.Input.Handlers; using osu.Game.Replays; -using osuTK; namespace osu.Game.Rulesets.Replays { @@ -52,7 +51,7 @@ namespace osu.Game.Rulesets.Replays private int? currentFrameIndex; - private int nextFrameIndex => currentFrameIndex.HasValue ? MathHelper.Clamp(currentFrameIndex.Value + (currentDirection > 0 ? 1 : -1), 0, Frames.Count - 1) : 0; + private int nextFrameIndex => currentFrameIndex.HasValue ? Math.Clamp(currentFrameIndex.Value + (currentDirection > 0 ? 1 : -1), 0, Frames.Count - 1) : 0; protected FramedReplayInputHandler(Replay replay) { diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs index 5316585493..fe22a86fad 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs @@ -1,9 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Lists; using osu.Game.Rulesets.Timing; -using osuTK; namespace osu.Game.Rulesets.UI.Scrolling.Algorithms { @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms } } - i = MathHelper.Clamp(i, 0, controlPoints.Count - 1); + i = Math.Clamp(i, 0, controlPoints.Count - 1); return controlPoints[i].StartTime + (position - pos) * timeRange / controlPoints[i].Multiplier / scrollLength; } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs index 07d307f293..79ada40a89 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.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 osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -59,7 +60,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts if (Beatmap.Value == null) return; - float markerPos = MathHelper.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth); + float markerPos = Math.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth); adjustableClock.Seek(markerPos / DrawWidth * Beatmap.Value.Track.Length); }); } diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index 0f2bae6305..fc7d51db4b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -75,7 +75,7 @@ namespace osu.Game.Screens.Edit.Compose.Components float distance = direction.Length; float radius = DistanceSpacing; - int radialCount = MathHelper.Clamp((int)Math.Round(distance / radius), 1, MaxIntervals); + int radialCount = Math.Clamp((int)Math.Round(distance / radius), 1, MaxIntervals); Vector2 normalisedDirection = direction * new Vector2(1f / distance); Vector2 snappedPosition = CentrePosition + normalisedDirection * radialCount * radius; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index cffb6bedf3..54922fec5e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -84,7 +84,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline get => zoomTarget; set { - value = MathHelper.Clamp(value, MinZoom, MaxZoom); + value = Math.Clamp(value, MinZoom, MaxZoom); if (IsLoaded) setZoomTarget(value, ToSpaceOfOtherDrawable(new Vector2(DrawWidth / 2, 0), zoomedContent).X); @@ -117,7 +117,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void setZoomTarget(float newZoom, float focusPoint) { - zoomTarget = MathHelper.Clamp(newZoom, MinZoom, MaxZoom); + zoomTarget = Math.Clamp(newZoom, MinZoom, MaxZoom); transformZoomTo(zoomTarget, focusPoint, ZoomDuration, ZoomEasing); } diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index bd2db4ae2b..93a5f19121 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -7,7 +7,6 @@ using osu.Framework.MathUtils; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osuTK; namespace osu.Game.Screens.Edit { @@ -125,7 +124,7 @@ namespace osu.Game.Screens.Edit seekTime = nextTimingPoint.Time; // Ensure the sought point is within the boundaries - seekTime = MathHelper.Clamp(seekTime, 0, TrackLength); + seekTime = Math.Clamp(seekTime, 0, TrackLength); Seek(seekTime); } } diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs index ffeadb96c7..fac6b69e1f 100644 --- a/osu.Game/Screens/Menu/Button.cs +++ b/osu.Game/Screens/Menu/Button.cs @@ -236,7 +236,7 @@ namespace osu.Game.Screens.Menu protected override void Update() { - iconText.Alpha = MathHelper.Clamp((box.Scale.X - 0.5f) / 0.3f, 0, 1); + iconText.Alpha = Math.Clamp((box.Scale.X - 0.5f) / 0.3f, 0, 1); base.Update(); } diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 968b83e68c..640224c057 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -102,8 +102,8 @@ namespace osu.Game.Screens.Play.HUD else { Alpha = Interpolation.ValueAt( - MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 200), - Alpha, MathHelper.Clamp(1 - positionalAdjust, 0.04f, 1), 0, 200, Easing.OutQuint); + Math.Clamp(Clock.ElapsedFrameTime, 0, 200), + Alpha, Math.Clamp(1 - positionalAdjust, 0.04f, 1), 0, 200, Easing.OutQuint); } } diff --git a/osu.Game/Screens/Play/SongProgressBar.cs b/osu.Game/Screens/Play/SongProgressBar.cs index 33c7595b37..cdf495e257 100644 --- a/osu.Game/Screens/Play/SongProgressBar.cs +++ b/osu.Game/Screens/Play/SongProgressBar.cs @@ -116,7 +116,7 @@ namespace osu.Game.Screens.Play { base.Update(); - float newX = (float)Interpolation.Lerp(handleBase.X, NormalizedValue * UsableWidth, MathHelper.Clamp(Time.Elapsed / 40, 0, 1)); + float newX = (float)Interpolation.Lerp(handleBase.X, NormalizedValue * UsableWidth, Math.Clamp(Time.Elapsed / 40, 0, 1)); fill.Width = newX; handleBase.X = newX; diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index 05f6128ac2..715ba3c065 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -256,7 +256,7 @@ namespace osu.Game.Screens.Play { Color4 colour = State == ColumnState.Lit ? LitColour : DimmedColour; - int countFilled = (int)MathHelper.Clamp(filled * drawableRows.Count, 0, drawableRows.Count); + int countFilled = (int)Math.Clamp(filled * drawableRows.Count, 0, drawableRows.Count); for (int i = 0; i < drawableRows.Count; i++) drawableRows[i].Colour = i < countFilled ? colour : EmptyColour; diff --git a/osu.Game/Screens/ScreenWhiteBox.cs b/osu.Game/Screens/ScreenWhiteBox.cs index e4971221c4..3d8fd5dad7 100644 --- a/osu.Game/Screens/ScreenWhiteBox.cs +++ b/osu.Game/Screens/ScreenWhiteBox.cs @@ -87,9 +87,9 @@ namespace osu.Game.Screens private static Color4 getColourFor(object type) { int hash = type.GetHashCode(); - byte r = (byte)MathHelper.Clamp(((hash & 0xFF0000) >> 16) * 0.8f, 20, 255); - byte g = (byte)MathHelper.Clamp(((hash & 0x00FF00) >> 8) * 0.8f, 20, 255); - byte b = (byte)MathHelper.Clamp((hash & 0x0000FF) * 0.8f, 20, 255); + byte r = (byte)Math.Clamp(((hash & 0xFF0000) >> 16) * 0.8f, 20, 255); + byte g = (byte)Math.Clamp(((hash & 0x00FF00) >> 8) * 0.8f, 20, 255); + byte b = (byte)Math.Clamp((hash & 0x0000FF) * 0.8f, 20, 255); return new Color4(r, g, b, 255); } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index fa0ca3d9b3..c328eebca7 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -677,7 +677,7 @@ namespace osu.Game.Screens.Select // We are applying a multiplicative alpha (which is internally done by nesting an // additional container and setting that container's alpha) such that we can // layer transformations on top, with a similar reasoning to the previous comment. - p.SetMultiplicativeAlpha(MathHelper.Clamp(1.75f - 1.5f * dist, 0, 1)); + p.SetMultiplicativeAlpha(Math.Clamp(1.75f - 1.5f * dist, 0, 1)); } private class CarouselRoot : CarouselGroupEagerSelect diff --git a/osu.Game/Screens/Select/Details/FailRetryGraph.cs b/osu.Game/Screens/Select/Details/FailRetryGraph.cs index 34297d89a4..121f8efe5a 100644 --- a/osu.Game/Screens/Select/Details/FailRetryGraph.cs +++ b/osu.Game/Screens/Select/Details/FailRetryGraph.cs @@ -1,7 +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 osuTK; +using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -35,7 +35,7 @@ namespace osu.Game.Screens.Select.Details retryGraph.MaxValue = maxValue; failGraph.Values = fails.Select(f => (float)f); - retryGraph.Values = retries.Zip(fails, (retry, fail) => retry + MathHelper.Clamp(fail, 0, maxValue)); + retryGraph.Values = retries.Zip(fails, (retry, fail) => retry + Math.Clamp(fail, 0, maxValue)); } } diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 345fff90aa..13e08c7d22 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -22,7 +22,6 @@ using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Tests.Beatmaps; -using osuTK; namespace osu.Game.Tests.Visual { @@ -250,7 +249,7 @@ namespace osu.Game.Tests.Visual public override bool Seek(double seek) { - offset = MathHelper.Clamp(seek, 0, Length); + offset = Math.Clamp(seek, 0, Length); lastReferenceTime = null; return offset == seek; From 66a3837ff4fe6c0cea4b12bb37f344885badf1ee Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Wed, 20 Nov 2019 14:25:44 +0100 Subject: [PATCH 2383/2815] Fix CI --- osu.Game/Graphics/Containers/ParallaxContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/ParallaxContainer.cs b/osu.Game/Graphics/Containers/ParallaxContainer.cs index f8c21eabc2..bf743b90ed 100644 --- a/osu.Game/Graphics/Containers/ParallaxContainer.cs +++ b/osu.Game/Graphics/Containers/ParallaxContainer.cs @@ -49,7 +49,7 @@ namespace osu.Game.Graphics.Containers if (!parallaxEnabled.Value) { content.MoveTo(Vector2.Zero, firstUpdate ? 0 : 1000, Easing.OutQuint); - content.Scale = new Vector2(1 + System.Math.Abs(ParallaxAmount)); + content.Scale = new Vector2(1 + Math.Abs(ParallaxAmount)); } }; } @@ -75,7 +75,7 @@ namespace osu.Game.Graphics.Containers double elapsed = Math.Clamp(Clock.ElapsedFrameTime, 0, parallax_duration); content.Position = Interpolation.ValueAt(elapsed, content.Position, offset, 0, parallax_duration, Easing.OutQuint); - content.Scale = Interpolation.ValueAt(elapsed, content.Scale, new Vector2(1 + System.Math.Abs(ParallaxAmount)), 0, 1000, Easing.OutQuint); + content.Scale = Interpolation.ValueAt(elapsed, content.Scale, new Vector2(1 + Math.Abs(ParallaxAmount)), 0, 1000, Easing.OutQuint); } firstUpdate = false; From e820ddd3e8c34da3b6f3d292062340942274589d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 20 Nov 2019 19:27:34 +0300 Subject: [PATCH 2384/2815] Implement settings keywords --- .../Settings/Sections/Audio/AudioDevicesSettings.cs | 5 ++++- .../Overlays/Settings/Sections/Audio/VolumeSettings.cs | 2 ++ .../Overlays/Settings/Sections/Gameplay/GeneralSettings.cs | 1 + .../Overlays/Settings/Sections/Gameplay/ModsSettings.cs | 3 ++- .../Settings/Sections/Gameplay/SongSelectSettings.cs | 7 +++++-- .../Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 4 +++- .../Settings/Sections/Graphics/RendererSettings.cs | 2 ++ osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs | 6 ++++-- osu.Game/Overlays/Settings/SettingsItem.cs | 7 ++++++- osu.Game/Overlays/Settings/SettingsSubsection.cs | 7 ++++++- 10 files changed, 35 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs index 2c25808170..e9ae784061 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs @@ -60,7 +60,10 @@ namespace osu.Game.Overlays.Settings.Sections.Audio Children = new Drawable[] { - dropdown = new AudioDeviceSettingsDropdown() + dropdown = new AudioDeviceSettingsDropdown + { + Keywords = new[] { "Speaker" } + } }; updateItems(); diff --git a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs index 0124f7090e..c297f694c0 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs @@ -22,6 +22,8 @@ namespace osu.Game.Overlays.Settings.Sections.Audio new SettingsSlider { LabelText = "Effect", Bindable = audio.VolumeSample, KeyboardStep = 0.01f }, new SettingsSlider { LabelText = "Music", Bindable = audio.VolumeTrack, KeyboardStep = 0.01f }, }; + + Keywords = new[] { "Sound" }; } } } diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 520a8852b3..3ebfae05b2 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -38,6 +38,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { LabelText = "Show health display even when you can't fail", Bindable = config.GetBindable(OsuSetting.ShowHealthDisplayWhenCantFail), + Keywords = new[] { "bar" } }, new SettingsCheckbox { diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs index 2c6b2663c6..06d120762b 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs @@ -18,7 +18,8 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay new SettingsCheckbox { LabelText = "Increase visibility of first object when visual impairment mods are enabled", - Bindable = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility) + Bindable = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility), + Keywords = new[] { "Hidden", "Traceable" } }, }; } diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs index 3e2272dba6..fce0e3dea8 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs @@ -26,18 +26,21 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { LabelText = "Show converted beatmaps", Bindable = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), + Keywords = new[] { "Converts" } }, new SettingsSlider { LabelText = "Display beatmaps from", Bindable = config.GetBindable(OsuSetting.DisplayStarsMinimum), - KeyboardStep = 0.1f + KeyboardStep = 0.1f, + Keywords = new[] { "Stars" } }, new SettingsSlider { LabelText = "up to", Bindable = config.GetBindable(OsuSetting.DisplayStarsMaximum), - KeyboardStep = 0.1f + KeyboardStep = 0.1f, + Keywords = new[] { "Stars" } }, new SettingsEnumDropdown { diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index f4de4c0c41..8275d6dc0a 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -75,12 +75,14 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics LabelText = "UI Scaling", TransferValueOnCommit = true, Bindable = osuConfig.GetBindable(OsuSetting.UIScale), - KeyboardStep = 0.01f + KeyboardStep = 0.01f, + Keywords = new[] { "Scale" }, }, new SettingsEnumDropdown { LabelText = "Screen Scaling", Bindable = osuConfig.GetBindable(OsuSetting.Scaling), + Keywords = new[] { "Scale" }, }, scalingSettings = new FillFlowContainer> { diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs index 7317076c54..1aee9a8efe 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs @@ -30,6 +30,8 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics Bindable = osuConfig.GetBindable(OsuSetting.ShowFpsDisplay) }, }; + + Keywords = new[] { "fps" }; } } } diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index 4f2f3dfd1a..f1071314c6 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -27,12 +27,14 @@ namespace osu.Game.Overlays.Settings.Sections.Input new SettingsCheckbox { LabelText = "Raw input", - Bindable = rawInputToggle + Bindable = rawInputToggle, + Keywords = new[] { "Speed" } }, sensitivity = new SensitivitySetting { LabelText = "Cursor sensitivity", - Bindable = config.GetBindable(FrameworkSetting.CursorSensitivity) + Bindable = config.GetBindable(FrameworkSetting.CursorSensitivity), + Keywords = new[] { "Speed" } }, new SettingsCheckbox { diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index d48c0b6b66..9a50185e11 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -76,7 +76,12 @@ namespace osu.Game.Overlays.Settings } } - public virtual IEnumerable FilterTerms => new[] { LabelText }; + public virtual IEnumerable FilterTerms => Keywords == null ? new[] { LabelText } : new List(Keywords) + { + LabelText + }.ToArray(); + + public IEnumerable Keywords { get; set; } public bool MatchingFilter { diff --git a/osu.Game/Overlays/Settings/SettingsSubsection.cs b/osu.Game/Overlays/Settings/SettingsSubsection.cs index c9c763e8d4..0023888b91 100644 --- a/osu.Game/Overlays/Settings/SettingsSubsection.cs +++ b/osu.Game/Overlays/Settings/SettingsSubsection.cs @@ -21,7 +21,12 @@ namespace osu.Game.Overlays.Settings protected abstract string Header { get; } public IEnumerable FilterableChildren => Children.OfType(); - public IEnumerable FilterTerms => new[] { Header }; + public virtual IEnumerable FilterTerms => Keywords == null ? new[] { Header } : new List(Keywords) + { + Header + }.ToArray(); + + public IEnumerable Keywords { get; set; } public bool MatchingFilter { From 7d8252183eed2a85ef2a0ee89615fcff9d8c1cb2 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 20 Nov 2019 19:42:57 +0300 Subject: [PATCH 2385/2815] CI fix --- osu.Game/Overlays/Settings/SettingsItem.cs | 5 +---- osu.Game/Overlays/Settings/SettingsSubsection.cs | 6 ++---- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 9a50185e11..8863e43cca 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -76,10 +76,7 @@ namespace osu.Game.Overlays.Settings } } - public virtual IEnumerable FilterTerms => Keywords == null ? new[] { LabelText } : new List(Keywords) - { - LabelText - }.ToArray(); + public virtual IEnumerable FilterTerms => Keywords == null ? new[] { LabelText } : new List(Keywords) { LabelText }.ToArray(); public IEnumerable Keywords { get; set; } diff --git a/osu.Game/Overlays/Settings/SettingsSubsection.cs b/osu.Game/Overlays/Settings/SettingsSubsection.cs index 0023888b91..15f20d252c 100644 --- a/osu.Game/Overlays/Settings/SettingsSubsection.cs +++ b/osu.Game/Overlays/Settings/SettingsSubsection.cs @@ -21,10 +21,8 @@ namespace osu.Game.Overlays.Settings protected abstract string Header { get; } public IEnumerable FilterableChildren => Children.OfType(); - public virtual IEnumerable FilterTerms => Keywords == null ? new[] { Header } : new List(Keywords) - { - Header - }.ToArray(); + + public virtual IEnumerable FilterTerms => Keywords == null ? new[] { Header } : new List(Keywords) { Header }.ToArray(); public IEnumerable Keywords { get; set; } From f3d9abc84aa1226c56f85861682e23cfd4a2eac3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 20 Nov 2019 19:57:02 +0300 Subject: [PATCH 2386/2815] Capitalize Bar for consistency --- osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 3ebfae05b2..ad9f3eec68 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { LabelText = "Show health display even when you can't fail", Bindable = config.GetBindable(OsuSetting.ShowHealthDisplayWhenCantFail), - Keywords = new[] { "bar" } + Keywords = new[] { "Bar" } }, new SettingsCheckbox { From 6b3010535fc289d2e5d1cd0c7836302e78122911 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 20 Nov 2019 21:03:31 +0300 Subject: [PATCH 2387/2815] Simplify Keywords usage for SettingsSubsection --- osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs | 6 ++++-- .../Overlays/Settings/Sections/Graphics/RendererSettings.cs | 6 ++++-- osu.Game/Overlays/Settings/SettingsSubsection.cs | 5 +---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs index c297f694c0..7bfec0fad8 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs @@ -1,6 +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.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Graphics; @@ -12,6 +14,8 @@ namespace osu.Game.Overlays.Settings.Sections.Audio { protected override string Header => "Volume"; + public override IEnumerable FilterTerms => base.FilterTerms.Concat(new[] { "Sound" }); + [BackgroundDependencyLoader] private void load(AudioManager audio, OsuConfigManager config) { @@ -22,8 +26,6 @@ namespace osu.Game.Overlays.Settings.Sections.Audio new SettingsSlider { LabelText = "Effect", Bindable = audio.VolumeSample, KeyboardStep = 0.01f }, new SettingsSlider { LabelText = "Music", Bindable = audio.VolumeTrack, KeyboardStep = 0.01f }, }; - - Keywords = new[] { "Sound" }; } } } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs index 1aee9a8efe..92f4823142 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs @@ -1,6 +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.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; @@ -12,6 +14,8 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { protected override string Header => "Renderer"; + public override IEnumerable FilterTerms => base.FilterTerms.Concat(new[] { "FPS" }); + [BackgroundDependencyLoader] private void load(FrameworkConfigManager config, OsuConfigManager osuConfig) { @@ -30,8 +34,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics Bindable = osuConfig.GetBindable(OsuSetting.ShowFpsDisplay) }, }; - - Keywords = new[] { "fps" }; } } } diff --git a/osu.Game/Overlays/Settings/SettingsSubsection.cs b/osu.Game/Overlays/Settings/SettingsSubsection.cs index 15f20d252c..9b3b2f570c 100644 --- a/osu.Game/Overlays/Settings/SettingsSubsection.cs +++ b/osu.Game/Overlays/Settings/SettingsSubsection.cs @@ -21,10 +21,7 @@ namespace osu.Game.Overlays.Settings protected abstract string Header { get; } public IEnumerable FilterableChildren => Children.OfType(); - - public virtual IEnumerable FilterTerms => Keywords == null ? new[] { Header } : new List(Keywords) { Header }.ToArray(); - - public IEnumerable Keywords { get; set; } + public virtual IEnumerable FilterTerms => new[] { Header }; public bool MatchingFilter { From f066d8434cafcce2335518085b3d212933bc3e0d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 21 Nov 2019 00:26:39 +0300 Subject: [PATCH 2388/2815] Apply suggestions --- osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs | 4 ---- osu.Game/Overlays/Settings/Sections/AudioSection.cs | 5 +++++ .../Overlays/Settings/Sections/Gameplay/GeneralSettings.cs | 2 +- .../Overlays/Settings/Sections/Gameplay/ModsSettings.cs | 2 +- .../Overlays/Settings/Sections/Graphics/RendererSettings.cs | 4 ---- osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs | 6 ++---- osu.Game/Overlays/Settings/SettingsSection.cs | 2 +- 7 files changed, 10 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs index 7bfec0fad8..0124f7090e 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.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 System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Graphics; @@ -14,8 +12,6 @@ namespace osu.Game.Overlays.Settings.Sections.Audio { protected override string Header => "Volume"; - public override IEnumerable FilterTerms => base.FilterTerms.Concat(new[] { "Sound" }); - [BackgroundDependencyLoader] private void load(AudioManager audio, OsuConfigManager config) { diff --git a/osu.Game/Overlays/Settings/Sections/AudioSection.cs b/osu.Game/Overlays/Settings/Sections/AudioSection.cs index 7ca313a751..254af6ea21 100644 --- a/osu.Game/Overlays/Settings/Sections/AudioSection.cs +++ b/osu.Game/Overlays/Settings/Sections/AudioSection.cs @@ -1,6 +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.Collections.Generic; +using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Settings.Sections.Audio; @@ -10,6 +12,9 @@ namespace osu.Game.Overlays.Settings.Sections public class AudioSection : SettingsSection { public override string Header => "Audio"; + + public override IEnumerable FilterTerms => base.FilterTerms.Concat(new[] { "Sound" }); + public override IconUsage Icon => FontAwesome.Solid.VolumeUp; public AudioSection() diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index ad9f3eec68..0ac145ffe2 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { LabelText = "Show health display even when you can't fail", Bindable = config.GetBindable(OsuSetting.ShowHealthDisplayWhenCantFail), - Keywords = new[] { "Bar" } + Keywords = new[] { "Bar", "Hp" } }, new SettingsCheckbox { diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs index 06d120762b..a71919a2fc 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { LabelText = "Increase visibility of first object when visual impairment mods are enabled", Bindable = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility), - Keywords = new[] { "Hidden", "Traceable" } + Keywords = new[] { "Mod" } }, }; } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs index 92f4823142..7317076c54 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.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 System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; @@ -14,8 +12,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { protected override string Header => "Renderer"; - public override IEnumerable FilterTerms => base.FilterTerms.Concat(new[] { "FPS" }); - [BackgroundDependencyLoader] private void load(FrameworkConfigManager config, OsuConfigManager osuConfig) { diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index f1071314c6..4f2f3dfd1a 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -27,14 +27,12 @@ namespace osu.Game.Overlays.Settings.Sections.Input new SettingsCheckbox { LabelText = "Raw input", - Bindable = rawInputToggle, - Keywords = new[] { "Speed" } + Bindable = rawInputToggle }, sensitivity = new SensitivitySetting { LabelText = "Cursor sensitivity", - Bindable = config.GetBindable(FrameworkSetting.CursorSensitivity), - Keywords = new[] { "Speed" } + Bindable = config.GetBindable(FrameworkSetting.CursorSensitivity) }, new SettingsCheckbox { diff --git a/osu.Game/Overlays/Settings/SettingsSection.cs b/osu.Game/Overlays/Settings/SettingsSection.cs index c878a9fc65..be3696029e 100644 --- a/osu.Game/Overlays/Settings/SettingsSection.cs +++ b/osu.Game/Overlays/Settings/SettingsSection.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Settings public abstract string Header { get; } public IEnumerable FilterableChildren => Children.OfType(); - public IEnumerable FilterTerms => new[] { Header }; + public virtual IEnumerable FilterTerms => new[] { Header }; private const int header_size = 26; private const int header_margin = 25; From 4a4f5ccbb2ecb5adee902922c3d6d43fe3bd7ac5 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 21 Nov 2019 02:35:58 +0300 Subject: [PATCH 2389/2815] Implement IAggregateAudioAdjustments properties for FallbackSampleStore --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index d1749d33c0..bb8149a57d 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -517,6 +517,12 @@ namespace osu.Game.Rulesets.UI public BindableDouble Frequency => throw new NotImplementedException(); + public IBindable AggregateVolume => throw new NotImplementedException(); + + public IBindable AggregateBalance => throw new NotImplementedException(); + + public IBindable AggregateFrequency => throw new NotImplementedException(); + public int PlaybackConcurrency { get => throw new NotImplementedException(); From ebae92db7b2fcafb6ce94f8f81f56af1b7b8d897 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 21 Nov 2019 03:18:31 +0300 Subject: [PATCH 2390/2815] Revert "Implement IAggregateAudioAdjustments properties for FallbackSampleStore" This reverts commit 4a4f5ccbb2ecb5adee902922c3d6d43fe3bd7ac5. --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index bb8149a57d..d1749d33c0 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -517,12 +517,6 @@ namespace osu.Game.Rulesets.UI public BindableDouble Frequency => throw new NotImplementedException(); - public IBindable AggregateVolume => throw new NotImplementedException(); - - public IBindable AggregateBalance => throw new NotImplementedException(); - - public IBindable AggregateFrequency => throw new NotImplementedException(); - public int PlaybackConcurrency { get => throw new NotImplementedException(); From 5391c752b4e896b44ca8782dd6c795d4c57dc447 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 21 Nov 2019 02:35:58 +0300 Subject: [PATCH 2391/2815] Implement IAggregateAudioAdjustments properties for FallbackSampleStore --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index aec0e58a10..e0aeff8330 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -517,6 +517,12 @@ namespace osu.Game.Rulesets.UI public BindableDouble Frequency => throw new NotImplementedException(); + public IBindable AggregateVolume => throw new NotImplementedException(); + + public IBindable AggregateBalance => throw new NotImplementedException(); + + public IBindable AggregateFrequency => throw new NotImplementedException(); + public int PlaybackConcurrency { get => throw new NotImplementedException(); From 76e63722cc51cb0a683fefab2b946198b4aa46a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2019 10:26:10 +0900 Subject: [PATCH 2392/2815] Update framework --- 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 6fab2e7868..cd317b76f4 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -53,6 +53,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index af60da3e70..515edf6f9d 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -21,7 +21,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 8124357312..9ea6576957 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -73,6 +73,6 @@ - + From be62e482964c08479d13d173793ad95ec57cf6a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2019 10:35:10 +0900 Subject: [PATCH 2393/2815] Fix tests not working if main or audio volume is zero --- .../Visual/Components/TestScenePreviewTrackManager.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs index 5dac40104a..d76905dab8 100644 --- a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs +++ b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs @@ -143,6 +143,17 @@ namespace osu.Game.Tests.Visual.Components PreviewTrack track = null; TestTrackOwner owner = null; + AddStep("ensure volume not zero", () => + { + if (audio.Volume.Value == 0) + audio.Volume.Value = 1; + + if (audio.VolumeTrack.Value == 0) + audio.VolumeTrack.Value = 1; + }); + + AddAssert("game not muted", () => audio.Tracks.AggregateVolume.Value != 0); + AddStep("get track", () => Add(owner = new TestTrackOwner(track = getTrack()))); AddUntilStep("wait loaded", () => track.IsLoaded); AddStep("start track", () => track.Start()); From cf0f0f8a1bf534f7bfaab3a2c69a502143311f6e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2019 10:37:50 +0900 Subject: [PATCH 2394/2815] Reword comment --- osu.Game/Audio/PreviewTrackManager.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index d85df42d2b..6f0b62543d 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -80,8 +80,7 @@ namespace osu.Game.Audio return; CurrentTrack.Stop(); - // CurrentTrack must not change until the scheduled stopped event has been invoked. - // To ensure that this doesn't early-return on (CurrentTrack != track) check. + // CurrentTrack should not be set to null here as it will result in incorrect handling in the track.Stopped callback above. } /// From 19dfbb0a45ba3a208ad34ba6b0e6b1507e3e605e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2019 10:55:31 +0900 Subject: [PATCH 2395/2815] Update obsolete usages --- osu.Game/Online/API/APIAccess.cs | 2 +- osu.Game/Online/API/APIRequest.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index d722c7a98a..b6127027cc 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -227,7 +227,7 @@ namespace osu.Game.Online.API { try { - return JObject.Parse(req.ResponseString).SelectToken("form_error", true).ToObject(); + return JObject.Parse(req.GetResponseString()).SelectToken("form_error", true).ToObject(); } catch { diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index ea0d50511f..53b23f5922 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -113,7 +113,7 @@ namespace osu.Game.Online.API cancelled = true; WebRequest?.Abort(); - string responseString = WebRequest?.ResponseString; + string responseString = WebRequest?.GetResponseString(); if (!string.IsNullOrEmpty(responseString)) { From 0cbe29dbecb7ab231480726edc515e33955ea313 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 Nov 2019 11:05:18 +0900 Subject: [PATCH 2396/2815] Refactor / cleanup debouncing --- .../Multi/Lounge/Components/FilterControl.cs | 11 +++++++++- .../Multi/Lounge/Components/RoomsContainer.cs | 20 +++---------------- osu.Game/Screens/Multi/RoomManager.cs | 11 +--------- 3 files changed, 14 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs b/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs index d0d983bbff..1f92806fbb 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Overlays.SearchableList; using osuTK.Graphics; @@ -37,10 +38,18 @@ namespace osu.Game.Screens.Multi.Lounge.Components { base.LoadComplete(); - Search.Current.BindValueChanged(_ => updateFilter()); + Search.Current.BindValueChanged(_ => scheduleUpdateFilter()); Tabs.Current.BindValueChanged(_ => updateFilter(), true); } + private ScheduledDelegate scheduledFilterUpdate; + + private void scheduleUpdateFilter() + { + scheduledFilterUpdate?.Cancel(); + scheduledFilterUpdate = Scheduler.AddDelayed(updateFilter, 200); + } + private void updateFilter() { filter.Value = new FilterCriteria diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index 3dde9452e4..607b081653 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -9,7 +9,6 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Threading; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osuTK; @@ -63,10 +62,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components protected override void LoadComplete() { - filter?.BindValueChanged(f => scheduleFilter()); - - if (filter != null) - Filter(filter.Value); + filter?.BindValueChanged(f => Filter(f.NewValue), true); } public void Filter(FilterCriteria criteria) @@ -93,23 +89,13 @@ namespace osu.Game.Screens.Multi.Lounge.Components }); } - private ScheduledDelegate scheduledFilter; - - private void scheduleFilter() - { - if (filter == null) - return; - - scheduledFilter?.Cancel(); - scheduledFilter = Scheduler.AddDelayed(() => Filter(filter.Value), 200); - } - private void addRooms(IEnumerable rooms) { foreach (var r in rooms) roomFlow.Add(new DrawableRoom(r) { Action = () => selectRoom(r) }); - filter?.TriggerChange(); + if (filter != null) + Filter(filter.Value); } private void removeRooms(IEnumerable rooms) diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs index d6d3ec37ef..cdaba85b9e 100644 --- a/osu.Game/Screens/Multi/RoomManager.cs +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -7,7 +7,6 @@ using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; -using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Online; using osu.Game.Online.API; @@ -45,7 +44,7 @@ namespace osu.Game.Screens.Multi currentFilter.BindValueChanged(_ => { if (IsLoaded) - schedulePoll(); + PollImmediately(); }); } @@ -158,14 +157,6 @@ namespace osu.Game.Screens.Multi return tcs.Task; } - private ScheduledDelegate scheduledPoll; - - private void schedulePoll() - { - scheduledPoll?.Cancel(); - scheduledPoll = Scheduler.AddDelayed(PollImmediately, 200); - } - /// /// Updates a local with a remote copy. /// From 2325f0382ca4413059b2ec68a1f0db4eb34b8dda Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2019 13:46:45 +0900 Subject: [PATCH 2397/2815] Cancel a potentially pending filter update when an update occurs --- osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs b/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs index 1f92806fbb..29d41132a7 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs @@ -52,6 +52,8 @@ namespace osu.Game.Screens.Multi.Lounge.Components private void updateFilter() { + scheduledFilterUpdate?.Cancel(); + filter.Value = new FilterCriteria { SearchString = Search.Current.Value ?? string.Empty, From 8369be90f20842c6b9a6aaf4eba5a4671c0c2915 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2019 15:19:06 +0900 Subject: [PATCH 2398/2815] Allow skip button to actuate more than once --- .../Visual/Gameplay/TestSceneSkipOverlay.cs | 13 +--------- .../Screens/Play/GameplayClockContainer.cs | 26 ++++++++++++++++--- osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Screens/Play/SkipOverlay.cs | 20 +++----------- 4 files changed, 29 insertions(+), 32 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index b152c21454..0d62d9a520 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Gameplay { skip = new SkipOverlay(6000) { - RequestSeek = _ => requestCount++ + RequestSkip = () => requestCount++ } }, }; @@ -60,17 +60,6 @@ namespace osu.Game.Tests.Visual.Gameplay checkRequestCount(1); } - [Test] - public void TestClickOnlyActuatesOnce() - { - AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); - AddStep("click", () => InputManager.Click(MouseButton.Left)); - AddStep("click", () => InputManager.Click(MouseButton.Left)); - AddStep("click", () => InputManager.Click(MouseButton.Left)); - AddStep("click", () => InputManager.Click(MouseButton.Left)); - checkRequestCount(1); - } - [Test] public void TestDoesntFadeOnMouseDown() { diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 671711f5a4..e1f4d8fa94 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -89,6 +89,11 @@ namespace osu.Game.Screens.Play private double totalOffset => userOffsetClock.Offset + platformOffsetClock.Offset; + /// + /// Duration before gameplay start time required before skip button displays. + /// + public const double MINIMUM_SKIP_TIME = 1000; + private readonly BindableDouble pauseFreqAdjust = new BindableDouble(1); [BackgroundDependencyLoader] @@ -104,11 +109,9 @@ namespace osu.Game.Screens.Play startTime = Math.Min(startTime, 0); Seek(startTime); + adjustableClock.ProcessFrame(); - UserPlaybackRate.ValueChanged += _ => updateRate(); - - Seek(Math.Min(-beatmap.BeatmapInfo.AudioLeadIn, gameplayStartTime)); } public void Restart() @@ -139,6 +142,23 @@ namespace osu.Game.Screens.Play this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In); } + /// + /// Skip forwardto the next valid skip point. + /// + public void Skip() + { + if (GameplayClock.CurrentTime > gameplayStartTime - MINIMUM_SKIP_TIME) + return; + + double skipTarget = gameplayStartTime - MINIMUM_SKIP_TIME; + + if (GameplayClock.CurrentTime < 0 && skipTarget > 6000) + // double skip exception for storyboards with very long intros + skipTarget = 0; + + Seek(skipTarget); + } + /// /// Seek to a specific time in gameplay. /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a9b0649fab..d6488dc209 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -203,7 +203,7 @@ namespace osu.Game.Screens.Play }, new SkipOverlay(DrawableRuleset.GameplayStartTime) { - RequestSeek = GameplayClockContainer.Seek + RequestSkip = GameplayClockContainer.Skip }, FailOverlay = new FailOverlay { diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 835867fe62..3abe61d1a9 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Play { private readonly double startTime; - public Action RequestSeek; + public Action RequestSkip; private Button button; private Box remainingTimeBox; @@ -90,11 +90,6 @@ namespace osu.Game.Screens.Play }; } - /// - /// Duration before gameplay start time required before skip button displays. - /// - private const double skip_buffer = 1000; - private const double fade_time = 300; private double beginFadeTime => startTime - fade_time; @@ -104,7 +99,7 @@ namespace osu.Game.Screens.Play base.LoadComplete(); // skip is not required if there is no extra "empty" time to skip. - if (Clock.CurrentTime > beginFadeTime - skip_buffer) + if (Clock.CurrentTime > beginFadeTime - GameplayClockContainer.MINIMUM_SKIP_TIME) { Alpha = 0; Expire(); @@ -115,10 +110,9 @@ namespace osu.Game.Screens.Play using (BeginAbsoluteSequence(beginFadeTime)) this.FadeOut(fade_time); - button.Action = () => RequestSeek?.Invoke(beginFadeTime); + button.Action = () => RequestSkip?.Invoke(); displayTime = Time.Current; - Expire(); } @@ -335,13 +329,7 @@ namespace osu.Game.Screens.Play box.FlashColour(Color4.White, 500, Easing.OutQuint); aspect.ScaleTo(1.2f, 2000, Easing.OutQuint); - bool result = base.OnClick(e); - - // for now, let's disable the skip button after the first press. - // this will likely need to be contextual in the future (bound from external components). - Enabled.Value = false; - - return result; + return base.OnClick(e); } } } From 586e31efc256964b722de2112d55d367ee88ea56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2019 15:56:08 +0900 Subject: [PATCH 2399/2815] Update tests --- osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index addf7c3b28..9275dc636c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Osu; @@ -22,9 +23,7 @@ namespace osu.Game.Tests.Visual.Gameplay BeatmapInfo = { AudioLeadIn = 1000 } }); - AddUntilStep("player loaded", () => player.IsLoaded && player.Alpha == 1); - - AddAssert("correct lead-in", () => player.FirstFrameClockTime == 0); + AddAssert("correct lead-in", () => Precision.AlmostEquals(player.FirstFrameClockTime.Value, 0, 100)); } [Test] @@ -35,7 +34,7 @@ namespace osu.Game.Tests.Visual.Gameplay BeatmapInfo = { AudioLeadIn = 10000 } }); - AddAssert("correct lead-in", () => player.FirstFrameClockTime == player.GameplayStartTime - 10000); + AddAssert("correct lead-in", () => Precision.AlmostEquals(player.FirstFrameClockTime.Value, player.GameplayStartTime - 10000, 100)); } private void loadPlayerWithBeatmap(IBeatmap beatmap) From 9acfc2587aa6a8dcd34a2535f3fc4bcf928bac1b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2019 15:59:59 +0900 Subject: [PATCH 2400/2815] Switch android to using managed network components --- osu.Android.props | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Android.props b/osu.Android.props index 4a1165d57f..0f0e82d56a 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -16,6 +16,8 @@ armeabi-v7a;x86;arm64-v8a true cjk,mideast,other,rare,west + System.Net.Http.HttpClientHandler + legacy SdkOnly prompt From 2dd2e3d861706d22991174ed0e068f7f3c738ae9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2019 16:55:18 +0900 Subject: [PATCH 2401/2815] Add correct AudioLeadIn support --- osu.Game/Screens/Play/GameplayClockContainer.cs | 17 +++++++++++++---- osu.Game/Screens/Play/Player.cs | 2 +- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index e1f4d8fa94..bc6f649492 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -41,6 +41,8 @@ namespace osu.Game.Screens.Play private readonly double gameplayStartTime; + private readonly double firstHitObjectTime; + public readonly Bindable UserPlaybackRate = new BindableDouble(1) { Default = 1, @@ -61,11 +63,12 @@ namespace osu.Game.Screens.Play private readonly FramedOffsetClock platformOffsetClock; - public GameplayClockContainer(WorkingBeatmap beatmap, IReadOnlyList mods, double gameplayStartTime) + public GameplayClockContainer(WorkingBeatmap beatmap, IReadOnlyList mods, double gameplayStartTime, double firstHitObjectTime) { this.beatmap = beatmap; this.mods = mods; this.gameplayStartTime = gameplayStartTime; + this.firstHitObjectTime = firstHitObjectTime; RelativeSizeAxes = Axes.Both; @@ -102,11 +105,17 @@ namespace osu.Game.Screens.Play userAudioOffset = config.GetBindable(OsuSetting.AudioOffset); userAudioOffset.BindValueChanged(offset => userOffsetClock.Offset = offset.NewValue, true); - double startTime = -beatmap.BeatmapInfo.AudioLeadIn; + // sane default provided by ruleset. + double startTime = Math.Min(0, gameplayStartTime); + // if a storyboard is present, it may dictate the appropriate start time by having events in negative time space. + // this is commonly used to display an intro before the audio track start. startTime = Math.Min(startTime, beatmap.Storyboard.FirstEventTime); - startTime = Math.Min(startTime, gameplayStartTime); - startTime = Math.Min(startTime, 0); + + // some beatmaps specify a current lead-in time which should be used instead of the ruleset-provided value when available. + // this is not available as an option in the live editor but can still be applied via .osu editing. + if (beatmap.BeatmapInfo.AudioLeadIn > 0) + startTime = Math.Min(startTime, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn); Seek(startTime); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index d6488dc209..cb71693312 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -129,7 +129,7 @@ namespace osu.Game.Screens.Play if (!ScoreProcessor.Mode.Disabled) config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); - InternalChild = GameplayClockContainer = new GameplayClockContainer(working, Mods.Value, DrawableRuleset.GameplayStartTime); + InternalChild = GameplayClockContainer = new GameplayClockContainer(working, Mods.Value, DrawableRuleset.GameplayStartTime, DrawableRuleset.Objects.First().StartTime); addUnderlayComponents(GameplayClockContainer); addGameplayComponents(GameplayClockContainer, working); From 29d23749285dfc0773a152ee81536a061cc0c9ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2019 17:12:29 +0900 Subject: [PATCH 2402/2815] Add back skip button actuation count tests --- .../Visual/Gameplay/TestSceneSkipOverlay.cs | 51 +++++++++++++++++-- osu.Game/Screens/Play/SkipOverlay.cs | 2 + 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index 0d62d9a520..1b4b0b4dcc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -18,27 +18,41 @@ namespace osu.Game.Tests.Visual.Gameplay private SkipOverlay skip; private int requestCount; + private FramedOffsetClock offsetClock; + private StopwatchClock stopwatchClock; + + private double increment; + [SetUp] public void SetUp() => Schedule(() => { requestCount = 0; + increment = 6000; + Child = new Container { RelativeSizeAxes = Axes.Both, - Clock = new FramedOffsetClock(Clock) - { - Offset = -Clock.CurrentTime, - }, + Clock = offsetClock = new FramedOffsetClock(stopwatchClock = new StopwatchClock(true)), Children = new Drawable[] { skip = new SkipOverlay(6000) { - RequestSkip = () => requestCount++ + RequestSkip = () => + { + requestCount++; + offsetClock.Offset += increment; + } } }, }; }); + protected override void Update() + { + if (stopwatchClock != null) + stopwatchClock.Rate = Clock.Rate; + } + [Test] public void TestFadeOnIdle() { @@ -60,6 +74,33 @@ namespace osu.Game.Tests.Visual.Gameplay checkRequestCount(1); } + [Test] + public void TestClickOnlyActuatesOnce() + { + AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); + AddStep("click", () => + { + increment = 6000 - offsetClock.CurrentTime - GameplayClockContainer.MINIMUM_SKIP_TIME / 2; + InputManager.Click(MouseButton.Left); + }); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + checkRequestCount(1); + } + + [Test] + public void TestClickOnlyActuatesMultipleTimes() + { + AddStep("set increment lower", () => increment = 3000); + AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + checkRequestCount(2); + } + [Test] public void TestDoesntFadeOnMouseDown() { diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 3abe61d1a9..2c6b33be39 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -124,6 +124,8 @@ namespace osu.Game.Screens.Play { base.Update(); remainingTimeBox.ResizeWidthTo((float)Math.Max(0, 1 - (Time.Current - displayTime) / (beginFadeTime - displayTime)), 120, Easing.OutQuint); + + button.Enabled.Value = Time.Current < startTime - GameplayClockContainer.MINIMUM_SKIP_TIME; } protected override bool OnMouseMove(MouseMoveEvent e) From 4e53bca8dd1e8ad8392a13010af04a9e42b69e50 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2019 18:21:49 +0900 Subject: [PATCH 2403/2815] Simplify tests --- .../Visual/Gameplay/TestSceneLeadIn.cs | 31 +++++++++++-------- osu.Game/Beatmaps/BeatmapInfo.cs | 2 +- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index 9275dc636c..6434387f51 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -1,6 +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.Diagnostics; +using System.Linq; using NUnit.Framework; using osu.Framework.MathUtils; using osu.Game.Beatmaps; @@ -15,26 +17,26 @@ namespace osu.Game.Tests.Visual.Gameplay { private LeadInPlayer player; - [Test] - public void TestShortLeadIn() + private const double lenience_ms = 10; + + private const double first_hit_object = 2170; + + [TestCase(1000, 0)] + [TestCase(2000, 0)] + [TestCase(3000, first_hit_object - 3000)] + [TestCase(10000, first_hit_object - 10000)] + public void TestLeadInProducesCorrectStartTime(double leadIn, double expectedStartTime) { loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo) { - BeatmapInfo = { AudioLeadIn = 1000 } + BeatmapInfo = { AudioLeadIn = leadIn } }); - AddAssert("correct lead-in", () => Precision.AlmostEquals(player.FirstFrameClockTime.Value, 0, 100)); - } - - [Test] - public void TestLongLeadIn() - { - loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo) + AddAssert($"first frame is {expectedStartTime}", () => { - BeatmapInfo = { AudioLeadIn = 10000 } + Debug.Assert(player.FirstFrameClockTime != null); + return Precision.AlmostEquals(player.FirstFrameClockTime.Value, expectedStartTime, lenience_ms); }); - - AddAssert("correct lead-in", () => Precision.AlmostEquals(player.FirstFrameClockTime.Value, player.GameplayStartTime - 10000, 100)); } private void loadPlayerWithBeatmap(IBeatmap beatmap) @@ -61,6 +63,8 @@ namespace osu.Game.Tests.Visual.Gameplay public double GameplayStartTime => DrawableRuleset.GameplayStartTime; + public double FirstHitObjectTime => DrawableRuleset.Objects.First().StartTime; + public double GameplayClockTime => GameplayClockContainer.GameplayClock.CurrentTime; protected override void UpdateAfterChildren() @@ -73,6 +77,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddInternal(new OsuSpriteText { Text = $"GameplayStartTime: {DrawableRuleset.GameplayStartTime} " + + $"FirstHitObjectTime: {FirstHitObjectTime} " + $"LeadInTime: {Beatmap.Value.BeatmapInfo.AudioLeadIn} " + $"FirstFrameClockTime: {FirstFrameClockTime}" }); diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 198046df4f..6e82c465dc 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -76,7 +76,7 @@ namespace osu.Game.Beatmaps public string MD5Hash { get; set; } // General - public int AudioLeadIn { get; set; } + public double AudioLeadIn { get; set; } public bool Countdown { get; set; } = true; public float StackLeniency { get; set; } = 0.7f; public bool SpecialStyle { get; set; } From 8d1b11d4bd86b644c683bd74eac0dc893f61823a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2019 18:50:54 +0900 Subject: [PATCH 2404/2815] Add storyboard lead-in tests --- .../TestSceneSkinFallbacks.cs | 7 +++-- .../TestSceneSpinnerRotation.cs | 5 ++-- .../Visual/Gameplay/TestSceneAutoplay.cs | 5 ++-- .../Gameplay/TestSceneGameplayRewinding.cs | 5 ++-- .../Visual/Gameplay/TestSceneLeadIn.cs | 29 +++++++++++++++++-- .../TestScenePlayerReferenceLeaking.cs | 5 ++-- .../Drawables/DrawableStoryboardSprite.cs | 2 +- osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs | 10 +++++-- osu.Game/Tests/Visual/OsuTestScene.cs | 14 +++++---- 9 files changed, 60 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index 02c65db6ad..7ca311118e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -19,6 +19,7 @@ using osu.Game.Graphics; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Screens.Play; using osu.Game.Skinning; +using osu.Game.Storyboards; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests @@ -75,14 +76,14 @@ namespace osu.Game.Rulesets.Osu.Tests protected override Player CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(testUserSkin); - protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) => new CustomSkinWorkingBeatmap(beatmap, Clock, audio, testBeatmapSkin); + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, audio, testBeatmapSkin); public class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap { private readonly ISkinSource skin; - public CustomSkinWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock frameBasedClock, AudioManager audio, ISkinSource skin) - : base(beatmap, frameBasedClock, audio) + public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock frameBasedClock, AudioManager audio, ISkinSource skin) + : base(beatmap, storyboard, frameBasedClock, audio) { this.skin = skin; } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index cded7f0e95..d0ce0c33c2 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -15,6 +15,7 @@ using osu.Game.Tests.Visual; using osuTK; using System.Collections.Generic; using System.Linq; +using osu.Game.Storyboards; using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap; namespace osu.Game.Rulesets.Osu.Tests @@ -28,9 +29,9 @@ namespace osu.Game.Rulesets.Osu.Tests protected override bool Autoplay => true; - protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) { - var working = new ClockBackedTestWorkingBeatmap(beatmap, new FramedClock(new ManualClock { Rate = 1 }), audioManager); + var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); track = (TrackVirtualManual)working.Track; return working; } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index f94071a7a9..5ee109e3dd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -7,6 +7,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; +using osu.Game.Storyboards; namespace osu.Game.Tests.Visual.Gameplay { @@ -29,9 +30,9 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("key counter reset", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); } - protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) { - var working = base.CreateWorkingBeatmap(beatmap); + var working = base.CreateWorkingBeatmap(beatmap, storyboard); track = (ClockBackedTestWorkingBeatmap.TrackVirtualManual)working.Track; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs index ffc025a942..b2b58a63fb 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs @@ -17,6 +17,7 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; +using osu.Game.Storyboards; using osuTK; namespace osu.Game.Tests.Visual.Gameplay @@ -35,9 +36,9 @@ namespace osu.Game.Tests.Visual.Gameplay private Track track; - protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) { - var working = new ClockBackedTestWorkingBeatmap(beatmap, new FramedClock(new ManualClock { Rate = 1 }), audioManager); + var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); track = working.Track; return working; } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index 6434387f51..0150c6ea74 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -4,12 +4,15 @@ using System.Diagnostics; using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics; using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play; +using osu.Game.Storyboards; using osu.Game.Tests.Beatmaps; +using osuTK; namespace osu.Game.Tests.Visual.Gameplay { @@ -39,11 +42,33 @@ namespace osu.Game.Tests.Visual.Gameplay }); } - private void loadPlayerWithBeatmap(IBeatmap beatmap) + [TestCase(1000, 0)] + [TestCase(0, 0)] + [TestCase(-1000, -1000)] + [TestCase(-10000, -10000)] + public void TestStoryboardProducesCorrectStartTime(double firstStoryboardEvent, double expectedStartTime) + { + var storyboard = new Storyboard(); + + var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); + sprite.TimelineGroup.Alpha.Add(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1); + + storyboard.GetLayer("Background").Add(sprite); + + loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo), storyboard); + + AddAssert($"first frame is {expectedStartTime}", () => + { + Debug.Assert(player.FirstFrameClockTime != null); + return Precision.AlmostEquals(player.FirstFrameClockTime.Value, expectedStartTime, lenience_ms); + }); + } + + private void loadPlayerWithBeatmap(IBeatmap beatmap, Storyboard storyboard = null) { AddStep("create player", () => { - Beatmap.Value = CreateWorkingBeatmap(beatmap); + Beatmap.Value = CreateWorkingBeatmap(beatmap, storyboard); LoadScreen(player = new LeadInPlayer()); }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerReferenceLeaking.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerReferenceLeaking.cs index 65b56319e8..4d701f56a9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerReferenceLeaking.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerReferenceLeaking.cs @@ -6,6 +6,7 @@ using osu.Framework.Lists; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Screens.Play; +using osu.Game.Storyboards; namespace osu.Game.Tests.Visual.Gameplay { @@ -42,9 +43,9 @@ namespace osu.Game.Tests.Visual.Gameplay }); } - protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) { - var working = base.CreateWorkingBeatmap(beatmap); + var working = base.CreateWorkingBeatmap(beatmap, storyboard); workingWeakReferences.Add(working); return working; } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index 5f1f5ddacb..3a117d1713 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -66,7 +66,7 @@ namespace osu.Game.Storyboards.Drawables [BackgroundDependencyLoader] private void load(IBindable beatmap, TextureStore textureStore) { - var path = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; + var path = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; if (path == null) return; diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs index 0d9f4f51be..871d8ee3f1 100644 --- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs @@ -5,25 +5,31 @@ using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Video; using osu.Game.Beatmaps; +using osu.Game.Storyboards; namespace osu.Game.Tests.Beatmaps { public class TestWorkingBeatmap : WorkingBeatmap { private readonly IBeatmap beatmap; + private readonly Storyboard storyboard; /// /// Create an instance which provides the when requested. /// - /// The beatmap - public TestWorkingBeatmap(IBeatmap beatmap) + /// The beatmap. + /// An optional storyboard. + public TestWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) : base(beatmap.BeatmapInfo, null) { this.beatmap = beatmap; + this.storyboard = storyboard; } protected override IBeatmap GetBeatmap() => beatmap; + protected override Storyboard GetStoryboard() => storyboard ?? base.GetStoryboard(); + protected override Texture GetBackground() => null; protected override VideoSprite GetVideo() => null; diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 13e08c7d22..be67c2fe23 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -21,6 +21,7 @@ using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Storyboards; using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual @@ -119,10 +120,10 @@ namespace osu.Game.Tests.Visual protected virtual IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset); protected WorkingBeatmap CreateWorkingBeatmap(RulesetInfo ruleset) => - CreateWorkingBeatmap(CreateBeatmap(ruleset)); + CreateWorkingBeatmap(CreateBeatmap(ruleset), null); - protected virtual WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) => - new ClockBackedTestWorkingBeatmap(beatmap, Clock, audio); + protected virtual WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => + new ClockBackedTestWorkingBeatmap(beatmap, storyboard, Clock, audio); [BackgroundDependencyLoader] private void load(RulesetStore rulesets) @@ -168,7 +169,7 @@ namespace osu.Game.Tests.Visual /// A clock which should be used instead of a stopwatch for virtual time progression. /// Audio manager. Required if a reference clock isn't provided. public ClockBackedTestWorkingBeatmap(RulesetInfo ruleset, IFrameBasedClock referenceClock, AudioManager audio) - : this(new TestBeatmap(ruleset), referenceClock, audio) + : this(new TestBeatmap(ruleset), null, referenceClock, audio) { } @@ -176,11 +177,12 @@ namespace osu.Game.Tests.Visual /// Create an instance which provides the when requested. /// /// The beatmap + /// The storyboard. /// An optional clock which should be used instead of a stopwatch for virtual time progression. /// Audio manager. Required if a reference clock isn't provided. /// The length of the returned virtual track. - public ClockBackedTestWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock referenceClock, AudioManager audio, double length = 60000) - : base(beatmap) + public ClockBackedTestWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio, double length = 60000) + : base(beatmap, storyboard) { if (referenceClock != null) { From 46a94821d44129975336136556b2b18e9e791e1b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2019 18:57:19 +0900 Subject: [PATCH 2405/2815] Add support for consecutive skips --- .../Visual/Gameplay/TestSceneSkipOverlay.cs | 42 ++++++++++++++++--- .../Screens/Play/GameplayClockContainer.cs | 22 ++++++++++ osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Screens/Play/SkipOverlay.cs | 22 +++------- 4 files changed, 65 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index b152c21454..1b4b0b4dcc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -18,27 +18,41 @@ namespace osu.Game.Tests.Visual.Gameplay private SkipOverlay skip; private int requestCount; + private FramedOffsetClock offsetClock; + private StopwatchClock stopwatchClock; + + private double increment; + [SetUp] public void SetUp() => Schedule(() => { requestCount = 0; + increment = 6000; + Child = new Container { RelativeSizeAxes = Axes.Both, - Clock = new FramedOffsetClock(Clock) - { - Offset = -Clock.CurrentTime, - }, + Clock = offsetClock = new FramedOffsetClock(stopwatchClock = new StopwatchClock(true)), Children = new Drawable[] { skip = new SkipOverlay(6000) { - RequestSeek = _ => requestCount++ + RequestSkip = () => + { + requestCount++; + offsetClock.Offset += increment; + } } }, }; }); + protected override void Update() + { + if (stopwatchClock != null) + stopwatchClock.Rate = Clock.Rate; + } + [Test] public void TestFadeOnIdle() { @@ -64,13 +78,29 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestClickOnlyActuatesOnce() { AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); - AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("click", () => + { + increment = 6000 - offsetClock.CurrentTime - GameplayClockContainer.MINIMUM_SKIP_TIME / 2; + InputManager.Click(MouseButton.Left); + }); AddStep("click", () => InputManager.Click(MouseButton.Left)); AddStep("click", () => InputManager.Click(MouseButton.Left)); AddStep("click", () => InputManager.Click(MouseButton.Left)); checkRequestCount(1); } + [Test] + public void TestClickOnlyActuatesMultipleTimes() + { + AddStep("set increment lower", () => increment = 3000); + AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + checkRequestCount(2); + } + [Test] public void TestDoesntFadeOnMouseDown() { diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 2f2028ff53..6903ccf06d 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -89,6 +89,11 @@ namespace osu.Game.Screens.Play private double totalOffset => userOffsetClock.Offset + platformOffsetClock.Offset; + /// + /// Duration before gameplay start time required before skip button displays. + /// + public const double MINIMUM_SKIP_TIME = 1000; + private readonly BindableDouble pauseFreqAdjust = new BindableDouble(1); [BackgroundDependencyLoader] @@ -130,6 +135,23 @@ namespace osu.Game.Screens.Play this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In); } + /// + /// Skip forward to the next valid skip point. + /// + public void Skip() + { + if (GameplayClock.CurrentTime > gameplayStartTime - MINIMUM_SKIP_TIME) + return; + + double skipTarget = gameplayStartTime - MINIMUM_SKIP_TIME; + + if (GameplayClock.CurrentTime < 0 && skipTarget > 6000) + // double skip exception for storyboards with very long intros + skipTarget = 0; + + Seek(skipTarget); + } + /// /// Seek to a specific time in gameplay. /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a9b0649fab..d6488dc209 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -203,7 +203,7 @@ namespace osu.Game.Screens.Play }, new SkipOverlay(DrawableRuleset.GameplayStartTime) { - RequestSeek = GameplayClockContainer.Seek + RequestSkip = GameplayClockContainer.Skip }, FailOverlay = new FailOverlay { diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 835867fe62..2c6b33be39 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Play { private readonly double startTime; - public Action RequestSeek; + public Action RequestSkip; private Button button; private Box remainingTimeBox; @@ -90,11 +90,6 @@ namespace osu.Game.Screens.Play }; } - /// - /// Duration before gameplay start time required before skip button displays. - /// - private const double skip_buffer = 1000; - private const double fade_time = 300; private double beginFadeTime => startTime - fade_time; @@ -104,7 +99,7 @@ namespace osu.Game.Screens.Play base.LoadComplete(); // skip is not required if there is no extra "empty" time to skip. - if (Clock.CurrentTime > beginFadeTime - skip_buffer) + if (Clock.CurrentTime > beginFadeTime - GameplayClockContainer.MINIMUM_SKIP_TIME) { Alpha = 0; Expire(); @@ -115,10 +110,9 @@ namespace osu.Game.Screens.Play using (BeginAbsoluteSequence(beginFadeTime)) this.FadeOut(fade_time); - button.Action = () => RequestSeek?.Invoke(beginFadeTime); + button.Action = () => RequestSkip?.Invoke(); displayTime = Time.Current; - Expire(); } @@ -130,6 +124,8 @@ namespace osu.Game.Screens.Play { base.Update(); remainingTimeBox.ResizeWidthTo((float)Math.Max(0, 1 - (Time.Current - displayTime) / (beginFadeTime - displayTime)), 120, Easing.OutQuint); + + button.Enabled.Value = Time.Current < startTime - GameplayClockContainer.MINIMUM_SKIP_TIME; } protected override bool OnMouseMove(MouseMoveEvent e) @@ -335,13 +331,7 @@ namespace osu.Game.Screens.Play box.FlashColour(Color4.White, 500, Easing.OutQuint); aspect.ScaleTo(1.2f, 2000, Easing.OutQuint); - bool result = base.OnClick(e); - - // for now, let's disable the skip button after the first press. - // this will likely need to be contextual in the future (bound from external components). - Enabled.Value = false; - - return result; + return base.OnClick(e); } } } From bd6831624a58802f022eccc616d90f977c9155e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2019 19:51:22 +0900 Subject: [PATCH 2406/2815] Decouple skip button animations from gameplay clock --- .../Visual/Gameplay/TestSceneSkipOverlay.cs | 32 +++++++-------- osu.Game/Screens/Play/SkipOverlay.cs | 40 +++++++++---------- 2 files changed, 33 insertions(+), 39 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index 1b4b0b4dcc..a21f55e361 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -4,8 +4,8 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Timing; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play; using osuTK; using osuTK.Input; @@ -18,40 +18,38 @@ namespace osu.Game.Tests.Visual.Gameplay private SkipOverlay skip; private int requestCount; - private FramedOffsetClock offsetClock; - private StopwatchClock stopwatchClock; - private double increment; + private GameplayClockContainer gameplayClockContainer; + private GameplayClock gameplayClock; + + private const double skip_time = 6000; + [SetUp] public void SetUp() => Schedule(() => { requestCount = 0; - increment = 6000; + increment = skip_time; - Child = new Container + Child = gameplayClockContainer = new GameplayClockContainer(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), new Mod[] { }, 0) { RelativeSizeAxes = Axes.Both, - Clock = offsetClock = new FramedOffsetClock(stopwatchClock = new StopwatchClock(true)), Children = new Drawable[] { - skip = new SkipOverlay(6000) + skip = new SkipOverlay(skip_time) { RequestSkip = () => { requestCount++; - offsetClock.Offset += increment; + gameplayClockContainer.Seek(gameplayClock.CurrentTime + increment); } } }, }; - }); - protected override void Update() - { - if (stopwatchClock != null) - stopwatchClock.Rate = Clock.Rate; - } + gameplayClockContainer.Start(); + gameplayClock = gameplayClockContainer.GameplayClock; + }); [Test] public void TestFadeOnIdle() @@ -80,7 +78,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); AddStep("click", () => { - increment = 6000 - offsetClock.CurrentTime - GameplayClockContainer.MINIMUM_SKIP_TIME / 2; + increment = skip_time - gameplayClock.CurrentTime - GameplayClockContainer.MINIMUM_SKIP_TIME / 2; InputManager.Click(MouseButton.Left); }); AddStep("click", () => InputManager.Click(MouseButton.Left)); diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 2c6b33be39..1a5ed20953 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -19,6 +19,7 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Containers; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.MathUtils; using osu.Game.Input.Bindings; namespace osu.Game.Screens.Play @@ -35,6 +36,9 @@ namespace osu.Game.Screens.Play private FadeContainer fadeContainer; private double displayTime; + [Resolved] + private GameplayClock gameplayClock { get; set; } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; /// @@ -45,8 +49,6 @@ namespace osu.Game.Screens.Play { this.startTime = startTime; - Show(); - RelativePositionAxes = Axes.Both; RelativeSizeAxes = Axes.X; @@ -57,13 +59,8 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, GameplayClock clock) + private void load(OsuColour colours) { - var baseClock = Clock; - - if (clock != null) - Clock = clock; - Children = new Drawable[] { fadeContainer = new FadeContainer @@ -73,7 +70,6 @@ namespace osu.Game.Screens.Play { button = new Button { - Clock = baseClock, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, @@ -92,40 +88,40 @@ namespace osu.Game.Screens.Play private const double fade_time = 300; - private double beginFadeTime => startTime - fade_time; + private double fadeOutBeginTime => startTime - GameplayClockContainer.MINIMUM_SKIP_TIME; protected override void LoadComplete() { base.LoadComplete(); // skip is not required if there is no extra "empty" time to skip. - if (Clock.CurrentTime > beginFadeTime - GameplayClockContainer.MINIMUM_SKIP_TIME) + // we may need to remove this if rewinding before the initial player load position becomes a thing. + if (fadeOutBeginTime < gameplayClock.CurrentTime) { - Alpha = 0; Expire(); return; } - this.FadeInFromZero(fade_time); - using (BeginAbsoluteSequence(beginFadeTime)) - this.FadeOut(fade_time); - button.Action = () => RequestSkip?.Invoke(); + displayTime = gameplayClock.CurrentTime; - displayTime = Time.Current; - Expire(); + Show(); } - protected override void PopIn() => this.FadeIn(); + protected override void PopIn() => this.FadeIn(fade_time); - protected override void PopOut() => this.FadeOut(); + protected override void PopOut() => this.FadeOut(fade_time); protected override void Update() { base.Update(); - remainingTimeBox.ResizeWidthTo((float)Math.Max(0, 1 - (Time.Current - displayTime) / (beginFadeTime - displayTime)), 120, Easing.OutQuint); - button.Enabled.Value = Time.Current < startTime - GameplayClockContainer.MINIMUM_SKIP_TIME; + var progress = Math.Max(0, 1 - (gameplayClock.CurrentTime - displayTime) / (fadeOutBeginTime - displayTime)); + + remainingTimeBox.Width = (float)Interpolation.Lerp(remainingTimeBox.Width, progress, Math.Clamp(Time.Elapsed / 40, 0, 1)); + + button.Enabled.Value = progress > 0; + State.Value = progress > 0 ? Visibility.Visible : Visibility.Hidden; } protected override bool OnMouseMove(MouseMoveEvent e) From 71a64da566a685cce2ad929d989afab589c2b60e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2019 22:07:37 +0900 Subject: [PATCH 2407/2815] Fix test regressions --- osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index a21f55e361..875e7b9758 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -104,7 +104,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); AddStep("button down", () => InputManager.PressButton(MouseButton.Left)); - AddUntilStep("wait for overlay disapper", () => !skip.IsAlive); + AddUntilStep("wait for overlay disappear", () => !skip.IsPresent); AddAssert("ensure button didn't disappear", () => skip.Children.First().Alpha > 0); AddStep("button up", () => InputManager.ReleaseButton(MouseButton.Left)); checkRequestCount(0); From 8b1fb2d5a1ab47fdf05f46e6f24b713d739440ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2019 22:35:15 +0900 Subject: [PATCH 2408/2815] Standardise keyword specifications --- .../Settings/Sections/Audio/AudioDevicesSettings.cs | 2 +- osu.Game/Overlays/Settings/Sections/AudioSection.cs | 2 +- .../Overlays/Settings/Sections/Gameplay/GeneralSettings.cs | 2 +- .../Overlays/Settings/Sections/Gameplay/ModsSettings.cs | 5 ++++- .../Settings/Sections/Gameplay/SongSelectSettings.cs | 6 +++--- .../Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 4 ++-- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs index e9ae784061..0612f028bc 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs @@ -62,7 +62,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio { dropdown = new AudioDeviceSettingsDropdown { - Keywords = new[] { "Speaker" } + Keywords = new[] { "speaker", "headphone", "output" } } }; diff --git a/osu.Game/Overlays/Settings/Sections/AudioSection.cs b/osu.Game/Overlays/Settings/Sections/AudioSection.cs index 254af6ea21..b18488b616 100644 --- a/osu.Game/Overlays/Settings/Sections/AudioSection.cs +++ b/osu.Game/Overlays/Settings/Sections/AudioSection.cs @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Settings.Sections { public override string Header => "Audio"; - public override IEnumerable FilterTerms => base.FilterTerms.Concat(new[] { "Sound" }); + public override IEnumerable FilterTerms => base.FilterTerms.Concat(new[] { "sound" }); public override IconUsage Icon => FontAwesome.Solid.VolumeUp; diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 0ac145ffe2..f4aa9a0144 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { LabelText = "Show health display even when you can't fail", Bindable = config.GetBindable(OsuSetting.ShowHealthDisplayWhenCantFail), - Keywords = new[] { "Bar", "Hp" } + Keywords = new[] { "hp", "bar" } }, new SettingsCheckbox { diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs index a71919a2fc..0babb98066 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs @@ -1,6 +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.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Game.Configuration; @@ -10,6 +12,8 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { protected override string Header => "Mods"; + public override IEnumerable FilterTerms => base.FilterTerms.Concat(new[] { "mod" }); + [BackgroundDependencyLoader] private void load(OsuConfigManager config) { @@ -19,7 +23,6 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { LabelText = "Increase visibility of first object when visual impairment mods are enabled", Bindable = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility), - Keywords = new[] { "Mod" } }, }; } diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs index fce0e3dea8..f4a861141f 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs @@ -26,21 +26,21 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { LabelText = "Show converted beatmaps", Bindable = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), - Keywords = new[] { "Converts" } + Keywords = new[] { "convert" } }, new SettingsSlider { LabelText = "Display beatmaps from", Bindable = config.GetBindable(OsuSetting.DisplayStarsMinimum), KeyboardStep = 0.1f, - Keywords = new[] { "Stars" } + Keywords = new[] { "star", "difficulty" } }, new SettingsSlider { LabelText = "up to", Bindable = config.GetBindable(OsuSetting.DisplayStarsMaximum), KeyboardStep = 0.1f, - Keywords = new[] { "Stars" } + Keywords = new[] { "star", "difficulty" } }, new SettingsEnumDropdown { diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 8275d6dc0a..02b9edd975 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -76,13 +76,13 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics TransferValueOnCommit = true, Bindable = osuConfig.GetBindable(OsuSetting.UIScale), KeyboardStep = 0.01f, - Keywords = new[] { "Scale" }, + Keywords = new[] { "scale", "letterbox" }, }, new SettingsEnumDropdown { LabelText = "Screen Scaling", Bindable = osuConfig.GetBindable(OsuSetting.Scaling), - Keywords = new[] { "Scale" }, + Keywords = new[] { "scale", "letterbox" }, }, scalingSettings = new FillFlowContainer> { From d7cb23a9094304d2e1f70172c2c2093c886de11f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2019 22:51:00 +0900 Subject: [PATCH 2409/2815] Remove redundant keyword --- .../Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs index f4a861141f..a5f56ae76e 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs @@ -26,7 +26,6 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { LabelText = "Show converted beatmaps", Bindable = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), - Keywords = new[] { "convert" } }, new SettingsSlider { From 4475307707251bc591335cd02424274430fb6777 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2019 22:55:31 +0900 Subject: [PATCH 2410/2815] Fix spacing --- osu.Game/Utils/SentryLogger.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index 7e879741d6..981251784e 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -26,6 +26,7 @@ namespace osu.Game.Utils Dsn = new Dsn("https://5e342cd55f294edebdc9ad604d28bbd3@sentry.io/1255255"), Release = game.Version }; + sentry = new SentryClient(options); sentryScope = new Scope(options); From 737c2bd1c8fa1a66bcf47160cc675fe082985941 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 21 Nov 2019 18:50:29 +0300 Subject: [PATCH 2411/2815] Remove pointless const --- osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index e62a754e92..ac8952bebb 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -97,7 +97,6 @@ namespace osu.Game.Overlays.BeatmapSet private class ModButton : ModIcon { - private const float mod_scale = 0.4f; private const int duration = 200; public readonly BindableBool Selected = new BindableBool(); @@ -106,7 +105,7 @@ namespace osu.Game.Overlays.BeatmapSet public ModButton(Mod mod) : base(mod) { - Scale = new Vector2(mod_scale); + Scale = new Vector2(0.4f); Highlighted.Value = true; Add(new HoverClickSounds()); } From 20f01ff3e9c68ea9ffd45cc60e76121ae1101038 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 21 Nov 2019 23:46:39 +0800 Subject: [PATCH 2412/2815] Revert false positives of 'unused assignment'. --- .../Legacy/HitObjectPatternGenerator.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs index 3b7a24726e..ba9aa9e87f 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs @@ -168,43 +168,43 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy } if (convertType.HasFlag(PatternType.KeepSingle)) - return generateRandomNotes(1); + return pattern = generateRandomNotes(1); if (convertType.HasFlag(PatternType.Mirror)) { if (ConversionDifficulty > 6.5) - return generateRandomPatternWithMirrored(0.12, 0.38, 0.12); + return pattern = generateRandomPatternWithMirrored(0.12, 0.38, 0.12); if (ConversionDifficulty > 4) - return generateRandomPatternWithMirrored(0.12, 0.17, 0); + return pattern = generateRandomPatternWithMirrored(0.12, 0.17, 0); - return generateRandomPatternWithMirrored(0.12, 0, 0); + return pattern = generateRandomPatternWithMirrored(0.12, 0, 0); } if (ConversionDifficulty > 6.5) { if (convertType.HasFlag(PatternType.LowProbability)) - return generateRandomPattern(0.78, 0.42, 0, 0); + return pattern = generateRandomPattern(0.78, 0.42, 0, 0); - return generateRandomPattern(1, 0.62, 0, 0); + return pattern = generateRandomPattern(1, 0.62, 0, 0); } if (ConversionDifficulty > 4) { if (convertType.HasFlag(PatternType.LowProbability)) - return generateRandomPattern(0.35, 0.08, 0, 0); + return pattern = generateRandomPattern(0.35, 0.08, 0, 0); - return generateRandomPattern(0.52, 0.15, 0, 0); + return pattern = generateRandomPattern(0.52, 0.15, 0, 0); } if (ConversionDifficulty > 2) { if (convertType.HasFlag(PatternType.LowProbability)) - return generateRandomPattern(0.18, 0, 0, 0); + return pattern = generateRandomPattern(0.18, 0, 0, 0); - return generateRandomPattern(0.45, 0, 0, 0); + return pattern = generateRandomPattern(0.45, 0, 0, 0); } - return generateRandomPattern(0, 0, 0, 0); + return pattern = generateRandomPattern(0, 0, 0, 0); } finally { From 984ec11a78ca071e70d04cd5899ee7b07cfad140 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 21 Nov 2019 18:56:48 +0300 Subject: [PATCH 2413/2815] Make Ruleset a bindable --- .../Online/TestSceneLeaderboardModSelector.cs | 14 ++++-- .../BeatmapSet/LeaderboardModSelector.cs | 49 ++++++++----------- 2 files changed, 30 insertions(+), 33 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs index c0c18c2583..ebe233a5f4 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs @@ -12,6 +12,8 @@ using osu.Game.Rulesets.Catch; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Bindables; +using osu.Game.Rulesets; namespace osu.Game.Tests.Visual.Online { @@ -26,6 +28,7 @@ namespace osu.Game.Tests.Visual.Online { LeaderboardModSelector modSelector; FillFlowContainer selectedMods; + var ruleset = new Bindable(); Add(selectedMods = new FillFlowContainer { @@ -37,6 +40,7 @@ namespace osu.Game.Tests.Visual.Online { Anchor = Anchor.Centre, Origin = Anchor.Centre, + Ruleset = { BindTarget = ruleset } }); modSelector.SelectedMods.ItemsAdded += mods => @@ -62,12 +66,12 @@ namespace osu.Game.Tests.Visual.Online }); }; - AddStep("osu ruleset", () => modSelector.Ruleset = new OsuRuleset().RulesetInfo); - AddStep("mania ruleset", () => modSelector.Ruleset = new ManiaRuleset().RulesetInfo); - AddStep("taiko ruleset", () => modSelector.Ruleset = new TaikoRuleset().RulesetInfo); - AddStep("catch ruleset", () => modSelector.Ruleset = new CatchRuleset().RulesetInfo); + AddStep("osu ruleset", () => ruleset.Value = new OsuRuleset().RulesetInfo); + AddStep("mania ruleset", () => ruleset.Value = new ManiaRuleset().RulesetInfo); + AddStep("taiko ruleset", () => ruleset.Value = new TaikoRuleset().RulesetInfo); + AddStep("catch ruleset", () => ruleset.Value = new CatchRuleset().RulesetInfo); AddStep("Deselect all", () => modSelector.DeselectAll()); - AddStep("null ruleset", () => modSelector.Ruleset = null); + AddStep("null ruleset", () => ruleset.Value = null); } } } diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index ac8952bebb..ba42e8c310 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -20,34 +20,7 @@ namespace osu.Game.Overlays.BeatmapSet public class LeaderboardModSelector : CompositeDrawable { public readonly BindableList SelectedMods = new BindableList(); - - private RulesetInfo ruleset; - - public RulesetInfo Ruleset - { - get => ruleset; - set - { - if (ruleset?.Equals(value) ?? false) - { - DeselectAll(); - return; - } - - ruleset = value; - - SelectedMods.Clear(); - modsContainer.Clear(); - - if (ruleset == null) - return; - - modsContainer.Add(new ModButton(new ModNoMod())); - modsContainer.AddRange(ruleset.CreateInstance().GetAllMods().Where(m => m.Ranked).Select(m => new ModButton(m))); - - modsContainer.ForEach(button => button.OnSelectionChanged = selectionChanged); - } - } + public readonly Bindable Ruleset = new Bindable(); private readonly FillFlowContainer modsContainer; @@ -64,6 +37,26 @@ namespace osu.Game.Overlays.BeatmapSet }; } + protected override void LoadComplete() + { + base.LoadComplete(); + Ruleset.BindValueChanged(onRulesetChanged, true); + } + + private void onRulesetChanged(ValueChangedEvent ruleset) + { + SelectedMods.Clear(); + modsContainer.Clear(); + + if (ruleset.NewValue == null) + return; + + modsContainer.Add(new ModButton(new ModNoMod())); + modsContainer.AddRange(ruleset.NewValue.CreateInstance().GetAllMods().Where(m => m.Ranked).Select(m => new ModButton(m))); + + modsContainer.ForEach(button => button.OnSelectionChanged = selectionChanged); + } + private void selectionChanged(Mod mod, bool selected) { if (selected) From 23fc7b198782d79333edfa849a29bfe27f9805cd Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 21 Nov 2019 19:02:24 +0300 Subject: [PATCH 2414/2815] Implement updateHighlighted method --- .../BeatmapSet/LeaderboardModSelector.cs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index ba42e8c310..0a053c98db 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -64,24 +64,34 @@ namespace osu.Game.Overlays.BeatmapSet else SelectedMods.Remove(mod); - if (!SelectedMods.Any() && !IsHovered) + updateHighlighted(); + } + + private void updateHighlighted() + { + if (SelectedMods.Any()) + return; + + if (IsHovered) + { + modsContainer.Children.Where(button => !button.IsHovered).ForEach(button => button.Highlighted.Value = false); + } + else + { highlightAll(); + } } protected override bool OnHover(HoverEvent e) { - if (!SelectedMods.Any()) - modsContainer.Children.Where(button => !button.IsHovered).ForEach(button => button.Highlighted.Value = false); - + updateHighlighted(); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { base.OnHoverLost(e); - - if (!SelectedMods.Any()) - highlightAll(); + updateHighlighted(); } public void DeselectAll() => modsContainer.ForEach(mod => mod.Selected.Value = false); From 4cd7d67fe4eef0213128dfc619091c1844b50c63 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Fri, 22 Nov 2019 00:02:40 +0800 Subject: [PATCH 2415/2815] Use early return for if-pattern-matching. --- .../Beatmaps/ManiaBeatmapConverter.cs | 12 +++---- .../Legacy/DistanceObjectPatternGenerator.cs | 12 +++---- .../TestSceneSlider.cs | 32 +++++++++---------- osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs | 18 +++++------ .../Components/ScrollingTeamContainer.cs | 24 +++++++------- 5 files changed, 47 insertions(+), 51 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 6e3d5761ac..f5bd52146c 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -263,15 +263,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// private IList sampleInfoListAt(double time) { - if (HitObject is IHasCurve curveData) - { - double segmentTime = (curveData.EndTime - HitObject.StartTime) / curveData.SpanCount(); + if (!(HitObject is IHasCurve curveData)) + return HitObject.Samples; - int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); - return curveData.NodeSamples[index]; - } + double segmentTime = (curveData.EndTime - HitObject.StartTime) / curveData.SpanCount(); - return HitObject.Samples; + int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); + return curveData.NodeSamples[index]; } } } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 0cbf5cf51c..cf75441200 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -474,15 +474,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// private IList sampleInfoListAt(double time) { - if (HitObject is IHasCurve curveData) - { - double segmentTime = (EndTime - HitObject.StartTime) / spanCount; + if (!(HitObject is IHasCurve curveData)) + return HitObject.Samples; - int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); - return curveData.NodeSamples[index]; - } + double segmentTime = (EndTime - HitObject.StartTime) / spanCount; - return HitObject.Samples; + int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); + return curveData.NodeSamples[index]; } /// diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index 88c9324d4c..a9d5c03517 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -380,25 +380,25 @@ namespace osu.Game.Rulesets.Osu.Tests private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) { - if (judgedObject is DrawableOsuHitObject osuObject) + if (!(judgedObject is DrawableOsuHitObject osuObject)) + return; + + OsuSpriteText text; + Add(text = new OsuSpriteText { - OsuSpriteText text; - Add(text = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = result.IsHit ? "Hit!" : "Miss!", - Colour = result.IsHit ? Color4.Green : Color4.Red, - Font = OsuFont.GetFont(size: 30), - Position = osuObject.HitObject.StackedEndPosition + judgementOffsetDirection * new Vector2(0, 45) - }); + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = result.IsHit ? "Hit!" : "Miss!", + Colour = result.IsHit ? Color4.Green : Color4.Red, + Font = OsuFont.GetFont(size: 30), + Position = osuObject.HitObject.StackedEndPosition + judgementOffsetDirection * new Vector2(0, 45) + }); - text.Delay(150) - .Then().FadeOut(200) - .Then().Expire(); + text.Delay(150) + .Then().FadeOut(200) + .Then().Expire(); - judgementOffsetDirection *= -1; - } + judgementOffsetDirection *= -1; } } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs index b031707248..3d566362ae 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs @@ -22,17 +22,17 @@ namespace osu.Game.Rulesets.Osu.Mods osuObject.Position = new Vector2(osuObject.Position.X, OsuPlayfield.BASE_SIZE.Y - osuObject.Y); - if (hitObject is Slider slider) - { - slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); - slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); + if (!(hitObject is Slider slider)) + return; - var newControlPoints = new Vector2[slider.Path.ControlPoints.Length]; - for (int i = 0; i < slider.Path.ControlPoints.Length; i++) - newControlPoints[i] = new Vector2(slider.Path.ControlPoints[i].X, -slider.Path.ControlPoints[i].Y); + slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); + slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); - slider.Path = new SliderPath(slider.Path.Type, newControlPoints, slider.Path.ExpectedDistance); - } + var newControlPoints = new Vector2[slider.Path.ControlPoints.Length]; + for (int i = 0; i < slider.Path.ControlPoints.Length; i++) + newControlPoints[i] = new Vector2(slider.Path.ControlPoints[i].X, -slider.Path.ControlPoints[i].Y); + + slider.Path = new SliderPath(slider.Path.Type, newControlPoints, slider.Path.ExpectedDistance); } } } diff --git a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs index fff73fcf70..a345f93896 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs @@ -125,20 +125,20 @@ namespace osu.Game.Tournament.Screens.Drawings.Components foreach (var c in Children) { - if (c is ScrollingTeam stc) + if (!(c is ScrollingTeam stc)) + continue; + + if (closest == null) { - if (closest == null) - { - closest = stc; - continue; - } - - float o = Math.Abs(c.Position.X + c.DrawWidth / 2f - DrawWidth / 2f); - float lastOffset = Math.Abs(closest.Position.X + closest.DrawWidth / 2f - DrawWidth / 2f); - - if (o < lastOffset) - closest = stc; + closest = stc; + continue; } + + float o = Math.Abs(c.Position.X + c.DrawWidth / 2f - DrawWidth / 2f); + float lastOffset = Math.Abs(closest.Position.X + closest.DrawWidth / 2f - DrawWidth / 2f); + + if (o < lastOffset) + closest = stc; } Trace.Assert(closest != null, "closest != null"); From 8239c2da61228e0045a23b06a55ce4c1a3461e56 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Fri, 22 Nov 2019 00:12:03 +0800 Subject: [PATCH 2416/2815] Refactor to avoid using try-finally as control flow. --- .../Legacy/HitObjectPatternGenerator.cs | 52 +++++++++---------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs index ba9aa9e87f..84f950997d 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; +using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { @@ -88,15 +89,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy public override IEnumerable Generate() { - yield return generate(); - } - - private Pattern generate() - { - var pattern = new Pattern(); - - try + Pattern generateCore() { + var pattern = new Pattern(); + if (TotalColumns == 1) { addToPattern(pattern, 0); @@ -168,54 +164,56 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy } if (convertType.HasFlag(PatternType.KeepSingle)) - return pattern = generateRandomNotes(1); + return generateRandomNotes(1); if (convertType.HasFlag(PatternType.Mirror)) { if (ConversionDifficulty > 6.5) - return pattern = generateRandomPatternWithMirrored(0.12, 0.38, 0.12); + return generateRandomPatternWithMirrored(0.12, 0.38, 0.12); if (ConversionDifficulty > 4) - return pattern = generateRandomPatternWithMirrored(0.12, 0.17, 0); + return generateRandomPatternWithMirrored(0.12, 0.17, 0); - return pattern = generateRandomPatternWithMirrored(0.12, 0, 0); + return generateRandomPatternWithMirrored(0.12, 0, 0); } if (ConversionDifficulty > 6.5) { if (convertType.HasFlag(PatternType.LowProbability)) - return pattern = generateRandomPattern(0.78, 0.42, 0, 0); + return generateRandomPattern(0.78, 0.42, 0, 0); - return pattern = generateRandomPattern(1, 0.62, 0, 0); + return generateRandomPattern(1, 0.62, 0, 0); } if (ConversionDifficulty > 4) { if (convertType.HasFlag(PatternType.LowProbability)) - return pattern = generateRandomPattern(0.35, 0.08, 0, 0); + return generateRandomPattern(0.35, 0.08, 0, 0); - return pattern = generateRandomPattern(0.52, 0.15, 0, 0); + return generateRandomPattern(0.52, 0.15, 0, 0); } if (ConversionDifficulty > 2) { if (convertType.HasFlag(PatternType.LowProbability)) - return pattern = generateRandomPattern(0.18, 0, 0, 0); + return generateRandomPattern(0.18, 0, 0, 0); - return pattern = generateRandomPattern(0.45, 0, 0, 0); + return generateRandomPattern(0.45, 0, 0, 0); } - return pattern = generateRandomPattern(0, 0, 0, 0); + return generateRandomPattern(0, 0, 0, 0); } - finally + + var p = generateCore(); + + foreach (var obj in p.HitObjects) { - foreach (var obj in pattern.HitObjects) - { - if (convertType.HasFlag(PatternType.Stair) && obj.Column == TotalColumns - 1) - StairType = PatternType.ReverseStair; - if (convertType.HasFlag(PatternType.ReverseStair) && obj.Column == RandomStart) - StairType = PatternType.Stair; - } + if (convertType.HasFlag(PatternType.Stair) && obj.Column == TotalColumns - 1) + StairType = PatternType.ReverseStair; + if (convertType.HasFlag(PatternType.ReverseStair) && obj.Column == RandomStart) + StairType = PatternType.Stair; } + + return p.Yield(); } /// From e22a71c6b80125e63660b3a0799b7325bce50348 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 21 Nov 2019 19:42:07 +0300 Subject: [PATCH 2417/2815] Add visual difference between hovered and selected states --- .../BeatmapSet/LeaderboardModSelector.cs | 44 ++++++++----------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index 0a053c98db..0e9e848e7a 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -72,14 +72,7 @@ namespace osu.Game.Overlays.BeatmapSet if (SelectedMods.Any()) return; - if (IsHovered) - { - modsContainer.Children.Where(button => !button.IsHovered).ForEach(button => button.Highlighted.Value = false); - } - else - { - highlightAll(); - } + modsContainer.Children.Where(button => !button.IsHovered).ForEach(button => button.IsActive.Value = !IsHovered); } protected override bool OnHover(HoverEvent e) @@ -94,22 +87,19 @@ namespace osu.Game.Overlays.BeatmapSet updateHighlighted(); } - public void DeselectAll() => modsContainer.ForEach(mod => mod.Selected.Value = false); - - private void highlightAll() => modsContainer.ForEach(mod => mod.Highlighted.Value = true); + public void DeselectAll() => modsContainer.ForEach(mod => mod.Highlighted.Value = false); private class ModButton : ModIcon { private const int duration = 200; - public readonly BindableBool Selected = new BindableBool(); + public readonly BindableBool IsActive = new BindableBool(); public Action OnSelectionChanged; public ModButton(Mod mod) : base(mod) { Scale = new Vector2(0.4f); - Highlighted.Value = true; Add(new HoverClickSounds()); } @@ -117,35 +107,39 @@ namespace osu.Game.Overlays.BeatmapSet { base.LoadComplete(); - Selected.BindValueChanged(selected => + IsActive.BindValueChanged(hovered => { - updateState(); - OnSelectionChanged?.Invoke(Mod, selected.NewValue); - }); + if (Highlighted.Value) + return; + + this.FadeColour(hovered.NewValue ? Color4.White : Color4.DimGray, duration, Easing.OutQuint); + }, true); + } + + protected override void OnHighlightedChanged(ValueChangedEvent highlighted) + { + base.OnHighlightedChanged(highlighted); + OnSelectionChanged?.Invoke(Mod, highlighted.NewValue); + IsActive.TriggerChange(); } protected override bool OnClick(ClickEvent e) { - Selected.Toggle(); + Highlighted.Toggle(); return true; } protected override bool OnHover(HoverEvent e) { - updateState(); + IsActive.Value = true; return false; } protected override void OnHoverLost(HoverLostEvent e) { base.OnHoverLost(e); - updateState(); + IsActive.Value = false; } - - private void updateState() => Highlighted.Value = IsHovered || Selected.Value; - - protected override void OnHighlightedChanged(ValueChangedEvent highlighted) => - this.FadeColour(highlighted.NewValue ? Color4.White : Color4.Gray, duration, Easing.OutQuint); } } } From c7c8527f5ff780ccf012b41720545084757486b8 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 21 Nov 2019 20:22:15 +0300 Subject: [PATCH 2418/2815] Remove OnHighlightedChanged function --- .../Overlays/BeatmapSet/LeaderboardModSelector.cs | 11 +++++------ osu.Game/Rulesets/UI/ModIcon.cs | 9 +-------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index 0e9e848e7a..bb6f889c40 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -114,13 +114,12 @@ namespace osu.Game.Overlays.BeatmapSet this.FadeColour(hovered.NewValue ? Color4.White : Color4.DimGray, duration, Easing.OutQuint); }, true); - } - protected override void OnHighlightedChanged(ValueChangedEvent highlighted) - { - base.OnHighlightedChanged(highlighted); - OnSelectionChanged?.Invoke(Mod, highlighted.NewValue); - IsActive.TriggerChange(); + Highlighted.BindValueChanged(highlighted => + { + OnSelectionChanged?.Invoke(Mod, highlighted.NewValue); + IsActive.TriggerChange(); + }, true); } protected override bool OnClick(ClickEvent e) diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 19211e0c80..cf1bbadb57 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -107,19 +107,12 @@ namespace osu.Game.Rulesets.UI modIcon.Colour = colours.Yellow; break; } - - background.Colour = backgroundColour; } protected override void LoadComplete() { base.LoadComplete(); - Highlighted.BindValueChanged(OnHighlightedChanged, true); - } - - protected virtual void OnHighlightedChanged(ValueChangedEvent highlighted) - { - background.Colour = highlighted.NewValue ? highlightedColour : backgroundColour; + Highlighted.BindValueChanged(highlighted => background.Colour = highlighted.NewValue ? highlightedColour : backgroundColour, true); } } } From f390e558c76532b4ac2d784a9cc02af0058122bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2019 02:32:02 +0900 Subject: [PATCH 2419/2815] Combine and simplify beatmap change logic --- osu.Game/Graphics/Containers/ScalingContainer.cs | 10 ---------- .../Screens/Backgrounds/BackgroundScreenDefault.cs | 1 + osu.Game/Screens/Menu/MainMenu.cs | 11 ----------- 3 files changed, 1 insertion(+), 21 deletions(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index 023d295b08..8f07c3a656 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -6,7 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; -using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Screens; using osu.Game.Screens.Backgrounds; @@ -155,15 +154,6 @@ namespace osu.Game.Graphics.Containers private class ScalingBackgroundScreen : BackgroundScreenDefault { - private IBindable beatmap; - - [BackgroundDependencyLoader] - private void load(IBindable beatmap) - { - this.beatmap = beatmap.GetBoundCopy(); - this.beatmap.ValueChanged += _ => Next(); - } - public override void OnEntering(IScreen last) { this.FadeInFromZero(4000, Easing.OutQuint); diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 93590b0543..d49751f511 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -46,6 +46,7 @@ namespace osu.Game.Screens.Backgrounds user.ValueChanged += _ => Next(); skin.ValueChanged += _ => Next(); mode.ValueChanged += _ => Next(); + beatmap.ValueChanged += _ => Next(); currentDisplay = RNG.Next(0, background_count); diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 08338845e6..231115d1e1 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Platform; using osu.Framework.Screens; -using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -170,8 +169,6 @@ namespace osu.Game.Screens.Menu track.Start(); } } - - Beatmap.ValueChanged += beatmap_ValueChanged; } private bool exitConfirmed; @@ -220,14 +217,6 @@ namespace osu.Game.Screens.Menu seq.OnAbort(_ => buttons.SetOsuLogo(null)); } - private void beatmap_ValueChanged(ValueChangedEvent e) - { - if (!this.IsCurrentScreen()) - return; - - ((BackgroundScreenDefault)Background).Next(); - } - public override void OnSuspending(IScreen next) { base.OnSuspending(next); From 0f1a3d97c8d5c7a6daf9330663e3d81677d6d3f6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 21 Nov 2019 20:34:19 +0300 Subject: [PATCH 2420/2815] Naming adjustments --- .../BeatmapSet/LeaderboardModSelector.cs | 32 +++++++++---------- osu.Game/Overlays/Mods/ModButton.cs | 2 +- osu.Game/Rulesets/UI/ModIcon.cs | 4 +-- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index bb6f889c40..a6f69617d0 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -72,7 +72,7 @@ namespace osu.Game.Overlays.BeatmapSet if (SelectedMods.Any()) return; - modsContainer.Children.Where(button => !button.IsHovered).ForEach(button => button.IsActive.Value = !IsHovered); + modsContainer.Children.Where(button => !button.IsHovered).ForEach(button => button.Highlighted.Value = !IsHovered); } protected override bool OnHover(HoverEvent e) @@ -87,13 +87,13 @@ namespace osu.Game.Overlays.BeatmapSet updateHighlighted(); } - public void DeselectAll() => modsContainer.ForEach(mod => mod.Highlighted.Value = false); + public void DeselectAll() => modsContainer.ForEach(mod => mod.Selected.Value = false); private class ModButton : ModIcon { private const int duration = 200; - public readonly BindableBool IsActive = new BindableBool(); + public readonly BindableBool Highlighted = new BindableBool(); public Action OnSelectionChanged; public ModButton(Mod mod) @@ -107,37 +107,37 @@ namespace osu.Game.Overlays.BeatmapSet { base.LoadComplete(); - IsActive.BindValueChanged(hovered => - { - if (Highlighted.Value) - return; - - this.FadeColour(hovered.NewValue ? Color4.White : Color4.DimGray, duration, Easing.OutQuint); - }, true); - Highlighted.BindValueChanged(highlighted => { - OnSelectionChanged?.Invoke(Mod, highlighted.NewValue); - IsActive.TriggerChange(); + if (Selected.Value) + return; + + this.FadeColour(highlighted.NewValue ? Color4.White : Color4.DimGray, duration, Easing.OutQuint); + }, true); + + Selected.BindValueChanged(selected => + { + OnSelectionChanged?.Invoke(Mod, selected.NewValue); + Highlighted.TriggerChange(); }, true); } protected override bool OnClick(ClickEvent e) { - Highlighted.Toggle(); + Selected.Toggle(); return true; } protected override bool OnHover(HoverEvent e) { - IsActive.Value = true; + Highlighted.Value = true; return false; } protected override void OnHoverLost(HoverLostEvent e) { base.OnHoverLost(e); - IsActive.Value = false; + Highlighted.Value = false; } } } diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 8252020e9b..c6b4787ff1 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -96,7 +96,7 @@ namespace osu.Game.Overlays.Mods } } - foregroundIcon.Highlighted.Value = Selected; + foregroundIcon.Selected.Value = Selected; SelectionChanged?.Invoke(SelectedMod); return true; diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index cf1bbadb57..1be70ff48f 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.UI { public class ModIcon : Container, IHasTooltip { - public readonly BindableBool Highlighted = new BindableBool(); + public readonly BindableBool Selected = new BindableBool(); private readonly SpriteIcon modIcon; private readonly SpriteIcon background; @@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.UI protected override void LoadComplete() { base.LoadComplete(); - Highlighted.BindValueChanged(highlighted => background.Colour = highlighted.NewValue ? highlightedColour : backgroundColour, true); + Selected.BindValueChanged(highlighted => background.Colour = highlighted.NewValue ? highlightedColour : backgroundColour, true); } } } From eb2f7c1d0a63f2bd0aa21cc56eeb61f58a066d5a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 21 Nov 2019 20:37:02 +0300 Subject: [PATCH 2421/2815] Rename forgotten variable --- osu.Game/Rulesets/UI/ModIcon.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 1be70ff48f..945dbe4cc9 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.UI protected override void LoadComplete() { base.LoadComplete(); - Selected.BindValueChanged(highlighted => background.Colour = highlighted.NewValue ? highlightedColour : backgroundColour, true); + Selected.BindValueChanged(selected => background.Colour = selected.NewValue ? highlightedColour : backgroundColour, true); } } } From ab42fac43abe3250ece9eb306e07ad2a49db94cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2019 02:38:31 +0900 Subject: [PATCH 2422/2815] Improve naming --- .../{BackgroundMode.cs => BackgroundSource.cs} | 4 ++-- osu.Game/Configuration/OsuConfigManager.cs | 4 ++-- .../Overlays/Settings/Sections/Audio/MainMenuSettings.cs | 8 ++++---- osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) rename osu.Game/Configuration/{BackgroundMode.cs => BackgroundSource.cs} (81%) diff --git a/osu.Game/Configuration/BackgroundMode.cs b/osu.Game/Configuration/BackgroundSource.cs similarity index 81% rename from osu.Game/Configuration/BackgroundMode.cs rename to osu.Game/Configuration/BackgroundSource.cs index 50d54f1eb2..5726e96eb1 100644 --- a/osu.Game/Configuration/BackgroundMode.cs +++ b/osu.Game/Configuration/BackgroundSource.cs @@ -3,9 +3,9 @@ namespace osu.Game.Configuration { - public enum BackgroundMode + public enum BackgroundSource { - Default, + Skin, Beatmap } } diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index b847f96a1f..b71463841a 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -118,7 +118,7 @@ namespace osu.Game.Configuration Set(OsuSetting.IntroSequence, IntroSequence.Triangles); - Set(OsuSetting.BackgroundMode, BackgroundMode.Default); + Set(OsuSetting.MenuBackgroundSource, BackgroundSource.Skin); } public OsuConfigManager(Storage storage) @@ -189,6 +189,6 @@ namespace osu.Game.Configuration IntroSequence, UIHoldActivationDelay, HitLighting, - BackgroundMode + MenuBackgroundSource } } diff --git a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs index 76a6aafe45..a303f93b34 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs @@ -34,11 +34,11 @@ namespace osu.Game.Overlays.Settings.Sections.Audio Bindable = config.GetBindable(OsuSetting.IntroSequence), Items = Enum.GetValues(typeof(IntroSequence)).Cast() }, - new SettingsDropdown + new SettingsDropdown { - LabelText = "Background", - Bindable = config.GetBindable(OsuSetting.BackgroundMode), - Items = Enum.GetValues(typeof(BackgroundMode)).Cast() + LabelText = "Background source", + Bindable = config.GetBindable(OsuSetting.MenuBackgroundSource), + Items = Enum.GetValues(typeof(BackgroundSource)).Cast() } }; } diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index d49751f511..095985e9d1 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Backgrounds private Bindable user; private Bindable skin; - private Bindable mode; + private Bindable mode; [Resolved] private IBindable beatmap { get; set; } @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Backgrounds { user = api.LocalUser.GetBoundCopy(); skin = skinManager.CurrentSkin.GetBoundCopy(); - mode = config.GetBindable(OsuSetting.BackgroundMode); + mode = config.GetBindable(OsuSetting.MenuBackgroundSource); user.ValueChanged += _ => Next(); skin.ValueChanged += _ => Next(); @@ -78,7 +78,7 @@ namespace osu.Game.Screens.Backgrounds { switch (mode.Value) { - case BackgroundMode.Beatmap: + case BackgroundSource.Beatmap: newBackground = new BeatmapBackground(beatmap.Value, backgroundName); break; From 57d38c5c74c956e36920f43de95a4af9c11a0f9d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2019 02:39:54 +0900 Subject: [PATCH 2423/2815] Add final newline --- osu.Game/Graphics/Backgrounds/BeatmapBackground.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs index 40c6dae43c..387e189dc4 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs @@ -25,4 +25,4 @@ namespace osu.Game.Graphics.Backgrounds Sprite.Texture = Beatmap?.Background ?? textures.Get(fallbackTextureName); } } -} \ No newline at end of file +} From e170cd289269f9a028fb699953f2a28551a3e570 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2019 10:29:16 +0900 Subject: [PATCH 2424/2815] Move private methods below --- .../BeatmapSet/LeaderboardModSelector.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index a6f69617d0..60fd520681 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -57,6 +57,18 @@ namespace osu.Game.Overlays.BeatmapSet modsContainer.ForEach(button => button.OnSelectionChanged = selectionChanged); } + protected override bool OnHover(HoverEvent e) + { + updateHighlighted(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + updateHighlighted(); + } + private void selectionChanged(Mod mod, bool selected) { if (selected) @@ -75,18 +87,6 @@ namespace osu.Game.Overlays.BeatmapSet modsContainer.Children.Where(button => !button.IsHovered).ForEach(button => button.Highlighted.Value = !IsHovered); } - protected override bool OnHover(HoverEvent e) - { - updateHighlighted(); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - base.OnHoverLost(e); - updateHighlighted(); - } - public void DeselectAll() => modsContainer.ForEach(mod => mod.Selected.Value = false); private class ModButton : ModIcon From d8260f4a6511ce6118507f114e06eed4cd0bc229 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2019 10:51:49 +0900 Subject: [PATCH 2425/2815] Reduce carousel scroll motion on initial display --- osu.Game/Screens/Select/BeatmapCarousel.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 538656a5fa..17736e7819 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -640,10 +640,19 @@ namespace osu.Game.Screens.Select itemsCache.Validate(); } + private bool firstScroll = true; + private void updateScrollPosition() { if (scrollTarget != null) { + if (firstScroll) + { + // reduce movement when first displaying the carousel. + scroll.ScrollTo(scrollTarget.Value - 200, false); + firstScroll = false; + } + scroll.ScrollTo(scrollTarget.Value); scrollPositionCache.Validate(); } From da425c93fac6a629490690a3555d8d84e64e4118 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 22 Nov 2019 05:08:50 +0300 Subject: [PATCH 2426/2815] Fix beatmap switch doesn't trigger scores update --- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 3e3f823368..b33b5feb4d 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -193,7 +193,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores var beatmapRuleset = beatmap.NewValue?.Ruleset; if (ruleset.Value?.Equals(beatmapRuleset) ?? false) + { modSelector.DeselectAll(); + ruleset.TriggerChange(); + } else ruleset.Value = beatmapRuleset; From 745047fd193ce406f4c6d2896429279918536d67 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 22 Nov 2019 05:10:47 +0300 Subject: [PATCH 2427/2815] Remove useless line from ModIcon --- osu.Game/Rulesets/UI/ModIcon.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index c22120d6e0..945dbe4cc9 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -107,8 +107,6 @@ namespace osu.Game.Rulesets.UI modIcon.Colour = colours.Yellow; break; } - - background.Colour = backgroundColour; } protected override void LoadComplete() From f079ebe857ebf708c5aae6ee0a7c102ea4c9c5f5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2019 16:13:48 +0900 Subject: [PATCH 2428/2815] Simplify beatmap lookup to use a single endpoint --- osu.Game/Online/API/Requests/GetBeatmapRequest.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs index b37a6804fe..8e92cf0611 100644 --- a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs +++ b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs @@ -10,13 +10,11 @@ namespace osu.Game.Online.API.Requests { private readonly BeatmapInfo beatmap; - private string lookupString => beatmap.OnlineBeatmapID > 0 ? beatmap.OnlineBeatmapID.ToString() : $@"lookup?checksum={beatmap.MD5Hash}&filename={System.Uri.EscapeUriString(beatmap.Path)}"; - public GetBeatmapRequest(BeatmapInfo beatmap) { this.beatmap = beatmap; } - protected override string Target => $@"beatmaps/{lookupString}"; + protected override string Target => $@"beatmaps/lookup?id={beatmap.OnlineBeatmapID}&checksum={beatmap.MD5Hash}&filename={System.Uri.EscapeUriString(beatmap.Path)}"; } } From 0cc1698b6d1764b183e663db9d27f7d3e2b5ee0e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2019 18:05:12 +0900 Subject: [PATCH 2429/2815] Rename incorrectly named container --- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index b33b5feb4d..ac39671f56 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -34,7 +34,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly ScoreTable scoreTable; private readonly FillFlowContainer topScoresContainer; private readonly DimmedLoadingLayer loading; - private readonly FillFlowContainer modFilter; + private readonly FillFlowContainer filterControls; private readonly LeaderboardModSelector modSelector; private readonly NoScoresPlaceholder noScoresPlaceholder; private readonly FillFlowContainer content; @@ -94,7 +94,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Margin = new MarginPadding { Vertical = spacing }, Children = new Drawable[] { - modFilter = new FillFlowContainer + filterControls = new FillFlowContainer { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -207,7 +207,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { scope.Value = BeatmapLeaderboardScope.Global; modSelector.DeselectAll(); - modFilter.FadeTo(api.IsLoggedIn && api.LocalUser.Value.IsSupporter ? 1 : 0); + filterControls.FadeTo(api.IsLoggedIn && api.LocalUser.Value.IsSupporter ? 1 : 0); } private void getScores() From 16bdf4e6bd250ac5dd9cb6fa497d80e984002af1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2019 18:07:56 +0900 Subject: [PATCH 2430/2815] Update english to be more readable --- osu.Game/Overlays/BeatmapSet/Scores/NoScoresPlaceholder.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/NoScoresPlaceholder.cs b/osu.Game/Overlays/BeatmapSet/Scores/NoScoresPlaceholder.cs index aefff5d567..9cfce4ef5b 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/NoScoresPlaceholder.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/NoScoresPlaceholder.cs @@ -27,16 +27,15 @@ namespace osu.Game.Overlays.BeatmapSet.Scores switch (scope) { default: - case BeatmapLeaderboardScope.Global: - text.Text = @"No scores yet. Maybe should try setting some?"; + text.Text = @"No scores have been set yet. Maybe you can be the first!"; return; case BeatmapLeaderboardScope.Friend: - text.Text = @"None of your friends has set a score on this map yet!"; + text.Text = @"None of your friends have set a score on this map yet."; return; case BeatmapLeaderboardScope.Country: - text.Text = @"No one from your country has set a score on this map yet!"; + text.Text = @"No one from your country has set a score on this map yet."; return; } } From dad2e5c678dc6f781ea46d4e6acf82411da46bd0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2019 18:10:44 +0900 Subject: [PATCH 2431/2815] Improve legibility of NoScroesPlaceholder --- .../BeatmapSet/Scores/NoScoresPlaceholder.cs | 21 +++++++++++-------- .../BeatmapSet/Scores/ScoresContainer.cs | 8 ++----- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/NoScoresPlaceholder.cs b/osu.Game/Overlays/BeatmapSet/Scores/NoScoresPlaceholder.cs index 9cfce4ef5b..391ba93a4b 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/NoScoresPlaceholder.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/NoScoresPlaceholder.cs @@ -3,9 +3,9 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; using osu.Game.Screens.Select.Leaderboards; using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -16,27 +16,30 @@ namespace osu.Game.Overlays.BeatmapSet.Scores public NoScoresPlaceholder() { AutoSizeAxes = Axes.Both; - Child = text = new SpriteText - { - Font = OsuFont.GetFont(), - }; + Child = text = new OsuSpriteText(); } - public void UpdateText(BeatmapLeaderboardScope scope) + public override void Show() => this.FadeIn(200, Easing.OutQuint); + + public override void Hide() => this.FadeOut(200, Easing.OutQuint); + + public void ShowWithScope(BeatmapLeaderboardScope scope) { + Show(); + switch (scope) { default: text.Text = @"No scores have been set yet. Maybe you can be the first!"; - return; + break; case BeatmapLeaderboardScope.Friend: text.Text = @"None of your friends have set a score on this map yet."; - return; + break; case BeatmapLeaderboardScope.Country: text.Text = @"No one from your country has set a score on this map yet."; - return; + break; } } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index ac39671f56..86fb455eb8 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -23,7 +23,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores public class ScoresContainer : CompositeDrawable { private const int spacing = 15; - private const int duration = 200; public readonly Bindable Beatmap = new Bindable(); private readonly Bindable ruleset = new Bindable(); @@ -215,7 +214,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores getScoresRequest?.Cancel(); getScoresRequest = null; - noScoresPlaceholder.FadeOut(duration, Easing.OutQuint); + noScoresPlaceholder.Hide(); if (Beatmap.Value?.OnlineBeatmapID.HasValue != true || Beatmap.Value.Status <= BeatmapSetOnlineStatus.Pending) { @@ -233,10 +232,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Scores = scores; if (!scores.Scores.Any()) - { - noScoresPlaceholder.UpdateText(scope.Value); - noScoresPlaceholder.FadeIn(duration, Easing.OutQuint); - } + noScoresPlaceholder.ShowWithScope(scope.Value); }; api.Queue(getScoresRequest); } From 66e2a259f0fcdcf7e349816f8f83a918edd7fcea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2019 18:21:40 +0900 Subject: [PATCH 2432/2815] Fix usage of SpriteText instead of OsuSpriteText --- CodeAnalysis/BannedSymbols.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index 9fb86485d2..3ad8d5db5b 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -2,3 +2,4 @@ M:System.Object.Equals(System.Object,System.Object)~System.Boolean;Don't use obj M:System.Object.Equals(System.Object)~System.Boolean;Don't use object.Equals. Use IEquatable or EqualityComparer.Default instead. M:System.ValueType.Equals(System.Object)~System.Boolean;Don't use object.Equals(Fallbacks to ValueType). Use IEquatable or EqualityComparer.Default instead. T:System.IComparable;Don't use non-generic IComparable. Use generic version instead. +T:osu.Framework.Graphics.Sprites.SpriteText;Use OsuSpriteText. From b026197859bd0fb362f80323621f7a53576d3fbd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2019 18:23:48 +0900 Subject: [PATCH 2433/2815] Add spacing --- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 86fb455eb8..f6f4b39c14 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -225,6 +225,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores content.Show(); loading.Show(); + getScoresRequest = new GetScoresRequest(Beatmap.Value, Beatmap.Value.Ruleset, scope.Value, modSelector.SelectedMods); getScoresRequest.Success += scores => { @@ -234,6 +235,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (!scores.Scores.Any()) noScoresPlaceholder.ShowWithScope(scope.Value); }; + api.Queue(getScoresRequest); } } From 6469199f0dd594049208f1634d127d4d04a4b5ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2019 19:47:21 +0900 Subject: [PATCH 2434/2815] 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 0f0e82d56a..0da3e66236 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -55,6 +55,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 28ef7d3d92..449b4dc4e3 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -21,7 +21,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 364b0b2c78..871a9238e3 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -73,7 +73,7 @@ - + @@ -81,7 +81,7 @@ - + From c2e85a205733bfad5d8433d5688dd94dd81f0012 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2019 19:49:20 +0900 Subject: [PATCH 2435/2815] Apply CornerExponent fixes --- .../Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs | 2 ++ .../Objects/Drawables/Connections/FollowPoint.cs | 3 +-- .../Objects/Drawables/Pieces/CirclePiece.cs | 2 ++ osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs | 3 +-- osu.Game.Tournament/Components/SongBar.cs | 2 ++ osu.Game.Tournament/Components/TournamentBeatmapPanel.cs | 1 + osu.Game/Overlays/Comments/DrawableComment.cs | 1 + osu.Game/Overlays/Volume/MuteButton.cs | 1 + osu.Game/Screens/Edit/Components/CircularButton.cs | 1 + osu.Game/Users/Drawables/UpdateableAvatar.cs | 6 ++++++ 10 files changed, 18 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs index 2b6b93a590..2868ddeaa4 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs @@ -17,7 +17,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components Origin = Anchor.Centre; Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); + CornerRadius = Size.X / 2; + CornerExponent = 2; InternalChild = new RingPiece(); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index db34ae1d87..7e530ca047 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -25,11 +25,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { Origin = Anchor.Centre; - Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.FollowPoint), _ => new Container + Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.FollowPoint), _ => new CircularContainer { Masking = true, AutoSizeAxes = Axes.Both, - CornerRadius = width / 2, EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs index 210d5ff839..aab01f45d4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -16,7 +16,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); Masking = true; + CornerRadius = Size.X / 2; + CornerExponent = 2; Anchor = Anchor.Centre; Origin = Anchor.Centre; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs index c97b74756a..82e4383143 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs @@ -18,10 +18,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Anchor = Anchor.Centre; Origin = Anchor.Centre; - InternalChild = new Container + InternalChild = new CircularContainer { Masking = true, - CornerRadius = Size.X / 2, BorderThickness = 10, BorderColour = Color4.White, RelativeSizeAxes = Axes.Both, diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index 7005c068ae..43958aca7a 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -106,6 +106,7 @@ namespace osu.Game.Tournament.Components Width = main_width, Height = TournamentBeatmapPanel.HEIGHT, CornerRadius = TournamentBeatmapPanel.HEIGHT / 2, + CornerExponent = 2, Children = new Drawable[] { new Box @@ -126,6 +127,7 @@ namespace osu.Game.Tournament.Components { Masking = true, CornerRadius = TournamentBeatmapPanel.HEIGHT / 2, + CornerExponent = 2, Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs index 0908814537..51483a0964 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -52,6 +52,7 @@ namespace osu.Game.Tournament.Components currentMatch.BindTo(ladder.CurrentMatch); CornerRadius = HEIGHT / 2; + CornerExponent = 2; Masking = true; AddRangeInternal(new Drawable[] diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 3fb9867f0e..0d00f7c63b 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -100,6 +100,7 @@ namespace osu.Game.Overlays.Comments Size = new Vector2(avatar_size), Masking = true, CornerRadius = avatar_size / 2f, + CornerExponent = 2, }, } }, diff --git a/osu.Game/Overlays/Volume/MuteButton.cs b/osu.Game/Overlays/Volume/MuteButton.cs index 6d876a77b1..bcc9394aba 100644 --- a/osu.Game/Overlays/Volume/MuteButton.cs +++ b/osu.Game/Overlays/Volume/MuteButton.cs @@ -43,6 +43,7 @@ namespace osu.Game.Overlays.Volume { Content.BorderThickness = 3; Content.CornerRadius = HEIGHT / 2; + Content.CornerExponent = 2; Size = new Vector2(width, HEIGHT); diff --git a/osu.Game/Screens/Edit/Components/CircularButton.cs b/osu.Game/Screens/Edit/Components/CircularButton.cs index 931c7d03a0..40b5ac663a 100644 --- a/osu.Game/Screens/Edit/Components/CircularButton.cs +++ b/osu.Game/Screens/Edit/Components/CircularButton.cs @@ -20,6 +20,7 @@ namespace osu.Game.Screens.Edit.Components { base.Update(); Content.CornerRadius = DrawHeight / 2f; + Content.CornerExponent = 2; } } } diff --git a/osu.Game/Users/Drawables/UpdateableAvatar.cs b/osu.Game/Users/Drawables/UpdateableAvatar.cs index 795b90ba11..59fbb5f910 100644 --- a/osu.Game/Users/Drawables/UpdateableAvatar.cs +++ b/osu.Game/Users/Drawables/UpdateableAvatar.cs @@ -31,6 +31,12 @@ namespace osu.Game.Users.Drawables set => base.CornerRadius = value; } + public new float CornerExponent + { + get => base.CornerExponent; + set => base.CornerExponent = value; + } + public new EdgeEffectParameters EdgeEffect { get => base.EdgeEffect; From d0002cc1c25e9da8ede3277257c588b884a28c65 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2019 19:50:30 +0900 Subject: [PATCH 2436/2815] Update performance logging setting --- osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs index 7eec971b62..457f064f89 100644 --- a/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Settings.Sections.Debug new SettingsCheckbox { LabelText = "Performance logging", - Bindable = frameworkConfig.GetBindable(FrameworkSetting.PerformanceLogging) + Bindable = config.GetBindable(DebugSetting.PerformanceLogging) }, new SettingsCheckbox { From 4063135a3a894e262f880cca2b91904c51fb33c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2019 20:45:05 +0900 Subject: [PATCH 2437/2815] Fix result mode button's corner exponent --- osu.Game/Screens/Ranking/ResultModeButton.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Ranking/ResultModeButton.cs b/osu.Game/Screens/Ranking/ResultModeButton.cs index 1383511241..38636b0c3b 100644 --- a/osu.Game/Screens/Ranking/ResultModeButton.cs +++ b/osu.Game/Screens/Ranking/ResultModeButton.cs @@ -36,7 +36,9 @@ namespace osu.Game.Screens.Ranking Size = new Vector2(50); Masking = true; + CornerRadius = 25; + CornerExponent = 2; activeColour = colours.PinkDarker; inactiveColour = OsuColour.Gray(0.8f); From 4cb09df754aa0b03e145936660cd518117b1959d Mon Sep 17 00:00:00 2001 From: Albie Spriddell Date: Fri, 22 Nov 2019 18:23:48 +0000 Subject: [PATCH 2438/2815] increase padding --- osu.Game/Overlays/Mods/ModSection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 3b16189e73..9c0a164ad6 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -167,7 +167,7 @@ namespace osu.Game.Overlays.Mods Spacing = new Vector2(50f, 0f), Margin = new MarginPadding { - Top = 6, + Top = 20, }, AlwaysPresent = true }, From d6c9387bebcc44dafc92693e6e6d6ceb3f46117e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 22 Nov 2019 21:55:21 +0300 Subject: [PATCH 2439/2815] Implement NotSupporterPlaceholder --- .../Online/TestSceneBeatmapSetOverlay.cs | 1 + .../Scores/NotSupporterPlaceholder.cs | 51 +++++++++++++++++++ .../BeatmapSet/Scores/ScoresContainer.cs | 1 + 3 files changed, 53 insertions(+) create mode 100644 osu.Game/Overlays/BeatmapSet/Scores/NotSupporterPlaceholder.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index 286971bc90..5ca2c9868f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -42,6 +42,7 @@ namespace osu.Game.Tests.Visual.Online typeof(BeatmapAvailability), typeof(BeatmapRulesetSelector), typeof(BeatmapRulesetTabItem), + typeof(NotSupporterPlaceholder) }; protected override bool UseOnlineAPI => true; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/NotSupporterPlaceholder.cs b/osu.Game/Overlays/BeatmapSet/Scores/NotSupporterPlaceholder.cs new file mode 100644 index 0000000000..86b02c0cff --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/Scores/NotSupporterPlaceholder.cs @@ -0,0 +1,51 @@ +// 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.Containers; +using osu.Game.Graphics.Sprites; +using osuTK; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Overlays.BeatmapSet.Scores +{ + public class NotSupporterPlaceholder : Container + { + public NotSupporterPlaceholder() + { + LinkFlowContainer text; + + AutoSizeAxes = Axes.Both; + Anchor = Anchor.TopCentre; + Origin = Anchor.TopCentre; + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = @"You need to be an osu!supporter to access the friend and country rankings!", + Font = OsuFont.GetFont(weight: FontWeight.Bold), + }, + text = new LinkFlowContainer(t => t.Font = t.Font.With(size: 12)) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + } + } + }; + + text.AddText("Click "); + text.AddLink("here", "/home/support"); + text.AddText(" to see all the fancy features that you can get!"); + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index f6f4b39c14..fe42ef2975 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -106,6 +106,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { Current = { BindTarget = scope } }, + new NotSupporterPlaceholder(), modSelector = new LeaderboardModSelector { Ruleset = { BindTarget = ruleset } From aede1b183a597b0a354f2919e7787928eb409445 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 22 Nov 2019 22:04:58 +0300 Subject: [PATCH 2440/2815] Make ScopeSelector always visible --- .../Scores/NotSupporterPlaceholder.cs | 2 -- .../BeatmapSet/Scores/ScoresContainer.cs | 22 +++++++++++++------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/NotSupporterPlaceholder.cs b/osu.Game/Overlays/BeatmapSet/Scores/NotSupporterPlaceholder.cs index 86b02c0cff..ba08a78a61 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/NotSupporterPlaceholder.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/NotSupporterPlaceholder.cs @@ -17,8 +17,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores LinkFlowContainer text; AutoSizeAxes = Axes.Both; - Anchor = Anchor.TopCentre; - Origin = Anchor.TopCentre; Child = new FillFlowContainer { AutoSizeAxes = Axes.Both, diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index fe42ef2975..0389d4cf25 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -33,10 +33,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly ScoreTable scoreTable; private readonly FillFlowContainer topScoresContainer; private readonly DimmedLoadingLayer loading; - private readonly FillFlowContainer filterControls; private readonly LeaderboardModSelector modSelector; private readonly NoScoresPlaceholder noScoresPlaceholder; private readonly FillFlowContainer content; + private readonly NotSupporterPlaceholder notSupporterPlaceholder; [Resolved] private IAPIProvider api { get; set; } @@ -93,22 +93,30 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Margin = new MarginPadding { Vertical = spacing }, Children = new Drawable[] { - filterControls = new FillFlowContainer + new FillFlowContainer { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, Spacing = new Vector2(0, spacing), Children = new Drawable[] { new LeaderboardScopeSelector { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, Current = { BindTarget = scope } }, - new NotSupporterPlaceholder(), + notSupporterPlaceholder = new NotSupporterPlaceholder + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Alpha = 0, + }, modSelector = new LeaderboardModSelector { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, Ruleset = { BindTarget = ruleset } } } @@ -207,7 +215,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { scope.Value = BeatmapLeaderboardScope.Global; modSelector.DeselectAll(); - filterControls.FadeTo(api.IsLoggedIn && api.LocalUser.Value.IsSupporter ? 1 : 0); + modSelector.FadeTo(api.IsLoggedIn && api.LocalUser.Value.IsSupporter ? 1 : 0); } private void getScores() From 2e161f7e997538dea478ca788a895473006ebd27 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 22 Nov 2019 22:25:58 +0300 Subject: [PATCH 2441/2815] Hook up the rest of the logic --- .../BeatmapSet/Scores/ScoresContainer.cs | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 0389d4cf25..e91d12b9fc 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores public readonly Bindable Beatmap = new Bindable(); private readonly Bindable ruleset = new Bindable(); - private readonly Bindable scope = new Bindable(); + private readonly Bindable scope = new Bindable(BeatmapLeaderboardScope.Global); private readonly Bindable user = new Bindable(); private readonly Box background; @@ -107,12 +107,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Origin = Anchor.TopCentre, Current = { BindTarget = scope } }, - notSupporterPlaceholder = new NotSupporterPlaceholder - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Alpha = 0, - }, modSelector = new LeaderboardModSelector { Anchor = Anchor.TopCentre, @@ -136,6 +130,12 @@ namespace osu.Game.Overlays.BeatmapSet.Scores AlwaysPresent = true, Margin = new MarginPadding { Vertical = 10 } }, + notSupporterPlaceholder = new NotSupporterPlaceholder + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Alpha = 0, + }, new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -213,9 +213,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private void onUserChanged(ValueChangedEvent user) { - scope.Value = BeatmapLeaderboardScope.Global; modSelector.DeselectAll(); - modSelector.FadeTo(api.IsLoggedIn && api.LocalUser.Value.IsSupporter ? 1 : 0); + modSelector.FadeTo(userIsSupporter ? 1 : 0); + getScores(); } private void getScores() @@ -232,6 +232,18 @@ namespace osu.Game.Overlays.BeatmapSet.Scores return; } + if (scope.Value != BeatmapLeaderboardScope.Global && !userIsSupporter) + { + Scores = null; + notSupporterPlaceholder.Show(); + loading.Hide(); + return; + } + else + { + notSupporterPlaceholder.Hide(); + } + content.Show(); loading.Show(); @@ -247,5 +259,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores api.Queue(getScoresRequest); } + + private bool userIsSupporter => api.IsLoggedIn && api.LocalUser.Value.IsSupporter; } } From e3f3b1ab1a1101f86e7b9cdfe6c3d1af7c3c5226 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Nov 2019 20:52:41 +0900 Subject: [PATCH 2442/2815] Display useful statistics on song bar depending on current ruleset --- osu.Game.Tournament/Components/SongBar.cs | 48 +++++++++++++++++++---- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index 43958aca7a..4a738ae36b 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -13,6 +14,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Legacy; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; using osu.Game.Screens.Menu; using osuTK; using osuTK.Graphics; @@ -23,6 +25,9 @@ namespace osu.Game.Tournament.Components { private BeatmapInfo beatmap; + [Resolved] + private IBindable ruleset { get; set; } + public BeatmapInfo Beatmap { get => beatmap; @@ -165,7 +170,8 @@ namespace osu.Game.Tournament.Components string hardRockExtra = ""; string srExtra = ""; - //var ar = beatmap.BaseDifficulty.ApproachRate; + var ar = beatmap.BaseDifficulty.ApproachRate; + if ((mods & LegacyMods.HardRock) > 0) { hardRockExtra = "*"; @@ -174,12 +180,43 @@ namespace osu.Game.Tournament.Components if ((mods & LegacyMods.DoubleTime) > 0) { - //ar *= 1.5f; + ar *= 1.5f; bpm *= 1.5f; length /= 1.5f; srExtra = "*"; } + (string heading, string content)[] stats; + + switch (ruleset.Value.ID) + { + default: + stats = new (string heading, string content)[] + { + ("CS", $"{beatmap.BaseDifficulty.CircleSize:0.#}{hardRockExtra}"), + ("AR", $"{ar:0.#}{srExtra}"), + ("OD", $"{beatmap.BaseDifficulty.OverallDifficulty:0.#}{hardRockExtra}"), + }; + break; + + case 1: + case 3: + stats = new (string heading, string content)[] + { + ("OD", $"{beatmap.BaseDifficulty.OverallDifficulty:0.#}{hardRockExtra}"), + ("HP", $"{beatmap.BaseDifficulty.DrainRate:0.#}{hardRockExtra}") + }; + break; + + case 2: + stats = new (string heading, string content)[] + { + ("CS", $"{beatmap.BaseDifficulty.CircleSize:0.#}{hardRockExtra}"), + ("AR", $"{ar:0.#}{srExtra}"), + }; + break; + } + panelContents.Children = new Drawable[] { new DiffPiece(("Length", TimeSpan.FromMilliseconds(length).ToString(@"mm\:ss"))) @@ -192,12 +229,7 @@ namespace osu.Game.Tournament.Components Anchor = Anchor.CentreLeft, Origin = Anchor.TopLeft }, - new DiffPiece( - //("CS", $"{beatmap.BaseDifficulty.CircleSize:0.#}{hardRockExtra}"), - //("AR", $"{ar:0.#}{srExtra}"), - ("OD", $"{beatmap.BaseDifficulty.OverallDifficulty:0.#}{hardRockExtra}"), - ("HP", $"{beatmap.BaseDifficulty.DrainRate:0.#}{hardRockExtra}") - ) + new DiffPiece(stats) { Anchor = Anchor.CentreRight, Origin = Anchor.BottomRight From eb81d15463721c4e3abaf9a89fbef8a9a5eca562 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Nov 2019 20:52:55 +0900 Subject: [PATCH 2443/2815] Don't attempt to repopulate already populated users on startup --- osu.Game.Tournament/TournamentGameBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index f2a158971b..ec696c818d 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -202,7 +202,8 @@ namespace osu.Game.Tournament { foreach (var p in t.Players) { - PopulateUser(p); + if (p.Username == null || p.Statistics == null) + PopulateUser(p); addedInfo = true; } } @@ -243,7 +244,6 @@ namespace osu.Game.Tournament { user.Username = res.Username; user.Statistics = res.Statistics; - user.Username = res.Username; user.Country = res.Country; user.Cover = res.Cover; From 65d71b94424ab46a3fa307784a8637334d75f172 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Nov 2019 20:54:01 +0900 Subject: [PATCH 2444/2815] Fix beatmap lookups failing for beatmaps with no local path Turns out the underlying EscapeUriString doesn't like nulls --- osu.Game/Online/API/Requests/GetBeatmapRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs index 8e92cf0611..87925b94c6 100644 --- a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs +++ b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs @@ -15,6 +15,6 @@ namespace osu.Game.Online.API.Requests this.beatmap = beatmap; } - protected override string Target => $@"beatmaps/lookup?id={beatmap.OnlineBeatmapID}&checksum={beatmap.MD5Hash}&filename={System.Uri.EscapeUriString(beatmap.Path)}"; + protected override string Target => $@"beatmaps/lookup?id={beatmap.OnlineBeatmapID}&checksum={beatmap.MD5Hash}&filename={System.Uri.EscapeUriString(beatmap.Path ?? string.Empty)}"; } } From 1bd9fa413e76c4f65300cf1005ba8231e9548be3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Nov 2019 23:29:34 +0900 Subject: [PATCH 2445/2815] Fix incorrect NativeLibs version reference --- osu.iOS.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.iOS.props b/osu.iOS.props index 871a9238e3..ff6839f5ca 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -86,6 +86,6 @@ - + From 326a8f62eb49da63a172a4a6eb8aadbfeb35570a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 24 Nov 2019 00:21:34 +0900 Subject: [PATCH 2446/2815] Calculate more correct AR locally for now --- osu.Game.Tournament/Components/SongBar.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index 4a738ae36b..23dc4bb22c 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -180,7 +180,10 @@ namespace osu.Game.Tournament.Components if ((mods & LegacyMods.DoubleTime) > 0) { - ar *= 1.5f; + // temporary local calculation (taken from OsuDifficultyCalculator) + double preempt = (int)BeatmapDifficulty.DifficultyRange(ar, 1800, 1200, 450) / 1.5; + ar = (float)(preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5); + bpm *= 1.5f; length /= 1.5f; srExtra = "*"; From 244177880e3ce004a11942e469cbdbe91f2eca1a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 24 Nov 2019 01:45:21 +0900 Subject: [PATCH 2447/2815] Only show star on HR --- osu.Game.Tournament/Components/SongBar.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index 23dc4bb22c..727dddeb7b 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -197,7 +197,7 @@ namespace osu.Game.Tournament.Components stats = new (string heading, string content)[] { ("CS", $"{beatmap.BaseDifficulty.CircleSize:0.#}{hardRockExtra}"), - ("AR", $"{ar:0.#}{srExtra}"), + ("AR", $"{ar:0.#}{hardRockExtra}"), ("OD", $"{beatmap.BaseDifficulty.OverallDifficulty:0.#}{hardRockExtra}"), }; break; @@ -215,7 +215,7 @@ namespace osu.Game.Tournament.Components stats = new (string heading, string content)[] { ("CS", $"{beatmap.BaseDifficulty.CircleSize:0.#}{hardRockExtra}"), - ("AR", $"{ar:0.#}{srExtra}"), + ("AR", $"{ar:0.#}"), }; break; } From 20edaf4ba6a04adf9836ef4c3f2b2b61991e6a21 Mon Sep 17 00:00:00 2001 From: Albie Spriddell Date: Sat, 23 Nov 2019 17:32:16 +0000 Subject: [PATCH 2448/2815] add cinema mod support --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 6 ++++-- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 6 ++++-- osu.Game.Rulesets.Osu/OsuRuleset.cs | 6 ++++-- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 6 ++++-- osu.Game/Rulesets/Mods/ModCinema.cs | 24 ++++++++++++++++++++++-- 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 71d68ace94..bf2b1c0def 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -51,7 +51,9 @@ namespace osu.Game.Rulesets.Catch else if (mods.HasFlag(LegacyMods.SuddenDeath)) yield return new CatchModSuddenDeath(); - if (mods.HasFlag(LegacyMods.Autoplay)) + if (mods.HasFlag(LegacyMods.Cinema)) + yield return new CatchModCinema(); + else if (mods.HasFlag(LegacyMods.Autoplay)) yield return new CatchModAutoplay(); if (mods.HasFlag(LegacyMods.Easy)) @@ -101,7 +103,7 @@ namespace osu.Game.Rulesets.Catch case ModType.Automation: return new Mod[] { - new MultiMod(new CatchModAutoplay(), new ModCinema()), + new MultiMod(new CatchModAutoplay(), new CatchModCinema()), new CatchModRelax(), }; diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index c74a292331..c632cc0b7b 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -51,7 +51,9 @@ namespace osu.Game.Rulesets.Mania else if (mods.HasFlag(LegacyMods.SuddenDeath)) yield return new ManiaModSuddenDeath(); - if (mods.HasFlag(LegacyMods.Autoplay)) + if (mods.HasFlag(LegacyMods.Cinema)) + yield return new ManiaModCinema(); + else if (mods.HasFlag(LegacyMods.Autoplay)) yield return new ManiaModAutoplay(); if (mods.HasFlag(LegacyMods.Easy)) @@ -148,7 +150,7 @@ namespace osu.Game.Rulesets.Mania case ModType.Automation: return new Mod[] { - new MultiMod(new ManiaModAutoplay(), new ModCinema()), + new MultiMod(new ManiaModAutoplay(), new ManiaModCinema()), }; case ModType.Fun: diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index fa69cec78d..2b5a0df3ed 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -60,7 +60,9 @@ namespace osu.Game.Rulesets.Osu if (mods.HasFlag(LegacyMods.Autopilot)) yield return new OsuModAutopilot(); - if (mods.HasFlag(LegacyMods.Autoplay)) + if (mods.HasFlag(LegacyMods.Cinema)) + yield return new OsuModCinema(); + else if (mods.HasFlag(LegacyMods.Autoplay)) yield return new OsuModAutoplay(); if (mods.HasFlag(LegacyMods.Easy)) @@ -126,7 +128,7 @@ namespace osu.Game.Rulesets.Osu case ModType.Automation: return new Mod[] { - new MultiMod(new OsuModAutoplay(), new ModCinema()), + new MultiMod(new OsuModAutoplay(), new OsuModCinema()), new OsuModRelax(), new OsuModAutopilot(), }; diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index b2655f592c..3f3a198f4a 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -50,7 +50,9 @@ namespace osu.Game.Rulesets.Taiko else if (mods.HasFlag(LegacyMods.SuddenDeath)) yield return new TaikoModSuddenDeath(); - if (mods.HasFlag(LegacyMods.Autoplay)) + if (mods.HasFlag(LegacyMods.Cinema)) + yield return new TaikoModCinema(); + else if (mods.HasFlag(LegacyMods.Autoplay)) yield return new TaikoModAutoplay(); if (mods.HasFlag(LegacyMods.Easy)) @@ -100,7 +102,7 @@ namespace osu.Game.Rulesets.Taiko case ModType.Automation: return new Mod[] { - new MultiMod(new TaikoModAutoplay(), new ModCinema()), + new MultiMod(new TaikoModAutoplay(), new TaikoModCinema()), new TaikoModRelax(), }; diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 3c6a3a54aa..4396c3384b 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -3,15 +3,35 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Mods { - public class ModCinema : ModAutoplay + public abstract class ModCinema : ModCinema, IApplicableToDrawableRuleset + where T : HitObject + { + public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + drawableRuleset.SetReplayScore(CreateReplayScore(drawableRuleset.Beatmap)); + + drawableRuleset.Playfield.AlwaysPresent = true; + drawableRuleset.Playfield.Hide(); + } + } + + public class ModCinema : ModAutoplay, IApplicableToHUD { public override string Name => "Cinema"; public override string Acronym => "CN"; - public override bool HasImplementation => false; public override IconUsage Icon => OsuIcon.ModCinema; public override string Description => "Watch the video without visual distractions."; + + public void ApplyToHUD(HUDOverlay overlay) + { + overlay.AlwaysPresent = true; + overlay.Hide(); + } } } From 3b9f59cb335bb84964737fca27138668127cd371 Mon Sep 17 00:00:00 2001 From: Albie Spriddell Date: Sat, 23 Nov 2019 17:34:53 +0000 Subject: [PATCH 2449/2815] add cinema mod support --- .../Mods/CatchModCinema.cs | 21 ++++++++++++++++ .../Mods/ManiaModCinema.cs | 22 ++++++++++++++++ osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs | 25 +++++++++++++++++++ .../Mods/TaikoModCinema.cs | 21 ++++++++++++++++ 4 files changed, 89 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs create mode 100644 osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs create mode 100644 osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs b/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs new file mode 100644 index 0000000000..3bc1ee5bf5 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Mods/CatchModCinema.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.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Replays; +using osu.Game.Rulesets.Mods; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Rulesets.Catch.Mods +{ + public class CatchModCinema : ModCinema + { + public override Score CreateReplayScore(IBeatmap beatmap) => new Score + { + ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } }, + Replay = new CatchAutoGenerator(beatmap).Generate(), + }; + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs new file mode 100644 index 0000000000..02c1fc1b79 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs @@ -0,0 +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.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Replays; +using osu.Game.Rulesets.Mods; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModCinema : ModCinema + { + public override Score CreateReplayScore(IBeatmap beatmap) => new Score + { + ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus!" } }, + Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(), + }; + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs new file mode 100644 index 0000000000..5d9a524577 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.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 System; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModCinema : ModCinema + { + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray(); + + public override Score CreateReplayScore(IBeatmap beatmap) => new Score + { + ScoreInfo = new ScoreInfo { User = new User { Username = "Autoplay" } }, + Replay = new OsuAutoGenerator(beatmap).Generate() + }; + } +} diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs new file mode 100644 index 0000000000..71aa007d3b --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.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.Rulesets.Mods; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Replays; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Rulesets.Taiko.Mods +{ + public class TaikoModCinema : ModCinema + { + public override Score CreateReplayScore(IBeatmap beatmap) => new Score + { + ScoreInfo = new ScoreInfo { User = new User { Username = "mekkadosu!" } }, + Replay = new TaikoAutoGenerator(beatmap).Generate(), + }; + } +} From 29b05e293913c75490242416786c9a537b307041 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 24 Nov 2019 00:01:49 +0300 Subject: [PATCH 2450/2815] Fix getScores can be fired twice on user change --- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index e91d12b9fc..d65a03acad 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -213,9 +213,12 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private void onUserChanged(ValueChangedEvent user) { - modSelector.DeselectAll(); + if (modSelector.SelectedMods.Any()) + modSelector.DeselectAll(); + else + getScores(); + modSelector.FadeTo(userIsSupporter ? 1 : 0); - getScores(); } private void getScores() From 96533631fc546046a2b75f94d10324a0145aa446 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 24 Nov 2019 01:29:11 +0300 Subject: [PATCH 2451/2815] Simplify if/else statement --- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index d65a03acad..112903b0f3 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -242,10 +242,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores loading.Hide(); return; } - else - { - notSupporterPlaceholder.Hide(); - } + + notSupporterPlaceholder.Hide(); content.Show(); loading.Show(); From 20a8a653c2f7f9fd8e64e6ef30c5116bb4c4be50 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 24 Nov 2019 04:10:04 +0300 Subject: [PATCH 2452/2815] Change LegacySkin.Configuration accessibility --- osu.Game/Skinning/LegacySkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 7ffed18d7b..868e3921bb 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -26,7 +26,7 @@ namespace osu.Game.Skinning [CanBeNull] protected IResourceStore Samples; - protected new LegacySkinConfiguration Configuration + public new LegacySkinConfiguration Configuration { get => base.Configuration as LegacySkinConfiguration; set => base.Configuration = value; From 2437cfd28c008e9f9af01c7ca10ebca41246971d Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 24 Nov 2019 04:36:16 +0300 Subject: [PATCH 2453/2815] Add test ensuring correct version lookup --- osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs index 578030748b..8b9c648442 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs @@ -116,6 +116,14 @@ namespace osu.Game.Tests.Skins }); } + [Test] + public void TestLegacyVersionLookup() + { + AddStep("Set source1 version 2.3", () => source1.Configuration.LegacyVersion = 2.3m); + AddStep("Set source2 version null", () => source2.Configuration.LegacyVersion = null); + AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 2.3m); + } + public enum LookupType { Test From b58afa3eb693c29d74d5aac93f7160f5f35dc639 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 24 Nov 2019 04:36:34 +0300 Subject: [PATCH 2454/2815] Remove unnecessary mentioning in xmldoc --- osu.Game/Skinning/LegacySkinConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkinConfiguration.cs b/osu.Game/Skinning/LegacySkinConfiguration.cs index 6a667e69f4..b1679bd464 100644 --- a/osu.Game/Skinning/LegacySkinConfiguration.cs +++ b/osu.Game/Skinning/LegacySkinConfiguration.cs @@ -8,7 +8,7 @@ namespace osu.Game.Skinning public const decimal LATEST_VERSION = 2.7m; /// - /// Legacy version of this skin. Null if no version was set to allow fallback to a parent skin version. + /// Legacy version of this skin. /// public decimal? LegacyVersion { get; internal set; } From 6126fd9a6b573a422aae719d240b31f80fea4950 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sun, 24 Nov 2019 10:42:05 +0900 Subject: [PATCH 2455/2815] ApplyToDrawableHitObjects should be able to get all the hitobject in nasted Playfield --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index e0aeff8330..5ed1e5a368 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -224,7 +224,7 @@ namespace osu.Game.Rulesets.UI Playfield.PostProcess(); foreach (var mod in mods.OfType()) - mod.ApplyToDrawableHitObjects(Playfield.HitObjectContainer.Objects); + mod.ApplyToDrawableHitObjects(Playfield.AllHitObjects); } public override void RequestResume(Action continueResume) From b8e5796af51eb47f8f897906b7da72b3b1cbc9d5 Mon Sep 17 00:00:00 2001 From: Albie Date: Sun, 24 Nov 2019 07:37:06 +0000 Subject: [PATCH 2456/2815] add forced video/storyboard and disabled dim for mod inside new interface --- .../Graphics/Containers/UserDimContainer.cs | 6 +++++ osu.Game/Rulesets/Mods/IApplicableToScreen.cs | 26 +++++++++++++++++++ osu.Game/Rulesets/Mods/ModCinema.cs | 9 ++++++- osu.Game/Screens/Play/DimmableStoryboard.cs | 4 +-- osu.Game/Screens/Play/DimmableVideo.cs | 5 ++-- osu.Game/Screens/Play/Player.cs | 20 ++++++++++++-- 6 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/IApplicableToScreen.cs diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 7683bbcd63..42a25a79b1 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -21,6 +21,11 @@ namespace osu.Game.Graphics.Containers /// public readonly Bindable EnableUserDim = new Bindable(true); + /// + /// Whether or not user-configured settings relating to visibility of elements should be ignored + /// + public readonly Bindable IgnoreUserSettings = new Bindable(); + /// /// Whether or not the storyboard loaded should completely hide the background behind it. /// @@ -63,6 +68,7 @@ namespace osu.Game.Graphics.Containers ShowStoryboard.ValueChanged += _ => UpdateVisuals(); ShowVideo.ValueChanged += _ => UpdateVisuals(); StoryboardReplacesBackground.ValueChanged += _ => UpdateVisuals(); + IgnoreUserSettings.ValueChanged += _ => UpdateVisuals(); } protected override void LoadComplete() diff --git a/osu.Game/Rulesets/Mods/IApplicableToScreen.cs b/osu.Game/Rulesets/Mods/IApplicableToScreen.cs new file mode 100644 index 0000000000..f1a631ccba --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableToScreen.cs @@ -0,0 +1,26 @@ +// 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.Mods +{ + /// + /// An interface for a mod which can temporarily override screen settings. + /// + public interface IApplicableToScreen : IApplicableMod + { + /// + /// Whether to enable image, video and storyboard dimming + /// + bool EnableDim { get; } + + /// + /// Weather to force the video (if present) + /// + bool ForceVideo { get; } + + /// + /// Weather to force the storyboard (if present) + /// + bool ForceStoryboard { get; } + } +} diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 4396c3384b..8a777b364a 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -1,7 +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.Linq; using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; @@ -21,7 +24,7 @@ namespace osu.Game.Rulesets.Mods } } - public class ModCinema : ModAutoplay, IApplicableToHUD + public class ModCinema : ModAutoplay, IApplicableToHUD, IApplicableToScreen { public override string Name => "Cinema"; public override string Acronym => "CN"; @@ -33,5 +36,9 @@ namespace osu.Game.Rulesets.Mods overlay.AlwaysPresent = true; overlay.Hide(); } + + public bool EnableDim => false; + public bool ForceVideo => true; + public bool ForceStoryboard => true; } } diff --git a/osu.Game/Screens/Play/DimmableStoryboard.cs b/osu.Game/Screens/Play/DimmableStoryboard.cs index 2154526e54..4c7bb272cc 100644 --- a/osu.Game/Screens/Play/DimmableStoryboard.cs +++ b/osu.Game/Screens/Play/DimmableStoryboard.cs @@ -33,14 +33,14 @@ namespace osu.Game.Screens.Play base.LoadComplete(); } - protected override bool ShowDimContent => ShowStoryboard.Value && DimLevel < 1; + protected override bool ShowDimContent => IgnoreUserSettings.Value || ShowStoryboard.Value && DimLevel < 1; private void initializeStoryboard(bool async) { if (drawableStoryboard != null) return; - if (!ShowStoryboard.Value) + if (!ShowStoryboard.Value && !IgnoreUserSettings.Value) return; drawableStoryboard = storyboard.CreateDrawable(); diff --git a/osu.Game/Screens/Play/DimmableVideo.cs b/osu.Game/Screens/Play/DimmableVideo.cs index 4d6c10d69d..09ec5a3f5d 100644 --- a/osu.Game/Screens/Play/DimmableVideo.cs +++ b/osu.Game/Screens/Play/DimmableVideo.cs @@ -2,6 +2,7 @@ // 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; @@ -33,7 +34,7 @@ namespace osu.Game.Screens.Play base.LoadComplete(); } - protected override bool ShowDimContent => ShowVideo.Value && DimLevel < 1; + protected override bool ShowDimContent => IgnoreUserSettings.Value || ShowVideo.Value && DimLevel < 1; private void initializeVideo(bool async) { @@ -43,7 +44,7 @@ namespace osu.Game.Screens.Play if (drawableVideo != null) return; - if (!ShowVideo.Value) + if (!ShowVideo.Value && !IgnoreUserSettings.Value) return; drawableVideo = new DrawableVideo(video); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index d6488dc209..3d6a9fe78f 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -498,8 +498,24 @@ namespace osu.Game.Screens.Play .Delay(250) .FadeIn(250); - Background.EnableUserDim.Value = true; - Background.BlurAmount.Value = 0; + var screenOverride = Mods.Value.OfType(); + + if (screenOverride.Count() == 1) + { + var setting = screenOverride.Single(); + + Background.EnableUserDim.Value = setting.EnableDim; + DimmableVideo.EnableUserDim.Value = setting.EnableDim; + DimmableStoryboard.EnableUserDim.Value = setting.EnableDim; + + DimmableVideo.IgnoreUserSettings.Value = setting.ForceVideo; + DimmableStoryboard.IgnoreUserSettings.Value = setting.ForceStoryboard; + } + else + { + Background.EnableUserDim.Value = true; + Background.BlurAmount.Value = 0; + } Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); DimmableStoryboard.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); From 1d6665fe57510c4dc950881b15728c5e64e7fa74 Mon Sep 17 00:00:00 2001 From: Albie Date: Sun, 24 Nov 2019 07:42:39 +0000 Subject: [PATCH 2457/2815] improve code quality using resharper and codefactor advice --- osu.Game/Rulesets/Mods/ModCinema.cs | 3 --- osu.Game/Screens/Play/DimmableStoryboard.cs | 2 +- osu.Game/Screens/Play/DimmableVideo.cs | 3 +-- osu.Game/Screens/Play/Player.cs | 2 +- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 8a777b364a..5a876dbf51 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -1,10 +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.Linq; using osu.Framework.Graphics.Sprites; -using osu.Game.Beatmaps; -using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; diff --git a/osu.Game/Screens/Play/DimmableStoryboard.cs b/osu.Game/Screens/Play/DimmableStoryboard.cs index 4c7bb272cc..0fe315fbab 100644 --- a/osu.Game/Screens/Play/DimmableStoryboard.cs +++ b/osu.Game/Screens/Play/DimmableStoryboard.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play base.LoadComplete(); } - protected override bool ShowDimContent => IgnoreUserSettings.Value || ShowStoryboard.Value && DimLevel < 1; + protected override bool ShowDimContent => IgnoreUserSettings.Value || (ShowStoryboard.Value && DimLevel < 1); private void initializeStoryboard(bool async) { diff --git a/osu.Game/Screens/Play/DimmableVideo.cs b/osu.Game/Screens/Play/DimmableVideo.cs index 09ec5a3f5d..1a01cace17 100644 --- a/osu.Game/Screens/Play/DimmableVideo.cs +++ b/osu.Game/Screens/Play/DimmableVideo.cs @@ -2,7 +2,6 @@ // 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; @@ -34,7 +33,7 @@ namespace osu.Game.Screens.Play base.LoadComplete(); } - protected override bool ShowDimContent => IgnoreUserSettings.Value || ShowVideo.Value && DimLevel < 1; + protected override bool ShowDimContent => IgnoreUserSettings.Value || (ShowVideo.Value && DimLevel < 1); private void initializeVideo(bool async) { diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 3d6a9fe78f..dc4612a525 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -432,7 +432,7 @@ namespace osu.Game.Screens.Play // cannot pause if we are already in a fail state && !HasFailed // cannot pause if already paused (or in a cooldown state) unless we are in a resuming state. - && (IsResuming || (GameplayClockContainer.IsPaused.Value == false && !pauseCooldownActive)); + && (IsResuming || GameplayClockContainer.IsPaused.Value == false && !pauseCooldownActive); private bool pauseCooldownActive => lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown; From 9a8e3fe1da79ea53328b2f293b7996eaa43bb138 Mon Sep 17 00:00:00 2001 From: Albie Date: Sun, 24 Nov 2019 07:44:35 +0000 Subject: [PATCH 2458/2815] add brackets --- 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 dc4612a525..3d6a9fe78f 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -432,7 +432,7 @@ namespace osu.Game.Screens.Play // cannot pause if we are already in a fail state && !HasFailed // cannot pause if already paused (or in a cooldown state) unless we are in a resuming state. - && (IsResuming || GameplayClockContainer.IsPaused.Value == false && !pauseCooldownActive); + && (IsResuming || (GameplayClockContainer.IsPaused.Value == false && !pauseCooldownActive)); private bool pauseCooldownActive => lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown; From fccdbffa9370d23cc606d4d3ced16a702801ca07 Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Mon, 25 Nov 2019 00:45:42 +0100 Subject: [PATCH 2459/2815] Use MathF instead of Math- functions when possible MathF-functions are faster than the Math-counterpart and it looks cleaner, so use MathF when we cast to float or int anyway. --- .../Difficulty/CatchPerformanceCalculator.cs | 2 +- .../Objects/Drawable/DrawableFruit.cs | 4 ++-- .../Beatmaps/ManiaBeatmapConverter.cs | 2 +- .../Beatmaps/Patterns/Legacy/PatternGenerator.cs | 4 ++-- osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs | 2 +- .../Objects/Drawables/DrawableRepeatPoint.cs | 2 +- .../Objects/Drawables/Pieces/SpinnerTicks.cs | 10 +++++----- osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 10 +++++----- .../Screens/Drawings/Components/GroupContainer.cs | 2 +- .../Screens/Gameplay/Components/MatchScoreDisplay.cs | 2 +- osu.Game/Graphics/Backgrounds/Triangles.cs | 2 +- osu.Game/Overlays/NowPlayingOverlay.cs | 2 +- .../Overlays/Profile/Header/Components/RankGraph.cs | 4 ++-- .../Edit/Compose/Components/BeatDivisorControl.cs | 2 +- .../Compose/Components/CircularDistanceSnapGrid.cs | 2 +- osu.Game/Screens/Menu/LogoVisualisation.cs | 4 ++-- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 ++-- osu.Game/Screens/Select/Details/AdvancedStats.cs | 2 +- 18 files changed, 31 insertions(+), 31 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index a7f0d358ed..b876c774b2 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty // Longer maps are worth more float lengthBonus = 0.95f + 0.4f * Math.Min(1.0f, numTotalHits / 3000.0f) + - (numTotalHits > 3000 ? (float)Math.Log10(numTotalHits / 3000.0f) * 0.5f : 0.0f); + (numTotalHits > 3000 ? MathF.Log10(numTotalHits / 3000.0f) * 0.5f : 0.0f); // Longer maps are worth more value *= lengthBonus; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs index eae652573b..2c97e5083d 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs @@ -99,8 +99,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable const float small_pulp = large_pulp_3 / 2; Vector2 positionAt(float angle, float distance) => new Vector2( - distance * (float)Math.Sin(angle * Math.PI / 180), - distance * (float)Math.Cos(angle * Math.PI / 180)); + distance * MathF.Sin(angle * MathF.PI / 180), + distance * MathF.Cos(angle * MathF.PI / 180)); switch (representation) { diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 6c5bb304bf..5fa1e31c78 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps { BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty; - int seed = (int)Math.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)Math.Round(difficulty.ApproachRate); + int seed = (int)MathF.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)MathF.Round(difficulty.ApproachRate); Random = new FastRandom(seed); return base.ConvertBeatmap(original); diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index b9984a8b90..a249c6f0f3 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -53,11 +53,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (allowSpecial && TotalColumns == 8) { const float local_x_divisor = 512f / 7; - return Math.Clamp((int)Math.Floor(position / local_x_divisor), 0, 6) + 1; + return Math.Clamp((int)MathF.Floor(position / local_x_divisor), 0, 6) + 1; } float localXDivisor = 512f / TotalColumns; - return Math.Clamp((int)Math.Floor(position / localXDivisor), 0, TotalColumns - 1); + return Math.Clamp((int)MathF.Floor(position / localXDivisor), 0, TotalColumns - 1); } /// diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs index 9b079895fa..a9475af638 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Mods float appearDistance = (float)(hitObject.TimePreempt - hitObject.TimeFadeIn) / 2; Vector2 originalPosition = drawable.Position; - Vector2 appearOffset = new Vector2((float)Math.Cos(theta), (float)Math.Sin(theta)) * appearDistance; + Vector2 appearOffset = new Vector2(MathF.Cos(theta), MathF.Sin(theta)) * appearDistance; //the - 1 and + 1 prevents the hit objects to appear in the wrong position. double appearTime = hitObject.StartTime - hitObject.TimePreempt - 1; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index 122975d55e..71cb9a9691 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables break; } - float aimRotation = MathHelper.RadiansToDegrees((float)Math.Atan2(aimRotationVector.Y - Position.Y, aimRotationVector.X - Position.X)); + float aimRotation = MathHelper.RadiansToDegrees(MathF.Atan2(aimRotationVector.Y - Position.Y, aimRotationVector.X - Position.X)); while (Math.Abs(aimRotation - Rotation) > 180) aimRotation += aimRotation < Rotation ? 360 : -360; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs index 9219fab830..676cefb236 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs @@ -20,9 +20,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Anchor = Anchor.Centre; RelativeSizeAxes = Axes.Both; - const int count = 18; + const float count = 18; - for (int i = 0; i < count; i++) + for (float i = 0; i < count; i++) { Add(new Container { @@ -40,10 +40,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Size = new Vector2(60, 10), Origin = Anchor.Centre, Position = new Vector2( - 0.5f + (float)Math.Sin((float)i / count * 2 * MathHelper.Pi) / 2 * 0.86f, - 0.5f + (float)Math.Cos((float)i / count * 2 * MathHelper.Pi) / 2 * 0.86f + 0.5f + MathF.Sin(i / count * 2 * MathF.PI) / 2 * 0.86f, + 0.5f + MathF.Cos(i / count * 2 * MathF.PI) / 2 * 0.86f ), - Rotation = -(float)i / count * 360 + 90, + Rotation = -i / count * 360 + 90, Children = new[] { new Box diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 24320b6579..d50b2098d6 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -185,14 +185,14 @@ namespace osu.Game.Rulesets.Osu.Replays { Vector2 spinCentreOffset = SPINNER_CENTRE - prevPos; float distFromCentre = spinCentreOffset.Length; - float distToTangentPoint = (float)Math.Sqrt(distFromCentre * distFromCentre - SPIN_RADIUS * SPIN_RADIUS); + float distToTangentPoint = MathF.Sqrt(distFromCentre * distFromCentre - SPIN_RADIUS * SPIN_RADIUS); if (distFromCentre > SPIN_RADIUS) { // Previous cursor position was outside spin circle, set startPosition to the tangent point. // Angle between centre offset and tangent point offset. - float angle = (float)Math.Asin(SPIN_RADIUS / distFromCentre); + float angle = MathF.Asin(SPIN_RADIUS / distFromCentre); if (angle > 0) { @@ -204,8 +204,8 @@ namespace osu.Game.Rulesets.Osu.Replays } // Rotate by angle so it's parallel to tangent line - spinCentreOffset.X = spinCentreOffset.X * (float)Math.Cos(angle) - spinCentreOffset.Y * (float)Math.Sin(angle); - spinCentreOffset.Y = spinCentreOffset.X * (float)Math.Sin(angle) + spinCentreOffset.Y * (float)Math.Cos(angle); + spinCentreOffset.X = spinCentreOffset.X * MathF.Cos(angle) - spinCentreOffset.Y * MathF.Sin(angle); + spinCentreOffset.Y = spinCentreOffset.X * MathF.Sin(angle) + spinCentreOffset.Y * MathF.Cos(angle); // Set length to distToTangentPoint spinCentreOffset.Normalize(); @@ -331,7 +331,7 @@ namespace osu.Game.Rulesets.Osu.Replays Vector2 difference = startPosition - SPINNER_CENTRE; float radius = difference.Length; - float angle = radius == 0 ? 0 : (float)Math.Atan2(difference.Y, difference.X); + float angle = radius == 0 ? 0 : MathF.Atan2(difference.Y, difference.X); double t; diff --git a/osu.Game.Tournament/Screens/Drawings/Components/GroupContainer.cs b/osu.Game.Tournament/Screens/Drawings/Components/GroupContainer.cs index 8a66ca7bf6..b9a19090df 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/GroupContainer.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/GroupContainer.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components groups.Add(g); nextGroupName++; - if (i < (int)Math.Ceiling(numGroups / 2f)) + if (i < (int)MathF.Ceiling(numGroups / 2f)) topGroups.Add(g); else bottomGroups.Add(g); diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs index 78455c8bb7..cc7903f2fa 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs @@ -100,7 +100,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components var diff = Math.Max(score1.Value, score2.Value) - Math.Min(score1.Value, score2.Value); losingBar.ResizeWidthTo(0, 400, Easing.OutQuint); - winningBar.ResizeWidthTo(Math.Min(0.4f, (float)Math.Pow(diff / 1500000f, 0.5) / 2), 400, Easing.OutQuint); + winningBar.ResizeWidthTo(Math.Min(0.4f, MathF.Pow(diff / 1500000f, 0.5f) / 2), 400, Easing.OutQuint); } protected override void Update() diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index dffa0c4fd5..6d88808150 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -111,7 +111,7 @@ namespace osu.Game.Graphics.Backgrounds float adjustedAlpha = HideAlphaDiscrepancies // Cubically scale alpha to make it drop off more sharply. - ? (float)Math.Pow(DrawColourInfo.Colour.AverageColour.Linear.A, 3) + ? MathF.Pow(DrawColourInfo.Colour.AverageColour.Linear.A, 3) : 1; float elapsedSeconds = (float)Time.Elapsed / 1000; diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 4f73cbfacd..de30e1a754 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -390,7 +390,7 @@ namespace osu.Game.Overlays Vector2 change = e.MousePosition - e.MouseDownPosition; // Diminish the drag distance as we go further to simulate "rubber band" feeling. - change *= change.Length <= 0 ? 0 : (float)Math.Pow(change.Length, 0.7f) / change.Length; + change *= change.Length <= 0 ? 0 : MathF.Pow(change.Length, 0.7f) / change.Length; this.MoveTo(change); return true; diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index c6d96c5917..250b345db7 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -90,7 +90,7 @@ namespace osu.Game.Overlays.Profile.Header.Components placeholder.FadeOut(fade_duration, Easing.Out); graph.DefaultValueCount = ranks.Length; - graph.Values = ranks.Select(x => -(float)Math.Log(x.Value)); + graph.Values = ranks.Select(x => -MathF.Log(x.Value)); } graph.FadeTo(ranks.Length > 1 ? 1 : 0, fade_duration, Easing.Out); @@ -187,7 +187,7 @@ namespace osu.Game.Overlays.Profile.Header.Components public void HideBar() => bar.FadeOut(fade_duration); - private int calculateIndex(float mouseXPosition) => (int)Math.Round(mouseXPosition / DrawWidth * (DefaultValueCount - 1)); + private int calculateIndex(float mouseXPosition) => (int)MathF.Round(mouseXPosition / DrawWidth * (DefaultValueCount - 1)); private Vector2 calculateBallPosition(int index) { diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 9e4691e4dd..42773ef687 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -289,7 +289,7 @@ namespace osu.Game.Screens.Edit.Compose.Components OnUserChange(Current.Value); } - private float getMappedPosition(float divisor) => (float)Math.Pow((divisor - 1) / (availableDivisors.Last() - 1), 0.90f); + private float getMappedPosition(float divisor) => MathF.Pow((divisor - 1) / (availableDivisors.Last() - 1), 0.90f); private class Tick : CompositeDrawable { diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index fc7d51db4b..91e19f9cc0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -75,7 +75,7 @@ namespace osu.Game.Screens.Edit.Compose.Components float distance = direction.Length; float radius = DistanceSpacing; - int radialCount = Math.Clamp((int)Math.Round(distance / radius), 1, MaxIntervals); + int radialCount = Math.Clamp((int)MathF.Round(distance / radius), 1, MaxIntervals); Vector2 normalisedDirection = direction * new Vector2(1f / distance); Vector2 snappedPosition = CentrePosition + normalisedDirection * radialCount * radius; diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 59ab6ad265..1a625f8d83 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -206,8 +206,8 @@ namespace osu.Game.Screens.Menu continue; float rotation = MathHelper.DegreesToRadians(i / (float)bars_per_visualiser * 360 + j * 360 / visualiser_rounds); - float rotationCos = (float)Math.Cos(rotation); - float rotationSin = (float)Math.Sin(rotation); + float rotationCos = MathF.Cos(rotation); + float rotationSin = MathF.Sin(rotation); //taking the cos and sin to the 0..1 range var barPosition = new Vector2(rotationCos / 2 + 0.5f, rotationSin / 2 + 0.5f) * size; diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 17736e7819..838ca5322c 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -670,8 +670,8 @@ namespace osu.Game.Screens.Select { // The radius of the circle the carousel moves on. const float circle_radius = 3; - double discriminant = Math.Max(0, circle_radius * circle_radius - dist * dist); - float x = (circle_radius - (float)Math.Sqrt(discriminant)) * halfHeight; + float discriminant = MathF.Max(0, circle_radius * circle_radius - dist * dist); + float x = (circle_radius - MathF.Sqrt(discriminant)) * halfHeight; return 125 + x; } diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 52a57dd506..c5bdc230d0 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Select.Details if ((Beatmap?.Ruleset?.ID ?? 0) == 3) { firstValue.Title = "Key Amount"; - firstValue.Value = (int)Math.Round(Beatmap?.BaseDifficulty?.CircleSize ?? 0); + firstValue.Value = (int)MathF.Round(Beatmap?.BaseDifficulty?.CircleSize ?? 0); } else { From 45514ff660a403a41e2c284c6e6be5ef81406331 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Nov 2019 11:30:55 +0900 Subject: [PATCH 2460/2815] Apply fixes --- osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs | 4 ++-- osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs | 3 ++- .../Visual/Gameplay/TestSceneBarHitErrorMeter.cs | 8 ++++---- .../Visual/Online/TestSceneLeaderboardModSelector.cs | 3 ++- .../Visual/Online/TestSceneRankingsDismissableFlag.cs | 3 ++- osu.Game.Tests/Visual/Online/TestSceneUserRequest.cs | 6 +++--- osu.Game.Tournament/TournamentGameBase.cs | 4 ++-- osu.Game/Online/API/APIRequest.cs | 2 +- osu.Game/Overlays/Chat/DrawableChannel.cs | 3 ++- osu.Game/Overlays/Comments/CommentsHeader.cs | 5 +++-- .../Overlays/Comments/DeletedChildrenPlaceholder.cs | 3 ++- osu.Game/Overlays/Comments/DrawableComment.cs | 11 ++++++----- osu.Game/Overlays/Comments/SortTabControl.cs | 3 ++- osu.Game/Overlays/Comments/TotalCommentsCounter.cs | 5 +++-- osu.Game/Overlays/Comments/VotePill.cs | 2 +- .../Profile/Header/Components/PreviousUsernames.cs | 3 ++- osu.Game/Overlays/Rankings/HeaderTitle.cs | 5 +++-- 17 files changed, 42 insertions(+), 31 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 720ef1db42..9b529a2e4c 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -11,12 +11,12 @@ using System; using System.Collections.Generic; using osu.Game.Skinning; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osuTK.Graphics; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics.Textures; using osu.Game.Audio; +using osu.Game.Graphics.Sprites; namespace osu.Game.Rulesets.Catch.Tests { @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Catch.Tests RelativeSizeAxes = Axes.Both, Colour = Color4.Blue }, - new SpriteText + new OsuSpriteText { Text = "custom" } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index 7ca311118e..4da1b1dae0 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -16,6 +16,7 @@ using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Screens.Play; using osu.Game.Skinning; @@ -125,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Tests { if (!enabled) return null; - return new SpriteText + return new OsuSpriteText { Text = identifier, Font = OsuFont.Default.With(size: 30), diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs index a934d22b5d..e3688c276f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.Judgements; using osu.Framework.MathUtils; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Catch.Scoring; using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Osu.Scoring; @@ -85,9 +85,9 @@ namespace osu.Game.Tests.Visual.Gameplay AutoSizeAxes = Axes.Both, Children = new[] { - new SpriteText { Text = $@"Great: {hitWindows?.WindowFor(HitResult.Great)}" }, - new SpriteText { Text = $@"Good: {hitWindows?.WindowFor(HitResult.Good)}" }, - new SpriteText { Text = $@"Meh: {hitWindows?.WindowFor(HitResult.Meh)}" }, + new OsuSpriteText { Text = $@"Great: {hitWindows?.WindowFor(HitResult.Great)}" }, + new OsuSpriteText { Text = $@"Good: {hitWindows?.WindowFor(HitResult.Good)}" }, + new OsuSpriteText { Text = $@"Meh: {hitWindows?.WindowFor(HitResult.Meh)}" }, } }); diff --git a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs index ebe233a5f4..e0e5a088ce 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Bindables; +using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; namespace osu.Game.Tests.Visual.Online @@ -45,7 +46,7 @@ namespace osu.Game.Tests.Visual.Online modSelector.SelectedMods.ItemsAdded += mods => { - mods.ForEach(mod => selectedMods.Add(new SpriteText + mods.ForEach(mod => selectedMods.Add(new OsuSpriteText { Text = mod.Acronym, })); diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsDismissableFlag.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsDismissableFlag.cs index db6afa9bf3..cd954cd6bd 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsDismissableFlag.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsDismissableFlag.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Overlays.Rankings; using osu.Game.Users; using osuTK; @@ -45,7 +46,7 @@ namespace osu.Game.Tests.Visual.Online Size = new Vector2(30, 20), Country = countryA, }, - text = new SpriteText + text = new OsuSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserRequest.cs b/osu.Game.Tests/Visual/Online/TestSceneUserRequest.cs index 18d6028cb8..0f41247571 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserRequest.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserRequest.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mania; using osu.Game.Users; using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Taiko; using osu.Game.Graphics.UserInterface; @@ -94,11 +94,11 @@ namespace osu.Game.Tests.Visual.Online AddRange(new Drawable[] { - new SpriteText + new OsuSpriteText { Text = $@"Username: {user.NewValue?.Username}" }, - new SpriteText + new OsuSpriteText { Text = $@"RankedScore: {user.NewValue?.Statistics.RankedScore}" }, diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index f2a158971b..9e3dfa3ea6 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -12,13 +12,13 @@ using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests; using osu.Game.Tournament.IPC; using osu.Game.Tournament.Models; @@ -104,7 +104,7 @@ namespace osu.Game.Tournament Colour = Color4.Red, RelativeSizeAxes = Axes.Both, }, - new SpriteText + new OsuSpriteText { Text = "Please make the window wider", Font = OsuFont.Default.With(weight: "bold"), diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 53b23f5922..b424e0f086 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -150,7 +150,7 @@ namespace osu.Game.Online.API private class DisplayableError { [JsonProperty("error")] - public string ErrorMessage; + public string ErrorMessage { get; set; } } } diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 427bd8dcde..443f2b7bf7 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -16,6 +16,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays.Chat { @@ -202,7 +203,7 @@ namespace osu.Game.Overlays.Chat RelativeSizeAxes = Axes.X, Height = lineHeight, }, - text = new SpriteText + text = new OsuSpriteText { Margin = new MarginPadding { Horizontal = 10 }, Text = time.ToLocalTime().ToString("dd MMM yyyy"), diff --git a/osu.Game/Overlays/Comments/CommentsHeader.cs b/osu.Game/Overlays/Comments/CommentsHeader.cs index 66fe7ff3fa..6a7a678cc7 100644 --- a/osu.Game/Overlays/Comments/CommentsHeader.cs +++ b/osu.Game/Overlays/Comments/CommentsHeader.cs @@ -10,6 +10,7 @@ using osu.Game.Graphics; using osu.Framework.Graphics.Sprites; using osuTK; using osu.Framework.Input.Events; +using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays.Comments { @@ -48,7 +49,7 @@ namespace osu.Game.Overlays.Comments Origin = Anchor.CentreLeft, Children = new Drawable[] { - new SpriteText + new OsuSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -101,7 +102,7 @@ namespace osu.Game.Overlays.Comments Origin = Anchor.CentreLeft, Size = new Vector2(10), }, - new SpriteText + new OsuSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game/Overlays/Comments/DeletedChildrenPlaceholder.cs b/osu.Game/Overlays/Comments/DeletedChildrenPlaceholder.cs index e849691597..6b41453b91 100644 --- a/osu.Game/Overlays/Comments/DeletedChildrenPlaceholder.cs +++ b/osu.Game/Overlays/Comments/DeletedChildrenPlaceholder.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Sprites; using osuTK; using osu.Framework.Bindables; using Humanizer; +using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays.Comments { @@ -31,7 +32,7 @@ namespace osu.Game.Overlays.Comments Icon = FontAwesome.Solid.Trash, Size = new Vector2(14), }, - countText = new SpriteText + countText = new OsuSpriteText { Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true), } diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 3fb9867f0e..9a11e85a7b 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Bindables; using osu.Framework.Graphics.Shapes; using System.Linq; +using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; namespace osu.Game.Overlays.Comments @@ -122,7 +123,7 @@ namespace osu.Game.Overlays.Comments AutoSizeAxes = Axes.Both, }, new ParentUsername(comment), - new SpriteText + new OsuSpriteText { Alpha = comment.IsDeleted ? 1 : 0, Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true), @@ -144,7 +145,7 @@ namespace osu.Game.Overlays.Comments Colour = OsuColour.Gray(0.7f), Children = new Drawable[] { - new SpriteText + new OsuSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -195,7 +196,7 @@ namespace osu.Game.Overlays.Comments if (comment.EditedAt.HasValue) { - info.Add(new SpriteText + info.Add(new OsuSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -290,7 +291,7 @@ namespace osu.Game.Overlays.Comments this.count = count; Alpha = count == 0 ? 0 : 1; - Child = text = new SpriteText + Child = text = new OsuSpriteText { Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), }; @@ -323,7 +324,7 @@ namespace osu.Game.Overlays.Comments Icon = FontAwesome.Solid.Reply, Size = new Vector2(14), }, - new SpriteText + new OsuSpriteText { Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true), Text = parentComment?.User?.Username ?? parentComment?.LegacyName diff --git a/osu.Game/Overlays/Comments/SortTabControl.cs b/osu.Game/Overlays/Comments/SortTabControl.cs index f5423e692f..a114197b8d 100644 --- a/osu.Game/Overlays/Comments/SortTabControl.cs +++ b/osu.Game/Overlays/Comments/SortTabControl.cs @@ -11,6 +11,7 @@ using osu.Game.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Bindables; using osu.Framework.Allocation; +using osu.Game.Graphics.Sprites; using osuTK.Graphics; namespace osu.Game.Overlays.Comments @@ -61,7 +62,7 @@ namespace osu.Game.Overlays.Comments public TabButton(CommentsSortCriteria value) { - Add(text = new SpriteText + Add(text = new OsuSpriteText { Font = OsuFont.GetFont(size: 14), Text = value.ToString() diff --git a/osu.Game/Overlays/Comments/TotalCommentsCounter.cs b/osu.Game/Overlays/Comments/TotalCommentsCounter.cs index c71ca762e1..376853c1de 100644 --- a/osu.Game/Overlays/Comments/TotalCommentsCounter.cs +++ b/osu.Game/Overlays/Comments/TotalCommentsCounter.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Sprites; using osuTK; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays.Comments { @@ -32,7 +33,7 @@ namespace osu.Game.Overlays.Comments Spacing = new Vector2(5, 0), Children = new Drawable[] { - new SpriteText + new OsuSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -52,7 +53,7 @@ namespace osu.Game.Overlays.Comments RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(0.05f) }, - counter = new SpriteText + counter = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index ab35a477aa..978846549e 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -110,7 +110,7 @@ namespace osu.Game.Overlays.Comments } } }, - sideNumber = new SpriteText + sideNumber = new OsuSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreRight, diff --git a/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs b/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs index f18f319e27..e4c0fe3a5a 100644 --- a/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs +++ b/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Users; using osuTK; @@ -63,7 +64,7 @@ namespace osu.Game.Overlays.Profile.Header.Components new Drawable[] { hoverIcon = new HoverIconContainer(), - header = new SpriteText + header = new OsuSpriteText { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, diff --git a/osu.Game/Overlays/Rankings/HeaderTitle.cs b/osu.Game/Overlays/Rankings/HeaderTitle.cs index cba407ecf7..a1a893fa6b 100644 --- a/osu.Game/Overlays/Rankings/HeaderTitle.cs +++ b/osu.Game/Overlays/Rankings/HeaderTitle.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osuTK; using osu.Game.Graphics; using osu.Framework.Allocation; +using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays.Rankings { @@ -41,13 +42,13 @@ namespace osu.Game.Overlays.Rankings Margin = new MarginPadding { Bottom = flag_margin }, Size = new Vector2(30, 20), }, - scopeText = new SpriteText + scopeText = new OsuSpriteText { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Light) }, - new SpriteText + new OsuSpriteText { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, From 4904346814feeb39dd95a5879abd6eb0120be701 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Nov 2019 11:31:03 +0900 Subject: [PATCH 2461/2815] Fix false warnings --- CodeAnalysis/BannedSymbols.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index 3ad8d5db5b..c54c639b4f 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -2,4 +2,4 @@ M:System.Object.Equals(System.Object,System.Object)~System.Boolean;Don't use obj M:System.Object.Equals(System.Object)~System.Boolean;Don't use object.Equals. Use IEquatable or EqualityComparer.Default instead. M:System.ValueType.Equals(System.Object)~System.Boolean;Don't use object.Equals(Fallbacks to ValueType). Use IEquatable or EqualityComparer.Default instead. T:System.IComparable;Don't use non-generic IComparable. Use generic version instead. -T:osu.Framework.Graphics.Sprites.SpriteText;Use OsuSpriteText. +T:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText. From 36cabe72cf854cbfd05f01fb0d03dc91e9717078 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Nov 2019 11:49:54 +0900 Subject: [PATCH 2462/2815] Make DimmedLoadingLayer block input when active --- osu.Game/Graphics/UserInterface/DimmedLoadingLayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/DimmedLoadingLayer.cs b/osu.Game/Graphics/UserInterface/DimmedLoadingLayer.cs index f7138827cc..f2f6dd429b 100644 --- a/osu.Game/Graphics/UserInterface/DimmedLoadingLayer.cs +++ b/osu.Game/Graphics/UserInterface/DimmedLoadingLayer.cs @@ -10,7 +10,7 @@ using osuTK; namespace osu.Game.Graphics.UserInterface { - public class DimmedLoadingLayer : VisibilityContainer + public class DimmedLoadingLayer : OverlayContainer { private const float transition_duration = 250; From 9fdbb2a58e8aea3e7232d9853170d96502c3e2bb Mon Sep 17 00:00:00 2001 From: Albie Date: Mon, 25 Nov 2019 07:24:29 +0000 Subject: [PATCH 2463/2815] change name of interface and expose method instead of seperate values --- osu.Game/Rulesets/Mods/IApplicableToPlayer.cs | 15 +++++++++++ osu.Game/Rulesets/Mods/IApplicableToScreen.cs | 26 ------------------ osu.Game/Rulesets/Mods/ModCinema.cs | 14 +++++++--- osu.Game/Screens/Play/Player.cs | 27 +++++-------------- .../Play/ScreenWithBeatmapBackground.cs | 2 +- 5 files changed, 33 insertions(+), 51 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/IApplicableToPlayer.cs delete mode 100644 osu.Game/Rulesets/Mods/IApplicableToScreen.cs diff --git a/osu.Game/Rulesets/Mods/IApplicableToPlayer.cs b/osu.Game/Rulesets/Mods/IApplicableToPlayer.cs new file mode 100644 index 0000000000..bf78428470 --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableToPlayer.cs @@ -0,0 +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 osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// An interface for a mod which can temporarily override the settings. + /// + public interface IApplicableToPlayer : IApplicableMod + { + void ApplyToPlayer(Player player); + } +} diff --git a/osu.Game/Rulesets/Mods/IApplicableToScreen.cs b/osu.Game/Rulesets/Mods/IApplicableToScreen.cs deleted file mode 100644 index f1a631ccba..0000000000 --- a/osu.Game/Rulesets/Mods/IApplicableToScreen.cs +++ /dev/null @@ -1,26 +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.Mods -{ - /// - /// An interface for a mod which can temporarily override screen settings. - /// - public interface IApplicableToScreen : IApplicableMod - { - /// - /// Whether to enable image, video and storyboard dimming - /// - bool EnableDim { get; } - - /// - /// Weather to force the video (if present) - /// - bool ForceVideo { get; } - - /// - /// Weather to force the storyboard (if present) - /// - bool ForceStoryboard { get; } - } -} diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 5a876dbf51..77bf80b149 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mods } } - public class ModCinema : ModAutoplay, IApplicableToHUD, IApplicableToScreen + public class ModCinema : ModAutoplay, IApplicableToHUD, IApplicableToPlayer { public override string Name => "Cinema"; public override string Acronym => "CN"; @@ -34,8 +34,14 @@ namespace osu.Game.Rulesets.Mods overlay.Hide(); } - public bool EnableDim => false; - public bool ForceVideo => true; - public bool ForceStoryboard => true; + public void ApplyToPlayer(Player player) + { + player.Background.EnableUserDim.Value = false; + player.DimmableVideo.EnableUserDim.Value = false; + player.DimmableStoryboard.EnableUserDim.Value = false; + + player.DimmableVideo.IgnoreUserSettings.Value = true; + player.DimmableStoryboard.IgnoreUserSettings.Value = true; + } } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 3d6a9fe78f..90171da747 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -80,8 +80,8 @@ namespace osu.Game.Screens.Play protected GameplayClockContainer GameplayClockContainer { get; private set; } - protected DimmableStoryboard DimmableStoryboard { get; private set; } - protected DimmableVideo DimmableVideo { get; private set; } + public DimmableStoryboard DimmableStoryboard { get; private set; } + public DimmableVideo DimmableVideo { get; private set; } [Cached] [Cached(Type = typeof(IBindable>))] @@ -498,24 +498,8 @@ namespace osu.Game.Screens.Play .Delay(250) .FadeIn(250); - var screenOverride = Mods.Value.OfType(); - - if (screenOverride.Count() == 1) - { - var setting = screenOverride.Single(); - - Background.EnableUserDim.Value = setting.EnableDim; - DimmableVideo.EnableUserDim.Value = setting.EnableDim; - DimmableStoryboard.EnableUserDim.Value = setting.EnableDim; - - DimmableVideo.IgnoreUserSettings.Value = setting.ForceVideo; - DimmableStoryboard.IgnoreUserSettings.Value = setting.ForceStoryboard; - } - else - { - Background.EnableUserDim.Value = true; - Background.BlurAmount.Value = 0; - } + Background.EnableUserDim.Value = true; + Background.BlurAmount.Value = 0; Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); DimmableStoryboard.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); @@ -525,6 +509,9 @@ namespace osu.Game.Screens.Play GameplayClockContainer.Restart(); GameplayClockContainer.FadeInFromZero(750, Easing.OutQuint); + foreach (var mod in Mods.Value.OfType()) + mod.ApplyToPlayer(this); + foreach (var mod in Mods.Value.OfType()) mod.ApplyToHUD(HUDOverlay); } diff --git a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs index d7d2c97598..8eb253608b 100644 --- a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs +++ b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs @@ -9,6 +9,6 @@ namespace osu.Game.Screens.Play { protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value); - protected new BackgroundScreenBeatmap Background => (BackgroundScreenBeatmap)base.Background; + public new BackgroundScreenBeatmap Background => (BackgroundScreenBeatmap)base.Background; } } From 94e6bfeed33e9c3f1f3acf2ce0fc256d7fca30eb Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2019 07:30:17 +0000 Subject: [PATCH 2464/2815] Bump Microsoft.Build.Traversal from 2.0.19 to 2.0.24 Bumps [Microsoft.Build.Traversal](https://github.com/Microsoft/MSBuildSdks) from 2.0.19 to 2.0.24. - [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.19...Microsoft.Build.Traversal.2.0.24) 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 d8b8d14c36..43bb34912a 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "msbuild-sdks": { - "Microsoft.Build.Traversal": "2.0.19" + "Microsoft.Build.Traversal": "2.0.24" } } \ No newline at end of file From 137f3495e3d5ffa8c84ccec04ba83d3507f195e4 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 23 Nov 2019 22:10:27 +0800 Subject: [PATCH 2465/2815] Use appveyor yml back for build. --- appveyor.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index f911d67c6e..b64d56d4f6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,5 +2,10 @@ clone_depth: 1 version: '{branch}-{build}' image: Visual Studio 2019 test: off -build_script: - - cmd: PowerShell -Version 2.0 .\build.ps1 +before_build: + - ps: dotnet --info # Useful when version mismatch between CI and local + - ps: nuget restore -verbosity quiet # Only nuget.exe knows both new (.NET Core) and old (Xamarin) projects +build: + project: osu.sln + parallel: true + verbosity: minimal From 8479f8531e8c7d6fdfa929f9b65d5e755121f5b7 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 23 Nov 2019 22:20:38 +0800 Subject: [PATCH 2466/2815] Android package name cannot contain `Catch`. --- .../Properties/AndroidManifest.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml index 0fa3b7730d..f8c3fcd894 100644 --- a/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml +++ b/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml @@ -1,5 +1,6 @@  - + + \ No newline at end of file From fbb83045f37174556080b8e58b3bf0f926cd1ae8 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 23 Nov 2019 23:13:30 +0800 Subject: [PATCH 2467/2815] Enable tests. --- appveyor.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index b64d56d4f6..d8f54f7494 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,6 @@ clone_depth: 1 version: '{branch}-{build}' image: Visual Studio 2019 -test: off before_build: - ps: dotnet --info # Useful when version mismatch between CI and local - ps: nuget restore -verbosity quiet # Only nuget.exe knows both new (.NET Core) and old (Xamarin) projects @@ -9,3 +8,8 @@ build: project: osu.sln parallel: true verbosity: minimal +test: + assemblies: + except: + - '**\*Android*' + - '**\*iOS*' From 18840c63190d33c480ca407e969be743b4af284c Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 24 Nov 2019 16:53:57 +0800 Subject: [PATCH 2468/2815] Run format check in CI. --- .config/dotnet-tools.json | 6 ++++++ appveyor.yml | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 6ba6ae82c8..e1a2c6025e 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -13,6 +13,12 @@ "commands": [ "dotnet-format" ] + }, + "codefilesanity": { + "version": "0.0.33", + "commands": [ + "CodeFileSanity" + ] } } } \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index d8f54f7494..6c8f073419 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,6 +8,10 @@ build: project: osu.sln parallel: true verbosity: minimal +after_build: + - ps: dotnet tool restore + - ps: dotnet CodeFileSanity + - ps: dotnet format --dry-run --check test: assemblies: except: From 38dae996a18b512a0645580846a7ec3b04319626 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 24 Nov 2019 17:50:48 +0800 Subject: [PATCH 2469/2815] Add InspectCode to CI. --- .config/dotnet-tools.json | 6 ----- InspectCode.ps1 | 27 ++++++++++++++++++++ appveyor.yml | 2 +- build/InspectCode.cake | 52 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 7 deletions(-) create mode 100644 InspectCode.ps1 create mode 100644 build/InspectCode.cake diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index e1a2c6025e..6ba6ae82c8 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -13,12 +13,6 @@ "commands": [ "dotnet-format" ] - }, - "codefilesanity": { - "version": "0.0.33", - "commands": [ - "CodeFileSanity" - ] } } } \ No newline at end of file diff --git a/InspectCode.ps1 b/InspectCode.ps1 new file mode 100644 index 0000000000..6ed935fdbb --- /dev/null +++ b/InspectCode.ps1 @@ -0,0 +1,27 @@ +[CmdletBinding()] +Param( + [string]$Target, + [string]$Configuration, + [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] + [string]$Verbosity, + [switch]$ShowDescription, + [Alias("WhatIf", "Noop")] + [switch]$DryRun, + [Parameter(Position = 0, Mandatory = $false, ValueFromRemainingArguments = $true)] + [string[]]$ScriptArgs +) + +# Build Cake arguments +$cakeArguments = ""; +if ($Target) { $cakeArguments += "-target=$Target" } +if ($Configuration) { $cakeArguments += "-configuration=$Configuration" } +if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" } +if ($ShowDescription) { $cakeArguments += "-showdescription" } +if ($DryRun) { $cakeArguments += "-dryrun" } +if ($Experimental) { $cakeArguments += "-experimental" } +$cakeArguments += $ScriptArgs + +dotnet tool restore +dotnet cake ./build/InspectCode.cake --bootstrap +dotnet cake ./build/InspectCode.cake $cakeArguments +exit $LASTEXITCODE \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index 6c8f073419..20cf85f44b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,8 +10,8 @@ build: verbosity: minimal after_build: - ps: dotnet tool restore - - ps: dotnet CodeFileSanity - ps: dotnet format --dry-run --check + - ps: .\InspectCode.ps1 test: assemblies: except: diff --git a/build/InspectCode.cake b/build/InspectCode.cake new file mode 100644 index 0000000000..bd3fdf5f93 --- /dev/null +++ b/build/InspectCode.cake @@ -0,0 +1,52 @@ +#addin "nuget:?package=CodeFileSanity&version=0.0.33" +#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.2.1" +#tool "nuget:?package=NVika.MSBuild&version=1.0.1" +var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First(); + +/////////////////////////////////////////////////////////////////////////////// +// ARGUMENTS +/////////////////////////////////////////////////////////////////////////////// + +var target = Argument("target", "CodeAnalysis"); +var configuration = Argument("configuration", "Release"); + +var rootDirectory = new DirectoryPath(".."); +var sln = rootDirectory.CombineWithFilePath("osu.sln"); +var desktopSlnf = rootDirectory.CombineWithFilePath("osu.Desktop.slnf"); + +/////////////////////////////////////////////////////////////////////////////// +// TASKS +/////////////////////////////////////////////////////////////////////////////// + +// windows only because both inspectcode and nvika depend on net45 +Task("InspectCode") + .WithCriteria(IsRunningOnWindows()) + .Does(() => { + InspectCode(desktopSlnf, new InspectCodeSettings { + CachesHome = "inspectcode", + OutputFile = "inspectcodereport.xml", + ArgumentCustomization = arg => { + if (AppVeyor.IsRunningOnAppVeyor) // Don't flood CI output + arg.Append("--verbosity:WARN"); + return arg; + }, + }); + + int returnCode = StartProcess(nVikaToolPath, $@"parsereport ""inspectcodereport.xml"" --treatwarningsaserrors"); + if (returnCode != 0) + throw new Exception($"inspectcode failed with return code {returnCode}"); + }); + +Task("CodeFileSanity") + .Does(() => { + ValidateCodeSanity(new ValidateCodeSanitySettings { + RootDirectory = rootDirectory.FullPath, + IsAppveyorBuild = AppVeyor.IsRunningOnAppVeyor + }); + }); + +Task("CodeAnalysis") + .IsDependentOn("CodeFileSanity") + .IsDependentOn("InspectCode"); + +RunTarget(target); \ No newline at end of file From 3a62406c581dd533dab5d93c66186d268a292fb4 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 24 Nov 2019 19:44:13 +0800 Subject: [PATCH 2470/2815] Update deploy script. --- appveyor.yml | 2 ++ appveyor_deploy.yml | 12 ++++++++++-- osu.Game/osu.Game.csproj | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 20cf85f44b..7588520e36 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -12,6 +12,8 @@ after_build: - ps: dotnet tool restore - ps: dotnet format --dry-run --check - ps: .\InspectCode.ps1 +artifacts: + - path: '**\*.nupkg' test: assemblies: except: diff --git a/appveyor_deploy.yml b/appveyor_deploy.yml index fb7825b31d..208ec38977 100644 --- a/appveyor_deploy.yml +++ b/appveyor_deploy.yml @@ -1,10 +1,18 @@ clone_depth: 1 version: '{build}' image: Visual Studio 2019 +before_build: + - ps: dotnet --info # Useful when version mismatch between CI and local + - ps: nuget restore -verbosity quiet # Only nuget.exe knows both new (.NET Core) and old (Xamarin) projects test: off skip_non_tags: true -build_script: - - cmd: PowerShell -Version 2.0 .\build.ps1 +configuration: Release +build: + project: osu.sln + parallel: true + verbosity: minimal +artifacts: + - path: '**\*.nupkg' deploy: - provider: Environment name: nuget diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 449b4dc4e3..e7614ff395 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -8,6 +8,7 @@ osu! ppy.osu.Game icon.png + true From 575b4db1864fb911b304b45d879b3b9af584651e Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 24 Nov 2019 20:39:11 +0800 Subject: [PATCH 2471/2815] Patch csproj version. --- appveyor.yml | 4 ++++ appveyor_deploy.yml | 6 +++++- osu.Game/osu.Game.csproj | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 7588520e36..333ed94f6e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,10 @@ clone_depth: 1 version: '{branch}-{build}' image: Visual Studio 2019 +dotnet_csproj: + patch: true + file: 'osu.Game\osu.Game.csproj' # Use wildcard when it's able to exclude Xamarin projects + version: '0.0.{build}' before_build: - ps: dotnet --info # Useful when version mismatch between CI and local - ps: nuget restore -verbosity quiet # Only nuget.exe knows both new (.NET Core) and old (Xamarin) projects diff --git a/appveyor_deploy.yml b/appveyor_deploy.yml index 208ec38977..d8d18028de 100644 --- a/appveyor_deploy.yml +++ b/appveyor_deploy.yml @@ -1,6 +1,10 @@ clone_depth: 1 version: '{build}' image: Visual Studio 2019 +dotnet_csproj: + patch: true + file: 'osu.Game\osu.Game.csproj' # Use wildcard when it's able to exclude Xamarin projects + version: $(APPVEYOR_REPO_TAG_NAME) before_build: - ps: dotnet --info # Useful when version mismatch between CI and local - ps: nuget restore -verbosity quiet # Only nuget.exe knows both new (.NET Core) and old (Xamarin) projects @@ -8,7 +12,7 @@ test: off skip_non_tags: true configuration: Release build: - project: osu.sln + project: build\Desktop.proj # Skipping Xamarin Release that's slow and covered by fastlane parallel: true verbosity: minimal artifacts: diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e7614ff395..6c25e9e5fe 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -7,6 +7,7 @@ osu! ppy.osu.Game + 0.0.0 icon.png true From 3c6526bbd2afd3f8b5a45086a9c8ee0541aa30b8 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 25 Nov 2019 15:59:38 +0800 Subject: [PATCH 2472/2815] Add nuget http cache --- appveyor.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index 333ed94f6e..dbe7ed4cf2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,6 +5,8 @@ dotnet_csproj: patch: true file: 'osu.Game\osu.Game.csproj' # Use wildcard when it's able to exclude Xamarin projects version: '0.0.{build}' +cache: + - '%LOCALAPPDATA%\NuGet\v3-cache' -> appveyor.yml before_build: - ps: dotnet --info # Useful when version mismatch between CI and local - ps: nuget restore -verbosity quiet # Only nuget.exe knows both new (.NET Core) and old (Xamarin) projects From e31e83deaaa29e7f6e69bd11b65fe445fec3a96f Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 25 Nov 2019 16:40:13 +0800 Subject: [PATCH 2473/2815] Clean up and update readme. --- README.md | 11 ++++++-- build.ps1 | 27 ------------------ build.sh | 17 ----------- build/build.cake | 73 ------------------------------------------------ 4 files changed, 9 insertions(+), 119 deletions(-) delete mode 100755 build.ps1 delete mode 100755 build.sh delete mode 100644 build/build.cake diff --git a/README.md b/README.md index 65fb97eb5d..7bbd43c961 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,8 @@ Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh - A desktop platform with the [.NET Core SDK 3.0](https://www.microsoft.com/net/learn/get-started) or higher installed. - When running on Linux, please have a system-wide FFmpeg installation available to support video decoding. -- When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs. +- When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/dependencies?tabs=netcore30&pivots=os-windows)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs. +- When developing with mobile, [Xamarin](https://docs.microsoft.com/en-us/xamarin/) is required, which is shipped together with Visual Studio or [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/). - When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/). ## Running osu! @@ -73,13 +74,19 @@ If you are not interested in debugging osu!, you can add `-c Release` to gain pe If the build fails, try to restore NuGet packages with `dotnet restore`. +_Due to historical feature gap between .NET Core and Xamarin, running `dotnet` CLI from the root directory will probably not work. You can try to run the CLI towards single .NET Core project, or helper project at `build/Desktop.proj`. Or you can get help from your IDE of choice to run the correct commands. We are keeping configuration files in the repository for the popular IDEs._ + ### Testing with resource/framework modifications Sometimes it may be necessary to cross-test changes in [osu-resources](https://github.com/ppy/osu-resources) or [osu-framework](https://github.com/ppy/osu-framework). This can be achieved by running some commands as documented on the [osu-resources](https://github.com/ppy/osu-resources/wiki/Testing-local-resources-checkout-with-other-projects) and [osu-framework](https://github.com/ppy/osu-framework/wiki/Testing-local-framework-checkout-with-other-projects) wiki pages. ### Code analysis -Code analysis can be run with `powershell ./build.ps1` or `build.sh`. This is currently only supported under Windows due to [ReSharper CLI shortcomings](https://youtrack.jetbrains.com/issue/RSRP-410004). Alternatively, you can install ReSharper or use Rider to get inline support in your IDE of choice. +Before committing your code, please run code formatter. It can be achieved with command `dotnet format`, or `Format code` command in your IDE. (Defaults to `Ctrl+K,Ctrl+D` in Visual Studio, `Shift+Alt+F` in Visual Studio Code, or `Ctrl+Alt+L` in Rider) + +We have adopted some cross-platform, compiler integrated analyzers. They can provide warnings when you are editing, building inside IDE or from command line, as-if they are provided by the compiler itself. + +JetBrains ReSharper InspectCode is also used for wider rule sets. You can run it from PowerShell with `.\InspectCode.ps1`, which is [only supported on Windows](https://youtrack.jetbrains.com/issue/RSRP-410004). Alternatively, you can install ReSharper or use Rider to get inline support in your IDE of choice. ## Contributing diff --git a/build.ps1 b/build.ps1 deleted file mode 100755 index 4b3b1f717a..0000000000 --- a/build.ps1 +++ /dev/null @@ -1,27 +0,0 @@ -[CmdletBinding()] -Param( - [string]$Target, - [string]$Configuration, - [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] - [string]$Verbosity, - [switch]$ShowDescription, - [Alias("WhatIf", "Noop")] - [switch]$DryRun, - [Parameter(Position = 0, Mandatory = $false, ValueFromRemainingArguments = $true)] - [string[]]$ScriptArgs -) - -# Build Cake arguments -$cakeArguments = ""; -if ($Target) { $cakeArguments += "-target=$Target" } -if ($Configuration) { $cakeArguments += "-configuration=$Configuration" } -if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" } -if ($ShowDescription) { $cakeArguments += "-showdescription" } -if ($DryRun) { $cakeArguments += "-dryrun" } -if ($Experimental) { $cakeArguments += "-experimental" } -$cakeArguments += $ScriptArgs - -dotnet tool restore -dotnet cake ./build/build.cake --bootstrap -dotnet cake ./build/build.cake $cakeArguments -exit $LASTEXITCODE \ No newline at end of file diff --git a/build.sh b/build.sh deleted file mode 100755 index 2c22f08574..0000000000 --- a/build.sh +++ /dev/null @@ -1,17 +0,0 @@ -echo "Installing Cake.Tool..." -dotnet tool restore - -# Parse arguments. -CAKE_ARGUMENTS=() -for i in "$@"; do - case $1 in - -s|--script) SCRIPT="$2"; shift ;; - --) shift; CAKE_ARGUMENTS+=("$@"); break ;; - *) CAKE_ARGUMENTS+=("$1") ;; - esac - shift -done - -echo "Running build script..." -dotnet cake ./build/build.cake --bootstrap -dotnet cake ./build/build.cake "${CAKE_ARGUMENTS[@]}" \ No newline at end of file diff --git a/build/build.cake b/build/build.cake deleted file mode 100644 index 274e57ef4e..0000000000 --- a/build/build.cake +++ /dev/null @@ -1,73 +0,0 @@ -#addin "nuget:?package=CodeFileSanity&version=0.0.33" -#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.2.1" -#tool "nuget:?package=NVika.MSBuild&version=1.0.1" -var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First(); - -/////////////////////////////////////////////////////////////////////////////// -// ARGUMENTS -/////////////////////////////////////////////////////////////////////////////// - -var target = Argument("target", "Build"); -var configuration = Argument("configuration", "Release"); - -var rootDirectory = new DirectoryPath(".."); -var sln = rootDirectory.CombineWithFilePath("osu.sln"); -var desktopBuilds = rootDirectory.CombineWithFilePath("build/Desktop.proj"); -var desktopSlnf = rootDirectory.CombineWithFilePath("osu.Desktop.slnf"); - -/////////////////////////////////////////////////////////////////////////////// -// TASKS -/////////////////////////////////////////////////////////////////////////////// - -Task("Compile") - .Does(() => { - DotNetCoreBuild(desktopBuilds.FullPath, new DotNetCoreBuildSettings { - Configuration = configuration, - }); - }); - -Task("Test") - .IsDependentOn("Compile") - .Does(() => { - var testAssemblies = GetFiles(rootDirectory + "/**/*.Tests/bin/**/*.Tests.dll"); - - DotNetCoreVSTest(testAssemblies, new DotNetCoreVSTestSettings { - Logger = AppVeyor.IsRunningOnAppVeyor ? "Appveyor" : $"trx", - Parallel = true, - ToolTimeout = TimeSpan.FromMinutes(10), - }); - }); - -// windows only because both inspectcore and nvika depend on net45 -Task("InspectCode") - .WithCriteria(IsRunningOnWindows()) - .IsDependentOn("Compile") - .Does(() => { - InspectCode(desktopSlnf, new InspectCodeSettings { - CachesHome = "inspectcode", - OutputFile = "inspectcodereport.xml", - }); - - int returnCode = StartProcess(nVikaToolPath, $@"parsereport ""inspectcodereport.xml"" --treatwarningsaserrors"); - if (returnCode != 0) - throw new Exception($"inspectcode failed with return code {returnCode}"); - }); - -Task("CodeFileSanity") - .Does(() => { - ValidateCodeSanity(new ValidateCodeSanitySettings { - RootDirectory = rootDirectory.FullPath, - IsAppveyorBuild = AppVeyor.IsRunningOnAppVeyor - }); - }); - -Task("DotnetFormat") - .Does(() => DotNetCoreTool(sln.FullPath, "format", "--dry-run --check")); - -Task("Build") - .IsDependentOn("CodeFileSanity") - .IsDependentOn("DotnetFormat") - .IsDependentOn("InspectCode") - .IsDependentOn("Test"); - -RunTarget(target); \ No newline at end of file From 719f0d5947e4d92db11682259a8033c361a6d2b7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 25 Nov 2019 18:28:43 +0900 Subject: [PATCH 2474/2815] Fix stutters when changing beatmap difficulties --- osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 3de0ab191c..7b68460e6b 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Backgrounds Schedule(() => { - if ((Background as BeatmapBackground)?.Beatmap == beatmap) + if ((Background as BeatmapBackground)?.Beatmap.BeatmapInfo.BackgroundEquals(beatmap?.BeatmapInfo) ?? false) return; cancellationSource?.Cancel(); From de3079d8e9bfa577914823eaacb594b6eeed82ee Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 25 Nov 2019 17:14:35 +0800 Subject: [PATCH 2475/2815] Fix appveyor configuration --- appveyor.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index dbe7ed4cf2..f8469f9f80 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,7 +6,7 @@ dotnet_csproj: file: 'osu.Game\osu.Game.csproj' # Use wildcard when it's able to exclude Xamarin projects version: '0.0.{build}' cache: - - '%LOCALAPPDATA%\NuGet\v3-cache' -> appveyor.yml + - '%LOCALAPPDATA%\NuGet\v3-cache -> appveyor.yml' before_build: - ps: dotnet --info # Useful when version mismatch between CI and local - ps: nuget restore -verbosity quiet # Only nuget.exe knows both new (.NET Core) and old (Xamarin) projects @@ -25,3 +25,4 @@ test: except: - '**\*Android*' - '**\*iOS*' + - 'build\**\*' From 6b8983b489a095c6aa43d7346d9b9d47e1af826e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Nov 2019 18:48:58 +0900 Subject: [PATCH 2476/2815] Consider intro and outro time as "break" time --- osu.Game/Screens/Play/BreakOverlay.cs | 12 +++++++++--- osu.Game/Screens/Play/Player.cs | 4 ++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 6fdee85f45..28691c0d09 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.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.Collections.Generic; @@ -16,6 +16,8 @@ namespace osu.Game.Screens.Play { public class BreakOverlay : Container { + private readonly ScoreProcessor scoreProcessor; + /// /// The duration of the break overlay fading. /// @@ -60,9 +62,12 @@ namespace osu.Game.Screens.Play private readonly RemainingTimeCounter remainingTimeCounter; private readonly BreakInfo info; private readonly BreakArrows breakArrows; + private readonly double gameplayStartTime; - public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor = null) + public BreakOverlay(bool letterboxing, double gameplayStartTime = 0, ScoreProcessor scoreProcessor = null) { + this.gameplayStartTime = gameplayStartTime; + this.scoreProcessor = scoreProcessor; RelativeSizeAxes = Axes.Both; Child = fadeContainer = new Container { @@ -154,7 +159,8 @@ namespace osu.Game.Screens.Play } var currentBreak = breaks[CurrentBreakIndex]; - isBreakTime.Value = currentBreak.HasEffect && currentBreak.Contains(time); + + isBreakTime.Value = (currentBreak.HasEffect && currentBreak.Contains(time)) || (time < gameplayStartTime || scoreProcessor.HasCompleted); } private void initializeBreaks() diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index d6488dc209..7f32db2ebf 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -179,7 +179,7 @@ namespace osu.Game.Screens.Play { target.AddRange(new[] { - breakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) + breakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, DrawableRuleset.GameplayStartTime, ScoreProcessor) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -468,7 +468,7 @@ namespace osu.Game.Screens.Play PauseOverlay.Hide(); // breaks and time-based conditions may allow instant resume. - if (breakOverlay.IsBreakTime.Value || GameplayClockContainer.GameplayClock.CurrentTime < Beatmap.Value.Beatmap.HitObjects.First().StartTime) + if (breakOverlay.IsBreakTime.Value) completeResume(); else DrawableRuleset.RequestResume(completeResume); From 709ec1404f650cb368a56f88593ceed9f265f303 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Nov 2019 19:01:24 +0900 Subject: [PATCH 2477/2815] Centralise End/StartTime retrieval to extension method --- .../Objects/Drawable/DrawableCatchHitObject.cs | 4 ++-- .../ManiaBeatmapConversionTest.cs | 3 +-- .../Legacy/DistanceObjectPatternGenerator.cs | 2 +- .../Replays/ManiaAutoGenerator.cs | 3 +-- .../OsuBeatmapConversionTest.cs | 3 +-- .../Beatmaps/OsuBeatmapProcessor.cs | 12 ++++++------ osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 4 ++-- .../Drawables/Connections/FollowPointConnection.cs | 4 ++-- osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 6 +++--- .../TaikoBeatmapConversionTest.cs | 3 +-- osu.Game/Beatmaps/BeatmapManager.cs | 5 +++-- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 3 +-- osu.Game/Rulesets/Objects/BarLineGenerator.cs | 3 +-- .../Rulesets/Objects/Drawables/DrawableHitObject.cs | 6 +++--- osu.Game/Rulesets/Objects/HitObject.cs | 13 +++++++++++++ .../UI/Scrolling/DrawableScrollingRuleset.cs | 3 +-- .../Edit/Compose/Components/DistanceSnapGrid.cs | 3 +-- osu.Game/Screens/Play/SongProgress.cs | 4 ++-- osu.Game/Screens/Play/SongProgressGraph.cs | 5 ++--- 19 files changed, 47 insertions(+), 42 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs index dd4a58a5ef..b7c05392f3 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs @@ -4,8 +4,8 @@ using System; using osuTK; using osu.Framework.Graphics; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Catch.Objects.Drawable @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable protected override void UpdateStateTransforms(ArmedState state) { - var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; + var endTime = HitObject.GetEndTime(); using (BeginAbsoluteSequence(endTime, true)) { diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs index 6f10540973..12865385b6 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs @@ -9,7 +9,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Mania.Tests @@ -27,7 +26,7 @@ namespace osu.Game.Rulesets.Mania.Tests yield return new ConvertValue { StartTime = hitObject.StartTime, - EndTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime, + EndTime = hitObject.GetEndTime(), Column = ((ManiaHitObject)hitObject).Column }; } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 6297a68e08..5404cf2d23 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy foreach (var obj in originalPattern.HitObjects) { - if (!Precision.AlmostEquals(EndTime, (obj as IHasEndTime)?.EndTime ?? obj.StartTime)) + if (!Precision.AlmostEquals(EndTime, obj.GetEndTime())) intermediatePattern.Add(obj); else endTimePattern.Add(obj); diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 2b336ca16d..483327d5b3 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Game.Replays; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.Mania.Replays @@ -84,7 +83,7 @@ namespace osu.Game.Rulesets.Mania.Replays var currentObject = Beatmap.HitObjects[i]; var nextObjectInColumn = GetNextObject(i); // Get the next object that requires pressing the same button - double endTime = (currentObject as IHasEndTime)?.EndTime ?? currentObject.StartTime; + double endTime = currentObject.GetEndTime(); bool canDelayKeyUp = nextObjectInColumn == null || nextObjectInColumn.StartTime > endTime + RELEASE_DELAY; diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs index e9fdf924c3..1a36bb37d5 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Framework.MathUtils; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Tests.Beatmaps; @@ -44,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests ConvertValue createConvertValue(OsuHitObject obj) => new ConvertValue { StartTime = obj.StartTime, - EndTime = (obj as IHasEndTime)?.EndTime ?? obj.StartTime, + EndTime = obj.GetEndTime(), X = obj.StackedPosition.X, Y = obj.StackedPosition.Y }; diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs index bb19b783aa..3a829f72fa 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs @@ -4,7 +4,7 @@ using System; using osu.Framework.Graphics; using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osuTK; @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps if (objectN is Spinner) continue; - double endTime = (stackBaseObject as IHasEndTime)?.EndTime ?? stackBaseObject.StartTime; + double endTime = stackBaseObject.GetEndTime(); double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo.StackLeniency; if (objectN.StartTime - endTime > stackThreshold) @@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps OsuHitObject objectN = beatmap.HitObjects[n]; if (objectN is Spinner) continue; - double endTime = (objectN as IHasEndTime)?.EndTime ?? objectN.StartTime; + double endTime = objectN.GetEndTime(); if (objectI.StartTime - endTime > stackThreshold) //We are no longer within stacking range of the previous object. @@ -199,7 +199,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps if (currHitObject.StackHeight != 0 && !(currHitObject is Slider)) continue; - double startTime = (currHitObject as IHasEndTime)?.EndTime ?? currHitObject.StartTime; + double startTime = currHitObject.GetEndTime(); int sliderStack = 0; for (int j = i + 1; j < beatmap.HitObjects.Count; j++) @@ -217,14 +217,14 @@ namespace osu.Game.Rulesets.Osu.Beatmaps if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.Position) < stack_distance) { currHitObject.StackHeight++; - startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[j].StartTime; + startTime = beatmap.HitObjects[j].GetEndTime(); } else if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, position2) < stack_distance) { //Case for sliders - bump notes down and right, rather than up and left. sliderStack++; beatmap.HitObjects[j].StackHeight -= sliderStack; - startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[j].StartTime; + startTime = beatmap.HitObjects[j].GetEndTime(); } } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 32c9e913c6..9f384ea2e8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -6,9 +6,9 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Mods @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Mods var fadeOutDuration = h.TimePreempt * fade_out_duration_multiplier; // new duration from completed fade in to end (before fading out) - var longFadeDuration = ((h as IHasEndTime)?.EndTime ?? h.StartTime) - fadeOutStartTime; + var longFadeDuration = h.GetEndTime() - fadeOutStartTime; switch (drawable) { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 1e032eb977..6c4fbbac17 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -6,7 +6,7 @@ using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Objects; using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections Vector2 startPosition = osuStart.EndPosition; Vector2 endPosition = osuEnd.Position; - double startTime = (osuStart as IHasEndTime)?.EndTime ?? osuStart.StartTime; + double startTime = osuStart.GetEndTime(); double endTime = osuEnd.StartTime; Vector2 distanceVector = endPosition - startPosition; diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index d50b2098d6..bd59e8a03f 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -10,7 +10,7 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Graphics; using osu.Game.Replays; -using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.Replays private void addDelayedMovements(OsuHitObject h, OsuHitObject prev) { - double endTime = (prev as IHasEndTime)?.EndTime ?? prev.StartTime; + double endTime = prev.GetEndTime(); HitWindows hitWindows = null; @@ -275,7 +275,7 @@ namespace osu.Game.Rulesets.Osu.Replays var startFrame = new OsuReplayFrame(h.StartTime, new Vector2(startPosition.X, startPosition.Y), action); // TODO: Why do we delay 1 ms if the object is a spinner? There already is KEY_UP_DELAY from hEndTime. - double hEndTime = ((h as IHasEndTime)?.EndTime ?? h.StartTime) + KEY_UP_DELAY; + double hEndTime = h.GetEndTime() + KEY_UP_DELAY; int endDelay = h is Spinner ? 1 : 0; var endFrame = new OsuReplayFrame(hEndTime + endDelay, new Vector2(h.StackedEndPosition.X, h.StackedEndPosition.Y)); diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs index 6c1882b4e2..28f5d4d301 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Framework.MathUtils; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Tests.Beatmaps; @@ -27,7 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Tests yield return new ConvertValue { StartTime = hitObject.StartTime, - EndTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime, + EndTime = hitObject.GetEndTime(), IsRim = hitObject is RimHit, IsCentre = hitObject is CentreHit, IsDrumRoll = hitObject is DrumRoll, diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index e6783ec828..bc78e50f5d 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -25,7 +25,7 @@ using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Rulesets; -using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Objects; namespace osu.Game.Beatmaps { @@ -334,7 +334,8 @@ namespace osu.Game.Beatmaps var lastObject = b.HitObjects.Last(); - double endTime = (lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime; + //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). + double endTime = lastObject.GetEndTime(); double startTime = b.HitObjects.First().StartTime; return endTime - startTime; diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index f217512965..e231225e3c 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -8,7 +8,6 @@ using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Mods { @@ -42,7 +41,7 @@ namespace osu.Game.Rulesets.Mods HitObject lastObject = beatmap.HitObjects.LastOrDefault(); beginRampTime = beatmap.HitObjects.FirstOrDefault()?.StartTime ?? 0; - finalRateTime = final_rate_progress * ((lastObject as IHasEndTime)?.EndTime ?? lastObject?.StartTime ?? 0); + finalRateTime = final_rate_progress * (lastObject?.GetEndTime() ?? 0); } public virtual void Update(Playfield playfield) diff --git a/osu.Game/Rulesets/Objects/BarLineGenerator.cs b/osu.Game/Rulesets/Objects/BarLineGenerator.cs index 4f9395435e..07f0595015 100644 --- a/osu.Game/Rulesets/Objects/BarLineGenerator.cs +++ b/osu.Game/Rulesets/Objects/BarLineGenerator.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Objects { @@ -28,7 +27,7 @@ namespace osu.Game.Rulesets.Objects return; HitObject lastObject = beatmap.HitObjects.Last(); - double lastHitTime = 1 + ((lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime); + double lastHitTime = 1 + (lastObject.GetEndTime()); var timingPoints = beatmap.ControlPointInfo.TimingPoints; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 35228e9ad1..ed48ddbc2f 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -382,7 +382,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (Result != null && Result.HasResult) { - var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; + var endTime = HitObject.GetEndTime(); if (Result.TimeOffset + endTime > Time.Current) { @@ -460,7 +460,7 @@ namespace osu.Game.Rulesets.Objects.Drawables throw new InvalidOperationException($"{GetType().ReadableName()} applied a {nameof(JudgementResult)} but did not update {nameof(JudgementResult.Type)}."); // Ensure that the judgement is given a valid time offset, because this may not get set by the caller - var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; + var endTime = HitObject.GetEndTime(); Result.TimeOffset = Math.Min(HitObject.HitWindows.WindowFor(HitResult.Miss), Time.Current - endTime); @@ -495,7 +495,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (Judged) return false; - var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; + var endTime = HitObject.GetEndTime(); CheckForResult(userTriggered, Time.Current - endTime); return Judged; diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index ee0705ec5a..1179efaa6e 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -158,4 +158,17 @@ namespace osu.Game.Rulesets.Objects [NotNull] protected virtual HitWindows CreateHitWindows() => new HitWindows(); } + + public static class HitObjectExtensions + { + /// + /// Returns the end time of this object. + /// + /// + /// This returns the where available, falling back to otherwise. + /// + /// The object. + /// The end time of this object. + public static double GetEndTime(this HitObject hitObject) => (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime; + } } diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index f178c01fd6..cfec5d3f49 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -15,7 +15,6 @@ using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Timing; using osu.Game.Rulesets.UI.Scrolling.Algorithms; @@ -112,7 +111,7 @@ namespace osu.Game.Rulesets.UI.Scrolling [BackgroundDependencyLoader] private void load() { - double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; + double lastObjectTime = Objects.LastOrDefault()?.GetEndTime() ?? double.MaxValue; double baseBeatLength = TimingControlPoint.DEFAULT_BEAT_LENGTH; if (RelativeScaleBeatLengths) diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 475b6e7274..9508a2cdf0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components @@ -70,7 +69,7 @@ namespace osu.Game.Screens.Edit.Compose.Components [BackgroundDependencyLoader] private void load() { - StartTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime; + StartTime = hitObject.GetEndTime(); } protected override void LoadComplete() diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index 3df06ebfa8..713d27bd16 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -12,7 +12,6 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Timing; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Play @@ -34,7 +33,8 @@ namespace osu.Game.Screens.Play public override bool HandleNonPositionalInput => AllowSeeking; public override bool HandlePositionalInput => AllowSeeking; - private double lastHitTime => ((objects.Last() as IHasEndTime)?.EndTime ?? objects.Last().StartTime) + 1; + //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). + private double lastHitTime => objects.Last().GetEndTime() + 1; private double firstHitTime => objects.First().StartTime; diff --git a/osu.Game/Screens/Play/SongProgressGraph.cs b/osu.Game/Screens/Play/SongProgressGraph.cs index e480c5b502..e182a1a53d 100644 --- a/osu.Game/Screens/Play/SongProgressGraph.cs +++ b/osu.Game/Screens/Play/SongProgressGraph.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Collections.Generic; using System.Diagnostics; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Play @@ -26,7 +25,7 @@ namespace osu.Game.Screens.Play return; var firstHit = objects.First().StartTime; - var lastHit = objects.Max(o => (o as IHasEndTime)?.EndTime ?? o.StartTime); + var lastHit = objects.Max(o => (o.GetEndTime())); if (lastHit == 0) lastHit = objects.Last().StartTime; @@ -35,7 +34,7 @@ namespace osu.Game.Screens.Play foreach (var h in objects) { - var endTime = (h as IHasEndTime)?.EndTime ?? h.StartTime; + var endTime = h.GetEndTime(); Debug.Assert(endTime >= h.StartTime); From 9fcc56634121d21a5c34e8153d3c4f0bf23d7e1b Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 25 Nov 2019 18:32:41 +0800 Subject: [PATCH 2478/2815] Change the postition of nupkg artifacts. --- Directory.Build.props | 1 + appveyor.yml | 2 +- appveyor_deploy.yml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 838851b712..cee459faf9 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -36,5 +36,6 @@ ppy Pty Ltd Copyright (c) 2019 ppy Pty Ltd osu game + $(MSBuildThisFileDirectory)artifacts\nupkg\ \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index f8469f9f80..82c0a21aff 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -19,7 +19,7 @@ after_build: - ps: dotnet format --dry-run --check - ps: .\InspectCode.ps1 artifacts: - - path: '**\*.nupkg' + - path: 'artifacts\nupkg\*.nupkg' test: assemblies: except: diff --git a/appveyor_deploy.yml b/appveyor_deploy.yml index d8d18028de..c7f762d425 100644 --- a/appveyor_deploy.yml +++ b/appveyor_deploy.yml @@ -16,7 +16,7 @@ build: parallel: true verbosity: minimal artifacts: - - path: '**\*.nupkg' + - path: 'artifacts\nupkg\*.nupkg' deploy: - provider: Environment name: nuget From 1fd5ed3c0f88f284a2155a379fff07275d9e6a7c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Nov 2019 21:35:25 +0900 Subject: [PATCH 2479/2815] Change login placeholder text to ask for username --- osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index a8bbccb168..d8833ee2f4 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -225,7 +225,7 @@ namespace osu.Game.Overlays.Settings.Sections.General { username = new OsuTextBox { - PlaceholderText = "email address", + PlaceholderText = "username", RelativeSizeAxes = Axes.X, Text = api?.ProvidedUsername ?? string.Empty, TabbableContentContainer = this @@ -239,7 +239,7 @@ namespace osu.Game.Overlays.Settings.Sections.General }, new SettingsCheckbox { - LabelText = "Remember email address", + LabelText = "Remember username", Bindable = config.GetBindable(OsuSetting.SaveUsername), }, new SettingsCheckbox From fbebbab5dbb4db0faaf633accc82e99f29982c5a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Nov 2019 22:37:07 +0900 Subject: [PATCH 2480/2815] Remove excess parenthesis --- osu.Game/Rulesets/Objects/BarLineGenerator.cs | 2 +- osu.Game/Screens/Play/SongProgressGraph.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/BarLineGenerator.cs b/osu.Game/Rulesets/Objects/BarLineGenerator.cs index 07f0595015..99672240e2 100644 --- a/osu.Game/Rulesets/Objects/BarLineGenerator.cs +++ b/osu.Game/Rulesets/Objects/BarLineGenerator.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Objects return; HitObject lastObject = beatmap.HitObjects.Last(); - double lastHitTime = 1 + (lastObject.GetEndTime()); + double lastHitTime = 1 + lastObject.GetEndTime(); var timingPoints = beatmap.ControlPointInfo.TimingPoints; diff --git a/osu.Game/Screens/Play/SongProgressGraph.cs b/osu.Game/Screens/Play/SongProgressGraph.cs index e182a1a53d..78eb456bb5 100644 --- a/osu.Game/Screens/Play/SongProgressGraph.cs +++ b/osu.Game/Screens/Play/SongProgressGraph.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Play return; var firstHit = objects.First().StartTime; - var lastHit = objects.Max(o => (o.GetEndTime())); + var lastHit = objects.Max(o => o.GetEndTime()); if (lastHit == 0) lastHit = objects.Last().StartTime; From 41fd3f67494c1131affda5c9e9c62da994e4cde1 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 25 Nov 2019 23:42:59 +0800 Subject: [PATCH 2481/2815] Correct BannedSymbols.txt. --- CodeAnalysis/BannedSymbols.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index c54c639b4f..5d86b99fd7 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -2,4 +2,4 @@ M:System.Object.Equals(System.Object,System.Object)~System.Boolean;Don't use obj M:System.Object.Equals(System.Object)~System.Boolean;Don't use object.Equals. Use IEquatable or EqualityComparer.Default instead. M:System.ValueType.Equals(System.Object)~System.Boolean;Don't use object.Equals(Fallbacks to ValueType). Use IEquatable or EqualityComparer.Default instead. T:System.IComparable;Don't use non-generic IComparable. Use generic version instead. -T:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText. +M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText. From 8928f05f4f262c9fdcdb596e5debb95f05d21a5f Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 26 Nov 2019 01:39:48 +0800 Subject: [PATCH 2482/2815] Temporarily remove deepequal test from Android to make CI green. --- osu.Game.Tests.Android/osu.Game.Tests.Android.csproj | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj index c2dd194e09..c44ed69c4d 100644 --- a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj +++ b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj @@ -24,6 +24,7 @@ %(RecursiveDir)%(Filename)%(Extension) + %(RecursiveDir)%(Filename)%(Extension) @@ -68,10 +69,5 @@ osu.Game - - - 2.0.0 - - \ No newline at end of file From 8e25214eeea21dca5ea24727a968ba6b91223e9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 25 Nov 2019 21:21:56 +0100 Subject: [PATCH 2483/2815] Revert Android to using native network components This reverts commit 9acfc2587aa6a8dcd34a2535f3fc4bcf928bac1b ("Switch android to using managed network components") due to it causing crashes on every online call. The managed legacy TLS provider is slated for removal in later mono releases as it stands anyway. --- osu.Android.props | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 0da3e66236..f2d2af5678 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -16,8 +16,6 @@ armeabi-v7a;x86;arm64-v8a true cjk,mideast,other,rare,west - System.Net.Http.HttpClientHandler - legacy SdkOnly prompt From a453129d448ae718973248cd3eed11727425f7b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2019 15:13:50 +0900 Subject: [PATCH 2484/2815] Fix nullref in tests --- osu.Game/Screens/Play/BreakOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 28691c0d09..9ba18224ce 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -160,7 +160,7 @@ namespace osu.Game.Screens.Play var currentBreak = breaks[CurrentBreakIndex]; - isBreakTime.Value = (currentBreak.HasEffect && currentBreak.Contains(time)) || (time < gameplayStartTime || scoreProcessor.HasCompleted); + isBreakTime.Value = (currentBreak.HasEffect && currentBreak.Contains(time)) || (time < gameplayStartTime || scoreProcessor?.HasCompleted == true); } private void initializeBreaks() From 77ce9642ba616cb668ca8d9259c3b5a4a4792bfe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2019 15:21:43 +0900 Subject: [PATCH 2485/2815] Add test coverage and simplify break time updating --- .../Visual/Gameplay/TestSceneBreakOverlay.cs | 13 ++++++ osu.Game/Screens/Play/BreakOverlay.cs | 41 +++++++++++-------- 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs index 879e15c548..19dce303ea 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs @@ -95,6 +95,19 @@ namespace osu.Game.Tests.Visual.Gameplay seekAndAssertBreak("seek to break after end", testBreaks[1].EndTime + 500, false); } + [TestCase(true)] + [TestCase(false)] + public void TestBeforeGameplayStart(bool withBreaks) + { + setClock(true); + + if (withBreaks) + loadBreaksStep("multiple breaks", testBreaks); + + seekAndAssertBreak("seek to break intro time", -100, true); + seekAndAssertBreak("seek to break intro time", 0, false); + } + private void addShowBreakStep(double seconds) { AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = new List diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 9ba18224ce..ee8be87352 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -140,27 +140,34 @@ namespace osu.Game.Screens.Play updateBreakTimeBindable(); } - private void updateBreakTimeBindable() + private void updateBreakTimeBindable() => + isBreakTime.Value = getCurrentBreak()?.HasEffect == true + || Clock.CurrentTime < gameplayStartTime + || scoreProcessor?.HasCompleted == true; + + private BreakPeriod getCurrentBreak() { - if (breaks == null || breaks.Count == 0) - return; - - var time = Clock.CurrentTime; - - if (time > breaks[CurrentBreakIndex].EndTime) + if (breaks?.Count > 0) { - while (time > breaks[CurrentBreakIndex].EndTime && CurrentBreakIndex < breaks.Count - 1) - CurrentBreakIndex++; - } - else - { - while (time < breaks[CurrentBreakIndex].StartTime && CurrentBreakIndex > 0) - CurrentBreakIndex--; + 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; } - var currentBreak = breaks[CurrentBreakIndex]; - - isBreakTime.Value = (currentBreak.HasEffect && currentBreak.Contains(time)) || (time < gameplayStartTime || scoreProcessor?.HasCompleted == true); + return null; } private void initializeBreaks() From ab017ee648df15b51fe635e6c5cec70d96f1635e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2019 15:37:13 +0900 Subject: [PATCH 2486/2815] 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 f2d2af5678..b80881e2f6 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -53,6 +53,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 449b4dc4e3..2f633bdbc1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -21,7 +21,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index ff6839f5ca..38134074f2 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -73,7 +73,7 @@ - + @@ -81,7 +81,7 @@ - + From aa1545c9388a937db3ee4d1b4651d8cd0cf55747 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 26 Nov 2019 15:52:22 +0800 Subject: [PATCH 2487/2815] Refine words in README. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7bbd43c961..7adc8dc973 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ If you are not interested in debugging osu!, you can add `-c Release` to gain pe If the build fails, try to restore NuGet packages with `dotnet restore`. -_Due to historical feature gap between .NET Core and Xamarin, running `dotnet` CLI from the root directory will probably not work. You can try to run the CLI towards single .NET Core project, or helper project at `build/Desktop.proj`. Or you can get help from your IDE of choice to run the correct commands. We are keeping configuration files in the repository for the popular IDEs._ +_Due to historical feature gap between .NET Core and Xamarin, running `dotnet` CLI from the root directory will not work for most commands. This can be resolved by specifying a target `.csproj` or the helper project at `build/Desktop.proj`. Configurations have been provided to work around this issue for all supported IDEs mentioned above._ ### Testing with resource/framework modifications @@ -82,7 +82,7 @@ Sometimes it may be necessary to cross-test changes in [osu-resources](https://g ### Code analysis -Before committing your code, please run code formatter. It can be achieved with command `dotnet format`, or `Format code` command in your IDE. (Defaults to `Ctrl+K,Ctrl+D` in Visual Studio, `Shift+Alt+F` in Visual Studio Code, or `Ctrl+Alt+L` in Rider) +Before committing your code, please run a code formatter. It can be achieved with `dotnet format` in command line, or `Format code` command in your IDE. We have adopted some cross-platform, compiler integrated analyzers. They can provide warnings when you are editing, building inside IDE or from command line, as-if they are provided by the compiler itself. From deaeda7348c8cfcf67083dd2bd7631cbdfb00610 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 26 Nov 2019 16:40:48 +0800 Subject: [PATCH 2488/2815] Change packing to be runned by AppVeyor. --- Directory.Build.props | 2 +- appveyor.yml | 3 +-- appveyor_deploy.yml | 3 +-- osu.Game/osu.Game.csproj | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index cee459faf9..693e132c40 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -28,6 +28,7 @@ $(NoWarn);NU1701 + false ppy Pty Ltd MIT https://github.com/ppy/osu @@ -36,6 +37,5 @@ ppy Pty Ltd Copyright (c) 2019 ppy Pty Ltd osu game - $(MSBuildThisFileDirectory)artifacts\nupkg\ \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index 82c0a21aff..a4a0cedc66 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,12 +14,11 @@ build: project: osu.sln parallel: true verbosity: minimal + publish_nuget: true after_build: - ps: dotnet tool restore - ps: dotnet format --dry-run --check - ps: .\InspectCode.ps1 -artifacts: - - path: 'artifacts\nupkg\*.nupkg' test: assemblies: except: diff --git a/appveyor_deploy.yml b/appveyor_deploy.yml index c7f762d425..bb4482f501 100644 --- a/appveyor_deploy.yml +++ b/appveyor_deploy.yml @@ -15,8 +15,7 @@ build: project: build\Desktop.proj # Skipping Xamarin Release that's slow and covered by fastlane parallel: true verbosity: minimal -artifacts: - - path: 'artifacts\nupkg\*.nupkg' + publish_nuget: true deploy: - provider: Environment name: nuget diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 6c25e9e5fe..2e456f1fa1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -9,7 +9,7 @@ ppy.osu.Game 0.0.0 icon.png - true + true From 9425e80a5d23f29c45b91a6ef9e009cf61a17faf Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 26 Nov 2019 18:34:23 +0800 Subject: [PATCH 2489/2815] Unify to use double in performance calculators. --- .../Difficulty/CatchPerformanceCalculator.cs | 36 +++++---- .../Difficulty/OsuPerformanceCalculator.cs | 76 +++++++++---------- .../Difficulty/TaikoPerformanceCalculator.cs | 2 +- 3 files changed, 56 insertions(+), 58 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index b876c774b2..3c237c86be 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -47,55 +47,53 @@ namespace osu.Game.Rulesets.Catch.Difficulty return 0; // We are heavily relying on aim in catch the beat - double value = Math.Pow(5.0f * Math.Max(1.0f, Attributes.StarRating / 0.0049f) - 4.0f, 2.0f) / 100000.0f; + double value = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0049) - 4.0, 2.0) / 100000.0; // Longer maps are worth more. "Longer" means how many hits there are which can contribute to combo int numTotalHits = totalComboHits(); // Longer maps are worth more - float lengthBonus = - 0.95f + 0.4f * Math.Min(1.0f, numTotalHits / 3000.0f) + - (numTotalHits > 3000 ? MathF.Log10(numTotalHits / 3000.0f) * 0.5f : 0.0f); + double lengthBonus = + 0.95 + 0.4 * Math.Min(1.0, numTotalHits / 3000.0) + + (numTotalHits > 3000 ? Math.Log10(numTotalHits / 3000.0) * 0.5 : 0.0); // Longer maps are worth more value *= lengthBonus; // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available - value *= Math.Pow(0.97f, misses); + value *= Math.Pow(0.97, misses); // Combo scaling - float beatmapMaxCombo = Attributes.MaxCombo; - if (beatmapMaxCombo > 0) - value *= Math.Min(Math.Pow(Attributes.MaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f); + if (Attributes.MaxCombo > 0) + value *= Math.Min(Math.Pow(Attributes.MaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0); - float approachRate = (float)Attributes.ApproachRate; - float approachRateFactor = 1.0f; - if (approachRate > 9.0f) - approachRateFactor += 0.1f * (approachRate - 9.0f); // 10% for each AR above 9 - else if (approachRate < 8.0f) - approachRateFactor += 0.025f * (8.0f - approachRate); // 2.5% for each AR below 8 + double approachRateFactor = 1.0; + if (Attributes.ApproachRate > 9.0) + approachRateFactor += 0.1 * (Attributes.ApproachRate - 9.0); // 10% for each AR above 9 + else if (Attributes.ApproachRate < 8.0) + approachRateFactor += 0.025 * (8.0 - Attributes.ApproachRate); // 2.5% for each AR below 8 value *= approachRateFactor; if (mods.Any(m => m is ModHidden)) // Hiddens gives nothing on max approach rate, and more the lower it is - value *= 1.05f + 0.075f * (10.0f - Math.Min(10.0f, approachRate)); // 7.5% for each AR below 10 + value *= 1.05 + 0.075 * (10.0 - Math.Min(10.0, Attributes.ApproachRate)); // 7.5% for each AR below 10 if (mods.Any(m => m is ModFlashlight)) // Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps. - value *= 1.35f * lengthBonus; + value *= 1.35 * lengthBonus; // Scale the aim value with accuracy _slightly_ - value *= Math.Pow(accuracy(), 5.5f); + value *= Math.Pow(accuracy(), 5.5); // Custom multipliers for NoFail. SpunOut is not applicable. if (mods.Any(m => m is ModNoFail)) - value *= 0.90f; + value *= 0.90; return value; } - private float accuracy() => totalHits() == 0 ? 0 : Math.Clamp((float)totalSuccessfulHits() / totalHits(), 0f, 1f); + private float accuracy() => totalHits() == 0 ? 0 : Math.Clamp((float)totalSuccessfulHits() / totalHits(), 0, 1); private int totalHits() => tinyTicksHit + ticksHit + fruitsHit + misses + tinyTicksMissed; private int totalSuccessfulHits() => tinyTicksHit + ticksHit + fruitsHit; private int totalComboHits() => misses + ticksHit + fruitsHit; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 093081b6a1..05c78cbc95 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -55,22 +55,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty return 0; // Custom multipliers for NoFail and SpunOut. - double multiplier = 1.12f; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things + double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things if (mods.Any(m => m is OsuModNoFail)) - multiplier *= 0.90f; + multiplier *= 0.90; if (mods.Any(m => m is OsuModSpunOut)) - multiplier *= 0.95f; + multiplier *= 0.95; double aimValue = computeAimValue(); double speedValue = computeSpeedValue(); double accuracyValue = computeAccuracyValue(); double totalValue = Math.Pow( - Math.Pow(aimValue, 1.1f) + - Math.Pow(speedValue, 1.1f) + - Math.Pow(accuracyValue, 1.1f), 1.0f / 1.1f + Math.Pow(aimValue, 1.1) + + Math.Pow(speedValue, 1.1) + + Math.Pow(accuracyValue, 1.1), 1.0 / 1.1 ) * multiplier; if (categoryRatings != null) @@ -93,82 +93,82 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (mods.Any(m => m is OsuModTouchDevice)) rawAim = Math.Pow(rawAim, 0.8); - double aimValue = Math.Pow(5.0f * Math.Max(1.0f, rawAim / 0.0675f) - 4.0f, 3.0f) / 100000.0f; + double aimValue = Math.Pow(5.0 * Math.Max(1.0, rawAim / 0.0675) - 4.0, 3.0) / 100000.0; // Longer maps are worth more - double lengthBonus = 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) + - (totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f); + double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); aimValue *= lengthBonus; // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available - aimValue *= Math.Pow(0.97f, countMiss); + aimValue *= Math.Pow(0.97, countMiss); // Combo scaling if (beatmapMaxCombo > 0) - aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f); + aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(beatmapMaxCombo, 0.8), 1.0); - double approachRateFactor = 1.0f; + double approachRateFactor = 1.0; - if (Attributes.ApproachRate > 10.33f) - approachRateFactor += 0.3f * (Attributes.ApproachRate - 10.33f); - else if (Attributes.ApproachRate < 8.0f) + if (Attributes.ApproachRate > 10.33) + approachRateFactor += 0.3 * (Attributes.ApproachRate - 10.33); + else if (Attributes.ApproachRate < 8.0) { - approachRateFactor += 0.01f * (8.0f - Attributes.ApproachRate); + approachRateFactor += 0.01 * (8.0 - Attributes.ApproachRate); } aimValue *= approachRateFactor; // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. if (mods.Any(h => h is OsuModHidden)) - aimValue *= 1.0f + 0.04f * (12.0f - Attributes.ApproachRate); + aimValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); if (mods.Any(h => h is OsuModFlashlight)) { // Apply object-based bonus for flashlight. - aimValue *= 1.0f + 0.35f * Math.Min(1.0f, totalHits / 200.0f) + + aimValue *= 1.0 + 0.35 * Math.Min(1.0, totalHits / 200.0) + (totalHits > 200 - ? 0.3f * Math.Min(1.0f, (totalHits - 200) / 300.0f) + - (totalHits > 500 ? (totalHits - 500) / 1200.0f : 0.0f) - : 0.0f); + ? 0.3 * Math.Min(1.0, (totalHits - 200) / 300.0) + + (totalHits > 500 ? (totalHits - 500) / 1200.0 : 0.0) + : 0.0); } // Scale the aim value with accuracy _slightly_ - aimValue *= 0.5f + accuracy / 2.0f; + aimValue *= 0.5 + accuracy / 2.0; // It is important to also consider accuracy difficulty when doing that - aimValue *= 0.98f + Math.Pow(Attributes.OverallDifficulty, 2) / 2500; + aimValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500; return aimValue; } private double computeSpeedValue() { - double speedValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes.SpeedStrain / 0.0675f) - 4.0f, 3.0f) / 100000.0f; + double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedStrain / 0.0675) - 4.0, 3.0) / 100000.0; // Longer maps are worth more - speedValue *= 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) + - (totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f); + speedValue *= 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available - speedValue *= Math.Pow(0.97f, countMiss); + speedValue *= Math.Pow(0.97, countMiss); // Combo scaling if (beatmapMaxCombo > 0) - speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f); + speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(beatmapMaxCombo, 0.8), 1.0); - double approachRateFactor = 1.0f; - if (Attributes.ApproachRate > 10.33f) - approachRateFactor += 0.3f * (Attributes.ApproachRate - 10.33f); + double approachRateFactor = 1.0; + if (Attributes.ApproachRate > 10.33) + approachRateFactor += 0.3 * (Attributes.ApproachRate - 10.33); speedValue *= approachRateFactor; if (mods.Any(m => m is OsuModHidden)) - speedValue *= 1.0f + 0.04f * (12.0f - Attributes.ApproachRate); + speedValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); // Scale the speed value with accuracy _slightly_ - speedValue *= 0.02f + accuracy; + speedValue *= 0.02 + accuracy; // It is important to also consider accuracy difficulty when doing that - speedValue *= 0.96f + Math.Pow(Attributes.OverallDifficulty, 2) / 1600; + speedValue *= 0.96 + Math.Pow(Attributes.OverallDifficulty, 2) / 1600; return speedValue; } @@ -190,15 +190,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Lots of arbitrary values from testing. // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution - double accuracyValue = Math.Pow(1.52163f, Attributes.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83f; + double accuracyValue = Math.Pow(1.52163, Attributes.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83; // Bonus for many hitcircles - it's harder to keep good accuracy up for longer - accuracyValue *= Math.Min(1.15f, Math.Pow(amountHitObjectsWithAccuracy / 1000.0f, 0.3f)); + accuracyValue *= Math.Min(1.15, Math.Pow(amountHitObjectsWithAccuracy / 1000.0, 0.3)); if (mods.Any(m => m is OsuModHidden)) - accuracyValue *= 1.08f; + accuracyValue *= 1.08; if (mods.Any(m => m is OsuModFlashlight)) - accuracyValue *= 1.02f; + accuracyValue *= 1.02; return accuracyValue; } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 70249db0f6..c3638253e4 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty double strainValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0075) - 4.0, 2.0) / 100000.0; // Longer maps are worth more - double lengthBonus = 1 + 0.1f * Math.Min(1.0, totalHits / 1500.0); + double lengthBonus = 1 + 0.1 * Math.Min(1.0, totalHits / 1500.0); strainValue *= lengthBonus; // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available From eba8657d38b6cd26256b3fc461c87eaca3bc5bdd Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 26 Nov 2019 18:41:19 +0800 Subject: [PATCH 2490/2815] Remove newly introduced redundant `this`. --- osu.Game/Screens/Play/GameplayClockContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 0b8bab3ffc..ff78d85bf0 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Play this.beatmap = beatmap; this.mods = mods; this.gameplayStartTime = gameplayStartTime; - this.firstHitObjectTime = beatmap.Beatmap.HitObjects.First().StartTime; + firstHitObjectTime = beatmap.Beatmap.HitObjects.First().StartTime; RelativeSizeAxes = Axes.Both; From d1e3718038f806afff93aaebd9bf8a1481312f34 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Nov 2019 15:04:09 +0900 Subject: [PATCH 2491/2815] Reduce the scale of background blurs --- osu.Game/Graphics/Backgrounds/Background.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Backgrounds/Background.cs b/osu.Game/Graphics/Backgrounds/Background.cs index 0f923c3a28..21fca5d5da 100644 --- a/osu.Game/Graphics/Backgrounds/Background.cs +++ b/osu.Game/Graphics/Backgrounds/Background.cs @@ -16,6 +16,8 @@ namespace osu.Game.Graphics.Backgrounds /// public class Background : CompositeDrawable { + private const float blur_scale = 0.5f; + public readonly Sprite Sprite; private readonly string textureName; @@ -64,7 +66,10 @@ namespace osu.Game.Graphics.Backgrounds }); } - bufferedContainer?.BlurTo(newBlurSigma, duration, easing); + if (bufferedContainer != null) + bufferedContainer.FrameBufferScale = newBlurSigma == Vector2.Zero ? Vector2.One : new Vector2(blur_scale); + + bufferedContainer?.BlurTo(newBlurSigma * blur_scale, duration, easing); } } } From fe0785657cc8cc86df8a32f4f04fdcda3b7068cb Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 27 Nov 2019 19:37:11 +0800 Subject: [PATCH 2492/2815] Turn WarningAsError to always on. --- Directory.Build.props | 1 + 1 file changed, 1 insertion(+) diff --git a/Directory.Build.props b/Directory.Build.props index 693e132c40..572d97fafa 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,6 +2,7 @@ 7.3 + true $(MSBuildThisFileDirectory)app.manifest From b126700f01b206b4fc6fb97223b0c1ab8fc97913 Mon Sep 17 00:00:00 2001 From: phosphene47 Date: Wed, 27 Nov 2019 22:47:00 +1100 Subject: [PATCH 2493/2815] Debounce hover sounds --- .../UserInterface/HoverClickSounds.cs | 15 +++++++++++++- .../Graphics/UserInterface/HoverSounds.cs | 20 ++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs index 4f678b7218..b1a58b5d57 100644 --- a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs @@ -1,12 +1,14 @@ // 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.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Extensions; using osu.Framework.Input.Events; +using osu.Framework.Threading; using osuTK.Input; namespace osu.Game.Graphics.UserInterface @@ -34,10 +36,21 @@ namespace osu.Game.Graphics.UserInterface this.buttons = buttons ?? new[] { MouseButton.Left }; } + private ScheduledDelegate playDelegate; + protected override bool OnClick(ClickEvent e) { + playDelegate?.Cancel(); + if (buttons.Contains(e.Button) && Contains(e.ScreenSpaceMousePosition)) - sampleClick?.Play(); + { + var debounceMs = (int)DebounceTime.TotalMilliseconds; + + if (debounceMs == 0) + sampleClick?.Play(); + else + playDelegate = Scheduler.AddDelayed(() => sampleClick?.Play(), debounceMs); + } return base.OnClick(e); } diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs index f1ac8ced6e..f4258f2b6d 100644 --- a/osu.Game/Graphics/UserInterface/HoverSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverSounds.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.ComponentModel; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -9,6 +10,7 @@ using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; +using osu.Framework.Threading; namespace osu.Game.Graphics.UserInterface { @@ -20,6 +22,12 @@ namespace osu.Game.Graphics.UserInterface { private SampleChannel sampleHover; + /// + /// Length of debounce for sound playback. + /// Set this to to disable debouncing. + /// + public TimeSpan DebounceTime { get; set; } = TimeSpan.FromMilliseconds(50); + protected readonly HoverSampleSet SampleSet; public HoverSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal) @@ -28,9 +36,19 @@ namespace osu.Game.Graphics.UserInterface RelativeSizeAxes = Axes.Both; } + private ScheduledDelegate playDelegate; + protected override bool OnHover(HoverEvent e) { - sampleHover?.Play(); + playDelegate?.Cancel(); + + var debounceMs = (int)DebounceTime.TotalMilliseconds; + + if (debounceMs == 0) + sampleHover?.Play(); + else + playDelegate = Scheduler.AddDelayed(() => sampleHover?.Play(), debounceMs); + return base.OnHover(e); } From 037d927e4518ee16925ff08e4eb037f6b8b2d6dc Mon Sep 17 00:00:00 2001 From: phosphene47 Date: Wed, 27 Nov 2019 22:51:27 +1100 Subject: [PATCH 2494/2815] TimeSpans can be negative! --- osu.Game/Graphics/UserInterface/HoverClickSounds.cs | 2 +- osu.Game/Graphics/UserInterface/HoverSounds.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs index b1a58b5d57..fcb8f8d6bd 100644 --- a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs @@ -46,7 +46,7 @@ namespace osu.Game.Graphics.UserInterface { var debounceMs = (int)DebounceTime.TotalMilliseconds; - if (debounceMs == 0) + if (debounceMs <= 0) sampleClick?.Play(); else playDelegate = Scheduler.AddDelayed(() => sampleClick?.Play(), debounceMs); diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs index f4258f2b6d..2223ac21db 100644 --- a/osu.Game/Graphics/UserInterface/HoverSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverSounds.cs @@ -44,7 +44,7 @@ namespace osu.Game.Graphics.UserInterface var debounceMs = (int)DebounceTime.TotalMilliseconds; - if (debounceMs == 0) + if (debounceMs <= 0) sampleHover?.Play(); else playDelegate = Scheduler.AddDelayed(() => sampleHover?.Play(), debounceMs); From d4afea0b5e5197d81999ee7edbf774a5946e0319 Mon Sep 17 00:00:00 2001 From: phosphene47 Date: Wed, 27 Nov 2019 23:06:07 +1100 Subject: [PATCH 2495/2815] Use double instead of TimeSpan --- osu.Game/Graphics/UserInterface/HoverClickSounds.cs | 7 ++----- osu.Game/Graphics/UserInterface/HoverSounds.cs | 12 ++++-------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs index fcb8f8d6bd..a8e074d2ef 100644 --- a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverClickSounds.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.Allocation; using osu.Framework.Audio; @@ -44,12 +43,10 @@ namespace osu.Game.Graphics.UserInterface if (buttons.Contains(e.Button) && Contains(e.ScreenSpaceMousePosition)) { - var debounceMs = (int)DebounceTime.TotalMilliseconds; - - if (debounceMs <= 0) + if (DebounceTime <= 0) sampleClick?.Play(); else - playDelegate = Scheduler.AddDelayed(() => sampleClick?.Play(), debounceMs); + playDelegate = Scheduler.AddDelayed(() => sampleClick?.Play(), DebounceTime); } return base.OnClick(e); diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs index 2223ac21db..c98d50efff 100644 --- a/osu.Game/Graphics/UserInterface/HoverSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverSounds.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.ComponentModel; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -23,10 +22,9 @@ namespace osu.Game.Graphics.UserInterface private SampleChannel sampleHover; /// - /// Length of debounce for sound playback. - /// Set this to to disable debouncing. + /// Length of debounce for sound playback, in milliseconds. Default is 50ms. /// - public TimeSpan DebounceTime { get; set; } = TimeSpan.FromMilliseconds(50); + public double DebounceTime { get; set; } = 50; protected readonly HoverSampleSet SampleSet; @@ -42,12 +40,10 @@ namespace osu.Game.Graphics.UserInterface { playDelegate?.Cancel(); - var debounceMs = (int)DebounceTime.TotalMilliseconds; - - if (debounceMs <= 0) + if (DebounceTime <= 0) sampleHover?.Play(); else - playDelegate = Scheduler.AddDelayed(() => sampleHover?.Play(), debounceMs); + playDelegate = Scheduler.AddDelayed(() => sampleHover?.Play(), DebounceTime); return base.OnHover(e); } From 786fb9ede350164c1e9e79c760e5a472d3441b25 Mon Sep 17 00:00:00 2001 From: phosphene47 Date: Thu, 28 Nov 2019 00:44:01 +1100 Subject: [PATCH 2496/2815] Split click and hover and disable click debounce --- osu.Game/Graphics/UserInterface/HoverClickSounds.cs | 9 +++++++-- osu.Game/Graphics/UserInterface/HoverSounds.cs | 8 ++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs index a8e074d2ef..61cacee45f 100644 --- a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs @@ -21,6 +21,11 @@ namespace osu.Game.Graphics.UserInterface private SampleChannel sampleClick; private readonly MouseButton[] buttons; + /// + /// Length of debounce for click sound playback, in milliseconds. Default is 0ms. + /// + public double ClickDebounceTime { get; set; } + /// /// a container which plays sounds on hover and click for any specified s. /// @@ -43,10 +48,10 @@ namespace osu.Game.Graphics.UserInterface if (buttons.Contains(e.Button) && Contains(e.ScreenSpaceMousePosition)) { - if (DebounceTime <= 0) + if (ClickDebounceTime <= 0) sampleClick?.Play(); else - playDelegate = Scheduler.AddDelayed(() => sampleClick?.Play(), DebounceTime); + playDelegate = Scheduler.AddDelayed(() => sampleClick?.Play(), ClickDebounceTime); } return base.OnClick(e); diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs index c98d50efff..fcd8940348 100644 --- a/osu.Game/Graphics/UserInterface/HoverSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverSounds.cs @@ -22,9 +22,9 @@ namespace osu.Game.Graphics.UserInterface private SampleChannel sampleHover; /// - /// Length of debounce for sound playback, in milliseconds. Default is 50ms. + /// Length of debounce for hover sound playback, in milliseconds. Default is 50ms. /// - public double DebounceTime { get; set; } = 50; + public double HoverDebounceTime { get; set; } = 50; protected readonly HoverSampleSet SampleSet; @@ -40,10 +40,10 @@ namespace osu.Game.Graphics.UserInterface { playDelegate?.Cancel(); - if (DebounceTime <= 0) + if (HoverDebounceTime <= 0) sampleHover?.Play(); else - playDelegate = Scheduler.AddDelayed(() => sampleHover?.Play(), DebounceTime); + playDelegate = Scheduler.AddDelayed(() => sampleHover?.Play(), HoverDebounceTime); return base.OnHover(e); } From 2865f320524dd149de900aa09516075df17162d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Nov 2019 00:13:44 +0900 Subject: [PATCH 2497/2815] Fix nullref on clicking links in tests --- osu.Game/Graphics/Containers/LinkFlowContainer.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 61391b7102..2bbac92f7f 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -19,14 +19,8 @@ namespace osu.Game.Graphics.Containers { } - private OsuGame game; - - [BackgroundDependencyLoader(true)] - private void load(OsuGame game) - { - // will be null in tests - this.game = game; - } + [Resolved(CanBeNull = true)] + private OsuGame game { get; set; } public void AddLinks(string text, List links) { @@ -82,7 +76,7 @@ namespace osu.Game.Graphics.Containers if (action != null) action(); else - game.HandleLink(link); + game?.HandleLink(link); }, }); } From e1302d84ddd51f71bcb3b8e85c241323a9552e8c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Nov 2019 00:26:10 +0900 Subject: [PATCH 2498/2815] Use string.Empty --- osu.Game/Overlays/Rankings/Tables/CountriesTable.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs index 386225a606..ae686cd4ad 100644 --- a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs @@ -21,8 +21,8 @@ namespace osu.Game.Overlays.Rankings.Tables protected override TableColumn[] CreateHeaders() => new[] { - new TableColumn("", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 50)), // place - new TableColumn("", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed)), // flag and country name + new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.Absolute, 50)), // place + new TableColumn(string.Empty, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed)), // flag and country name new TableColumn("Active Users", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new TableColumn("Ranked Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), From 724fcecbc572f803478c12b9132296f28425fb01 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Nov 2019 00:32:31 +0900 Subject: [PATCH 2499/2815] Fix test scene not actually covering the class it's testing --- osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs index 49656900cc..2c76b1937b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs @@ -29,6 +29,7 @@ namespace osu.Game.Tests.Visual.Online typeof(ScoresTable), typeof(CountriesTable), typeof(TableRowBackground), + typeof(RankingsTable<>) }; [Resolved] From bb3152ac8b58def442b9a3ea996ea73c9d694bbf Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 27 Nov 2019 21:35:03 +0300 Subject: [PATCH 2500/2815] Use string.empty for empty headers --- osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs | 4 ++-- osu.Game/Overlays/Rankings/Tables/ScoresTable.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs index 11357a82a1..2a658ec0de 100644 --- a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs @@ -22,8 +22,8 @@ namespace osu.Game.Overlays.Rankings.Tables protected override TableColumn[] CreateHeaders() => new[] { - new TableColumn("", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 50)), // place - new TableColumn("", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed)), // flag and username + new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.Absolute, 50)), // place + new TableColumn(string.Empty, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed)), // flag and username new TableColumn("Accuracy", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new TableColumn("Performance", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), diff --git a/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs b/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs index 0a8ec50e30..b644edefd1 100644 --- a/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs @@ -22,8 +22,8 @@ namespace osu.Game.Overlays.Rankings.Tables protected override TableColumn[] CreateHeaders() => new[] { - new TableColumn("", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 50)), // place - new TableColumn("", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed)), // flag and username + new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.Absolute, 50)), // place + new TableColumn(string.Empty, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed)), // flag and username new TableColumn("Accuracy", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new TableColumn("Total Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), From 14c471fe5d8a4391be57546530d916338e3de71f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 27 Nov 2019 21:37:34 +0300 Subject: [PATCH 2501/2815] Move Rankings property below ctor --- .../Overlays/Rankings/Tables/RankingsTable.cs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index 07e2257e8f..58363929b3 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -23,23 +23,6 @@ namespace osu.Game.Overlays.Rankings.Tables private readonly int page; private readonly FillFlowContainer backgroundFlow; - public IReadOnlyList Rankings - { - set - { - Content = null; - backgroundFlow.Clear(); - - if (value?.Any() != true) - return; - - value.ForEach(_ => backgroundFlow.Add(new TableRowBackground())); - - Columns = CreateHeaders(); - Content = value.Select((s, i) => CreateContent(page * items_per_page - (items_per_page - i), s)).ToArray().ToRectangular(); - } - } - protected RankingsTable(int page) { this.page = page; @@ -58,6 +41,23 @@ namespace osu.Game.Overlays.Rankings.Tables }); } + public IReadOnlyList Rankings + { + set + { + Content = null; + backgroundFlow.Clear(); + + if (value?.Any() != true) + return; + + value.ForEach(_ => backgroundFlow.Add(new TableRowBackground())); + + Columns = CreateHeaders(); + Content = value.Select((s, i) => CreateContent(page * items_per_page - (items_per_page - i), s)).ToArray().ToRectangular(); + } + } + protected abstract TableColumn[] CreateHeaders(); protected abstract Drawable[] CreateContent(int index, TModel item); From 4cf59680263a2aa6f2883426a00d385cd72b6500 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 27 Nov 2019 21:39:38 +0300 Subject: [PATCH 2502/2815] Simplify place calculation --- osu.Game/Overlays/Rankings/Tables/RankingsTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index 58363929b3..98a6cc1caf 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -54,7 +54,7 @@ namespace osu.Game.Overlays.Rankings.Tables value.ForEach(_ => backgroundFlow.Add(new TableRowBackground())); Columns = CreateHeaders(); - Content = value.Select((s, i) => CreateContent(page * items_per_page - (items_per_page - i), s)).ToArray().ToRectangular(); + Content = value.Select((s, i) => CreateContent((page - 1) * items_per_page + i, s)).ToArray().ToRectangular(); } } From 2135a7fd7b32b8763cbe950a32705145f144ed54 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 27 Nov 2019 21:46:41 +0300 Subject: [PATCH 2503/2815] Don't use repetitive headers in each class --- osu.Game/Overlays/Rankings/Tables/CountriesTable.cs | 4 +--- osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs | 4 +--- osu.Game/Overlays/Rankings/Tables/RankingsTable.cs | 10 ++++++++-- osu.Game/Overlays/Rankings/Tables/ScoresTable.cs | 4 +--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs index ae686cd4ad..f5e95f316e 100644 --- a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs @@ -19,10 +19,8 @@ namespace osu.Game.Overlays.Rankings.Tables { } - protected override TableColumn[] CreateHeaders() => new[] + protected override TableColumn[] CreateAdditionalHeaders() => new[] { - new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.Absolute, 50)), // place - new TableColumn(string.Empty, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed)), // flag and country name new TableColumn("Active Users", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new TableColumn("Ranked Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), diff --git a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs index 2a658ec0de..7d4bd71526 100644 --- a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs @@ -20,10 +20,8 @@ namespace osu.Game.Overlays.Rankings.Tables { } - protected override TableColumn[] CreateHeaders() => new[] + protected override TableColumn[] CreateAdditionalHeaders() => new[] { - new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.Absolute, 50)), // place - new TableColumn(string.Empty, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed)), // flag and username new TableColumn("Accuracy", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new TableColumn("Performance", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index 98a6cc1caf..530295b039 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -53,12 +53,18 @@ namespace osu.Game.Overlays.Rankings.Tables value.ForEach(_ => backgroundFlow.Add(new TableRowBackground())); - Columns = CreateHeaders(); + Columns = mainHeaders.Concat(CreateAdditionalHeaders()).ToArray(); Content = value.Select((s, i) => CreateContent((page - 1) * items_per_page + i, s)).ToArray().ToRectangular(); } } - protected abstract TableColumn[] CreateHeaders(); + private static TableColumn[] mainHeaders => new[] + { + new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.Absolute, 50)), // place + new TableColumn(string.Empty, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed)), // flag and username (country name) + }; + + protected abstract TableColumn[] CreateAdditionalHeaders(); protected abstract Drawable[] CreateContent(int index, TModel item); diff --git a/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs b/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs index b644edefd1..9ddd89914a 100644 --- a/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs @@ -20,10 +20,8 @@ namespace osu.Game.Overlays.Rankings.Tables { } - protected override TableColumn[] CreateHeaders() => new[] + protected override TableColumn[] CreateAdditionalHeaders() => new[] { - new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.Absolute, 50)), // place - new TableColumn(string.Empty, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed)), // flag and username new TableColumn("Accuracy", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new TableColumn("Total Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), From c546df8a8050e5e220c9d8c372a00b6062b38947 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 27 Nov 2019 21:56:22 +0300 Subject: [PATCH 2504/2815] Refactor API logic --- .../Online/API/Requests/GetCountriesResponse.cs | 4 ++-- osu.Game/Online/API/Requests/GetUsersResponse.cs | 4 ++-- .../API/Requests/Responses/APICountryRankings.cs | 14 -------------- .../API/Requests/Responses/APIUserRankings.cs | 14 -------------- .../Overlays/Rankings/Tables/CountriesTable.cs | 6 +++--- .../Overlays/Rankings/Tables/PerformanceTable.cs | 6 +++--- osu.Game/Overlays/Rankings/Tables/ScoresTable.cs | 6 +++--- osu.Game/Users/CountryStatistics.cs | 3 +++ osu.Game/Users/UserStatistics.cs | 3 +++ 9 files changed, 19 insertions(+), 41 deletions(-) delete mode 100644 osu.Game/Online/API/Requests/Responses/APICountryRankings.cs delete mode 100644 osu.Game/Online/API/Requests/Responses/APIUserRankings.cs diff --git a/osu.Game/Online/API/Requests/GetCountriesResponse.cs b/osu.Game/Online/API/Requests/GetCountriesResponse.cs index 7b65c6fadd..6624344b44 100644 --- a/osu.Game/Online/API/Requests/GetCountriesResponse.cs +++ b/osu.Game/Online/API/Requests/GetCountriesResponse.cs @@ -3,13 +3,13 @@ using System.Collections.Generic; using Newtonsoft.Json; -using osu.Game.Online.API.Requests.Responses; +using osu.Game.Users; namespace osu.Game.Online.API.Requests { public class GetCountriesResponse : ResponseWithCursor { [JsonProperty("ranking")] - public List Countries; + public List Countries; } } diff --git a/osu.Game/Online/API/Requests/GetUsersResponse.cs b/osu.Game/Online/API/Requests/GetUsersResponse.cs index c60bc71096..b301f551e3 100644 --- a/osu.Game/Online/API/Requests/GetUsersResponse.cs +++ b/osu.Game/Online/API/Requests/GetUsersResponse.cs @@ -3,13 +3,13 @@ using System.Collections.Generic; using Newtonsoft.Json; -using osu.Game.Online.API.Requests.Responses; +using osu.Game.Users; namespace osu.Game.Online.API.Requests { public class GetUsersResponse : ResponseWithCursor { [JsonProperty("ranking")] - public List Users; + public List Users; } } diff --git a/osu.Game/Online/API/Requests/Responses/APICountryRankings.cs b/osu.Game/Online/API/Requests/Responses/APICountryRankings.cs deleted file mode 100644 index 91086876a9..0000000000 --- a/osu.Game/Online/API/Requests/Responses/APICountryRankings.cs +++ /dev/null @@ -1,14 +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 Newtonsoft.Json; -using osu.Game.Users; - -namespace osu.Game.Online.API.Requests.Responses -{ - public class APICountryRankings : CountryStatistics - { - [JsonProperty] - public Country Country; - } -} diff --git a/osu.Game/Online/API/Requests/Responses/APIUserRankings.cs b/osu.Game/Online/API/Requests/Responses/APIUserRankings.cs deleted file mode 100644 index 1cdb6ecb8c..0000000000 --- a/osu.Game/Online/API/Requests/Responses/APIUserRankings.cs +++ /dev/null @@ -1,14 +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 Newtonsoft.Json; -using osu.Game.Users; - -namespace osu.Game.Online.API.Requests.Responses -{ - public class APIUserRankings : UserStatistics - { - [JsonProperty] - public User User; - } -} diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs index f5e95f316e..878f85b346 100644 --- a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs @@ -7,12 +7,12 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Users.Drawables; using osuTK; -using osu.Game.Online.API.Requests.Responses; using System; +using osu.Game.Users; namespace osu.Game.Overlays.Rankings.Tables { - public class CountriesTable : RankingsTable + public class CountriesTable : RankingsTable { public CountriesTable(int page = 1) : base(page) @@ -29,7 +29,7 @@ namespace osu.Game.Overlays.Rankings.Tables new TableColumn("Avg. Perf.", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), }; - protected override Drawable[] CreateContent(int index, APICountryRankings item) => new Drawable[] + protected override Drawable[] CreateContent(int index, CountryStatistics item) => new Drawable[] { new OsuSpriteText { diff --git a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs index 7d4bd71526..010da07858 100644 --- a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs @@ -9,11 +9,11 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Users.Drawables; using osuTK; -using osu.Game.Online.API.Requests.Responses; +using osu.Game.Users; namespace osu.Game.Overlays.Rankings.Tables { - public class PerformanceTable : RankingsTable + public class PerformanceTable : RankingsTable { public PerformanceTable(int page = 1) : base(page) @@ -30,7 +30,7 @@ namespace osu.Game.Overlays.Rankings.Tables new TableColumn("A", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), }; - protected override Drawable[] CreateContent(int index, APIUserRankings item) + protected override Drawable[] CreateContent(int index, UserStatistics item) { var content = new List { diff --git a/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs b/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs index 9ddd89914a..d40da63f7f 100644 --- a/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs @@ -7,13 +7,13 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Users.Drawables; using osuTK; -using osu.Game.Online.API.Requests.Responses; using System.Collections.Generic; using osu.Game.Graphics.Containers; +using osu.Game.Users; namespace osu.Game.Overlays.Rankings.Tables { - public class ScoresTable : RankingsTable + public class ScoresTable : RankingsTable { public ScoresTable(int page = 1) : base(page) @@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Rankings.Tables new TableColumn("A", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), }; - protected override Drawable[] CreateContent(int index, APIUserRankings item) + protected override Drawable[] CreateContent(int index, UserStatistics item) { var content = new List { diff --git a/osu.Game/Users/CountryStatistics.cs b/osu.Game/Users/CountryStatistics.cs index 53fa70f0d4..000553c32b 100644 --- a/osu.Game/Users/CountryStatistics.cs +++ b/osu.Game/Users/CountryStatistics.cs @@ -7,6 +7,9 @@ namespace osu.Game.Users { public class CountryStatistics { + [JsonProperty] + public Country Country; + [JsonProperty(@"code")] public string FlagName; diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index 032ec2e05f..24f1f0b30e 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -10,6 +10,9 @@ namespace osu.Game.Users { public class UserStatistics { + [JsonProperty] + public User User; + [JsonProperty(@"level")] public LevelInfo Level; From 800bda7e815caa2ff0739642444e431acf49c1f3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 27 Nov 2019 23:35:02 +0300 Subject: [PATCH 2505/2815] Refactor tables to avoid code duplication --- .../Visual/Online/TestSceneRankingsTables.cs | 1 + .../Rankings/Tables/CountriesTable.cs | 39 +++------ .../Rankings/Tables/PerformanceTable.cs | 77 ++--------------- .../Overlays/Rankings/Tables/RankingsTable.cs | 35 +++++++- .../Overlays/Rankings/Tables/ScoresTable.cs | 85 +++---------------- .../Rankings/Tables/UserBasedTable.cs | 70 +++++++++++++++ 6 files changed, 133 insertions(+), 174 deletions(-) create mode 100644 osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs index 2c76b1937b..ff238803a5 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs @@ -29,6 +29,7 @@ namespace osu.Game.Tests.Visual.Online typeof(ScoresTable), typeof(CountriesTable), typeof(TableRowBackground), + typeof(UserBasedTable), typeof(RankingsTable<>) }; diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs index 878f85b346..208a4dbfe4 100644 --- a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs @@ -3,12 +3,10 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Users.Drawables; -using osuTK; using System; using osu.Game.Users; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics; namespace osu.Game.Overlays.Rankings.Tables { @@ -29,31 +27,16 @@ namespace osu.Game.Overlays.Rankings.Tables new TableColumn("Avg. Perf.", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), }; - protected override Drawable[] CreateContent(int index, CountryStatistics item) => new Drawable[] + protected override Country GetCountry(CountryStatistics item) => item.Country; + + protected override Drawable CreateFlagContent(CountryStatistics item) => new OsuSpriteText + { + Font = OsuFont.GetFont(size: TEXT_SIZE), + Text = $@"{item.Country.FullName}", + }; + + protected override Drawable[] CreateAdditionalContent(CountryStatistics item) => new[] { - new OsuSpriteText - { - Text = $"#{index + 1}", - Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold) - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(7, 0), - Children = new Drawable[] - { - new UpdateableFlag(item.Country) - { - Size = new Vector2(20, 13), - ShowPlaceholderOnNull = false, - }, - new RowText - { - Text = $@"{item.Country.FullName}", - } - } - }, new ColoredRowText { Text = $@"{item.ActiveUsers:N0}", diff --git a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs index 010da07858..263f6052cc 100644 --- a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs @@ -3,91 +3,28 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using System.Collections.Generic; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Users.Drawables; -using osuTK; using osu.Game.Users; namespace osu.Game.Overlays.Rankings.Tables { - public class PerformanceTable : RankingsTable + public class PerformanceTable : UserBasedTable { public PerformanceTable(int page = 1) : base(page) { } - protected override TableColumn[] CreateAdditionalHeaders() => new[] + protected override TableColumn[] CreateUniqueHeaders() => new[] { - new TableColumn("Accuracy", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new TableColumn("Performance", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("SS", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("S", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("A", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), }; - protected override Drawable[] CreateContent(int index, UserStatistics item) + protected override Drawable[] CreateUniqueContent(UserStatistics item) => new[] { - var content = new List + new RowText { - new OsuSpriteText - { - Text = $"#{index + 1}", - Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold) - }, - }; - - var username = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: TEXT_SIZE)) { AutoSizeAxes = Axes.Both }; - username.AddUserLink(item.User); - - content.AddRange(new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(7, 0), - Children = new Drawable[] - { - new UpdateableFlag(item.User.Country) - { - Size = new Vector2(20, 13), - ShowPlaceholderOnNull = false, - }, - username - } - }, - new ColoredRowText - { - Text = $@"{item.Accuracy:F2}%", - }, - new ColoredRowText - { - Text = $@"{item.PlayCount:N0}", - }, - new RowText - { - Text = $@"{item.PP:N0}", - }, - new ColoredRowText - { - Text = $@"{item.GradesCount.SS + item.GradesCount.SSPlus:N0}", - }, - new ColoredRowText - { - Text = $@"{item.GradesCount.S + item.GradesCount.SPlus:N0}", - }, - new ColoredRowText - { - Text = $@"{item.GradesCount.A:N0}", - }, - }); - - return content.ToArray(); - } + Text = $@"{item.PP:N0}", + } + }; } } diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index 530295b039..d085b3a9a8 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -10,6 +10,9 @@ using osu.Framework.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Users; +using osu.Game.Users.Drawables; +using osuTK; namespace osu.Game.Overlays.Rankings.Tables { @@ -54,10 +57,12 @@ namespace osu.Game.Overlays.Rankings.Tables value.ForEach(_ => backgroundFlow.Add(new TableRowBackground())); Columns = mainHeaders.Concat(CreateAdditionalHeaders()).ToArray(); - Content = value.Select((s, i) => CreateContent((page - 1) * items_per_page + i, s)).ToArray().ToRectangular(); + Content = value.Select((s, i) => createContent((page - 1) * items_per_page + i, s)).ToArray().ToRectangular(); } } + private Drawable[] createContent(int index, TModel item) => new Drawable[] { createIndexDrawable(index), createMainContent(item) }.Concat(CreateAdditionalContent(item)).ToArray(); + private static TableColumn[] mainHeaders => new[] { new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.Absolute, 50)), // place @@ -66,10 +71,36 @@ namespace osu.Game.Overlays.Rankings.Tables protected abstract TableColumn[] CreateAdditionalHeaders(); - protected abstract Drawable[] CreateContent(int index, TModel item); + protected abstract Drawable[] CreateAdditionalContent(TModel item); protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty, HighlightedColumn()); + protected abstract Country GetCountry(TModel item); + + protected abstract Drawable CreateFlagContent(TModel item); + + private OsuSpriteText createIndexDrawable(int index) => new OsuSpriteText + { + Text = $"#{index + 1}", + Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold) + }; + + private FillFlowContainer createMainContent(TModel item) => new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(7, 0), + Children = new Drawable[] + { + new UpdateableFlag(GetCountry(item)) + { + Size = new Vector2(20, 13), + ShowPlaceholderOnNull = false, + }, + CreateFlagContent(item) + } + }; + protected virtual string HighlightedColumn() => @"Performance"; private class HeaderText : OsuSpriteText diff --git a/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs b/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs index d40da63f7f..0d4999ee8f 100644 --- a/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs @@ -3,97 +3,34 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Users.Drawables; -using osuTK; -using System.Collections.Generic; -using osu.Game.Graphics.Containers; using osu.Game.Users; namespace osu.Game.Overlays.Rankings.Tables { - public class ScoresTable : RankingsTable + public class ScoresTable : UserBasedTable { public ScoresTable(int page = 1) : base(page) { } - protected override TableColumn[] CreateAdditionalHeaders() => new[] + protected override TableColumn[] CreateUniqueHeaders() => new[] { - new TableColumn("Accuracy", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new TableColumn("Total Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("Ranked Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("SS", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("S", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("A", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("Ranked Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)) }; - protected override Drawable[] CreateContent(int index, UserStatistics item) + protected override Drawable[] CreateUniqueContent(UserStatistics item) => new[] { - var content = new List + new ColoredRowText { - new OsuSpriteText - { - Text = $"#{index + 1}", - Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold) - }, - }; - - var username = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: TEXT_SIZE)) { AutoSizeAxes = Axes.Both }; - username.AddUserLink(item.User); - - content.AddRange(new Drawable[] + Text = $@"{item.TotalScore:N0}", + }, + new RowText { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(7, 0), - Children = new Drawable[] - { - new UpdateableFlag(item.User.Country) - { - Size = new Vector2(20, 13), - ShowPlaceholderOnNull = false, - }, - username - } - }, - new ColoredRowText - { - Text = $@"{item.Accuracy:F2}%", - }, - new ColoredRowText - { - Text = $@"{item.PlayCount:N0}", - }, - new ColoredRowText - { - Text = $@"{item.TotalScore:N0}", - }, - new RowText - { - Text = $@"{item.RankedScore:N0}", - }, - new ColoredRowText - { - Text = $@"{item.GradesCount.SS + item.GradesCount.SSPlus:N0}", - }, - new ColoredRowText - { - Text = $@"{item.GradesCount.S + item.GradesCount.SPlus:N0}", - }, - new ColoredRowText - { - Text = $@"{item.GradesCount.A:N0}", - }, - }); - - return content.ToArray(); - } + Text = $@"{item.RankedScore:N0}", + } + }; protected override string HighlightedColumn() => @"Ranked Score"; } diff --git a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs new file mode 100644 index 0000000000..1283db9e32 --- /dev/null +++ b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs @@ -0,0 +1,70 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Users; + +namespace osu.Game.Overlays.Rankings.Tables +{ + public abstract class UserBasedTable : RankingsTable + { + protected UserBasedTable(int page) + : base(page) + { + } + + protected override TableColumn[] CreateAdditionalHeaders() => new[] + { + new TableColumn("Accuracy", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + }.Concat(CreateUniqueHeaders()).Concat(new[] + { + new TableColumn("SS", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("S", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("A", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + }).ToArray(); + + protected override Country GetCountry(UserStatistics item) => item.User.Country; + + protected override Drawable CreateFlagContent(UserStatistics item) + { + var username = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: TEXT_SIZE)) { AutoSizeAxes = Axes.Both }; + username.AddUserLink(item.User); + return username; + } + + protected override Drawable[] CreateAdditionalContent(UserStatistics item) => new[] + { + new ColoredRowText + { + Text = $@"{item.Accuracy:F2}%", + }, + new ColoredRowText + { + Text = $@"{item.PlayCount:N0}", + }, + }.Concat(CreateUniqueContent(item)).Concat(new[] + { + new ColoredRowText + { + Text = $@"{item.GradesCount.SS + item.GradesCount.SSPlus:N0}", + }, + new ColoredRowText + { + Text = $@"{item.GradesCount.S + item.GradesCount.SPlus:N0}", + }, + new ColoredRowText + { + Text = $@"{item.GradesCount.A:N0}", + } + }).ToArray(); + + protected abstract TableColumn[] CreateUniqueHeaders(); + + protected abstract Drawable[] CreateUniqueContent(UserStatistics item); + } +} From 44cfe98278a60e0fea2ccf43caf6e4d3a52568c8 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 27 Nov 2019 23:40:52 +0300 Subject: [PATCH 2506/2815] Simplify test scene --- .../Visual/Online/TestSceneRankingsTables.cs | 57 ++++++++----------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs index ff238803a5..5501bb54d0 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs @@ -68,11 +68,7 @@ namespace osu.Game.Tests.Visual.Online private void createCountryTable(RulesetInfo ruleset, int page = 1) { - loading.Show(); - - request?.Cancel(); - cancellationToken?.Cancel(); - cancellationToken = new CancellationTokenSource(); + onLoadStarted(); request = new GetCountryRankingsRequest(ruleset, page); ((GetCountryRankingsRequest)request).Success += rankings => Schedule(() => @@ -82,12 +78,7 @@ namespace osu.Game.Tests.Visual.Online Rankings = rankings.Countries, }; - LoadComponentAsync(table, t => - { - scrollFlow.Clear(); - scrollFlow.Add(t); - loading.Hide(); - }, cancellationToken.Token); + loadTable(table); }); api.Queue(request); @@ -95,11 +86,7 @@ namespace osu.Game.Tests.Visual.Online private void createPerformanceTable(RulesetInfo ruleset, string country, int page = 1) { - loading.Show(); - - request?.Cancel(); - cancellationToken?.Cancel(); - cancellationToken = new CancellationTokenSource(); + onLoadStarted(); request = new GetUserRankingsRequest(ruleset, country: country, page: page); ((GetUserRankingsRequest)request).Success += rankings => Schedule(() => @@ -109,12 +96,7 @@ namespace osu.Game.Tests.Visual.Online Rankings = rankings.Users, }; - LoadComponentAsync(table, t => - { - scrollFlow.Clear(); - scrollFlow.Add(t); - loading.Hide(); - }, cancellationToken.Token); + loadTable(table); }); api.Queue(request); @@ -122,11 +104,7 @@ namespace osu.Game.Tests.Visual.Online private void createScoreTable(RulesetInfo ruleset, int page = 1) { - loading.Show(); - - request?.Cancel(); - cancellationToken?.Cancel(); - cancellationToken = new CancellationTokenSource(); + onLoadStarted(); request = new GetUserRankingsRequest(ruleset, UserRankingsType.Score, page); ((GetUserRankingsRequest)request).Success += rankings => Schedule(() => @@ -136,15 +114,28 @@ namespace osu.Game.Tests.Visual.Online Rankings = rankings.Users, }; - LoadComponentAsync(table, t => - { - scrollFlow.Clear(); - scrollFlow.Add(t); - loading.Hide(); - }, cancellationToken.Token); + loadTable(table); }); api.Queue(request); } + + private void onLoadStarted() + { + loading.Show(); + request?.Cancel(); + cancellationToken?.Cancel(); + cancellationToken = new CancellationTokenSource(); + } + + private void loadTable(Drawable table) + { + LoadComponentAsync(table, t => + { + scrollFlow.Clear(); + scrollFlow.Add(t); + loading.Hide(); + }, cancellationToken.Token); + } } } From 8077c86d13265e2d58677269d3ae457fc9ce3f86 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 27 Nov 2019 23:58:31 +0300 Subject: [PATCH 2507/2815] CI fixes --- osu.Game/Overlays/Rankings/Tables/CountriesTable.cs | 2 +- osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs | 2 +- osu.Game/Overlays/Rankings/Tables/RankingsTable.cs | 2 +- osu.Game/Overlays/Rankings/Tables/ScoresTable.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs index 208a4dbfe4..b49583f0e2 100644 --- a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs @@ -35,7 +35,7 @@ namespace osu.Game.Overlays.Rankings.Tables Text = $@"{item.Country.FullName}", }; - protected override Drawable[] CreateAdditionalContent(CountryStatistics item) => new[] + protected override Drawable[] CreateAdditionalContent(CountryStatistics item) => new Drawable[] { new ColoredRowText { diff --git a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs index 263f6052cc..ec3252b22a 100644 --- a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Rankings.Tables new TableColumn("Performance", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), }; - protected override Drawable[] CreateUniqueContent(UserStatistics item) => new[] + protected override Drawable[] CreateUniqueContent(UserStatistics item) => new Drawable[] { new RowText { diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index d085b3a9a8..7a20d60f6e 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -90,7 +90,7 @@ namespace osu.Game.Overlays.Rankings.Tables AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(7, 0), - Children = new Drawable[] + Children = new[] { new UpdateableFlag(GetCountry(item)) { diff --git a/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs b/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs index 0d4999ee8f..cc3c5ccf94 100644 --- a/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Rankings.Tables new TableColumn("Ranked Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)) }; - protected override Drawable[] CreateUniqueContent(UserStatistics item) => new[] + protected override Drawable[] CreateUniqueContent(UserStatistics item) => new Drawable[] { new ColoredRowText { From a2d935bebf7bb43bb5a35f966166ad3c1d1e42c6 Mon Sep 17 00:00:00 2001 From: Luxiono <55364481+Luxiono@users.noreply.github.com> Date: Wed, 27 Nov 2019 20:58:13 -0500 Subject: [PATCH 2508/2815] Adjust wording and make small format changes This makes small changes to the README file to make certain parts properly formatted (such as adding 'a' in front of a sentence with 'to' straight before it, as done in the Building section) and added special formatting where it feels needed. This may also make certain sentences slightly more natural. Please let me know if you want any other changes. --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 7adc8dc973..e343e498cd 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,11 @@ [![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu) [![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](https://discord.gg/ppy) -Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commonly known by the codename "osu!lazer". Pew pew. +Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commonly known by the codename *osu!lazer*. Pew pew. ## Status -This project is still heavily under development, but is in a state where users are encouraged to try it out and keep it installed alongside the stable osu! client. It will continue to evolve over the coming months and hopefully bring some new unique features to the table. +This project is still heavily under development, but is in a state where users are encouraged to try it out and keep it installed alongside the stable *osu!* client. It will continue to evolve over the coming months and hopefully bring some new unique features to the table. We are accepting bug reports (please report with as much detail as possible). Feature requests are welcome as long as you read and understand the contribution guidelines listed below. @@ -59,22 +59,22 @@ git pull ### Building -Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this provided [below](#contributing). +Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this is provided [below](#contributing). -- Visual Studio / Rider users should load the project via one of the platform-specific .slnf files, rather than the main .sln. This will allow access to template run configurations. +- Visual Studio / Rider users should load the project via one of the platform-specific `.slnf` files, rather than the main `.sln.` This will allow access to template run configurations. - Visual Studio Code users must run the `Restore` task before any build attempt. -You can also build and run osu! from the command-line with a single command: +You can also build and run *osu!* from the command-line with a single command: ```shell dotnet run --project osu.Desktop ``` -If you are not interested in debugging osu!, you can add `-c Release` to gain performance. In this case, you must replace `Debug` with `Release` in any commands mentioned in this document. +If you are not interested in debugging *osu!*, you can add `-c Release` to gain performance. In this case, you must replace `Debug` with `Release` in any commands mentioned in this document. If the build fails, try to restore NuGet packages with `dotnet restore`. -_Due to historical feature gap between .NET Core and Xamarin, running `dotnet` CLI from the root directory will not work for most commands. This can be resolved by specifying a target `.csproj` or the helper project at `build/Desktop.proj`. Configurations have been provided to work around this issue for all supported IDEs mentioned above._ +_Due to a historical feature gap between .NET Core and Xamarin, running `dotnet` CLI from the root directory will not work for most commands. This can be resolved by specifying a target `.csproj` or the helper project at `build/Desktop.proj`. Configurations have been provided to work around this issue for all supported IDEs mentioned above._ ### Testing with resource/framework modifications @@ -82,7 +82,7 @@ Sometimes it may be necessary to cross-test changes in [osu-resources](https://g ### Code analysis -Before committing your code, please run a code formatter. It can be achieved with `dotnet format` in command line, or `Format code` command in your IDE. +Before committing your code, please run a code formatter. This can be achieved with running `dotnet format` in the command line, or the `Format code` command in your IDE. We have adopted some cross-platform, compiler integrated analyzers. They can provide warnings when you are editing, building inside IDE or from command line, as-if they are provided by the compiler itself. @@ -90,19 +90,19 @@ JetBrains ReSharper InspectCode is also used for wider rule sets. You can run it ## Contributing -We welcome all contributions, but keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention of having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time, to ensure no effort is wasted. +We welcome all contributions, but keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention of having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time to ensure no effort is wasted. If you're unsure of what you can help with, check out the [list of open issues](https://github.com/ppy/osu/issues) (especially those with the ["good first issue"](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22) label). Before starting, please make sure you are familiar with the [development and testing](https://github.com/ppy/osu-framework/wiki/Development-and-Testing) procedure we have set up. New component development, and where possible, bug fixing and debugging existing components **should always be done under VisualTests**. -Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured; with any libraries we are using; with any processes involved with contributing, *please* bring it up. We welcome all feedback so we can make contributing to this project as pain-free as possible. +Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured, with any libraries we are using, or with any processes involved with contributing, *please* bring it up. We welcome all feedback so we can make contributing to this project as painless as possible. For those interested, we love to reward quality contributions via [bounties](https://docs.google.com/spreadsheets/d/1jNXfj_S3Pb5PErA-czDdC9DUu4IgUbe1Lt8E7CYUJuE/view?&rm=minimal#gid=523803337), paid out via PayPal or osu!supporter tags. Don't hesitate to [request a bounty](https://docs.google.com/forms/d/e/1FAIpQLSet_8iFAgPMG526pBZ2Kic6HSh7XPM3fE8xPcnWNkMzINDdYg/viewform) for your work on this project. ## Licence -The osu! client code and framework are licensed under the [MIT licence](https://opensource.org/licenses/MIT). Please see [the licence file](LICENCE) for more information. [tl;dr](https://tldrlegal.com/license/mit-license) you can do whatever you want as long as you include the original copyright and license notice in any copy of the software/source. +The *osu!client* code and framework are licensed under the [MIT licence](https://opensource.org/licenses/MIT). Please see [the licence file](LICENCE) for more information. [tl;dr](https://tldrlegal.com/license/mit-license) you can do whatever you want as long as you include the original copyright and license notice in any copy of the software/source. Please note that this *does not cover* the usage of the "osu!" or "ppy" branding in any software, resources, advertising or promotion, as this is protected by trademark law. From 078e0c10581527b9aa6323454722ad8b2a986d62 Mon Sep 17 00:00:00 2001 From: Luxiono <55364481+Luxiono@users.noreply.github.com> Date: Wed, 27 Nov 2019 21:11:42 -0500 Subject: [PATCH 2509/2815] Add peppy's suggestion Co-Authored-By: Dean Herbert --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e343e498cd..ac164b3e8d 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ For those interested, we love to reward quality contributions via [bounties](htt ## Licence -The *osu!client* code and framework are licensed under the [MIT licence](https://opensource.org/licenses/MIT). Please see [the licence file](LICENCE) for more information. [tl;dr](https://tldrlegal.com/license/mit-license) you can do whatever you want as long as you include the original copyright and license notice in any copy of the software/source. +*osu!*'s code and framework are licensed under the [MIT licence](https://opensource.org/licenses/MIT). Please see [the licence file](LICENCE) for more information. [tl;dr](https://tldrlegal.com/license/mit-license) you can do whatever you want as long as you include the original copyright and license notice in any copy of the software/source. Please note that this *does not cover* the usage of the "osu!" or "ppy" branding in any software, resources, advertising or promotion, as this is protected by trademark law. From 32aa624ef3e941bc838ea956b487211310d05b3b Mon Sep 17 00:00:00 2001 From: Luxiono <55364481+Luxiono@users.noreply.github.com> Date: Wed, 27 Nov 2019 21:12:45 -0500 Subject: [PATCH 2510/2815] Add peppy's suggestion (part 2) Co-Authored-By: Dean Herbert --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ac164b3e8d..e2e854c755 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ Sometimes it may be necessary to cross-test changes in [osu-resources](https://g ### Code analysis -Before committing your code, please run a code formatter. This can be achieved with running `dotnet format` in the command line, or the `Format code` command in your IDE. +Before committing your code, please run a code formatter. This can be achieved by running `dotnet format` in the command line, or using the `Format code` command in your IDE. We have adopted some cross-platform, compiler integrated analyzers. They can provide warnings when you are editing, building inside IDE or from command line, as-if they are provided by the compiler itself. From 92ab8026a006bd9e7977bd659dcb3e9b1a41d63e Mon Sep 17 00:00:00 2001 From: Min Date: Thu, 28 Nov 2019 16:03:59 +1100 Subject: [PATCH 2511/2815] Completely remove click sound debounce --- .../UserInterface/HoverClickSounds.cs | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs index 61cacee45f..803facae04 100644 --- a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverClickSounds.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.Linq; @@ -7,7 +7,6 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Extensions; using osu.Framework.Input.Events; -using osu.Framework.Threading; using osuTK.Input; namespace osu.Game.Graphics.UserInterface @@ -21,11 +20,6 @@ namespace osu.Game.Graphics.UserInterface private SampleChannel sampleClick; private readonly MouseButton[] buttons; - /// - /// Length of debounce for click sound playback, in milliseconds. Default is 0ms. - /// - public double ClickDebounceTime { get; set; } - /// /// a container which plays sounds on hover and click for any specified s. /// @@ -40,19 +34,10 @@ namespace osu.Game.Graphics.UserInterface this.buttons = buttons ?? new[] { MouseButton.Left }; } - private ScheduledDelegate playDelegate; - protected override bool OnClick(ClickEvent e) { - playDelegate?.Cancel(); - if (buttons.Contains(e.Button) && Contains(e.ScreenSpaceMousePosition)) - { - if (ClickDebounceTime <= 0) - sampleClick?.Play(); - else - playDelegate = Scheduler.AddDelayed(() => sampleClick?.Play(), ClickDebounceTime); - } + sampleClick?.Play(); return base.OnClick(e); } From f504370867a1f221444c743a2520a686dc5459c2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 28 Nov 2019 15:58:26 +0900 Subject: [PATCH 2512/2815] Make player block exit if pausable --- osu.Game.Tests/Visual/Gameplay/TestScenePause.cs | 7 ++++++- osu.Game/Screens/Play/Player.cs | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 6e8975f11b..803cab9325 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -115,8 +115,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestExitTooSoon() { - pauseAndConfirm(); + AddStep("seek before gameplay", () => Player.GameplayClockContainer.Seek(-5000)); + pauseAndConfirm(); resume(); AddStep("exit too soon", () => Player.Exit()); @@ -176,7 +177,9 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestExitFromGameplay() { AddStep("exit", () => Player.Exit()); + confirmPaused(); + AddStep("exit", () => Player.Exit()); confirmExited(); } @@ -214,6 +217,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestRestartAfterResume() { + AddStep("seek before gameplay", () => Player.GameplayClockContainer.Seek(-5000)); + pauseAndConfirm(); resumeAndConfirm(); restart(); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 7f32db2ebf..d40c448452 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -536,6 +536,12 @@ namespace osu.Game.Screens.Play return true; } + if (canPause) + { + Pause(); + return true; + } + // GameplayClockContainer performs seeks / start / stop operations on the beatmap's track. // as we are no longer the current screen, we cannot guarantee the track is still usable. GameplayClockContainer.StopUsingBeatmapClock(); From aa9776d51ad200634d87e38fea50946552bebbe6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Nov 2019 19:07:43 +0900 Subject: [PATCH 2513/2815] seal and compact rows --- .../Rankings/Tables/PerformanceTable.cs | 5 +-- .../Rankings/Tables/UserBasedTable.cs | 31 +++++-------------- 2 files changed, 9 insertions(+), 27 deletions(-) diff --git a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs index ec3252b22a..f604efb8d5 100644 --- a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs @@ -21,10 +21,7 @@ namespace osu.Game.Overlays.Rankings.Tables protected override Drawable[] CreateUniqueContent(UserStatistics item) => new Drawable[] { - new RowText - { - Text = $@"{item.PP:N0}", - } + new RowText { Text = $@"{item.PP:N0}", } }; } } diff --git a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs index 1283db9e32..6ea145be9c 100644 --- a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs @@ -28,39 +28,24 @@ namespace osu.Game.Overlays.Rankings.Tables new TableColumn("A", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), }).ToArray(); - protected override Country GetCountry(UserStatistics item) => item.User.Country; + protected sealed override Country GetCountry(UserStatistics item) => item.User.Country; - protected override Drawable CreateFlagContent(UserStatistics item) + protected sealed override Drawable CreateFlagContent(UserStatistics item) { var username = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: TEXT_SIZE)) { AutoSizeAxes = Axes.Both }; username.AddUserLink(item.User); return username; } - protected override Drawable[] CreateAdditionalContent(UserStatistics item) => new[] + protected sealed override Drawable[] CreateAdditionalContent(UserStatistics item) => new[] { - new ColoredRowText - { - Text = $@"{item.Accuracy:F2}%", - }, - new ColoredRowText - { - Text = $@"{item.PlayCount:N0}", - }, + new ColoredRowText { Text = $@"{item.Accuracy:F2}%", }, + new ColoredRowText { Text = $@"{item.PlayCount:N0}", }, }.Concat(CreateUniqueContent(item)).Concat(new[] { - new ColoredRowText - { - Text = $@"{item.GradesCount.SS + item.GradesCount.SSPlus:N0}", - }, - new ColoredRowText - { - Text = $@"{item.GradesCount.S + item.GradesCount.SPlus:N0}", - }, - new ColoredRowText - { - Text = $@"{item.GradesCount.A:N0}", - } + new ColoredRowText { Text = $@"{item.GradesCount.SS + item.GradesCount.SSPlus:N0}", }, + new ColoredRowText { Text = $@"{item.GradesCount.S + item.GradesCount.SPlus:N0}", }, + new ColoredRowText { Text = $@"{item.GradesCount.A:N0}", } }).ToArray(); protected abstract TableColumn[] CreateUniqueHeaders(); From a4f584c6a4771c0f8c40bafaa544d4e83e8a571e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Nov 2019 22:00:01 +0900 Subject: [PATCH 2514/2815] 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 f2d2af5678..b5e8c62123 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -53,6 +53,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 449b4dc4e3..143ad48c7f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -21,7 +21,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index ff6839f5ca..105ef08c7a 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -73,7 +73,7 @@ - + @@ -81,7 +81,7 @@ - + From 71a871d7d1dede52cc9d75b435b7a70b664befc3 Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Thu, 28 Nov 2019 21:59:57 +0700 Subject: [PATCH 2515/2815] Add loved enum on BeatmapApproval --- osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs b/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs index 4908e5ecc2..123624d333 100644 --- a/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs @@ -42,5 +42,6 @@ namespace osu.Game.Online.API.Requests Ranked, Approved, Qualified, + Loved } } From b9a8a36e60e633e94042734d2a7dc8b81b74bfe3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Nov 2019 00:44:32 +0900 Subject: [PATCH 2516/2815] 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 3a6a6e9832..997bd32bac 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -54,6 +54,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ef873bd3ac..3f056cacfb 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 1723a6210c..b231e924ef 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -74,7 +74,7 @@ - + @@ -82,7 +82,7 @@ - + From 1c4063677ddd33d04dc4f9fd34a95f4af1d3d461 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Nov 2019 00:45:35 +0900 Subject: [PATCH 2517/2815] Ignore test temporarily --- osu.Game.Tests/Visual/Gameplay/TestScenePause.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 6e8975f11b..1c9132edef 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -113,6 +113,7 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] + [Ignore("Will be resolved with merge of https://github.com/ppy/osu/pull/6992")] public void TestExitTooSoon() { pauseAndConfirm(); From 14277d714e11ecf7309e2b65b9b606be30880e31 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Nov 2019 01:01:25 +0900 Subject: [PATCH 2518/2815] Fix tournament client crashing due to null ruleset --- osu.Game.Tournament/TournamentGameBase.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index b5e4a2756a..6bff3fb725 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -129,6 +129,9 @@ namespace osu.Game.Tournament ladder = new LadderInfo(); } + if (ladder.Ruleset.Value == null) + ladder.Ruleset.Value = RulesetStore.AvailableRulesets.First(); + Ruleset.BindTo(ladder.Ruleset); dependencies.Cache(ladder); From bb0a4db847ae8e084233576cd39845252f331dac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Nov 2019 01:16:26 +0900 Subject: [PATCH 2519/2815] Fix failing tests --- osu.Game/Graphics/Backgrounds/Background.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Backgrounds/Background.cs b/osu.Game/Graphics/Backgrounds/Background.cs index 21fca5d5da..c90b1e0e98 100644 --- a/osu.Game/Graphics/Backgrounds/Background.cs +++ b/osu.Game/Graphics/Backgrounds/Background.cs @@ -45,7 +45,7 @@ namespace osu.Game.Graphics.Backgrounds Sprite.Texture = textures.Get(textureName); } - public Vector2 BlurSigma => bufferedContainer?.BlurSigma ?? Vector2.Zero; + public Vector2 BlurSigma => bufferedContainer?.BlurSigma / blur_scale ?? Vector2.Zero; /// /// Smoothly adjusts over time. From e7ef919b0b12b31706f1eb9036259c663c706bb2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Nov 2019 01:47:52 +0900 Subject: [PATCH 2520/2815] Ignore another failing test --- osu.Game.Tests/Visual/Gameplay/TestScenePause.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 1c9132edef..e3f1694ee5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -213,6 +213,7 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] + [Ignore("Will be resolved with merge of https://github.com/ppy/osu/pull/6992")] public void TestRestartAfterResume() { pauseAndConfirm(); From 83e3ad9e696bcbaf2dcee329efbde84c92d11f5f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 28 Nov 2019 20:09:05 +0300 Subject: [PATCH 2521/2815] Make Rankings a ctor variable --- .../Visual/Online/TestSceneRankingsTables.cs | 18 +++---------- .../Rankings/Tables/CountriesTable.cs | 5 ++-- .../Rankings/Tables/PerformanceTable.cs | 5 ++-- .../Overlays/Rankings/Tables/RankingsTable.cs | 25 ++++--------------- .../Overlays/Rankings/Tables/ScoresTable.cs | 5 ++-- .../Rankings/Tables/UserBasedTable.cs | 5 ++-- 6 files changed, 20 insertions(+), 43 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs index 5501bb54d0..93da2a439e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs @@ -73,11 +73,7 @@ namespace osu.Game.Tests.Visual.Online request = new GetCountryRankingsRequest(ruleset, page); ((GetCountryRankingsRequest)request).Success += rankings => Schedule(() => { - var table = new CountriesTable(page) - { - Rankings = rankings.Countries, - }; - + var table = new CountriesTable(page, rankings.Countries); loadTable(table); }); @@ -91,11 +87,7 @@ namespace osu.Game.Tests.Visual.Online request = new GetUserRankingsRequest(ruleset, country: country, page: page); ((GetUserRankingsRequest)request).Success += rankings => Schedule(() => { - var table = new PerformanceTable(page) - { - Rankings = rankings.Users, - }; - + var table = new PerformanceTable(page, rankings.Users); loadTable(table); }); @@ -109,11 +101,7 @@ namespace osu.Game.Tests.Visual.Online request = new GetUserRankingsRequest(ruleset, UserRankingsType.Score, page); ((GetUserRankingsRequest)request).Success += rankings => Schedule(() => { - var table = new ScoresTable(page) - { - Rankings = rankings.Users, - }; - + var table = new ScoresTable(page, rankings.Users); loadTable(table); }); diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs index b49583f0e2..a0e4f694bd 100644 --- a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs @@ -7,13 +7,14 @@ using System; using osu.Game.Users; using osu.Game.Graphics.Sprites; using osu.Game.Graphics; +using System.Collections.Generic; namespace osu.Game.Overlays.Rankings.Tables { public class CountriesTable : RankingsTable { - public CountriesTable(int page = 1) - : base(page) + public CountriesTable(int page, IReadOnlyList rankings) + : base(page, rankings) { } diff --git a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs index f604efb8d5..1e6b2307e0 100644 --- a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.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.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Users; @@ -9,8 +10,8 @@ namespace osu.Game.Overlays.Rankings.Tables { public class PerformanceTable : UserBasedTable { - public PerformanceTable(int page = 1) - : base(page) + public PerformanceTable(int page, IReadOnlyList rankings) + : base(page, rankings) { } diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index 7a20d60f6e..08d396d88e 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -23,12 +23,9 @@ namespace osu.Game.Overlays.Rankings.Tables private const float row_height = 25; private const int items_per_page = 50; - private readonly int page; - private readonly FillFlowContainer backgroundFlow; - - protected RankingsTable(int page) + protected RankingsTable(int page, IReadOnlyList rankings) { - this.page = page; + FillFlowContainer backgroundFlow; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -42,23 +39,11 @@ namespace osu.Game.Overlays.Rankings.Tables Depth = 1f, Margin = new MarginPadding { Top = row_height } }); - } - public IReadOnlyList Rankings - { - set - { - Content = null; - backgroundFlow.Clear(); + rankings.ForEach(_ => backgroundFlow.Add(new TableRowBackground())); - if (value?.Any() != true) - return; - - value.ForEach(_ => backgroundFlow.Add(new TableRowBackground())); - - Columns = mainHeaders.Concat(CreateAdditionalHeaders()).ToArray(); - Content = value.Select((s, i) => createContent((page - 1) * items_per_page + i, s)).ToArray().ToRectangular(); - } + Columns = mainHeaders.Concat(CreateAdditionalHeaders()).ToArray(); + Content = rankings.Select((s, i) => createContent((page - 1) * items_per_page + i, s)).ToArray().ToRectangular(); } private Drawable[] createContent(int index, TModel item) => new Drawable[] { createIndexDrawable(index), createMainContent(item) }.Concat(CreateAdditionalContent(item)).ToArray(); diff --git a/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs b/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs index cc3c5ccf94..370ee506c2 100644 --- a/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/ScoresTable.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.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Users; @@ -9,8 +10,8 @@ namespace osu.Game.Overlays.Rankings.Tables { public class ScoresTable : UserBasedTable { - public ScoresTable(int page = 1) - : base(page) + public ScoresTable(int page, IReadOnlyList rankings) + : base(page, rankings) { } diff --git a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs index 6ea145be9c..019a278771 100644 --- a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.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.Collections.Generic; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -12,8 +13,8 @@ namespace osu.Game.Overlays.Rankings.Tables { public abstract class UserBasedTable : RankingsTable { - protected UserBasedTable(int page) - : base(page) + protected UserBasedTable(int page, IReadOnlyList rankings) + : base(page, rankings) { } From 9a941c4f941c7b0072b0bbe46379530dace1c6c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Nov 2019 02:15:13 +0900 Subject: [PATCH 2522/2815] Update font loading to use new method --- osu.Game.Tournament/TournamentGameBase.cs | 4 +-- osu.Game/OsuGameBase.cs | 40 +++++++++++------------ 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 6bff3fb725..6456ce2d58 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -54,8 +54,8 @@ namespace osu.Game.Tournament { Resources.AddStore(new DllResourceStore(@"osu.Game.Tournament.dll")); - Fonts.AddStore(new GlyphStore(Resources, @"Resources/Fonts/Aquatico-Regular")); - Fonts.AddStore(new GlyphStore(Resources, @"Resources/Fonts/Aquatico-Light")); + AddFont(Resources, @"Resources/Fonts/Aquatico-Regular"); + AddFont(Resources, @"Resources/Fonts/Aquatico-Light"); Textures.AddStore(new TextureLoaderStore(new ResourceStore(new StorageBackedResourceStore(storage)))); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 0845a33639..f310da3883 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -131,29 +131,29 @@ namespace osu.Game dependencies.CacheAs(this); dependencies.Cache(LocalConfig); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/osuFont")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Medium")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-MediumItalic")); + AddFont(Resources, @"Fonts/osuFont"); + AddFont(Resources, @"Fonts/Exo2.0-Medium"); + AddFont(Resources, @"Fonts/Exo2.0-MediumItalic"); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-Basic")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-Hangul")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-CJK-Basic")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-CJK-Compatibility")); + AddFont(Resources, @"Fonts/Noto-Basic"); + AddFont(Resources, @"Fonts/Noto-Hangul"); + AddFont(Resources, @"Fonts/Noto-CJK-Basic"); + AddFont(Resources, @"Fonts/Noto-CJK-Compatibility"); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Regular")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-RegularItalic")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-SemiBold")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-SemiBoldItalic")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Bold")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-BoldItalic")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Light")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-LightItalic")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Black")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-BlackItalic")); + AddFont(Resources, @"Fonts/Exo2.0-Regular"); + AddFont(Resources, @"Fonts/Exo2.0-RegularItalic"); + AddFont(Resources, @"Fonts/Exo2.0-SemiBold"); + AddFont(Resources, @"Fonts/Exo2.0-SemiBoldItalic"); + AddFont(Resources, @"Fonts/Exo2.0-Bold"); + AddFont(Resources, @"Fonts/Exo2.0-BoldItalic"); + AddFont(Resources, @"Fonts/Exo2.0-Light"); + AddFont(Resources, @"Fonts/Exo2.0-LightItalic"); + AddFont(Resources, @"Fonts/Exo2.0-Black"); + AddFont(Resources, @"Fonts/Exo2.0-BlackItalic"); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Venera")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Venera-Light")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Venera-Medium")); + AddFont(Resources, @"Fonts/Venera"); + AddFont(Resources, @"Fonts/Venera-Light"); + AddFont(Resources, @"Fonts/Venera-Medium"); runMigrations(); From f4f54bc46b6e7dc64a7e0f651ef0fb86f0f85621 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Nov 2019 13:02:15 +0900 Subject: [PATCH 2523/2815] Fix social browser calling game-wide load stalls --- osu.Game/Users/UserCoverBackground.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Users/UserCoverBackground.cs b/osu.Game/Users/UserCoverBackground.cs index a45fd85901..748d9bd939 100644 --- a/osu.Game/Users/UserCoverBackground.cs +++ b/osu.Game/Users/UserCoverBackground.cs @@ -23,6 +23,7 @@ namespace osu.Game.Users protected override Drawable CreateDrawable(User user) => new Cover(user); + [LongRunningLoad] private class Cover : CompositeDrawable { private readonly User user; From 2663e5d756854a043ef091c66a8973f7ba9b3432 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Nov 2019 13:52:23 +0900 Subject: [PATCH 2524/2815] Add some more missing LongRunningLoad flags --- osu.Game/Overlays/MedalSplash/DrawableMedal.cs | 1 + osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs | 1 + osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs | 1 + osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs | 1 + 4 files changed, 4 insertions(+) diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index f1ae5d64f5..a9b4bed334 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -16,6 +16,7 @@ using osu.Game.Users; namespace osu.Game.Overlays.MedalSplash { + [LongRunningLoad] public class DrawableMedal : Container, IStateful { private const float scale_when_unlocked = 0.76f; diff --git a/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs b/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs index ea259fe49a..7eed4d3b6b 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs @@ -12,6 +12,7 @@ using osuTK; namespace osu.Game.Overlays.Profile.Header.Components { + [LongRunningLoad] public class DrawableBadge : CompositeDrawable, IHasTooltip { public static readonly Vector2 DRAWABLE_BADGE_SIZE = new Vector2(86, 40); diff --git a/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs b/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs index 56ff4d4dec..4563510046 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Textures; namespace osu.Game.Overlays.Profile.Sections.Recent { + [LongRunningLoad] public class MedalIcon : Container { private readonly string slug; diff --git a/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs index 27cea99f1c..027c53bf58 100644 --- a/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs +++ b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs @@ -370,6 +370,7 @@ namespace osu.Game.Screens.Ranking.Pages } } + [LongRunningLoad] private class UserHeader : Container { private readonly User user; From e678fe7a773336e5ee2b8b7ed2bb0579101b9748 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Nov 2019 15:35:33 +0900 Subject: [PATCH 2525/2815] Move potentially expensive load to BDL --- osu.Game/Overlays/Rankings/Tables/RankingsTable.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index 08d396d88e..f947c5585c 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -23,15 +23,25 @@ namespace osu.Game.Overlays.Rankings.Tables private const float row_height = 25; private const int items_per_page = 50; + private readonly int page; + private readonly IReadOnlyList rankings; + protected RankingsTable(int page, IReadOnlyList rankings) { - FillFlowContainer backgroundFlow; + this.page = page; + this.rankings = rankings; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Padding = new MarginPadding { Horizontal = horizontal_inset }; RowSize = new Dimension(GridSizeMode.Absolute, row_height); + } + + [BackgroundDependencyLoader] + private void load() + { + FillFlowContainer backgroundFlow; AddInternal(backgroundFlow = new FillFlowContainer { From b51ebe443199a19806a8d8a51d03d9169fc33a44 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Nov 2019 15:50:50 +0900 Subject: [PATCH 2526/2815] Fix ScoreResultsPage usage --- osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs index 027c53bf58..43234c0b29 100644 --- a/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs +++ b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs @@ -70,7 +70,10 @@ namespace osu.Game.Screens.Ranking.Pages Direction = FillDirection.Vertical, Children = new Drawable[] { - new UserHeader(Score.User) + new DelayedLoadWrapper(new UserHeader(Score.User) + { + RelativeSizeAxes = Axes.Both, + }) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, From 3fc2afeb2629061b887220fff116def08f7343b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Nov 2019 15:51:00 +0900 Subject: [PATCH 2527/2815] Fix UserDimContainer test failing on subsequent runs --- .../Visual/Background/TestSceneUserDimContainer.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index f858174ff2..8f71584b4d 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -209,9 +209,10 @@ namespace osu.Game.Tests.Visual.Background public void TransitionTest() { performFullSetup(); - var results = new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" } }); - AddStep("Transition to Results", () => player.Push(results)); - AddUntilStep("Wait for results is current", results.IsCurrentScreen); + FadeAccessibleResults results = null; + AddStep("Transition to Results", () => player.Push(results = + new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" } }))); + AddUntilStep("Wait for results is current", () => results.IsCurrentScreen()); waitForDim(); AddAssert("Screen is undimmed, original background retained", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && results.IsBlurCorrect()); From 5dbc32f49be13313bdc37835fbd38e6cca956113 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Nov 2019 16:19:57 +0900 Subject: [PATCH 2528/2815] Fix MedalIcon usage --- .../Profile/Sections/DrawableProfileRow.cs | 35 +++++++++---------- .../Sections/Recent/DrawableRecentActivity.cs | 7 ++-- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs b/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs index 23fe6e9cd5..03ee29d0c2 100644 --- a/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs +++ b/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs @@ -19,8 +19,8 @@ namespace osu.Game.Overlays.Profile.Sections private const int fade_duration = 200; private Box underscoreLine; - private readonly Box coloredBackground; - private readonly Container background; + private Box coloredBackground; + private Container background; /// /// A visual element displayed to the left of content. @@ -36,6 +36,19 @@ namespace osu.Game.Overlays.Profile.Sections { RelativeSizeAxes = Axes.X; Height = 60; + + Content = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Width = 0.97f, + }; + } + + [BackgroundDependencyLoader(true)] + private void load(OsuColour colour) + { InternalChildren = new Drawable[] { background = new Container @@ -53,21 +66,7 @@ namespace osu.Game.Overlays.Profile.Sections }, Child = coloredBackground = new Box { RelativeSizeAxes = Axes.Both } }, - Content = new Container - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Width = 0.97f, - }, - }; - } - - [BackgroundDependencyLoader(true)] - private void load(OsuColour colour) - { - AddRange(new Drawable[] - { + Content, underscoreLine = new Box { Anchor = Anchor.BottomCentre, @@ -101,7 +100,7 @@ namespace osu.Game.Overlays.Profile.Sections Origin = Anchor.CentreRight, Direction = FillDirection.Vertical, }, - }); + }; coloredBackground.Colour = underscoreLine.Colour = colour.Gray4; } diff --git a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs index b5a508bff7..4e856845ac 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs @@ -66,11 +66,14 @@ namespace osu.Game.Overlays.Profile.Sections.Recent }; case RecentActivityType.Achievement: - return new MedalIcon(activity.Achievement.Slug) + return new DelayedLoadWrapper(new MedalIcon(activity.Achievement.Slug) + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + }) { RelativeSizeAxes = Axes.Y, Width = 60, - FillMode = FillMode.Fit, }; default: From f181ee18434cea3c22f09ae470a3bc651cb6e8d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Nov 2019 17:35:11 +0900 Subject: [PATCH 2529/2815] Hide the menu cursor while inside the playfield by default --- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 3 +++ osu.Game/Rulesets/UI/DrawableRuleset.cs | 4 ++++ osu.Game/Rulesets/UI/Playfield.cs | 18 +++++++++++++++--- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index aa61fb6922..49aea52902 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -18,6 +18,7 @@ using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; +using osuTK; namespace osu.Game.Rulesets.Osu.UI { @@ -30,6 +31,8 @@ namespace osu.Game.Rulesets.Osu.UI { } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // always show the gameplay cursor + public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(this); protected override Playfield CreatePlayfield() => new OsuPlayfield(); diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 5ed1e5a368..96275c1274 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -34,6 +34,7 @@ using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; +using osuTK; namespace osu.Game.Rulesets.UI { @@ -331,6 +332,9 @@ namespace osu.Game.Rulesets.UI protected override bool OnHover(HoverEvent e) => true; // required for IProvideCursor + // only show the cursor when within the playfield, by default. + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Playfield.ReceivePositionalInputAt(screenSpacePos); + CursorContainer IProvideCursor.Cursor => Playfield.Cursor; public override GameplayCursorContainer Cursor => Playfield.Cursor; diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index ca4983e3d7..047047ccfd 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -100,10 +100,13 @@ namespace osu.Game.Rulesets.UI public GameplayCursorContainer Cursor { get; private set; } /// - /// Provide an optional cursor which is to be used for gameplay. + /// Provide a cursor which is to be used for gameplay. /// - /// The cursor, or null if a cursor is not rqeuired. - protected virtual GameplayCursorContainer CreateCursor() => null; + /// + /// The default provided cursor is invisible when inside the bounds of the . + /// + /// The cursor, or null to show the menu cursor. + protected virtual GameplayCursorContainer CreateCursor() => new InvisibleCursorContainer(); /// /// Registers a as a nested . @@ -143,5 +146,14 @@ namespace osu.Game.Rulesets.UI /// Creates the container that will be used to contain the s. /// protected virtual HitObjectContainer CreateHitObjectContainer() => new HitObjectContainer(); + + public class InvisibleCursorContainer : GameplayCursorContainer + { + protected override Drawable CreateCursor() => new InvisibleCursor(); + + private class InvisibleCursor : Drawable + { + } + } } } From 4f081f2fe897ca4700696ab3e5d26a94aefb77c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Nov 2019 17:38:28 +0900 Subject: [PATCH 2530/2815] Remove catch-specific cursor provider --- .../UI/CatchCursorContainer.cs | 17 ----------------- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 3 --- 2 files changed, 20 deletions(-) delete mode 100644 osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs diff --git a/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs deleted file mode 100644 index 163718f202..0000000000 --- a/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs +++ /dev/null @@ -1,17 +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.Graphics; -using osu.Game.Rulesets.UI; - -namespace osu.Game.Rulesets.Catch.UI -{ - public class CatchCursorContainer : GameplayCursorContainer - { - protected override Drawable CreateCursor() => new InvisibleCursor(); - - private class InvisibleCursor : Drawable - { - } - } -} diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 7741096da2..b6d8cf9cbe 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -9,7 +9,6 @@ using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Catch.UI @@ -20,8 +19,6 @@ namespace osu.Game.Rulesets.Catch.UI internal readonly CatcherArea CatcherArea; - protected override GameplayCursorContainer CreateCursor() => new CatchCursorContainer(); - public CatchPlayfield(BeatmapDifficulty difficulty, Func> createDrawableRepresentation) { Container explodingFruitContainer; From 3864ea6ca3ddf8d59568558448fde9597af1a44a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Nov 2019 18:24:46 +0900 Subject: [PATCH 2531/2815] Move position updating logic back to CatcherArea --- osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs | 11 ++--------- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs index 062c0f8b26..a2554850c0 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.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 osu.Framework.Graphics; @@ -38,14 +38,7 @@ namespace osu.Game.Rulesets.Catch.Mods protected override bool OnMouseMove(MouseMoveEvent e) { - //lock catcher to mouse position horizontally - catcher.X = e.MousePosition.X / DrawSize.X; - - //make Yuzu face the direction he's moving - var direction = Math.Sign(e.Delta.X); - if (direction != 0) - catcher.Scale = new Vector2(Math.Abs(catcher.Scale.X) * direction, catcher.Scale.Y); - + catcher.UpdatePosition(e.MousePosition.X / DrawSize.X); return base.OnMouseMove(e); } } diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 49393a836f..2d6ce02e45 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -377,8 +377,7 @@ namespace osu.Game.Rulesets.Catch.UI double dashModifier = Dashing ? 1 : 0.5; double speed = BASE_SPEED * dashModifier * hyperDashModifier; - Scale = new Vector2(Math.Abs(Scale.X) * direction, Scale.Y); - X = (float)Math.Clamp(X + direction * Clock.ElapsedFrameTime * speed, 0, 1); + UpdatePosition((float)(X + direction * Clock.ElapsedFrameTime * speed)); // Correct overshooting. if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) || @@ -452,6 +451,17 @@ namespace osu.Game.Rulesets.Catch.UI fruit.LifetimeStart = Time.Current; fruit.Expire(); } + + public void UpdatePosition(float position) + { + position = Math.Clamp(position, 0, 1); + + if (position == X) + return; + + Scale = new Vector2(Math.Abs(Scale.X) * (position > X ? 1 : -1), Scale.Y); + X = position; + } } } } From 63128c9465669cda7e699d608cc33aeb46b8c844 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Nov 2019 18:25:11 +0900 Subject: [PATCH 2532/2815] Extend mouse hiding in catch to include catcher area --- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index b6d8cf9cbe..589503c35b 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -10,6 +10,7 @@ using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; +using osuTK; namespace osu.Game.Rulesets.Catch.UI { @@ -19,6 +20,8 @@ namespace osu.Game.Rulesets.Catch.UI internal readonly CatcherArea CatcherArea; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || CatcherArea.ReceivePositionalInputAt(screenSpacePos); + public CatchPlayfield(BeatmapDifficulty difficulty, Func> createDrawableRepresentation) { Container explodingFruitContainer; From c7305f0b4462cf77440d9f74dc42e37f9e7fe6ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Nov 2019 18:25:24 +0900 Subject: [PATCH 2533/2815] Simplify implementation structure --- osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs index a2554850c0..a47efcc10a 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs @@ -1,8 +1,7 @@ -// 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 osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; @@ -11,7 +10,6 @@ using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using osuTK; -using System; namespace osu.Game.Rulesets.Catch.Mods { @@ -19,16 +17,20 @@ namespace osu.Game.Rulesets.Catch.Mods { public override string Description => @"Use the mouse to control the catcher."; - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) => - ((Container)drawableRuleset.Playfield.Parent).Add(new CatchModRelaxHelper(drawableRuleset.Playfield as CatchPlayfield)); + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + drawableRuleset.Cursor.Add(new MouseInputHelper((CatchPlayfield)drawableRuleset.Playfield)); + } - private class CatchModRelaxHelper : Drawable, IKeyBindingHandler, IRequireHighFrequencyMousePosition + private class MouseInputHelper : Drawable, IKeyBindingHandler, IRequireHighFrequencyMousePosition { private readonly CatcherArea.Catcher catcher; - public CatchModRelaxHelper(CatchPlayfield catchPlayfield) + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + + public MouseInputHelper(CatchPlayfield playfield) { - catcher = catchPlayfield.CatcherArea.MovableCatcher; + catcher = playfield.CatcherArea.MovableCatcher; RelativeSizeAxes = Axes.Both; } From c49aeb08c4f2a135831f245eac39c2d6cdf976e7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Nov 2019 20:03:14 +0900 Subject: [PATCH 2534/2815] Add API methods to perform requests out-of-queue --- osu.Game.Tournament/TournamentGameBase.cs | 2 +- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- .../DownloadableArchiveModelManager.cs | 12 +----------- osu.Game/Online/API/APIAccess.cs | 17 +++++++++++++++++ osu.Game/Online/API/DummyAPIAccess.cs | 5 +++++ osu.Game/Online/API/IAPIProvider.cs | 19 +++++++++++++++++++ .../Changelog/ChangelogSingleBuild.cs | 14 +------------- osu.Game/Overlays/ChangelogOverlay.cs | 10 +--------- 8 files changed, 46 insertions(+), 35 deletions(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 6456ce2d58..4d7abfe272 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -228,7 +228,7 @@ namespace osu.Game.Tournament if (b.BeatmapInfo == null && b.ID > 0) { var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID }); - req.Perform(API); + API.Perform(req); b.BeatmapInfo = req.Result?.ToBeatmap(RulesetStore); addedInfo = true; diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index bc78e50f5d..a2e750cac5 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -398,7 +398,7 @@ namespace osu.Game.Beatmaps try { // intentionally blocking to limit web request concurrency - req.Perform(api); + api.Perform(req); var res = req.Result; diff --git a/osu.Game/Database/DownloadableArchiveModelManager.cs b/osu.Game/Database/DownloadableArchiveModelManager.cs index 0b7d63f469..243060388f 100644 --- a/osu.Game/Database/DownloadableArchiveModelManager.cs +++ b/osu.Game/Database/DownloadableArchiveModelManager.cs @@ -99,17 +99,7 @@ namespace osu.Game.Database currentDownloads.Add(request); PostNotification?.Invoke(notification); - Task.Factory.StartNew(() => - { - try - { - request.Perform(api); - } - catch (Exception error) - { - triggerFailure(error); - } - }, TaskCreationOptions.LongRunning); + api.PerformAsync(request); DownloadBegan?.Invoke(request); return true; diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index b6127027cc..1c45d26afd 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Net; using System.Net.Http; using System.Threading; +using System.Threading.Tasks; using Newtonsoft.Json.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -198,6 +199,22 @@ namespace osu.Game.Online.API } } + public void Perform(APIRequest request) + { + try + { + request.Perform(this); + } + catch (Exception e) + { + // todo: fix exception handling + request.Fail(e); + } + } + + public Task PerformAsync(APIRequest request) => + Task.Factory.StartNew(() => Perform(request), TaskCreationOptions.LongRunning); + public void Login(string username, string password) { Debug.Assert(State == APIState.Offline); diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 28132765d3..7f23f9b5d5 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading; +using System.Threading.Tasks; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Users; @@ -56,6 +57,10 @@ namespace osu.Game.Online.API { } + public void Perform(APIRequest request) { } + + public Task PerformAsync(APIRequest request) => Task.CompletedTask; + public void Register(IOnlineComponent component) { Scheduler.Add(delegate { components.Add(component); }); diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index 0cd41aee26..dff6d0b2ce 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.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.Threading.Tasks; using osu.Framework.Bindables; using osu.Game.Users; @@ -42,6 +43,24 @@ namespace osu.Game.Online.API /// The request to perform. void Queue(APIRequest request); + /// + /// Perform a request immediately, bypassing any API state checks. + /// + /// + /// Can be used to run requests as a guest user. + /// + /// The request to perform. + void Perform(APIRequest request); + + /// + /// Perform a request immediately, bypassing any API state checks. + /// + /// + /// Can be used to run requests as a guest user. + /// + /// The request to perform. + Task PerformAsync(APIRequest request); + /// /// Register a component to receive state changes. /// diff --git a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs index 3297b00322..67bcb6f558 100644 --- a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs @@ -4,7 +4,6 @@ using System; using System.Linq; using System.Threading; -using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -43,18 +42,7 @@ namespace osu.Game.Overlays.Changelog }; req.Failure += _ => complete = true; - // This is done on a separate thread to support cancellation below - Task.Run(() => - { - try - { - req.Perform(api); - } - catch - { - complete = true; - } - }); + api.PerformAsync(req); while (!complete) { diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index 559989af5c..fbc9dfcbd9 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -191,15 +191,7 @@ namespace osu.Game.Overlays tcs.SetResult(false); }; - try - { - req.Perform(API); - } - catch - { - initialFetchTask = null; - tcs.SetResult(false); - } + await API.PerformAsync(req); await tcs.Task; }); From c4515429158bb5ed4a7a14b10b65f218fd8cd481 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 30 Nov 2019 03:01:07 +0300 Subject: [PATCH 2535/2815] Rankings overlay basic implementation --- .../Visual/Online/TestSceneRankingsOverlay.cs | 59 ++++++ osu.Game/Overlays/RankingsOverlay.cs | 198 ++++++++++++++++++ osu.Game/Users/Drawables/UpdateableFlag.cs | 14 ++ 3 files changed, 271 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs create mode 100644 osu.Game/Overlays/RankingsOverlay.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs new file mode 100644 index 0000000000..1f08fe7530 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs @@ -0,0 +1,59 @@ +// 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.Overlays.Rankings.Tables; +using osu.Framework.Allocation; +using osu.Game.Overlays; +using NUnit.Framework; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneRankingsOverlay : OsuTestScene + { + protected override bool UseOnlineAPI => true; + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(PerformanceTable), + typeof(ScoresTable), + typeof(CountriesTable), + typeof(TableRowBackground), + typeof(UserBasedTable), + typeof(RankingsTable<>), + typeof(RankingsOverlay) + }; + + [Cached] + private RankingsOverlay rankingsOverlay; + + public TestSceneRankingsOverlay() + { + Add(rankingsOverlay = new RankingsOverlay()); + } + + [Test] + public void TestShow() + { + AddStep("Show", rankingsOverlay.Show); + } + + [Test] + public void TestShowCountry() + { + AddStep("Show US", () => rankingsOverlay.ShowCountry(new Country + { + FlagName = "US", + FullName = "United States" + })); + } + + [Test] + public void TestHide() + { + AddStep("Hide", rankingsOverlay.Hide); + } + } +} diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs new file mode 100644 index 0000000000..b9b2fe7232 --- /dev/null +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -0,0 +1,198 @@ +// 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.Game.Graphics; +using osu.Game.Overlays.Rankings; +using osu.Game.Users; +using osu.Game.Rulesets; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using System.Threading; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Rankings.Tables; + +namespace osu.Game.Overlays +{ + public class RankingsOverlay : FullscreenOverlay + { + private readonly Bindable country = new Bindable(); + private readonly Bindable scope = new Bindable(); + private readonly Bindable ruleset = new Bindable(); + + private readonly BasicScrollContainer scrollFlow; + private readonly Box background; + private readonly Container contentPlaceholder; + private readonly DimmedLoadingLayer loading; + + private APIRequest request; + private CancellationTokenSource cancellationToken; + + [Resolved] + private IAPIProvider api { get; set; } + + public RankingsOverlay() + { + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + scrollFlow = new BasicScrollContainer + { + RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new RankingsHeader + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Country = { BindTarget = country }, + Scope = { BindTarget = scope }, + Ruleset = { BindTarget = ruleset } + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + contentPlaceholder = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Vertical = 10 } + }, + loading = new DimmedLoadingLayer(), + } + } + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colour) + { + Waves.FirstWaveColour = colour.Green; + Waves.SecondWaveColour = colour.GreenLight; + Waves.ThirdWaveColour = colour.GreenDark; + Waves.FourthWaveColour = colour.GreenDarker; + + background.Colour = OsuColour.Gray(0.1f); + } + + protected override void LoadComplete() + { + country.BindValueChanged(_ => redraw(), true); + scope.BindValueChanged(_ => redraw(), true); + ruleset.BindValueChanged(_ => redraw(), true); + base.LoadComplete(); + } + + public void ShowCountry(Country requested) + { + if (requested == null) + return; + + Show(); + + if (country.Value?.FlagName == requested.FlagName) + return; + + country.Value = requested; + } + + private void redraw() + { + scrollFlow.ScrollToStart(); + + loading.Show(); + + cancellationToken?.Cancel(); + request?.Cancel(); + + cancellationToken = new CancellationTokenSource(); + + switch (scope.Value) + { + default: + contentPlaceholder.Clear(); + loading.Hide(); + return; + + case RankingsScope.Performance: + createPerformanceTable(); + return; + + case RankingsScope.Country: + createCountryTable(); + return; + + case RankingsScope.Score: + createScoreTable(); + return; + } + } + + private void createCountryTable() + { + request = new GetCountryRankingsRequest(ruleset.Value); + ((GetCountryRankingsRequest)request).Success += rankings => Schedule(() => + { + var table = new CountriesTable(1, rankings.Countries); + loadTable(table); + }); + + api.Queue(request); + } + + private void createPerformanceTable() + { + request = new GetUserRankingsRequest(ruleset.Value, country: country.Value?.FlagName); + ((GetUserRankingsRequest)request).Success += rankings => Schedule(() => + { + var table = new PerformanceTable(1, rankings.Users); + loadTable(table); + }); + + api.Queue(request); + } + + private void createScoreTable() + { + request = new GetUserRankingsRequest(ruleset.Value, UserRankingsType.Score); + ((GetUserRankingsRequest)request).Success += rankings => Schedule(() => + { + var table = new ScoresTable(1, rankings.Users); + loadTable(table); + }); + + api.Queue(request); + } + + private void loadTable(Drawable table) + { + LoadComponentAsync(table, t => + { + contentPlaceholder.Clear(); + contentPlaceholder.Add(t); + loading.Hide(); + }, cancellationToken.Token); + } + } +} diff --git a/osu.Game/Users/Drawables/UpdateableFlag.cs b/osu.Game/Users/Drawables/UpdateableFlag.cs index abc16b2390..e7c23d3c23 100644 --- a/osu.Game/Users/Drawables/UpdateableFlag.cs +++ b/osu.Game/Users/Drawables/UpdateableFlag.cs @@ -1,8 +1,11 @@ // 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.Input.Events; +using osu.Game.Overlays; namespace osu.Game.Users.Drawables { @@ -34,5 +37,16 @@ namespace osu.Game.Users.Drawables RelativeSizeAxes = Axes.Both, }; } + + [Resolved(canBeNull: true)] + private RankingsOverlay rankingsOverlay { get; set; } + + protected override bool OnClick(ClickEvent e) + { + if (Country != null) + rankingsOverlay?.ShowCountry(Country); + + return true; + } } } From 0ac46755468b9bf840c816707674a626ae7e95c6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 1 Dec 2019 03:52:41 +0300 Subject: [PATCH 2536/2815] Implement IEquatable --- osu.Game/Overlays/RankingsOverlay.cs | 3 --- osu.Game/Users/Country.cs | 5 ++++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index b9b2fe7232..d3586a538b 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -111,9 +111,6 @@ namespace osu.Game.Overlays Show(); - if (country.Value?.FlagName == requested.FlagName) - return; - country.Value = requested; } diff --git a/osu.Game/Users/Country.cs b/osu.Game/Users/Country.cs index 1dcce6e870..101d268a60 100644 --- a/osu.Game/Users/Country.cs +++ b/osu.Game/Users/Country.cs @@ -1,11 +1,12 @@ // 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 Newtonsoft.Json; namespace osu.Game.Users { - public class Country + public class Country : IEquatable { /// /// The name of this country. @@ -18,5 +19,7 @@ namespace osu.Game.Users /// [JsonProperty(@"code")] public string FlagName; + + public bool Equals(Country other) => FlagName == other.FlagName; } } From f375db368f1ac0208d7dd4dd5497da93ec6ab448 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 1 Dec 2019 03:56:03 +0300 Subject: [PATCH 2537/2815] Remove useless null check --- osu.Game/Users/Drawables/UpdateableFlag.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Users/Drawables/UpdateableFlag.cs b/osu.Game/Users/Drawables/UpdateableFlag.cs index e7c23d3c23..1d30720889 100644 --- a/osu.Game/Users/Drawables/UpdateableFlag.cs +++ b/osu.Game/Users/Drawables/UpdateableFlag.cs @@ -43,9 +43,7 @@ namespace osu.Game.Users.Drawables protected override bool OnClick(ClickEvent e) { - if (Country != null) - rankingsOverlay?.ShowCountry(Country); - + rankingsOverlay?.ShowCountry(Country); return true; } } From 62daea195c5ab9b90d170c878c5fbaba5161e360 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 1 Dec 2019 04:09:45 +0300 Subject: [PATCH 2538/2815] Fix possible null --- osu.Game/Users/Country.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Users/Country.cs b/osu.Game/Users/Country.cs index 101d268a60..a9fcd69286 100644 --- a/osu.Game/Users/Country.cs +++ b/osu.Game/Users/Country.cs @@ -20,6 +20,6 @@ namespace osu.Game.Users [JsonProperty(@"code")] public string FlagName; - public bool Equals(Country other) => FlagName == other.FlagName; + public bool Equals(Country other) => FlagName == other?.FlagName; } } From 3b88afd069fd179195d4593db439e9da9247cdaf Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 1 Dec 2019 12:02:47 -0800 Subject: [PATCH 2539/2815] Fix overlays closing when dragging from in/out or out/in --- .../Containers/OsuFocusedOverlayContainer.cs | 30 ++++++------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index b117d71006..4977b6b70a 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -67,33 +67,21 @@ namespace osu.Game.Graphics.Containers // receive input outside our bounds so we can trigger a close event on ourselves. public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BlockScreenWideMouse || base.ReceivePositionalInputAt(screenSpacePos); - protected override bool OnClick(ClickEvent e) - { - if (!base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) - Hide(); + private bool closeOnMouseUp; - return base.OnClick(e); + protected override bool OnMouseDown(MouseDownEvent e) + { + closeOnMouseUp = !base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition) ? true : false; + + return base.OnMouseDown(e); } - private bool closeOnDragEnd; - - protected override bool OnDragStart(DragStartEvent e) + protected override bool OnMouseUp(MouseUpEvent e) { - if (!base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) - closeOnDragEnd = true; - - return base.OnDragStart(e); - } - - protected override bool OnDragEnd(DragEndEvent e) - { - if (closeOnDragEnd) - { + if (closeOnMouseUp && !base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) Hide(); - closeOnDragEnd = false; - } - return base.OnDragEnd(e); + return base.OnMouseUp(e); } public virtual bool OnPressed(GlobalAction action) From 74176a69545d569895914ab03cfa359370408850 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 1 Dec 2019 12:54:37 -0800 Subject: [PATCH 2540/2815] Remove redundant ternary expression --- osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 4977b6b70a..facf70b47a 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -71,7 +71,7 @@ namespace osu.Game.Graphics.Containers protected override bool OnMouseDown(MouseDownEvent e) { - closeOnMouseUp = !base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition) ? true : false; + closeOnMouseUp = !base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition); return base.OnMouseDown(e); } From 59f956bb01e550e0723355467275b17348e32fe6 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2019 07:18:54 +0000 Subject: [PATCH 2541/2815] Bump Microsoft.CodeAnalysis.BannedApiAnalyzers from 2.9.7 to 2.9.8 Bumps [Microsoft.CodeAnalysis.BannedApiAnalyzers](https://github.com/dotnet/roslyn-analyzers) from 2.9.7 to 2.9.8. - [Release notes](https://github.com/dotnet/roslyn-analyzers/releases) - [Changelog](https://github.com/dotnet/roslyn-analyzers/blob/master/PostReleaseActivities.md) - [Commits](https://github.com/dotnet/roslyn-analyzers/compare/v2.9.7...v2.9.8) Signed-off-by: dependabot-preview[bot] --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index c8d441a78c..c0d740bac1 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -16,7 +16,7 @@ - + From 6893ec22bbf7610c2736e7ba485650c5bef4f722 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Dec 2019 16:14:52 +0900 Subject: [PATCH 2542/2815] Fix currenTrackCompleted not being run on main thread --- osu.Game/OsuGame.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e978df404f..c7c746bed3 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -406,11 +406,11 @@ namespace osu.Game nextBeatmap?.LoadBeatmapAsync(); } - private void currentTrackCompleted() + private void currentTrackCompleted() => Schedule(() => { if (!Beatmap.Value.Track.Looping && !Beatmap.Disabled) musicController.NextTrack(); - } + }); #endregion From 1ec11946c63f56467de94a303ed1be07272a1a44 Mon Sep 17 00:00:00 2001 From: recapitalverb <41869184+recapitalverb@users.noreply.github.com> Date: Mon, 2 Dec 2019 18:45:57 +0700 Subject: [PATCH 2543/2815] Fix spelling in comment on SearchTextBox.OnPressed --- osu.Game/Graphics/UserInterface/SearchTextBox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/SearchTextBox.cs b/osu.Game/Graphics/UserInterface/SearchTextBox.cs index e2b0e1b425..ff3618b263 100644 --- a/osu.Game/Graphics/UserInterface/SearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/SearchTextBox.cs @@ -35,7 +35,7 @@ namespace osu.Game.Graphics.UserInterface public override bool OnPressed(PlatformAction action) { // Shift+delete is handled via PlatformAction on macOS. this is not so useful in the context of a SearchTextBox - // as we do not allow arrow key navigation in the first place (ie. the care should always be at the end of text) + // as we do not allow arrow key navigation in the first place (ie. the caret should always be at the end of text) // Avoid handling it here to allow other components to potentially consume the shortcut. if (action.ActionType == PlatformActionType.CharNext && action.ActionMethod == PlatformActionMethod.Delete) return false; From aadbbb1af3c100e8145332384b8b07bb5c4bcff9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 3 Dec 2019 13:33:42 +0900 Subject: [PATCH 2544/2815] Fix replay download button not working --- osu.Game.Tests/Scores/IO/TestScoreEquality.cs | 73 +++++++++++++++++++ osu.Game/Scoring/ScoreInfo.cs | 21 ++++-- 2 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 osu.Game.Tests/Scores/IO/TestScoreEquality.cs diff --git a/osu.Game.Tests/Scores/IO/TestScoreEquality.cs b/osu.Game.Tests/Scores/IO/TestScoreEquality.cs new file mode 100644 index 0000000000..d1374eb6e5 --- /dev/null +++ b/osu.Game.Tests/Scores/IO/TestScoreEquality.cs @@ -0,0 +1,73 @@ +// 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.Scoring; + +namespace osu.Game.Tests.Scores.IO +{ + [TestFixture] + public class TestScoreEquality + { + [Test] + public void TestNonMatchingByReference() + { + ScoreInfo score1 = new ScoreInfo(); + ScoreInfo score2 = new ScoreInfo(); + + Assert.That(score1, Is.Not.EqualTo(score2)); + } + + [Test] + public void TestMatchingByReference() + { + ScoreInfo score = new ScoreInfo(); + + Assert.That(score, Is.EqualTo(score)); + } + + [Test] + public void TestNonMatchingByPrimaryKey() + { + ScoreInfo score1 = new ScoreInfo { ID = 1 }; + ScoreInfo score2 = new ScoreInfo { ID = 2 }; + + Assert.That(score1, Is.Not.EqualTo(score2)); + } + + [Test] + public void TestMatchingByPrimaryKey() + { + ScoreInfo score1 = new ScoreInfo { ID = 1 }; + ScoreInfo score2 = new ScoreInfo { ID = 1 }; + + Assert.That(score1, Is.EqualTo(score2)); + } + + [Test] + public void TestNonMatchingByHash() + { + ScoreInfo score1 = new ScoreInfo { Hash = "a" }; + ScoreInfo score2 = new ScoreInfo { Hash = "b" }; + + Assert.That(score1, Is.Not.EqualTo(score2)); + } + + [Test] + public void TestMatchingByHash() + { + ScoreInfo score1 = new ScoreInfo { Hash = "a" }; + ScoreInfo score2 = new ScoreInfo { Hash = "a" }; + + Assert.That(score1, Is.EqualTo(score2)); + } + + [Test] + public void TestNonMatchingByNull() + { + ScoreInfo score = new ScoreInfo(); + + Assert.That(score, Is.Not.EqualTo(null)); + } + } +} diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index f7bac82e74..c7609e8a0b 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -183,10 +183,21 @@ namespace osu.Game.Scoring public override string ToString() => $"{User} playing {Beatmap}"; - public bool Equals(ScoreInfo other) => - other != null - && other.OnlineScoreID == OnlineScoreID - && other.BeatmapInfoID == BeatmapInfoID - && other.Hash == Hash; + public bool Equals(ScoreInfo other) + { + if (other == null) + return false; + + if (ID != 0 && other.ID != 0) + return ID == other.ID; + + if (OnlineScoreID.HasValue && other.OnlineScoreID.HasValue) + return OnlineScoreID == other.OnlineScoreID; + + if (!string.IsNullOrEmpty(Hash) && !string.IsNullOrEmpty(other.Hash)) + return Hash == other.Hash; + + return ReferenceEquals(this, other); + } } } From f0d49d0cdf3283f8d1680ab118adc3f0f5e4315d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Dec 2019 15:28:10 +0900 Subject: [PATCH 2545/2815] Decouple APILegacyScoreInfo from ScoreInfo --- .../Difficulty/CatchPerformanceCalculator.cs | 10 +- .../Gameplay/TestSceneReplayDownloadButton.cs | 11 +- .../Visual/Online/TestSceneScoresContainer.cs | 50 +++-- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 3 +- .../TestSceneUserTopScoreContainer.cs | 3 +- .../Online/API/Requests/GetScoresRequest.cs | 7 +- .../Requests/Responses/APILegacyScoreInfo.cs | 161 ++++++++-------- .../BeatmapSet/Scores/ScoresContainer.cs | 14 +- .../Sections/Ranks/PaginatedScoreContainer.cs | 12 +- osu.Game/Scoring/Legacy/LegacyScoreInfo.cs | 177 ++++++++++-------- osu.Game/Scoring/Legacy/LegacyScoreParser.cs | 14 +- .../Ranking/Pages/ReplayDownloadButton.cs | 3 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 5 +- .../Leaderboards/UserTopScoreContainer.cs | 11 +- 14 files changed, 254 insertions(+), 227 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index 3c237c86be..a6283eb7c4 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -34,12 +34,10 @@ namespace osu.Game.Rulesets.Catch.Difficulty { mods = Score.Mods; - var legacyScore = Score as LegacyScoreInfo; - - fruitsHit = legacyScore?.Count300 ?? Score.Statistics[HitResult.Perfect]; - ticksHit = legacyScore?.Count100 ?? 0; - tinyTicksHit = legacyScore?.Count50 ?? 0; - tinyTicksMissed = legacyScore?.CountKatu ?? 0; + fruitsHit = Score?.GetCount300() ?? Score.Statistics[HitResult.Perfect]; + ticksHit = Score?.GetCount100() ?? 0; + tinyTicksHit = Score?.GetCount50() ?? 0; + tinyTicksMissed = Score?.GetCountKatu() ?? 0; misses = Score.Statistics[HitResult.Miss]; // Don't count scores made with supposedly unranked mods diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 7b22fedbd5..8cb44de8cb 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -5,11 +5,12 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Users; using System; using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Game.Rulesets; using osu.Game.Screens.Ranking.Pages; namespace osu.Game.Tests.Visual.Gameplay @@ -17,6 +18,9 @@ namespace osu.Game.Tests.Visual.Gameplay [TestFixture] public class TestSceneReplayDownloadButton : OsuTestScene { + [Resolved] + private RulesetStore rulesets { get; set; } + public override IReadOnlyList RequiredTypes => new[] { typeof(ReplayDownloadButton) @@ -49,16 +53,15 @@ namespace osu.Game.Tests.Visual.Gameplay { return new APILegacyScoreInfo { - ID = 1, OnlineScoreID = 2553163309, - Ruleset = new OsuRuleset().RulesetInfo, + OnlineRulesetID = 0, Replay = replayAvailable, User = new User { Id = 39828, Username = @"WubWoofWolf", } - }; + }.CreateScoreInfo(rulesets); } private class TestReplayDownloadButton : ReplayDownloadButton diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index b26de1984a..3386cc41e7 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.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; @@ -9,9 +9,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.MathUtils; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet.Scores; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Users; using osuTK.Graphics; @@ -66,12 +64,12 @@ namespace osu.Game.Tests.Visual.Online FlagName = @"ES", }, }, - Mods = new Mod[] + Mods = new[] { - new OsuModDoubleTime(), - new OsuModHidden(), - new OsuModFlashlight(), - new OsuModHardRock(), + new OsuModDoubleTime().Acronym, + new OsuModHidden().Acronym, + new OsuModFlashlight().Acronym, + new OsuModHardRock().Acronym, }, Rank = ScoreRank.XH, PP = 200, @@ -91,11 +89,11 @@ namespace osu.Game.Tests.Visual.Online FlagName = @"BR", }, }, - Mods = new Mod[] + Mods = new[] { - new OsuModDoubleTime(), - new OsuModHidden(), - new OsuModFlashlight(), + new OsuModDoubleTime().Acronym, + new OsuModHidden().Acronym, + new OsuModFlashlight().Acronym, }, Rank = ScoreRank.S, PP = 190, @@ -115,10 +113,10 @@ namespace osu.Game.Tests.Visual.Online FlagName = @"JP", }, }, - Mods = new Mod[] + Mods = new[] { - new OsuModDoubleTime(), - new OsuModHidden(), + new OsuModDoubleTime().Acronym, + new OsuModHidden().Acronym, }, Rank = ScoreRank.B, PP = 180, @@ -138,9 +136,9 @@ namespace osu.Game.Tests.Visual.Online FlagName = @"CA", }, }, - Mods = new Mod[] + Mods = new[] { - new OsuModDoubleTime(), + new OsuModDoubleTime().Acronym, }, Rank = ScoreRank.C, PP = 170, @@ -208,12 +206,12 @@ namespace osu.Game.Tests.Visual.Online FlagName = @"ES", }, }, - Mods = new Mod[] + Mods = new[] { - new OsuModDoubleTime(), - new OsuModHidden(), - new OsuModFlashlight(), - new OsuModHardRock(), + new OsuModDoubleTime().Acronym, + new OsuModHidden().Acronym, + new OsuModFlashlight().Acronym, + new OsuModHardRock().Acronym, }, Rank = ScoreRank.XH, PP = 200, @@ -226,10 +224,10 @@ namespace osu.Game.Tests.Visual.Online foreach (var s in allScores.Scores) { - s.Statistics.Add(HitResult.Great, RNG.Next(2000)); - s.Statistics.Add(HitResult.Good, RNG.Next(2000)); - s.Statistics.Add(HitResult.Meh, RNG.Next(2000)); - s.Statistics.Add(HitResult.Miss, RNG.Next(2000)); + s.Statistics.Add("count_300", RNG.Next(2000)); + s.Statistics.Add("count_100", RNG.Next(2000)); + s.Statistics.Add("count_50", RNG.Next(2000)); + s.Statistics.Add("count_miss", RNG.Next(2000)); } AddStep("Load all scores", () => diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index fb27ec7654..57e297bcd5 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Scoring; using osu.Game.Screens.Select.Leaderboards; @@ -62,7 +61,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, - Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new[] { new OsuModHidden().Acronym, new OsuModHardRock().Acronym, }, User = new User { Id = 6602580, diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs index 7fac45e0f1..e34e1844ce 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics.Shapes; using osuTK.Graphics; using osu.Game.Online.API.Requests.Responses; using osu.Game.Scoring; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; @@ -52,7 +51,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, - Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new[] { new OsuModHidden().Acronym, new OsuModHardRock().Acronym, }, User = new User { Id = 6602580, diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index 50844fa256..bf3441d2a0 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -9,6 +9,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Mods; using System.Text; using System.Collections.Generic; +using System.Diagnostics; namespace osu.Game.Online.API.Requests { @@ -37,10 +38,12 @@ namespace osu.Game.Online.API.Requests private void onSuccess(APILegacyScores r) { + Debug.Assert(ruleset.ID != null, "ruleset.ID != null"); + foreach (APILegacyScoreInfo score in r.Scores) { score.Beatmap = beatmap; - score.Ruleset = ruleset; + score.OnlineRulesetID = ruleset.ID.Value; } var userScore = r.UserScore; @@ -48,7 +51,7 @@ namespace osu.Game.Online.API.Requests if (userScore != null) { userScore.Score.Beatmap = beatmap; - userScore.Score.Ruleset = ruleset; + userScore.Score.OnlineRulesetID = ruleset.ID.Value; } } diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs index 17da255873..d231b8a5df 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs @@ -5,56 +5,103 @@ using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Scoring; using osu.Game.Scoring.Legacy; using osu.Game.Users; namespace osu.Game.Online.API.Requests.Responses { - public class APILegacyScoreInfo : LegacyScoreInfo + public class APILegacyScoreInfo { - [JsonProperty(@"score")] - private int totalScore + public ScoreInfo CreateScoreInfo(RulesetStore rulesets) { - set => TotalScore = value; + var ruleset = rulesets.GetRuleset(OnlineRulesetID); + + var mods = Mods != null ? ruleset.CreateInstance().GetAllMods().Where(mod => Mods.Contains(mod.Acronym)).ToArray() : Array.Empty(); + + var scoreInfo = new ScoreInfo + { + TotalScore = TotalScore, + MaxCombo = MaxCombo, + User = User, + Accuracy = Accuracy, + OnlineScoreID = OnlineScoreID, + Date = Date, + PP = PP, + Beatmap = Beatmap, + RulesetID = OnlineRulesetID, + Hash = "online", // todo: temporary? + Rank = Rank, + Ruleset = ruleset, + Mods = mods, + }; + + foreach (var kvp in Statistics) + { + switch (kvp.Key) + { + case @"count_geki": + scoreInfo.SetCountGeki(kvp.Value); + break; + + case @"count_300": + scoreInfo.SetCount300(kvp.Value); + break; + + case @"count_katu": + scoreInfo.SetCountKatu(kvp.Value); + break; + + case @"count_100": + scoreInfo.SetCount100(kvp.Value); + break; + + case @"count_50": + scoreInfo.SetCount50(kvp.Value); + break; + + case @"count_miss": + scoreInfo.SetCountMiss(kvp.Value); + break; + } + } + + return scoreInfo; } + [JsonProperty(@"score")] + public int TotalScore { get; set; } + [JsonProperty(@"max_combo")] - private int maxCombo - { - set => MaxCombo = value; - } + public int MaxCombo { get; set; } [JsonProperty(@"user")] - private User user - { - set => User = value; - } + public User User { get; set; } [JsonProperty(@"id")] - private long onlineScoreID - { - set => OnlineScoreID = value; - } + public long OnlineScoreID { get; set; } [JsonProperty(@"replay")] public bool Replay { get; set; } [JsonProperty(@"created_at")] - private DateTimeOffset date - { - set => Date = value; - } + public DateTimeOffset Date { get; set; } [JsonProperty(@"beatmap")] - private BeatmapInfo beatmap - { - set => Beatmap = value; - } + public BeatmapInfo Beatmap { get; set; } + + [JsonProperty("accuracy")] + public double Accuracy { get; set; } + + [JsonProperty(@"pp")] + public double? PP { get; set; } [JsonProperty(@"beatmapset")] - private BeatmapMetadata metadata + public BeatmapMetadata Metadata { set { @@ -67,68 +114,16 @@ namespace osu.Game.Online.API.Requests.Responses } [JsonProperty(@"statistics")] - private Dictionary jsonStats - { - set - { - foreach (var kvp in value) - { - switch (kvp.Key) - { - case @"count_geki": - CountGeki = kvp.Value; - break; - - case @"count_300": - Count300 = kvp.Value; - break; - - case @"count_katu": - CountKatu = kvp.Value; - break; - - case @"count_100": - Count100 = kvp.Value; - break; - - case @"count_50": - Count50 = kvp.Value; - break; - - case @"count_miss": - CountMiss = kvp.Value; - break; - - default: - continue; - } - } - } - } + public Dictionary Statistics { get; set; } [JsonProperty(@"mode_int")] - public int OnlineRulesetID - { - get => RulesetID; - set => RulesetID = value; - } + public int OnlineRulesetID { get; set; } [JsonProperty(@"mods")] - private string[] modStrings { get; set; } + public string[] Mods { get; set; } - public override RulesetInfo Ruleset - { - get => base.Ruleset; - set - { - base.Ruleset = value; - - if (modStrings != null) - { - // Evaluate the mod string - Mods = Ruleset.CreateInstance().GetAllMods().Where(mod => modStrings.Contains(mod.Acronym)).ToArray(); - } - } - } + [JsonProperty("rank")] + [JsonConverter(typeof(StringEnumConverter))] + public ScoreRank Rank { get; set; } } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index f06f2ac802..0378d364b8 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -41,6 +41,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores [Resolved] private IAPIProvider api { get; set; } + [Resolved] + private RulesetStore rulesets { get; set; } + private GetScoresRequest getScoresRequest; protected APILegacyScores Scores @@ -56,16 +59,19 @@ namespace osu.Game.Overlays.BeatmapSet.Scores return; } - scoreTable.Scores = value.Scores; + var scoreInfos = value.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToList(); + + scoreTable.Scores = scoreInfos; scoreTable.Show(); - var topScore = value.Scores.First(); + var topScore = scoreInfos.First(); var userScore = value.UserScore; + var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets); topScoresContainer.Add(new DrawableTopScore(topScore)); - if (userScore != null && userScore.Score.OnlineScoreID != topScore.OnlineScoreID) - topScoresContainer.Add(new DrawableTopScore(userScore.Score, userScore.Position)); + if (userScoreInfo != null && userScoreInfo.OnlineScoreID != topScore.OnlineScoreID) + topScoresContainer.Add(new DrawableTopScore(userScoreInfo, userScore.Position)); }); } diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 853b9db0a7..5b58fc0930 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -29,14 +29,6 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks ItemsContainer.Direction = FillDirection.Vertical; } - protected override void UpdateItems(List items) - { - foreach (var item in items) - item.Ruleset = Rulesets.GetRuleset(item.RulesetID); - - base.UpdateItems(items); - } - protected override APIRequest> CreateRequest() => new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); @@ -45,10 +37,10 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks switch (type) { default: - return new DrawablePerformanceScore(model, includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null); + return new DrawablePerformanceScore(model.CreateScoreInfo(Rulesets), includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null); case ScoreType.Recent: - return new DrawableTotalScore(model); + return new DrawableTotalScore(model.CreateScoreInfo(Rulesets)); } } } diff --git a/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs b/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs index e66f93ec8d..2ee450b501 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs @@ -5,114 +5,139 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Scoring.Legacy { - public class LegacyScoreInfo : ScoreInfo + public static class ScoreInfoLegacyExtensions { - private int countGeki; - - public int CountGeki + public static int? GetCountGeki(this ScoreInfo scoreInfo) { - get => countGeki; - set + switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) { - countGeki = value; + case 3: + return scoreInfo.Statistics[HitResult.Perfect]; + } - switch (Ruleset?.ID ?? RulesetID) - { - case 3: - Statistics[HitResult.Perfect] = value; - break; - } + return null; + } + + public static void SetCountGeki(this ScoreInfo scoreInfo, int value) + { + switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) + { + case 3: + scoreInfo.Statistics[HitResult.Perfect] = value; + break; } } - private int count300; - - public int Count300 + public static int? GetCount300(this ScoreInfo scoreInfo) { - get => count300; - set + switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) { - count300 = value; + case 0: + case 1: + case 3: + return scoreInfo.Statistics[HitResult.Great]; - switch (Ruleset?.ID ?? RulesetID) - { - case 0: - case 1: - case 3: - Statistics[HitResult.Great] = value; - break; + case 2: + return scoreInfo.Statistics[HitResult.Perfect]; + } - case 2: - Statistics[HitResult.Perfect] = value; - break; - } + return null; + } + + public static void SetCount300(this ScoreInfo scoreInfo, int value) + { + switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) + { + case 0: + case 1: + case 3: + scoreInfo.Statistics[HitResult.Great] = value; + break; + + case 2: + scoreInfo.Statistics[HitResult.Perfect] = value; + break; } } - private int countKatu; - - public int CountKatu + public static int? GetCountKatu(this ScoreInfo scoreInfo) { - get => countKatu; - set + switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) { - countKatu = value; + case 3: + return scoreInfo.Statistics[HitResult.Good]; + } - switch (Ruleset?.ID ?? RulesetID) - { - case 3: - Statistics[HitResult.Good] = value; - break; - } + return null; + } + + public static void SetCountKatu(this ScoreInfo scoreInfo, int value) + { + switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) + { + case 3: + scoreInfo.Statistics[HitResult.Good] = value; + break; } } - private int count100; - - public int Count100 + public static int? GetCount100(this ScoreInfo scoreInfo) { - get => count100; - set + switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) { - count100 = value; + case 0: + case 1: + return scoreInfo.Statistics[HitResult.Good]; - switch (Ruleset?.ID ?? RulesetID) - { - case 0: - case 1: - Statistics[HitResult.Good] = value; - break; + case 3: + return scoreInfo.Statistics[HitResult.Ok]; + } - case 3: - Statistics[HitResult.Ok] = value; - break; - } + return null; + } + + public static void SetCount100(this ScoreInfo scoreInfo, int value) + { + switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) + { + case 0: + case 1: + scoreInfo.Statistics[HitResult.Good] = value; + break; + + case 3: + scoreInfo.Statistics[HitResult.Ok] = value; + break; } } - private int count50; - - public int Count50 + public static int? GetCount50(this ScoreInfo scoreInfo) { - get => count50; - set + switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) { - count50 = value; + case 0: + case 3: + return scoreInfo.Statistics[HitResult.Meh]; + } - switch (Ruleset?.ID ?? RulesetID) - { - case 0: - case 3: - Statistics[HitResult.Meh] = value; - break; - } + return null; + } + + public static void SetCount50(this ScoreInfo scoreInfo, int value) + { + switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) + { + case 0: + case 3: + scoreInfo.Statistics[HitResult.Meh] = value; + break; } } - public int CountMiss - { - get => Statistics[HitResult.Miss]; - set => Statistics[HitResult.Miss] = value; - } + public static int? GetCountMiss(this ScoreInfo scoreInfo) => + scoreInfo.Statistics[HitResult.Miss]; + + public static void SetCountMiss(this ScoreInfo scoreInfo, int value) => + scoreInfo.Statistics[HitResult.Miss] = value; } } diff --git a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs index 2cdd0c4b5e..0029c843b4 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs @@ -35,7 +35,7 @@ namespace osu.Game.Scoring.Legacy using (SerializationReader sr = new SerializationReader(stream)) { currentRuleset = GetRuleset(sr.ReadByte()); - var scoreInfo = new LegacyScoreInfo { Ruleset = currentRuleset.RulesetInfo }; + var scoreInfo = new ScoreInfo { Ruleset = currentRuleset.RulesetInfo }; score.ScoreInfo = scoreInfo; @@ -53,12 +53,12 @@ namespace osu.Game.Scoring.Legacy // MD5Hash sr.ReadString(); - scoreInfo.Count300 = sr.ReadUInt16(); - scoreInfo.Count100 = sr.ReadUInt16(); - scoreInfo.Count50 = sr.ReadUInt16(); - scoreInfo.CountGeki = sr.ReadUInt16(); - scoreInfo.CountKatu = sr.ReadUInt16(); - scoreInfo.CountMiss = sr.ReadUInt16(); + scoreInfo.SetCount300(sr.ReadUInt16()); + scoreInfo.SetCount100(sr.ReadUInt16()); + scoreInfo.SetCount50(sr.ReadUInt16()); + scoreInfo.SetCountGeki(sr.ReadUInt16()); + scoreInfo.SetCountKatu(sr.ReadUInt16()); + scoreInfo.SetCountMiss(sr.ReadUInt16()); scoreInfo.TotalScore = sr.ReadInt32(); scoreInfo.MaxCombo = sr.ReadUInt16(); diff --git a/osu.Game/Screens/Ranking/Pages/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/Pages/ReplayDownloadButton.cs index 73c647d6fa..9cc6ea2628 100644 --- a/osu.Game/Screens/Ranking/Pages/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/Pages/ReplayDownloadButton.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Scoring; using osuTK; @@ -24,7 +23,7 @@ namespace osu.Game.Screens.Ranking.Pages if (State.Value == DownloadState.LocallyAvailable) return ReplayAvailability.Local; - if (Model.Value is APILegacyScoreInfo apiScore && apiScore.Replay) + if (!string.IsNullOrEmpty(Model.Value.Hash)) return ReplayAvailability.Online; return ReplayAvailability.NotAvailable; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 3ef1fe5bc5..1b45a9d270 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -21,6 +21,9 @@ namespace osu.Game.Screens.Select.Leaderboards { public Action ScoreSelected; + [Resolved] + private RulesetStore rulesets { get; set; } + private BeatmapInfo beatmap; public BeatmapInfo Beatmap @@ -172,7 +175,7 @@ namespace osu.Game.Screens.Select.Leaderboards req.Success += r => { - scoresCallback?.Invoke(r.Scores); + scoresCallback?.Invoke(r.Scores.Select(s => s.CreateScoreInfo(rulesets))); TopScore = r.UserScore; }; diff --git a/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs b/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs index a787eb5629..8e10734454 100644 --- a/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs +++ b/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs @@ -3,6 +3,7 @@ using System; using System.Threading; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -10,6 +11,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; +using osu.Game.Rulesets; using osu.Game.Scoring; using osuTK; @@ -27,6 +29,9 @@ namespace osu.Game.Screens.Select.Leaderboards protected override bool StartHidden => true; + [Resolved] + private RulesetStore rulesets { get; set; } + public UserTopScoreContainer() { RelativeSizeAxes = Axes.X; @@ -77,9 +82,11 @@ namespace osu.Game.Screens.Select.Leaderboards if (newScore == null) return; - LoadComponentAsync(new LeaderboardScore(newScore.Score, newScore.Position, false) + var scoreInfo = newScore.Score.CreateScoreInfo(rulesets); + + LoadComponentAsync(new LeaderboardScore(scoreInfo, newScore.Position, false) { - Action = () => ScoreSelected?.Invoke(newScore.Score) + Action = () => ScoreSelected?.Invoke(scoreInfo) }, drawableScore => { scoreContainer.Child = drawableScore; From 1ce6a5ceb36bf0a9d412c8bca374eeddafff3fef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Dec 2019 15:38:57 +0900 Subject: [PATCH 2546/2815] Rename class --- .../Legacy/{LegacyScoreInfo.cs => ScoreInfoExtensions.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game/Scoring/Legacy/{LegacyScoreInfo.cs => ScoreInfoExtensions.cs} (98%) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs b/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs similarity index 98% rename from osu.Game/Scoring/Legacy/LegacyScoreInfo.cs rename to osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs index 2ee450b501..66b1acf591 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs +++ b/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs @@ -5,7 +5,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Scoring.Legacy { - public static class ScoreInfoLegacyExtensions + public static class ScoreInfoExtensions { public static int? GetCountGeki(this ScoreInfo scoreInfo) { From e2591f154ba7b8d821fb6186da44d17506c91a16 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Dec 2019 16:16:27 +0900 Subject: [PATCH 2547/2815] Only parse statistics when not null --- .../Requests/Responses/APILegacyScoreInfo.cs | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs index d231b8a5df..79ce04ed66 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs @@ -40,33 +40,36 @@ namespace osu.Game.Online.API.Requests.Responses Mods = mods, }; - foreach (var kvp in Statistics) + if (Statistics != null) { - switch (kvp.Key) + foreach (var kvp in Statistics) { - case @"count_geki": - scoreInfo.SetCountGeki(kvp.Value); - break; + switch (kvp.Key) + { + case @"count_geki": + scoreInfo.SetCountGeki(kvp.Value); + break; - case @"count_300": - scoreInfo.SetCount300(kvp.Value); - break; + case @"count_300": + scoreInfo.SetCount300(kvp.Value); + break; - case @"count_katu": - scoreInfo.SetCountKatu(kvp.Value); - break; + case @"count_katu": + scoreInfo.SetCountKatu(kvp.Value); + break; - case @"count_100": - scoreInfo.SetCount100(kvp.Value); - break; + case @"count_100": + scoreInfo.SetCount100(kvp.Value); + break; - case @"count_50": - scoreInfo.SetCount50(kvp.Value); - break; + case @"count_50": + scoreInfo.SetCount50(kvp.Value); + break; - case @"count_miss": - scoreInfo.SetCountMiss(kvp.Value); - break; + case @"count_miss": + scoreInfo.SetCountMiss(kvp.Value); + break; + } } } From a225a35f91cd8030234a72dd33b37d4585c180bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Dec 2019 16:18:36 +0900 Subject: [PATCH 2548/2815] Fix failing tests --- .../Visual/Online/TestSceneScoresContainer.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 3386cc41e7..b19f2dbf31 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -224,10 +224,13 @@ namespace osu.Game.Tests.Visual.Online foreach (var s in allScores.Scores) { - s.Statistics.Add("count_300", RNG.Next(2000)); - s.Statistics.Add("count_100", RNG.Next(2000)); - s.Statistics.Add("count_50", RNG.Next(2000)); - s.Statistics.Add("count_miss", RNG.Next(2000)); + s.Statistics = new Dictionary + { + { "count_300", RNG.Next(2000) }, + { "count_100", RNG.Next(2000) }, + { "count_50", RNG.Next(2000) }, + { "count_miss", RNG.Next(2000) } + }; } AddStep("Load all scores", () => From a42f9447e629f81ec61d2d05e52a1f3e6e97ac07 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 3 Dec 2019 20:07:22 +0800 Subject: [PATCH 2549/2815] Don't use Equals(object) on T?. --- CodeAnalysis/BannedSymbols.txt | 1 + osu.Game/Rulesets/Objects/SliderPath.cs | 2 +- osu.Game/Screens/Select/FilterCriteria.cs | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index 5d86b99fd7..a92191a439 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -1,5 +1,6 @@ M:System.Object.Equals(System.Object,System.Object)~System.Boolean;Don't use object.Equals. Use IEquatable or EqualityComparer.Default instead. M:System.Object.Equals(System.Object)~System.Boolean;Don't use object.Equals. Use IEquatable or EqualityComparer.Default instead. M:System.ValueType.Equals(System.Object)~System.Boolean;Don't use object.Equals(Fallbacks to ValueType). Use IEquatable or EqualityComparer.Default instead. +M:System.Nullable`1.Equals(System.Object)~System.Boolean;Use == instead. T:System.IComparable;Don't use non-generic IComparable. Use generic version instead. M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText. diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 8161baba70..bd234675cb 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -280,7 +280,7 @@ namespace osu.Game.Rulesets.Objects if (other.ControlPoints == null && ControlPoints != null) return false; - return ControlPoints.SequenceEqual(other.ControlPoints) && ExpectedDistance.Equals(other.ExpectedDistance) && Type == other.Type; + return ControlPoints.SequenceEqual(other.ControlPoints) && ExpectedDistance == other.ExpectedDistance && Type == other.Type; } } } diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index c2cbac905e..7971506fa3 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -79,8 +79,8 @@ namespace osu.Game.Screens.Select public bool IsUpperInclusive; public bool Equals(OptionalRange other) - => Min.Equals(other.Min) - && Max.Equals(other.Max) + => EqualityComparer.Default.Equals(Min, other.Min) + && EqualityComparer.Default.Equals(Max, other.Max) && IsLowerInclusive.Equals(other.IsLowerInclusive) && IsUpperInclusive.Equals(other.IsUpperInclusive); } From 05cfef92f9d1827bacbaaaee21491c809518f2ef Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 3 Dec 2019 20:09:58 +0800 Subject: [PATCH 2550/2815] Don't compare spans with null. --- osu.Game/Rulesets/Objects/SliderPath.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index bd234675cb..ae6aad5b9c 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -273,14 +273,6 @@ namespace osu.Game.Rulesets.Objects return p0 + (p1 - p0) * (float)w; } - public bool Equals(SliderPath other) - { - if (ControlPoints == null && other.ControlPoints != null) - return false; - if (other.ControlPoints == null && ControlPoints != null) - return false; - - return ControlPoints.SequenceEqual(other.ControlPoints) && ExpectedDistance == other.ExpectedDistance && Type == other.Type; - } + public bool Equals(SliderPath other) => ControlPoints.SequenceEqual(other.ControlPoints) && ExpectedDistance == other.ExpectedDistance && Type == other.Type; } } From 5375af782051327afd704b60447e9af974ae8e44 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 3 Dec 2019 20:16:41 +0800 Subject: [PATCH 2551/2815] Remove other Equals(object) calls. --- osu.Game/Beatmaps/ControlPoints/ControlPoint.cs | 2 +- osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index 7351187ab9..39a0e6f6d4 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -25,6 +25,6 @@ namespace osu.Game.Beatmaps.ControlPoints /// Whether equivalent. public abstract bool EquivalentTo(ControlPoint other); - public bool Equals(ControlPoint other) => Time.Equals(other?.Time) && EquivalentTo(other); + public bool Equals(ControlPoint other) => Time == other?.Time && EquivalentTo(other); } } diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index b144de35c5..ef86186e41 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -240,6 +240,6 @@ namespace osu.Game.Tests.Beatmaps set => Objects = value; } - public virtual bool Equals(ConvertMapping other) => StartTime.Equals(other?.StartTime); + public virtual bool Equals(ConvertMapping other) => StartTime == other?.StartTime; } } From 9ddfdab27a7d1a2ea9e050f8cf140e83497b086a Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 3 Dec 2019 20:47:00 +0800 Subject: [PATCH 2552/2815] Remove meaningless Convert calls. --- .../Difficulty/ManiaPerformanceCalculator.cs | 12 ++++++------ .../Difficulty/OsuPerformanceCalculator.cs | 8 ++++---- .../Difficulty/TaikoPerformanceCalculator.cs | 8 ++++---- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index b99bddee96..3f7a2baedd 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -37,12 +37,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty { mods = Score.Mods; scaledScore = Score.TotalScore; - countPerfect = Convert.ToInt32(Score.Statistics[HitResult.Perfect]); - countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]); - countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]); - countOk = Convert.ToInt32(Score.Statistics[HitResult.Ok]); - countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]); - countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]); + countPerfect = Score.Statistics[HitResult.Perfect]; + countGreat = Score.Statistics[HitResult.Great]; + countGood = Score.Statistics[HitResult.Good]; + countOk = Score.Statistics[HitResult.Ok]; + countMeh = Score.Statistics[HitResult.Meh]; + countMiss = Score.Statistics[HitResult.Miss]; if (mods.Any(m => !m.Ranked)) return 0; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 05c78cbc95..ce8ecf02ac 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -45,10 +45,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty mods = Score.Mods; accuracy = Score.Accuracy; scoreMaxCombo = Score.MaxCombo; - countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]); - countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]); - countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]); - countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]); + countGreat = Score.Statistics[HitResult.Great]; + countGood = Score.Statistics[HitResult.Good]; + countMeh = Score.Statistics[HitResult.Meh]; + countMiss = Score.Statistics[HitResult.Miss]; // Don't count scores made with supposedly unranked mods if (mods.Any(m => !m.Ranked)) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index c3638253e4..ead70c4b0a 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -31,10 +31,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty public override double Calculate(Dictionary categoryDifficulty = null) { mods = Score.Mods; - countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]); - countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]); - countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]); - countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]); + countGreat = Score.Statistics[HitResult.Great]; + countGood = Score.Statistics[HitResult.Good]; + countMeh =Score.Statistics[HitResult.Meh]; + countMiss = Score.Statistics[HitResult.Miss]; // Don't count scores made with supposedly unranked mods if (mods.Any(m => !m.Ranked)) From 46c9bdcf6289c32586f2c14f1bb192e4a9b974c9 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 3 Dec 2019 20:49:41 +0800 Subject: [PATCH 2553/2815] Replace Convert.ChangeType with IConvertible. --- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 7412224f6c..563dc2dad9 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -151,18 +151,18 @@ namespace osu.Game.Graphics.UserInterface private void updateTooltipText(T value) { if (CurrentNumber.IsInteger) - TooltipText = ((int)Convert.ChangeType(value, typeof(int))).ToString("N0"); + TooltipText = value.ToInt32(NumberFormatInfo.InvariantInfo).ToString("N0"); else { - double floatValue = (double)Convert.ChangeType(value, typeof(double)); - double floatMinValue = (double)Convert.ChangeType(CurrentNumber.MinValue, typeof(double)); - double floatMaxValue = (double)Convert.ChangeType(CurrentNumber.MaxValue, typeof(double)); + double floatValue = value.ToDouble(NumberFormatInfo.InvariantInfo); + double floatMinValue = CurrentNumber.MinValue.ToDouble(NumberFormatInfo.InvariantInfo); + double floatMaxValue = CurrentNumber.MaxValue.ToDouble(NumberFormatInfo.InvariantInfo); if (floatMaxValue == 1 && floatMinValue >= -1) TooltipText = floatValue.ToString("P0"); else { - var decimalPrecision = normalise((decimal)Convert.ChangeType(CurrentNumber.Precision, typeof(decimal)), max_decimal_digits); + var decimalPrecision = normalise(CurrentNumber.Precision.ToDecimal(NumberFormatInfo.InvariantInfo), max_decimal_digits); // Find the number of significant digits (we could have less than 5 after normalize()) var significantDigits = findPrecision(decimalPrecision); From 6a58509f41b78d42fcbf8aed5b0d4c212aaa1996 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 3 Dec 2019 20:59:42 +0800 Subject: [PATCH 2554/2815] Fix format. --- .../Difficulty/TaikoPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index ead70c4b0a..3a0fb64622 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty mods = Score.Mods; countGreat = Score.Statistics[HitResult.Great]; countGood = Score.Statistics[HitResult.Good]; - countMeh =Score.Statistics[HitResult.Meh]; + countMeh = Score.Statistics[HitResult.Meh]; countMiss = Score.Statistics[HitResult.Miss]; // Don't count scores made with supposedly unranked mods From 0734b52483061db156eda0f491b5ff0938178413 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Dec 2019 23:33:26 +0900 Subject: [PATCH 2555/2815] 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 997bd32bac..ff6d7396ee 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -54,6 +54,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3f056cacfb..d7a34c2b1c 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 b231e924ef..d2feebbfc9 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -74,7 +74,7 @@ - + @@ -82,7 +82,7 @@ - + From 5b8ca8f84abce675b7021fa439ea108698ca0711 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Dec 2019 01:17:11 +0900 Subject: [PATCH 2556/2815] Remove test ignore rules --- osu.Game.Tests/Visual/Gameplay/TestScenePause.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 700c2561d5..803cab9325 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -113,7 +113,6 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - [Ignore("Will be resolved with merge of https://github.com/ppy/osu/pull/6992")] public void TestExitTooSoon() { AddStep("seek before gameplay", () => Player.GameplayClockContainer.Seek(-5000)); @@ -216,7 +215,6 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - [Ignore("Will be resolved with merge of https://github.com/ppy/osu/pull/6992")] public void TestRestartAfterResume() { AddStep("seek before gameplay", () => Player.GameplayClockContainer.Seek(-5000)); From 36224dca133b41a3a4b9d6028a81542add8268ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Dec 2019 16:10:25 +0900 Subject: [PATCH 2557/2815] Fix multiplayer aggregate score inheriting from ScoreInfo --- .../Multiplayer/TestSceneMatchResults.cs | 4 +- .../API/Requests/GetRoomScoresRequest.cs | 2 +- .../Requests/Responses/APIRoomScoreInfo.cs | 17 ------- .../Responses/APIUserScoreAggregate.cs | 45 +++++++++++++++++++ .../Match/Components/MatchLeaderboard.cs | 8 ++-- .../Match/Components/MatchLeaderboardScore.cs | 11 +++-- .../Ranking/Pages/RoomLeaderboardPage.cs | 8 ++-- 7 files changed, 63 insertions(+), 32 deletions(-) delete mode 100644 osu.Game/Online/API/Requests/Responses/APIRoomScoreInfo.cs create mode 100644 osu.Game/Online/API/Requests/Responses/APIUserScoreAggregate.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchResults.cs index 7915a981dd..58e9240026 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchResults.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchResults.cs @@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private class TestMatchLeaderboard : RoomLeaderboardPage.ResultsMatchLeaderboard { - protected override APIRequest FetchScores(Action> scoresCallback) + protected override APIRequest FetchScores(Action> scoresCallback) { var scores = Enumerable.Range(0, 50).Select(createRoomScore).ToArray(); @@ -93,7 +93,7 @@ namespace osu.Game.Tests.Visual.Multiplayer return null; } - private APIRoomScoreInfo createRoomScore(int id) => new APIRoomScoreInfo + private APIUserScoreAggregate createRoomScore(int id) => new APIUserScoreAggregate { User = new User { Id = id, Username = $"User {id}" }, Accuracy = 0.98, diff --git a/osu.Game/Online/API/Requests/GetRoomScoresRequest.cs b/osu.Game/Online/API/Requests/GetRoomScoresRequest.cs index 993e49dab2..eb53369d18 100644 --- a/osu.Game/Online/API/Requests/GetRoomScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetRoomScoresRequest.cs @@ -6,7 +6,7 @@ using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { - public class GetRoomScoresRequest : APIRequest> + public class GetRoomScoresRequest : APIRequest> { private readonly int roomId; diff --git a/osu.Game/Online/API/Requests/Responses/APIRoomScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APIRoomScoreInfo.cs deleted file mode 100644 index 33467b59b2..0000000000 --- a/osu.Game/Online/API/Requests/Responses/APIRoomScoreInfo.cs +++ /dev/null @@ -1,17 +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 Newtonsoft.Json; -using osu.Game.Scoring; - -namespace osu.Game.Online.API.Requests.Responses -{ - public class APIRoomScoreInfo : ScoreInfo - { - [JsonProperty("attempts")] - public int TotalAttempts { get; set; } - - [JsonProperty("completed")] - public int CompletedBeatmaps { get; set; } - } -} diff --git a/osu.Game/Online/API/Requests/Responses/APIUserScoreAggregate.cs b/osu.Game/Online/API/Requests/Responses/APIUserScoreAggregate.cs new file mode 100644 index 0000000000..0bba6a93bd --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIUserScoreAggregate.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 Newtonsoft.Json; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIUserScoreAggregate + { + [JsonProperty("attempts")] + public int TotalAttempts { get; set; } + + [JsonProperty("completed")] + public int CompletedBeatmaps { get; set; } + + [JsonProperty("accuracy")] + public double Accuracy { get; set; } + + [JsonProperty(@"pp")] + public double? PP { get; set; } + + [JsonProperty(@"room_id")] + public int RoomID { get; set; } + + [JsonProperty("total_score")] + public long TotalScore { get; set; } + + [JsonProperty(@"user_id")] + public long UserID { get; set; } + + [JsonProperty("user")] + public User User { get; set; } + + public ScoreInfo CreateScoreInfo() => + new ScoreInfo + { + Accuracy = Accuracy, + PP = PP, + TotalScore = TotalScore, + User = User, + }; + } +} diff --git a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs index ae27e53813..571bbde716 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs @@ -13,9 +13,9 @@ using osu.Game.Online.Multiplayer; namespace osu.Game.Screens.Multi.Match.Components { - public class MatchLeaderboard : Leaderboard + public class MatchLeaderboard : Leaderboard { - public Action> ScoresLoaded; + public Action> ScoresLoaded; [Resolved(typeof(Room), nameof(Room.RoomID))] private Bindable roomId { get; set; } @@ -35,7 +35,7 @@ namespace osu.Game.Screens.Multi.Match.Components protected override bool IsOnlineScope => true; - protected override APIRequest FetchScores(Action> scoresCallback) + protected override APIRequest FetchScores(Action> scoresCallback) { if (roomId.Value == null) return null; @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Multi.Match.Components return req; } - protected override LeaderboardScore CreateDrawableScore(APIRoomScoreInfo model, int index) => new MatchLeaderboardScore(model, index); + protected override LeaderboardScore CreateDrawableScore(APIUserScoreAggregate model, int index) => new MatchLeaderboardScore(model, index); } public enum MatchLeaderboardScope diff --git a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs index 92074abe4b..aa92451c77 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs @@ -12,9 +12,12 @@ namespace osu.Game.Screens.Multi.Match.Components { public class MatchLeaderboardScore : LeaderboardScore { - public MatchLeaderboardScore(APIRoomScoreInfo score, int rank) - : base(score, rank) + private readonly APIUserScoreAggregate score; + + public MatchLeaderboardScore(APIUserScoreAggregate score, int rank) + : base(score.CreateScoreInfo(), rank) { + this.score = score; } [BackgroundDependencyLoader] @@ -26,8 +29,8 @@ namespace osu.Game.Screens.Multi.Match.Components protected override IEnumerable GetStatistics(ScoreInfo model) => new[] { new LeaderboardScoreStatistic(FontAwesome.Solid.Crosshairs, "Accuracy", string.Format(model.Accuracy % 1 == 0 ? @"{0:P0}" : @"{0:P2}", model.Accuracy)), - new LeaderboardScoreStatistic(FontAwesome.Solid.Sync, "Total Attempts", ((APIRoomScoreInfo)model).TotalAttempts.ToString()), - new LeaderboardScoreStatistic(FontAwesome.Solid.Check, "Completed Beatmaps", ((APIRoomScoreInfo)model).CompletedBeatmaps.ToString()), + new LeaderboardScoreStatistic(FontAwesome.Solid.Sync, "Total Attempts", score.TotalAttempts.ToString()), + new LeaderboardScoreStatistic(FontAwesome.Solid.Check, "Completed Beatmaps", score.CompletedBeatmaps.ToString()), }; } } diff --git a/osu.Game/Screens/Multi/Ranking/Pages/RoomLeaderboardPage.cs b/osu.Game/Screens/Multi/Ranking/Pages/RoomLeaderboardPage.cs index e389611caf..ff5471cf4a 100644 --- a/osu.Game/Screens/Multi/Ranking/Pages/RoomLeaderboardPage.cs +++ b/osu.Game/Screens/Multi/Ranking/Pages/RoomLeaderboardPage.cs @@ -74,7 +74,7 @@ namespace osu.Game.Screens.Multi.Ranking.Pages leaderboard.ScoresLoaded = scoresLoaded; } - private void scoresLoaded(IEnumerable scores) + private void scoresLoaded(IEnumerable scores) { void gray(SpriteText s) => s.Colour = colours.GrayC; @@ -87,7 +87,7 @@ namespace osu.Game.Screens.Multi.Ranking.Pages rankText.AddText(name + "\n", white); rankText.AddText("You are placed ", gray); - int index = scores.IndexOf(new APIRoomScoreInfo { User = Score.User }, new FuncEqualityComparer((s1, s2) => s1.User.Id.Equals(s2.User.Id))); + int index = scores.IndexOf(new APIUserScoreAggregate { User = Score.User }, new FuncEqualityComparer((s1, s2) => s1.User.Id.Equals(s2.User.Id))); rankText.AddText($"#{index + 1} ", s => { @@ -104,7 +104,7 @@ namespace osu.Game.Screens.Multi.Ranking.Pages { protected override bool FadeTop => true; - protected override LeaderboardScore CreateDrawableScore(APIRoomScoreInfo model, int index) + protected override LeaderboardScore CreateDrawableScore(APIUserScoreAggregate model, int index) => new ResultsMatchLeaderboardScore(model, index); protected override FillFlowContainer CreateScoreFlow() @@ -120,7 +120,7 @@ namespace osu.Game.Screens.Multi.Ranking.Pages private class ResultsMatchLeaderboardScore : MatchLeaderboardScore { - public ResultsMatchLeaderboardScore(APIRoomScoreInfo score, int rank) + public ResultsMatchLeaderboardScore(APIUserScoreAggregate score, int rank) : base(score, rank) { } From 48287459a0aa6136e711eb7bc07df187338a7876 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Dec 2019 01:52:22 +0900 Subject: [PATCH 2558/2815] Optimise filters to avoid property retrieval Also reduces number of instantiations of SearchableTerms array in the case of multiple criteria terms. --- .../Select/Carousel/CarouselBeatmap.cs | 32 ++++++++++--------- osu.Game/Screens/Select/FilterCriteria.cs | 6 +++- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index afd6211dec..68a6ad8845 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Screens.Select.Filter; @@ -29,28 +30,29 @@ namespace osu.Game.Screens.Select.Carousel Beatmap.RulesetID == criteria.Ruleset.ID || (Beatmap.RulesetID == 0 && criteria.Ruleset.ID > 0 && criteria.AllowConvertedBeatmaps); - match &= criteria.StarDifficulty.IsInRange(Beatmap.StarDifficulty); - match &= criteria.ApproachRate.IsInRange(Beatmap.BaseDifficulty.ApproachRate); - match &= criteria.DrainRate.IsInRange(Beatmap.BaseDifficulty.DrainRate); - match &= criteria.CircleSize.IsInRange(Beatmap.BaseDifficulty.CircleSize); - match &= criteria.Length.IsInRange(Beatmap.Length); - match &= criteria.BPM.IsInRange(Beatmap.BPM); + match &= !criteria.StarDifficulty.HasFilter || criteria.StarDifficulty.IsInRange(Beatmap.StarDifficulty); + match &= !criteria.ApproachRate.HasFilter || criteria.ApproachRate.IsInRange(Beatmap.BaseDifficulty.ApproachRate); + match &= !criteria.DrainRate.HasFilter || criteria.DrainRate.IsInRange(Beatmap.BaseDifficulty.DrainRate); + match &= !criteria.CircleSize.HasFilter || criteria.CircleSize.IsInRange(Beatmap.BaseDifficulty.CircleSize); + match &= !criteria.Length.HasFilter || criteria.Length.IsInRange(Beatmap.Length); + match &= !criteria.BPM.HasFilter || criteria.BPM.IsInRange(Beatmap.BPM); - match &= criteria.BeatDivisor.IsInRange(Beatmap.BeatDivisor); - match &= criteria.OnlineStatus.IsInRange(Beatmap.Status); + match &= !criteria.BeatDivisor.HasFilter || criteria.BeatDivisor.IsInRange(Beatmap.BeatDivisor); + match &= !criteria.OnlineStatus.HasFilter || criteria.OnlineStatus.IsInRange(Beatmap.Status); - match &= criteria.Creator.Matches(Beatmap.Metadata.AuthorString); - match &= criteria.Artist.Matches(Beatmap.Metadata.Artist) || + match &= !criteria.Creator.HasFilter || criteria.Creator.Matches(Beatmap.Metadata.AuthorString); + match &= !criteria.Artist.HasFilter || criteria.Artist.Matches(Beatmap.Metadata.Artist) || criteria.Artist.Matches(Beatmap.Metadata.ArtistUnicode); if (match) { + var terms = new List(); + + terms.AddRange(Beatmap.Metadata.SearchableTerms); + terms.Add(Beatmap.Version); + foreach (var criteriaTerm in criteria.SearchTerms) - { - match &= - Beatmap.Metadata.SearchableTerms.Any(term => term.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0) || - Beatmap.Version.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0; - } + match &= terms.Any(term => term.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0); } Filtered.Value = !match; diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 7971506fa3..abcb1f2171 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -46,6 +46,8 @@ namespace osu.Game.Screens.Select public struct OptionalRange : IEquatable> where T : struct, IComparable { + public bool HasFilter => Max != null || Min != null; + public bool IsInRange(T value) { if (Min != null) @@ -87,9 +89,11 @@ namespace osu.Game.Screens.Select public struct OptionalTextFilter : IEquatable { + public bool HasFilter => !string.IsNullOrEmpty(SearchTerm); + public bool Matches(string value) { - if (string.IsNullOrEmpty(SearchTerm)) + if (!HasFilter) return true; // search term is guaranteed to be non-empty, so if the string we're comparing is empty, it's not matching From 48732e49b94967317181137d08f2cbc642f54b9f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 5 Dec 2019 04:20:13 +0300 Subject: [PATCH 2559/2815] Improve async logic --- osu.Game/Overlays/RankingsOverlay.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index d3586a538b..948d165b82 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -15,6 +15,7 @@ using osu.Game.Online.API; using System.Threading; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Rankings.Tables; +using osu.Framework.Threading; namespace osu.Game.Overlays { @@ -30,6 +31,7 @@ namespace osu.Game.Overlays private readonly DimmedLoadingLayer loading; private APIRequest request; + private ScheduledDelegate showTableDelegate; private CancellationTokenSource cancellationToken; [Resolved] @@ -120,11 +122,10 @@ namespace osu.Game.Overlays loading.Show(); + showTableDelegate?.Cancel(); cancellationToken?.Cancel(); request?.Cancel(); - cancellationToken = new CancellationTokenSource(); - switch (scope.Value) { default: @@ -184,12 +185,12 @@ namespace osu.Game.Overlays private void loadTable(Drawable table) { - LoadComponentAsync(table, t => + showTableDelegate = Schedule(() => LoadComponentAsync(table, t => { contentPlaceholder.Clear(); contentPlaceholder.Add(t); loading.Hide(); - }, cancellationToken.Token); + }, (cancellationToken = new CancellationTokenSource()).Token)); } } } From f8f144b6c04671f0daf7155791e7314e5cdf26e3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 5 Dec 2019 05:20:22 +0300 Subject: [PATCH 2560/2815] Remove pointless ScheduledDelegate --- osu.Game/Overlays/RankingsOverlay.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index 948d165b82..0ec03ebcc9 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -15,7 +15,6 @@ using osu.Game.Online.API; using System.Threading; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Rankings.Tables; -using osu.Framework.Threading; namespace osu.Game.Overlays { @@ -31,7 +30,6 @@ namespace osu.Game.Overlays private readonly DimmedLoadingLayer loading; private APIRequest request; - private ScheduledDelegate showTableDelegate; private CancellationTokenSource cancellationToken; [Resolved] @@ -122,7 +120,6 @@ namespace osu.Game.Overlays loading.Show(); - showTableDelegate?.Cancel(); cancellationToken?.Cancel(); request?.Cancel(); @@ -185,12 +182,12 @@ namespace osu.Game.Overlays private void loadTable(Drawable table) { - showTableDelegate = Schedule(() => LoadComponentAsync(table, t => + LoadComponentAsync(table, t => { contentPlaceholder.Clear(); contentPlaceholder.Add(t); loading.Hide(); - }, (cancellationToken = new CancellationTokenSource()).Token)); + }, (cancellationToken = new CancellationTokenSource()).Token); } } } From 40d0700fa514da9d4df1abd78fbe3a74a053d985 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Dec 2019 13:43:38 +0900 Subject: [PATCH 2561/2815] Add structure for path control points --- osu.Game/Rulesets/Objects/PathControlPoint.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 osu.Game/Rulesets/Objects/PathControlPoint.cs diff --git a/osu.Game/Rulesets/Objects/PathControlPoint.cs b/osu.Game/Rulesets/Objects/PathControlPoint.cs new file mode 100644 index 0000000000..40a8b0251b --- /dev/null +++ b/osu.Game/Rulesets/Objects/PathControlPoint.cs @@ -0,0 +1,16 @@ +using System; +using osu.Framework.Bindables; +using osu.Game.Rulesets.Objects.Types; +using osuTK; + +namespace osu.Game.Rulesets.Objects +{ + public class PathControlPoint : IEquatable + { + public readonly Bindable Position = new Bindable(); + + public readonly Bindable Type = new Bindable(); + + public bool Equals(PathControlPoint other) => Position.Value == other.Position.Value && Type.Value == other.Type.Value; + } +} From 5f9b9631ef38bae53c320a9aa1512250399b1f47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Dec 2019 14:07:39 +0900 Subject: [PATCH 2562/2815] Move scope/country restrictions into RankingsOverlay --- osu.Game/Overlays/Rankings/HeaderTitle.cs | 10 +--------- osu.Game/Overlays/RankingsOverlay.cs | 24 +++++++++++++++++++---- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/Rankings/HeaderTitle.cs b/osu.Game/Overlays/Rankings/HeaderTitle.cs index a1a893fa6b..b08a2a3900 100644 --- a/osu.Game/Overlays/Rankings/HeaderTitle.cs +++ b/osu.Game/Overlays/Rankings/HeaderTitle.cs @@ -74,13 +74,7 @@ namespace osu.Game.Overlays.Rankings base.LoadComplete(); } - private void onScopeChanged(ValueChangedEvent scope) - { - scopeText.Text = scope.NewValue.ToString(); - - if (scope.NewValue != RankingsScope.Performance) - Country.Value = null; - } + private void onScopeChanged(ValueChangedEvent scope) => scopeText.Text = scope.NewValue.ToString(); private void onCountryChanged(ValueChangedEvent country) { @@ -90,8 +84,6 @@ namespace osu.Game.Overlays.Rankings return; } - Scope.Value = RankingsScope.Performance; - flag.Country = country.NewValue; flag.Show(); } diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index 0ec03ebcc9..b989e9473b 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -98,9 +98,25 @@ namespace osu.Game.Overlays protected override void LoadComplete() { - country.BindValueChanged(_ => redraw(), true); - scope.BindValueChanged(_ => redraw(), true); - ruleset.BindValueChanged(_ => redraw(), true); + country.BindValueChanged(_ => + { + // if a country is requested, force performance scope. + if (country.Value != null) + scope.Value = RankingsScope.Performance; + + Scheduler.AddOnce(loadNewContent); + }, true); + scope.BindValueChanged(_ => + { + // country filtering is only valid for performance scope. + if (scope.Value != RankingsScope.Performance) + country.Value = null; + + Scheduler.AddOnce(loadNewContent); + }, true); + + ruleset.BindValueChanged(_ => Scheduler.AddOnce(loadNewContent), true); + base.LoadComplete(); } @@ -114,7 +130,7 @@ namespace osu.Game.Overlays country.Value = requested; } - private void redraw() + private void loadNewContent() { scrollFlow.ScrollToStart(); From cd473f207a1dab9f058cc1e370508fdc63e3a0a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Dec 2019 14:09:33 +0900 Subject: [PATCH 2563/2815] Use child set, not Clear/Add --- osu.Game/Overlays/RankingsOverlay.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index b989e9473b..154cf0e47b 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -200,8 +200,7 @@ namespace osu.Game.Overlays { LoadComponentAsync(table, t => { - contentPlaceholder.Clear(); - contentPlaceholder.Add(t); + contentPlaceholder.Child = t; loading.Hide(); }, (cancellationToken = new CancellationTokenSource()).Token); } From 6e9157d59c75cf680c11d216f68cd42b233b9dc2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Dec 2019 14:26:36 +0900 Subject: [PATCH 2564/2815] Standardise request/response handling --- .../API/Requests/GetUserRankingsRequest.cs | 7 +- osu.Game/Overlays/RankingsOverlay.cs | 97 ++++++++++--------- 2 files changed, 55 insertions(+), 49 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs b/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs index 9c3eba9fdc..6f657aee8d 100644 --- a/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs @@ -8,13 +8,14 @@ namespace osu.Game.Online.API.Requests { public class GetUserRankingsRequest : GetRankingsRequest { + public readonly UserRankingsType Type; + private readonly string country; - private readonly UserRankingsType type; public GetUserRankingsRequest(RulesetInfo ruleset, UserRankingsType type = UserRankingsType.Performance, int page = 1, string country = null) : base(ruleset, page) { - this.type = type; + this.Type = type; this.country = country; } @@ -28,7 +29,7 @@ namespace osu.Game.Online.API.Requests return req; } - protected override string TargetPostfix() => type.ToString().ToLowerInvariant(); + protected override string TargetPostfix() => Type.ToString().ToLowerInvariant(); } public enum UserRankingsType diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index 154cf0e47b..aa42b029e0 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -26,10 +26,10 @@ namespace osu.Game.Overlays private readonly BasicScrollContainer scrollFlow; private readonly Box background; - private readonly Container contentPlaceholder; + private readonly Container tableContainer; private readonly DimmedLoadingLayer loading; - private APIRequest request; + private APIRequest lastRequest; private CancellationTokenSource cancellationToken; [Resolved] @@ -68,7 +68,7 @@ namespace osu.Game.Overlays AutoSizeAxes = Axes.Y, Children = new Drawable[] { - contentPlaceholder = new Container + tableContainer = new Container { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -132,76 +132,81 @@ namespace osu.Game.Overlays private void loadNewContent() { - scrollFlow.ScrollToStart(); - loading.Show(); cancellationToken?.Cancel(); - request?.Cancel(); + lastRequest?.Cancel(); + var request = createScopedRequest(); + lastRequest = request; + + if (request == null) + { + loadTable(null); + return; + } + + request.Success += () => loadTable(createTableFromResponse(request)); + request.Failure += _ => loadTable(null); + + api.Queue(request); + } + + private APIRequest createScopedRequest() + { switch (scope.Value) { - default: - contentPlaceholder.Clear(); - loading.Hide(); - return; - case RankingsScope.Performance: - createPerformanceTable(); - return; + return new GetUserRankingsRequest(ruleset.Value, country: country.Value?.FlagName); case RankingsScope.Country: - createCountryTable(); - return; + return new GetCountryRankingsRequest(ruleset.Value); case RankingsScope.Score: - createScoreTable(); - return; + return new GetUserRankingsRequest(ruleset.Value, UserRankingsType.Score); } + + return null; } - private void createCountryTable() + private Drawable createTableFromResponse(APIRequest request) { - request = new GetCountryRankingsRequest(ruleset.Value); - ((GetCountryRankingsRequest)request).Success += rankings => Schedule(() => + switch (request) { - var table = new CountriesTable(1, rankings.Countries); - loadTable(table); - }); + case GetUserRankingsRequest userRequest: + switch (userRequest.Type) + { + case UserRankingsType.Performance: + return new PerformanceTable(1, userRequest.Result.Users); - api.Queue(request); - } + case UserRankingsType.Score: + return new ScoresTable(1, userRequest.Result.Users); + } - private void createPerformanceTable() - { - request = new GetUserRankingsRequest(ruleset.Value, country: country.Value?.FlagName); - ((GetUserRankingsRequest)request).Success += rankings => Schedule(() => - { - var table = new PerformanceTable(1, rankings.Users); - loadTable(table); - }); + return null; - api.Queue(request); - } + case GetCountryRankingsRequest countryRequest: + return new CountriesTable(1, countryRequest.Result.Countries); + } - private void createScoreTable() - { - request = new GetUserRankingsRequest(ruleset.Value, UserRankingsType.Score); - ((GetUserRankingsRequest)request).Success += rankings => Schedule(() => - { - var table = new ScoresTable(1, rankings.Users); - loadTable(table); - }); - - api.Queue(request); + return null; } private void loadTable(Drawable table) { + scrollFlow.ScrollToStart(); + + if (table == null) + { + tableContainer.Clear(); + loading.Hide(); + return; + } + LoadComponentAsync(table, t => { - contentPlaceholder.Child = t; loading.Hide(); + tableContainer.Child = table; }, (cancellationToken = new CancellationTokenSource()).Token); } } From 3e0f499e72a3149920ea13f16fd859a3e35fea6d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Dec 2019 14:38:21 +0900 Subject: [PATCH 2565/2815] Add xmldocs --- osu.Game/Rulesets/Objects/PathControlPoint.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Rulesets/Objects/PathControlPoint.cs b/osu.Game/Rulesets/Objects/PathControlPoint.cs index 40a8b0251b..fe66fb79cb 100644 --- a/osu.Game/Rulesets/Objects/PathControlPoint.cs +++ b/osu.Game/Rulesets/Objects/PathControlPoint.cs @@ -7,8 +7,15 @@ namespace osu.Game.Rulesets.Objects { public class PathControlPoint : IEquatable { + /// + /// The position of this . + /// public readonly Bindable Position = new Bindable(); + /// + /// The type of path segment starting at this . + /// If null, this will be a part of the previous path segment. + /// public readonly Bindable Type = new Bindable(); public bool Equals(PathControlPoint other) => Position.Value == other.Position.Value && Type.Value == other.Type.Value; From 0149e476739c1e86ddd3b4c32d7ec4292fe37355 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Dec 2019 14:38:32 +0900 Subject: [PATCH 2566/2815] Expose general control point change event --- osu.Game/Rulesets/Objects/PathControlPoint.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Rulesets/Objects/PathControlPoint.cs b/osu.Game/Rulesets/Objects/PathControlPoint.cs index fe66fb79cb..d68ef4112b 100644 --- a/osu.Game/Rulesets/Objects/PathControlPoint.cs +++ b/osu.Game/Rulesets/Objects/PathControlPoint.cs @@ -18,6 +18,17 @@ namespace osu.Game.Rulesets.Objects /// public readonly Bindable Type = new Bindable(); + /// + /// Invoked when any property of this is changed. + /// + internal event Action Changed; + + public PathControlPoint() + { + Position.ValueChanged += _ => Changed?.Invoke(); + Type.ValueChanged += _ => Changed?.Invoke(); + } + public bool Equals(PathControlPoint other) => Position.Value == other.Position.Value && Type.Value == other.Type.Value; } } From de413418c7404f7c34fcb75ef16a6fac942ae5ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Dec 2019 14:50:12 +0900 Subject: [PATCH 2567/2815] Remove redundant prefix --- osu.Game/Online/API/Requests/GetUserRankingsRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs b/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs index 6f657aee8d..143d21e40d 100644 --- a/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs @@ -15,7 +15,7 @@ namespace osu.Game.Online.API.Requests public GetUserRankingsRequest(RulesetInfo ruleset, UserRankingsType type = UserRankingsType.Performance, int page = 1, string country = null) : base(ruleset, page) { - this.Type = type; + Type = type; this.country = country; } From 33737d3d89b7381b29f695c5bfc6312464c74ff2 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 5 Dec 2019 09:53:25 +0300 Subject: [PATCH 2568/2815] Move tests to TestSceneRankingsOverlay due to refactoring --- .../Visual/Online/TestSceneRankingsHeader.cs | 2 - .../Online/TestSceneRankingsHeaderTitle.cs | 5 --- .../Visual/Online/TestSceneRankingsOverlay.cs | 39 ++++++++++++++++--- osu.Game/Overlays/RankingsOverlay.cs | 26 ++++++------- 4 files changed, 46 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs index c0da605cdb..e708934bc3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs @@ -68,9 +68,7 @@ namespace osu.Game.Tests.Visual.Online }; AddStep("Set country", () => countryBindable.Value = country); - AddAssert("Check scope is Performance", () => scope.Value == RankingsScope.Performance); AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score); - AddAssert("Check country is Null", () => countryBindable.Value == null); AddStep("Set country with no flag", () => countryBindable.Value = unknownCountry); } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs index 849ca2defc..0edf104da0 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs @@ -43,11 +43,6 @@ namespace osu.Game.Tests.Visual.Online FullName = "United States" }; - AddStep("Set country", () => countryBindable.Value = countryA); - AddAssert("Check scope is Performance", () => scope.Value == RankingsScope.Performance); - AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score); - AddAssert("Check country is Null", () => countryBindable.Value == null); - AddStep("Set country 1", () => countryBindable.Value = countryA); AddStep("Set country 2", () => countryBindable.Value = countryB); AddStep("Set null country", () => countryBindable.Value = null); diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs index 1f08fe7530..09f008b308 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs @@ -8,6 +8,8 @@ using osu.Framework.Allocation; using osu.Game.Overlays; using NUnit.Framework; using osu.Game.Users; +using osu.Framework.Bindables; +using osu.Game.Overlays.Rankings; namespace osu.Game.Tests.Visual.Online { @@ -29,9 +31,16 @@ namespace osu.Game.Tests.Visual.Online [Cached] private RankingsOverlay rankingsOverlay; + private readonly Bindable countryBindable = new Bindable(); + private readonly Bindable scope = new Bindable(); + public TestSceneRankingsOverlay() { - Add(rankingsOverlay = new RankingsOverlay()); + Add(rankingsOverlay = new TestRankingsOverlay + { + Country = { BindTarget = countryBindable }, + Scope = { BindTarget = scope }, + }); } [Test] @@ -40,14 +49,19 @@ namespace osu.Game.Tests.Visual.Online AddStep("Show", rankingsOverlay.Show); } + [Test] + public void TestFlagScopeDependency() + { + AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score); + AddAssert("Check country is Null", () => countryBindable.Value == null); + AddStep("Set country", () => countryBindable.Value = us_country); + AddAssert("Check scope is Performance", () => scope.Value == RankingsScope.Performance); + } + [Test] public void TestShowCountry() { - AddStep("Show US", () => rankingsOverlay.ShowCountry(new Country - { - FlagName = "US", - FullName = "United States" - })); + AddStep("Show US", () => rankingsOverlay.ShowCountry(us_country)); } [Test] @@ -55,5 +69,18 @@ namespace osu.Game.Tests.Visual.Online { AddStep("Hide", rankingsOverlay.Hide); } + + private static Country us_country = new Country + { + FlagName = "US", + FullName = "United States" + }; + + private class TestRankingsOverlay : RankingsOverlay + { + public new Bindable Country => base.Country; + + public new Bindable Scope => base.Scope; + } } } diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index aa42b029e0..e7c8b94a10 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -20,8 +20,8 @@ namespace osu.Game.Overlays { public class RankingsOverlay : FullscreenOverlay { - private readonly Bindable country = new Bindable(); - private readonly Bindable scope = new Bindable(); + protected readonly Bindable Country = new Bindable(); + protected readonly Bindable Scope = new Bindable(); private readonly Bindable ruleset = new Bindable(); private readonly BasicScrollContainer scrollFlow; @@ -58,8 +58,8 @@ namespace osu.Game.Overlays { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Country = { BindTarget = country }, - Scope = { BindTarget = scope }, + Country = { BindTarget = Country }, + Scope = { BindTarget = Scope }, Ruleset = { BindTarget = ruleset } }, new Container @@ -98,19 +98,19 @@ namespace osu.Game.Overlays protected override void LoadComplete() { - country.BindValueChanged(_ => + Country.BindValueChanged(_ => { // if a country is requested, force performance scope. - if (country.Value != null) - scope.Value = RankingsScope.Performance; + if (Country.Value != null) + Scope.Value = RankingsScope.Performance; Scheduler.AddOnce(loadNewContent); }, true); - scope.BindValueChanged(_ => + Scope.BindValueChanged(_ => { // country filtering is only valid for performance scope. - if (scope.Value != RankingsScope.Performance) - country.Value = null; + if (Scope.Value != RankingsScope.Performance) + Country.Value = null; Scheduler.AddOnce(loadNewContent); }, true); @@ -127,7 +127,7 @@ namespace osu.Game.Overlays Show(); - country.Value = requested; + Country.Value = requested; } private void loadNewContent() @@ -154,10 +154,10 @@ namespace osu.Game.Overlays private APIRequest createScopedRequest() { - switch (scope.Value) + switch (Scope.Value) { case RankingsScope.Performance: - return new GetUserRankingsRequest(ruleset.Value, country: country.Value?.FlagName); + return new GetUserRankingsRequest(ruleset.Value, country: Country.Value?.FlagName); case RankingsScope.Country: return new GetCountryRankingsRequest(ruleset.Value); From bf7c309d4c87c7e74f0bcca629c81976067d00dd Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 5 Dec 2019 10:05:04 +0300 Subject: [PATCH 2569/2815] Make field readonly --- osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs index 09f008b308..568e36df4c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("Hide", rankingsOverlay.Hide); } - private static Country us_country = new Country + private static readonly Country us_country = new Country { FlagName = "US", FullName = "United States" From 5e9b739b6718c44b643679fdb72b94f0ee378d91 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Dec 2019 16:45:02 +0900 Subject: [PATCH 2570/2815] Re-implement slider paths to support multiple segments --- .../Visual/Gameplay/TestSceneSliderPath2.cs | 159 ++++++++++ osu.Game/Rulesets/Objects/SliderPath2.cs | 274 ++++++++++++++++++ 2 files changed, 433 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath2.cs create mode 100644 osu.Game/Rulesets/Objects/SliderPath2.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath2.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath2.cs new file mode 100644 index 0000000000..08d54fcdda --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath2.cs @@ -0,0 +1,159 @@ +// 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; +using osu.Framework.Graphics.Lines; +using osu.Framework.MathUtils; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osuTK; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneSliderPath2 : OsuTestScene + { + private readonly SmoothPath drawablePath; + private SliderPath2 path; + + public TestSceneSliderPath2() + { + Child = drawablePath = new SmoothPath + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }; + } + + [SetUp] + public void Setup() => Schedule(() => + { + path = new SliderPath2(); + }); + + protected override void Update() + { + base.Update(); + + if (path != null) + { + List vertices = new List(); + path.GetPathToProgress(vertices, 0, 1); + + drawablePath.Vertices = vertices; + } + } + + [Test] + public void TestEmptyPath() + { + } + + [TestCase(PathType.Linear)] + [TestCase(PathType.Bezier)] + [TestCase(PathType.Catmull)] + [TestCase(PathType.PerfectCurve)] + public void TestSingleSegment(PathType type) + => AddStep("create path", () => path.ControlPoints.AddRange(createSegment(type, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + + [TestCase(PathType.Linear)] + [TestCase(PathType.Bezier)] + [TestCase(PathType.Catmull)] + [TestCase(PathType.PerfectCurve)] + public void TestMultipleSegment(PathType type) + { + AddStep("create path", () => + { + path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero)); + path.ControlPoints.AddRange(createSegment(type, new Vector2(0, 100), new Vector2(100), Vector2.Zero)); + }); + } + + [Test] + public void TestAddControlPoint() + { + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100)))); + AddStep("add point", () => path.ControlPoints.Add(new PathControlPoint { Position = { Value = new Vector2(100) } })); + } + + [Test] + public void TestInsertControlPoint() + { + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(100)))); + AddStep("insert point", () => path.ControlPoints.Insert(1, new PathControlPoint { Position = { Value = new Vector2(0, 100) } })); + } + + [Test] + public void TestRemoveControlPoint() + { + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("remove second point", () => path.ControlPoints.RemoveAt(1)); + } + + [Test] + public void TestChangePathType() + { + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("change type to bezier", () => path.ControlPoints[0].Type.Value = PathType.Bezier); + } + + [Test] + public void TestAddSegmentByChangingType() + { + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0)))); + AddStep("change second point type to bezier", () => path.ControlPoints[1].Type.Value = PathType.Bezier); + } + + [Test] + public void TestRemoveSegmentByChangingType() + { + AddStep("create path", () => + { + path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); + path.ControlPoints[1].Type.Value = PathType.Bezier; + }); + + AddStep("change second point type to null", () => path.ControlPoints[1].Type.Value = null); + } + + [Test] + public void TestRemoveSegmentByRemovingControlPoint() + { + AddStep("create path", () => + { + path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); + path.ControlPoints[1].Type.Value = PathType.Bezier; + }); + + AddStep("remove second point", () => path.ControlPoints.RemoveAt(1)); + } + + [TestCase(2)] + [TestCase(4)] + public void TestPerfectCurveFallbackScenarios(int points) + { + AddStep("create path", () => + { + switch (points) + { + case 2: + path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(0, 100))); + break; + case 4: + path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); + break; + } + }); + + } + + private List createSegment(PathType type, params Vector2[] controlPoints) + { + var points = controlPoints.Select(p => new PathControlPoint { Position = { Value = p } }).ToList(); + points[0].Type.Value = type; + return points; + } + } +} diff --git a/osu.Game/Rulesets/Objects/SliderPath2.cs b/osu.Game/Rulesets/Objects/SliderPath2.cs new file mode 100644 index 0000000000..560313a4d5 --- /dev/null +++ b/osu.Game/Rulesets/Objects/SliderPath2.cs @@ -0,0 +1,274 @@ +// 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.Linq; +using Newtonsoft.Json; +using osu.Framework.Bindables; +using osu.Framework.Caching; +using osu.Framework.MathUtils; +using osu.Game.Rulesets.Objects.Types; +using osuTK; + +namespace osu.Game.Rulesets.Objects +{ + public class SliderPath2 + { + /// + /// The user-set distance of the path. If non-null, will match this value, + /// and the path will be shortened/lengthened to match this length. + /// + public readonly double? ExpectedDistance; + + /// + /// The control points of the path. + /// + public readonly BindableList ControlPoints = new BindableList(); + + private readonly Cached pathCache = new Cached(); + + private readonly List calculatedPath = new List(); + private readonly List cumulativeLength = new List(); + + /// + /// Creates a new . + /// + /// A user-set distance of the path that may be shorter or longer than the true distance between all control points. + /// The path will be shortened/lengthened to match this length. If null, the path will use the true distance between all control points. + [JsonConstructor] + public SliderPath2(double? expectedDistance = null) + { + ExpectedDistance = expectedDistance; + + ControlPoints.ItemsAdded += items => + { + foreach (var c in items) + c.Changed += onControlPointChanged; + + onControlPointChanged(); + }; + + ControlPoints.ItemsRemoved += items => + { + foreach (var c in items) + c.Changed -= onControlPointChanged; + + onControlPointChanged(); + }; + + void onControlPointChanged() => pathCache.Invalidate(); + } + + /// + /// The distance of the path after lengthening/shortening to account for . + /// + [JsonIgnore] + public double Distance + { + get + { + ensureValid(); + return cumulativeLength.Count == 0 ? 0 : cumulativeLength[cumulativeLength.Count - 1]; + } + } + + /// + /// Computes the slider path until a given progress that ranges from 0 (beginning of the slider) + /// to 1 (end of the slider) and stores the generated path in the given list. + /// + /// The list to be filled with the computed path. + /// Start progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider). + /// End progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider). + public void GetPathToProgress(List path, double p0, double p1) + { + ensureValid(); + + double d0 = progressToDistance(p0); + double d1 = progressToDistance(p1); + + path.Clear(); + + int i = 0; + + for (; i < calculatedPath.Count && cumulativeLength[i] < d0; ++i) + { + } + + path.Add(interpolateVertices(i, d0)); + + for (; i < calculatedPath.Count && cumulativeLength[i] <= d1; ++i) + path.Add(calculatedPath[i]); + + path.Add(interpolateVertices(i, d1)); + } + + /// + /// Computes the position on the slider at a given progress that ranges from 0 (beginning of the path) + /// to 1 (end of the path). + /// + /// Ranges from 0 (beginning of the path) to 1 (end of the path). + /// + public Vector2 PositionAt(double progress) + { + ensureValid(); + + double d = progressToDistance(progress); + return interpolateVertices(indexOfDistance(d), d); + } + + private void ensureValid() + { + if (pathCache.IsValid) + return; + + calculatePath(); + calculateCumulativeLength(); + + pathCache.Validate(); + } + + private void calculatePath() + { + calculatedPath.Clear(); + + if (ControlPoints.Count == 0) + return; + + if (ControlPoints[0].Type.Value == null) + throw new InvalidOperationException($"The first control point in a {nameof(SliderPath2)} must have a non-null type."); + + Vector2[] vertices = new Vector2[ControlPoints.Count]; + for (int i = 0; i < ControlPoints.Count; i++) + vertices[i] = ControlPoints[i].Position.Value; + + int start = 0; + + for (int i = 0; i < ControlPoints.Count; i++) + { + if (ControlPoints[i].Type.Value == null && i < ControlPoints.Count - 1) + continue; + + Debug.Assert(ControlPoints[start].Type.Value.HasValue); + + // The current vertex ends the segment + var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); + var segmentType = ControlPoints[start].Type.Value.Value; + + foreach (Vector2 t in computeSubPath(segmentVertices, segmentType)) + { + if (calculatedPath.Count == 0 || calculatedPath.Last() != t) + calculatedPath.Add(t); + } + + // Start the new segment at the current vertex + start = i; + } + + static List computeSubPath(ReadOnlySpan subControlPoints, PathType type) + { + switch (type) + { + case PathType.Linear: + return PathApproximator.ApproximateLinear(subControlPoints); + + case PathType.PerfectCurve: + if (subControlPoints.Length != 3) + break; + + List subpath = PathApproximator.ApproximateCircularArc(subControlPoints); + + // If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation. + if (subpath.Count == 0) + break; + + return subpath; + + case PathType.Catmull: + return PathApproximator.ApproximateCatmull(subControlPoints); + } + + return PathApproximator.ApproximateBezier(subControlPoints); + } + } + + private void calculateCumulativeLength() + { + double l = 0; + + cumulativeLength.Clear(); + cumulativeLength.Add(l); + + for (int i = 0; i < calculatedPath.Count - 1; ++i) + { + Vector2 diff = calculatedPath[i + 1] - calculatedPath[i]; + double d = diff.Length; + + // Shorted slider paths that are too long compared to the expected distance + if (ExpectedDistance.HasValue && ExpectedDistance - l < d) + { + calculatedPath[i + 1] = calculatedPath[i] + diff * (float)((ExpectedDistance - l) / d); + calculatedPath.RemoveRange(i + 2, calculatedPath.Count - 2 - i); + + l = ExpectedDistance.Value; + cumulativeLength.Add(l); + break; + } + + l += d; + cumulativeLength.Add(l); + } + + // Lengthen slider paths that are too short compared to the expected distance + if (ExpectedDistance.HasValue && l < ExpectedDistance && calculatedPath.Count > 1) + { + Vector2 diff = calculatedPath[calculatedPath.Count - 1] - calculatedPath[calculatedPath.Count - 2]; + double d = diff.Length; + + if (d <= 0) + return; + + calculatedPath[calculatedPath.Count - 1] += diff * (float)((ExpectedDistance - l) / d); + cumulativeLength[calculatedPath.Count - 1] = ExpectedDistance.Value; + } + } + + private int indexOfDistance(double d) + { + int i = cumulativeLength.BinarySearch(d); + if (i < 0) i = ~i; + + return i; + } + + private double progressToDistance(double progress) + { + return Math.Clamp(progress, 0, 1) * Distance; + } + + private Vector2 interpolateVertices(int i, double d) + { + if (calculatedPath.Count == 0) + return Vector2.Zero; + + if (i <= 0) + return calculatedPath.First(); + if (i >= calculatedPath.Count) + return calculatedPath.Last(); + + Vector2 p0 = calculatedPath[i - 1]; + Vector2 p1 = calculatedPath[i]; + + double d0 = cumulativeLength[i - 1]; + double d1 = cumulativeLength[i]; + + // Avoid division by and almost-zero number in case two points are extremely close to each other. + if (Precision.AlmostEquals(d0, d1)) + return p0; + + double w = (d - d0) / (d1 - d0); + return p0 + (p1 - p0) * (float)w; + } + } +} From 0e9787146115d728eff1b04f3e79d6b6724955ed Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Thu, 5 Dec 2019 14:53:01 +0700 Subject: [PATCH 2571/2815] Change colour if difficulty is ExpertPlus --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 8014631eca..025b7e11fd 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -168,7 +168,7 @@ namespace osu.Game.Beatmaps.Drawables difficultyName.Text = beatmap.Version; starRating.Text = $"{beatmap.StarDifficulty:0.##}"; - difficultyFlow.Colour = colours.ForDifficultyRating(beatmap.DifficultyRating); + difficultyFlow.Colour = beatmap.DifficultyRating == DifficultyRating.ExpertPlus ? colours.Gray9 : colours.ForDifficultyRating(beatmap.DifficultyRating); return true; } From 2702edfa555fc98b68c9e11c45ce30b30ba02737 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Dec 2019 17:49:32 +0900 Subject: [PATCH 2572/2815] Rename new path, replace existing one --- ...eSliderPath2.cs => TestSceneSliderPath.cs} | 8 +- osu.Game/Rulesets/Objects/SliderPath.cs | 177 +++++------ osu.Game/Rulesets/Objects/SliderPath2.cs | 274 ------------------ 3 files changed, 93 insertions(+), 366 deletions(-) rename osu.Game.Tests/Visual/Gameplay/{TestSceneSliderPath2.cs => TestSceneSliderPath.cs} (97%) delete mode 100644 osu.Game/Rulesets/Objects/SliderPath2.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath2.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs similarity index 97% rename from osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath2.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs index 08d54fcdda..fe2cc188a5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath2.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs @@ -13,12 +13,12 @@ using osuTK; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneSliderPath2 : OsuTestScene + public class TestSceneSliderPath : OsuTestScene { private readonly SmoothPath drawablePath; - private SliderPath2 path; + private SliderPath path; - public TestSceneSliderPath2() + public TestSceneSliderPath() { Child = drawablePath = new SmoothPath { @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.Gameplay [SetUp] public void Setup() => Schedule(() => { - path = new SliderPath2(); + path = new SliderPath(); }); protected override void Update() diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index ae6aad5b9c..cc2b537afc 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -1,17 +1,20 @@ -// 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; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using Newtonsoft.Json; +using osu.Framework.Bindables; +using osu.Framework.Caching; using osu.Framework.MathUtils; using osu.Game.Rulesets.Objects.Types; using osuTK; namespace osu.Game.Rulesets.Objects { - public struct SliderPath : IEquatable + public class SliderPath { /// /// The user-set distance of the path. If non-null, will match this value, @@ -20,49 +23,47 @@ namespace osu.Game.Rulesets.Objects public readonly double? ExpectedDistance; /// - /// The type of path. + /// The control points of the path. /// - public readonly PathType Type; + public readonly BindableList ControlPoints = new BindableList(); - [JsonProperty] - private Vector2[] controlPoints; + public readonly List Test = new List(); - private List calculatedPath; - private List cumulativeLength; + private readonly Cached pathCache = new Cached(); - private bool isInitialised; + private readonly List calculatedPath = new List(); + private readonly List cumulativeLength = new List(); /// /// Creates a new . /// - /// The type of path. - /// The control points of the path. - /// A user-set distance of the path that may be shorter or longer than the true distance between all - /// . The path will be shortened/lengthened to match this length. - /// If null, the path will use the true distance between all . + /// An optional set of s to initialise the path with. + /// A user-set distance of the path that may be shorter or longer than the true distance between all control points. + /// The path will be shortened/lengthened to match this length. If null, the path will use the true distance between all control points. [JsonConstructor] - public SliderPath(PathType type, Vector2[] controlPoints, double? expectedDistance = null) + public SliderPath(PathControlPoint[] controlPoints = null, double? expectedDistance = null) { - this = default; - this.controlPoints = controlPoints; - - Type = type; ExpectedDistance = expectedDistance; - ensureInitialised(); - } - - /// - /// The control points of the path. - /// - [JsonIgnore] - public ReadOnlySpan ControlPoints - { - get + ControlPoints.ItemsAdded += items => { - ensureInitialised(); - return controlPoints.AsSpan(); - } + foreach (var c in items) + c.Changed += onControlPointChanged; + + onControlPointChanged(); + }; + + ControlPoints.ItemsRemoved += items => + { + foreach (var c in items) + c.Changed -= onControlPointChanged; + + onControlPointChanged(); + }; + + ControlPoints.AddRange(controlPoints); + + void onControlPointChanged() => pathCache.Invalidate(); } /// @@ -73,7 +74,7 @@ namespace osu.Game.Rulesets.Objects { get { - ensureInitialised(); + ensureValid(); return cumulativeLength.Count == 0 ? 0 : cumulativeLength[cumulativeLength.Count - 1]; } } @@ -87,7 +88,7 @@ namespace osu.Game.Rulesets.Objects /// End progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider). public void GetPathToProgress(List path, double p0, double p1) { - ensureInitialised(); + ensureValid(); double d0 = progressToDistance(p0); double d1 = progressToDistance(p1); @@ -116,82 +117,84 @@ namespace osu.Game.Rulesets.Objects /// public Vector2 PositionAt(double progress) { - ensureInitialised(); + ensureValid(); double d = progressToDistance(progress); return interpolateVertices(indexOfDistance(d), d); } - private void ensureInitialised() + private void ensureValid() { - if (isInitialised) + if (pathCache.IsValid) return; - isInitialised = true; - - controlPoints ??= Array.Empty(); - calculatedPath = new List(); - cumulativeLength = new List(); - calculatePath(); calculateCumulativeLength(); - } - private List calculateSubpath(ReadOnlySpan subControlPoints) - { - switch (Type) - { - case PathType.Linear: - return PathApproximator.ApproximateLinear(subControlPoints); - - case PathType.PerfectCurve: - //we can only use CircularArc iff we have exactly three control points and no dissection. - if (ControlPoints.Length != 3 || subControlPoints.Length != 3) - break; - - // Here we have exactly 3 control points. Attempt to fit a circular arc. - List subpath = PathApproximator.ApproximateCircularArc(subControlPoints); - - // If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation. - if (subpath.Count == 0) - break; - - return subpath; - - case PathType.Catmull: - return PathApproximator.ApproximateCatmull(subControlPoints); - } - - return PathApproximator.ApproximateBezier(subControlPoints); + pathCache.Validate(); } private void calculatePath() { calculatedPath.Clear(); - // Sliders may consist of various subpaths separated by two consecutive vertices - // with the same position. The following loop parses these subpaths and computes - // their shape independently, consecutively appending them to calculatedPath. + if (ControlPoints.Count == 0) + return; + + if (ControlPoints[0].Type.Value == null) + throw new InvalidOperationException($"The first control point in a {nameof(SliderPath)} must have a non-null type."); + + Vector2[] vertices = new Vector2[ControlPoints.Count]; + for (int i = 0; i < ControlPoints.Count; i++) + vertices[i] = ControlPoints[i].Position.Value; int start = 0; - int end = 0; - for (int i = 0; i < ControlPoints.Length; ++i) + for (int i = 0; i < ControlPoints.Count; i++) { - end++; + if (ControlPoints[i].Type.Value == null && i < ControlPoints.Count - 1) + continue; - if (i == ControlPoints.Length - 1 || ControlPoints[i] == ControlPoints[i + 1]) + Debug.Assert(ControlPoints[start].Type.Value.HasValue); + + // The current vertex ends the segment + var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); + var segmentType = ControlPoints[start].Type.Value.Value; + + foreach (Vector2 t in computeSubPath(segmentVertices, segmentType)) { - ReadOnlySpan cpSpan = ControlPoints.Slice(start, end - start); - - foreach (Vector2 t in calculateSubpath(cpSpan)) - { - if (calculatedPath.Count == 0 || calculatedPath.Last() != t) - calculatedPath.Add(t); - } - - start = end; + if (calculatedPath.Count == 0 || calculatedPath.Last() != t) + calculatedPath.Add(t); } + + // Start the new segment at the current vertex + start = i; + } + + static List computeSubPath(ReadOnlySpan subControlPoints, PathType type) + { + switch (type) + { + case PathType.Linear: + return PathApproximator.ApproximateLinear(subControlPoints); + + case PathType.PerfectCurve: + if (subControlPoints.Length != 3) + break; + + List subpath = PathApproximator.ApproximateCircularArc(subControlPoints); + + // If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation. + if (subpath.Count == 0) + break; + + return subpath; + + case PathType.Catmull: + return PathApproximator.ApproximateCatmull(subControlPoints); + } + + return PathApproximator.ApproximateBezier(subControlPoints); } } @@ -272,7 +275,5 @@ namespace osu.Game.Rulesets.Objects double w = (d - d0) / (d1 - d0); return p0 + (p1 - p0) * (float)w; } - - public bool Equals(SliderPath other) => ControlPoints.SequenceEqual(other.ControlPoints) && ExpectedDistance == other.ExpectedDistance && Type == other.Type; } } diff --git a/osu.Game/Rulesets/Objects/SliderPath2.cs b/osu.Game/Rulesets/Objects/SliderPath2.cs deleted file mode 100644 index 560313a4d5..0000000000 --- a/osu.Game/Rulesets/Objects/SliderPath2.cs +++ /dev/null @@ -1,274 +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 System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using Newtonsoft.Json; -using osu.Framework.Bindables; -using osu.Framework.Caching; -using osu.Framework.MathUtils; -using osu.Game.Rulesets.Objects.Types; -using osuTK; - -namespace osu.Game.Rulesets.Objects -{ - public class SliderPath2 - { - /// - /// The user-set distance of the path. If non-null, will match this value, - /// and the path will be shortened/lengthened to match this length. - /// - public readonly double? ExpectedDistance; - - /// - /// The control points of the path. - /// - public readonly BindableList ControlPoints = new BindableList(); - - private readonly Cached pathCache = new Cached(); - - private readonly List calculatedPath = new List(); - private readonly List cumulativeLength = new List(); - - /// - /// Creates a new . - /// - /// A user-set distance of the path that may be shorter or longer than the true distance between all control points. - /// The path will be shortened/lengthened to match this length. If null, the path will use the true distance between all control points. - [JsonConstructor] - public SliderPath2(double? expectedDistance = null) - { - ExpectedDistance = expectedDistance; - - ControlPoints.ItemsAdded += items => - { - foreach (var c in items) - c.Changed += onControlPointChanged; - - onControlPointChanged(); - }; - - ControlPoints.ItemsRemoved += items => - { - foreach (var c in items) - c.Changed -= onControlPointChanged; - - onControlPointChanged(); - }; - - void onControlPointChanged() => pathCache.Invalidate(); - } - - /// - /// The distance of the path after lengthening/shortening to account for . - /// - [JsonIgnore] - public double Distance - { - get - { - ensureValid(); - return cumulativeLength.Count == 0 ? 0 : cumulativeLength[cumulativeLength.Count - 1]; - } - } - - /// - /// Computes the slider path until a given progress that ranges from 0 (beginning of the slider) - /// to 1 (end of the slider) and stores the generated path in the given list. - /// - /// The list to be filled with the computed path. - /// Start progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider). - /// End progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider). - public void GetPathToProgress(List path, double p0, double p1) - { - ensureValid(); - - double d0 = progressToDistance(p0); - double d1 = progressToDistance(p1); - - path.Clear(); - - int i = 0; - - for (; i < calculatedPath.Count && cumulativeLength[i] < d0; ++i) - { - } - - path.Add(interpolateVertices(i, d0)); - - for (; i < calculatedPath.Count && cumulativeLength[i] <= d1; ++i) - path.Add(calculatedPath[i]); - - path.Add(interpolateVertices(i, d1)); - } - - /// - /// Computes the position on the slider at a given progress that ranges from 0 (beginning of the path) - /// to 1 (end of the path). - /// - /// Ranges from 0 (beginning of the path) to 1 (end of the path). - /// - public Vector2 PositionAt(double progress) - { - ensureValid(); - - double d = progressToDistance(progress); - return interpolateVertices(indexOfDistance(d), d); - } - - private void ensureValid() - { - if (pathCache.IsValid) - return; - - calculatePath(); - calculateCumulativeLength(); - - pathCache.Validate(); - } - - private void calculatePath() - { - calculatedPath.Clear(); - - if (ControlPoints.Count == 0) - return; - - if (ControlPoints[0].Type.Value == null) - throw new InvalidOperationException($"The first control point in a {nameof(SliderPath2)} must have a non-null type."); - - Vector2[] vertices = new Vector2[ControlPoints.Count]; - for (int i = 0; i < ControlPoints.Count; i++) - vertices[i] = ControlPoints[i].Position.Value; - - int start = 0; - - for (int i = 0; i < ControlPoints.Count; i++) - { - if (ControlPoints[i].Type.Value == null && i < ControlPoints.Count - 1) - continue; - - Debug.Assert(ControlPoints[start].Type.Value.HasValue); - - // The current vertex ends the segment - var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); - var segmentType = ControlPoints[start].Type.Value.Value; - - foreach (Vector2 t in computeSubPath(segmentVertices, segmentType)) - { - if (calculatedPath.Count == 0 || calculatedPath.Last() != t) - calculatedPath.Add(t); - } - - // Start the new segment at the current vertex - start = i; - } - - static List computeSubPath(ReadOnlySpan subControlPoints, PathType type) - { - switch (type) - { - case PathType.Linear: - return PathApproximator.ApproximateLinear(subControlPoints); - - case PathType.PerfectCurve: - if (subControlPoints.Length != 3) - break; - - List subpath = PathApproximator.ApproximateCircularArc(subControlPoints); - - // If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation. - if (subpath.Count == 0) - break; - - return subpath; - - case PathType.Catmull: - return PathApproximator.ApproximateCatmull(subControlPoints); - } - - return PathApproximator.ApproximateBezier(subControlPoints); - } - } - - private void calculateCumulativeLength() - { - double l = 0; - - cumulativeLength.Clear(); - cumulativeLength.Add(l); - - for (int i = 0; i < calculatedPath.Count - 1; ++i) - { - Vector2 diff = calculatedPath[i + 1] - calculatedPath[i]; - double d = diff.Length; - - // Shorted slider paths that are too long compared to the expected distance - if (ExpectedDistance.HasValue && ExpectedDistance - l < d) - { - calculatedPath[i + 1] = calculatedPath[i] + diff * (float)((ExpectedDistance - l) / d); - calculatedPath.RemoveRange(i + 2, calculatedPath.Count - 2 - i); - - l = ExpectedDistance.Value; - cumulativeLength.Add(l); - break; - } - - l += d; - cumulativeLength.Add(l); - } - - // Lengthen slider paths that are too short compared to the expected distance - if (ExpectedDistance.HasValue && l < ExpectedDistance && calculatedPath.Count > 1) - { - Vector2 diff = calculatedPath[calculatedPath.Count - 1] - calculatedPath[calculatedPath.Count - 2]; - double d = diff.Length; - - if (d <= 0) - return; - - calculatedPath[calculatedPath.Count - 1] += diff * (float)((ExpectedDistance - l) / d); - cumulativeLength[calculatedPath.Count - 1] = ExpectedDistance.Value; - } - } - - private int indexOfDistance(double d) - { - int i = cumulativeLength.BinarySearch(d); - if (i < 0) i = ~i; - - return i; - } - - private double progressToDistance(double progress) - { - return Math.Clamp(progress, 0, 1) * Distance; - } - - private Vector2 interpolateVertices(int i, double d) - { - if (calculatedPath.Count == 0) - return Vector2.Zero; - - if (i <= 0) - return calculatedPath.First(); - if (i >= calculatedPath.Count) - return calculatedPath.Last(); - - Vector2 p0 = calculatedPath[i - 1]; - Vector2 p1 = calculatedPath[i]; - - double d0 = cumulativeLength[i - 1]; - double d1 = cumulativeLength[i]; - - // Avoid division by and almost-zero number in case two points are extremely close to each other. - if (Precision.AlmostEquals(d0, d1)) - return p0; - - double w = (d - d0) / (d1 - d0); - return p0 + (p1 - p0) * (float)w; - } - } -} From 986ac1cee4df72dc07c93f15f24032bc714a1edc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Dec 2019 17:49:54 +0900 Subject: [PATCH 2573/2815] Make expected distance a bindable --- osu.Game/Rulesets/Objects/SliderPath.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index cc2b537afc..9d68e1337a 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Objects /// The user-set distance of the path. If non-null, will match this value, /// and the path will be shortened/lengthened to match this length. /// - public readonly double? ExpectedDistance; + public readonly Bindable ExpectedDistance = new Bindable(); /// /// The control points of the path. @@ -43,7 +43,8 @@ namespace osu.Game.Rulesets.Objects [JsonConstructor] public SliderPath(PathControlPoint[] controlPoints = null, double? expectedDistance = null) { - ExpectedDistance = expectedDistance; + ExpectedDistance.Value = expectedDistance; + ExpectedDistance.ValueChanged += _ => pathCache.Invalidate(); ControlPoints.ItemsAdded += items => { @@ -205,18 +206,20 @@ namespace osu.Game.Rulesets.Objects cumulativeLength.Clear(); cumulativeLength.Add(l); + double? expectedDistance = ExpectedDistance.Value; + for (int i = 0; i < calculatedPath.Count - 1; ++i) { Vector2 diff = calculatedPath[i + 1] - calculatedPath[i]; double d = diff.Length; // Shorted slider paths that are too long compared to the expected distance - if (ExpectedDistance.HasValue && ExpectedDistance - l < d) + if (expectedDistance.HasValue && expectedDistance - l < d) { - calculatedPath[i + 1] = calculatedPath[i] + diff * (float)((ExpectedDistance - l) / d); + calculatedPath[i + 1] = calculatedPath[i] + diff * (float)((expectedDistance - l) / d); calculatedPath.RemoveRange(i + 2, calculatedPath.Count - 2 - i); - l = ExpectedDistance.Value; + l = expectedDistance.Value; cumulativeLength.Add(l); break; } @@ -226,7 +229,7 @@ namespace osu.Game.Rulesets.Objects } // Lengthen slider paths that are too short compared to the expected distance - if (ExpectedDistance.HasValue && l < ExpectedDistance && calculatedPath.Count > 1) + if (expectedDistance.HasValue && l < expectedDistance && calculatedPath.Count > 1) { Vector2 diff = calculatedPath[calculatedPath.Count - 1] - calculatedPath[calculatedPath.Count - 2]; double d = diff.Length; @@ -234,8 +237,8 @@ namespace osu.Game.Rulesets.Objects if (d <= 0) return; - calculatedPath[calculatedPath.Count - 1] += diff * (float)((ExpectedDistance - l) / d); - cumulativeLength[calculatedPath.Count - 1] = ExpectedDistance.Value; + calculatedPath[calculatedPath.Count - 1] += diff * (float)((expectedDistance - l) / d); + cumulativeLength[calculatedPath.Count - 1] = expectedDistance.Value; } } From 1585d83b969016e287cb9c2af06abdbe2df431f3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Dec 2019 18:19:42 +0900 Subject: [PATCH 2574/2815] Add legacy constructor --- osu.Game/Rulesets/Objects/SliderPath.cs | 33 +++++++++++++++++++------ 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 9d68e1337a..bbeb03992e 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -37,13 +37,8 @@ namespace osu.Game.Rulesets.Objects /// /// Creates a new . /// - /// An optional set of s to initialise the path with. - /// A user-set distance of the path that may be shorter or longer than the true distance between all control points. - /// The path will be shortened/lengthened to match this length. If null, the path will use the true distance between all control points. - [JsonConstructor] - public SliderPath(PathControlPoint[] controlPoints = null, double? expectedDistance = null) + public SliderPath() { - ExpectedDistance.Value = expectedDistance; ExpectedDistance.ValueChanged += _ => pathCache.Invalidate(); ControlPoints.ItemsAdded += items => @@ -62,11 +57,33 @@ namespace osu.Game.Rulesets.Objects onControlPointChanged(); }; - ControlPoints.AddRange(controlPoints); - void onControlPointChanged() => pathCache.Invalidate(); } + /// + /// Creates a new . + /// + /// An optional set of s to initialise the path with. + /// A user-set distance of the path that may be shorter or longer than the true distance between all control points. + /// The path will be shortened/lengthened to match this length. If null, the path will use the true distance between all control points. + [JsonConstructor] + public SliderPath(PathControlPoint[] controlPoints, double? expectedDistance = null) + : this() + { + ControlPoints.AddRange(controlPoints); + ExpectedDistance.Value = expectedDistance; + } + + public SliderPath(PathType type, Vector2[] controlPoints, double? expectedDistance = null) + : this() + { + foreach (var c in controlPoints) + ControlPoints.Add(new PathControlPoint { Position = { Value = c } }); + ControlPoints[0].Type.Value = type; + + ExpectedDistance.Value = expectedDistance; + } + /// /// The distance of the path after lengthening/shortening to account for . /// From 2419077c0763612f0cb05f8e503e7208373dfd47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Dec 2019 18:31:13 +0900 Subject: [PATCH 2575/2815] 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 ff6d7396ee..301c615ce4 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -54,6 +54,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d7a34c2b1c..086359ee41 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 d2feebbfc9..0eb8d98a63 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -74,7 +74,7 @@ - + @@ -82,7 +82,7 @@ - + From c9a66c0d07a8a89267355133a0eb8caead972c88 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Dec 2019 18:31:28 +0900 Subject: [PATCH 2576/2815] Expose a version to indicate path changes --- osu.Game/Rulesets/Objects/SliderPath.cs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index bbeb03992e..6ad5d2f1c3 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -16,6 +16,13 @@ namespace osu.Game.Rulesets.Objects { public class SliderPath { + /// + /// The current version of this . Updated when any change to the path occurs. + /// + public IBindable Version => version; + + private readonly Bindable version = new Bindable(); + /// /// The user-set distance of the path. If non-null, will match this value, /// and the path will be shortened/lengthened to match this length. @@ -39,25 +46,23 @@ namespace osu.Game.Rulesets.Objects /// public SliderPath() { - ExpectedDistance.ValueChanged += _ => pathCache.Invalidate(); + ExpectedDistance.ValueChanged += _ => invalidate(); ControlPoints.ItemsAdded += items => { foreach (var c in items) - c.Changed += onControlPointChanged; + c.Changed += invalidate; - onControlPointChanged(); + invalidate(); }; ControlPoints.ItemsRemoved += items => { foreach (var c in items) - c.Changed -= onControlPointChanged; + c.Changed -= invalidate; - onControlPointChanged(); + invalidate(); }; - - void onControlPointChanged() => pathCache.Invalidate(); } /// @@ -141,6 +146,12 @@ namespace osu.Game.Rulesets.Objects return interpolateVertices(indexOfDistance(d), d); } + private void invalidate() + { + pathCache.Invalidate(); + version.Value++; + } + private void ensureValid() { if (pathCache.IsValid) From e225b0032aa092c79322fb948fd7e3adb7aac2b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Dec 2019 19:31:40 +0900 Subject: [PATCH 2577/2815] Add basic hitobject display to timeline --- .../Editor/TestSceneEditorComposeTimeline.cs | 16 ++++ .../Compose/Components/Timeline/Timeline.cs | 21 +++-- .../Timeline/TimelineHitObjectDisplay.cs | 85 +++++++++++++++++++ 3 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs index 6e5b3b93e9..b67ea798a0 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs @@ -13,6 +13,10 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; using osuTK.Graphics; @@ -25,11 +29,23 @@ namespace osu.Game.Tests.Visual.Editor public override IReadOnlyList RequiredTypes => new[] { typeof(TimelineArea), + typeof(TimelineHitObjectDisplay), typeof(Timeline), typeof(TimelineButton), typeof(CentreMarker) }; + [Cached(typeof(IEditorBeatmap))] + private readonly EditorBeatmap editorBeatmap; + + public TestSceneEditorComposeTimeline() + { + editorBeatmap = new EditorBeatmap( + (Beatmap) + CreateWorkingBeatmap(new OsuRuleset().RulesetInfo).GetPlayableBeatmap(new OsuRuleset().RulesetInfo, new Mod[] { }) + ); + } + [BackgroundDependencyLoader] private void load(AudioManager audio) { diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 748c9e2ba3..1f8c134d84 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -36,14 +36,21 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { this.adjustableClock = adjustableClock; - Child = waveform = new WaveformGraph + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = colours.Blue.Opacity(0.2f), - LowColour = colours.BlueLighter, - MidColour = colours.BlueDark, - HighColour = colours.BlueDarker, - Depth = float.MaxValue + waveform = new WaveformGraph + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Blue.Opacity(0.2f), + LowColour = colours.BlueLighter, + MidColour = colours.BlueDark, + HighColour = colours.BlueDarker, + Depth = float.MaxValue + }, + new TimelineHitObjectDisplay + { + RelativeSizeAxes = Axes.Both, + }, }; // We don't want the centre marker to scroll diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs new file mode 100644 index 0000000000..8bc0bf1a86 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs @@ -0,0 +1,85 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Compose.Components.Timeline +{ + internal class TimelineHitObjectDisplay : TimelinePart + { + [Resolved] + private IEditorBeatmap beatmap { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + foreach (var h in beatmap.HitObjects) + add(h); + + beatmap.HitObjectAdded += add; + beatmap.HitObjectRemoved += remove; + } + + private void remove(HitObject h) + { + foreach (var d in InternalChildren.OfType().Where(c => c.HitObject == h)) + d.Expire(); + } + + private void add(HitObject h) + { + Add(new TimelineHitObjectRepresentation(h)); + } + + private class TimelineHitObjectRepresentation : CompositeDrawable + { + public readonly HitObject HitObject; + + public TimelineHitObjectRepresentation(HitObject hitObject) + { + this.HitObject = hitObject; + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + + Width = (float)(hitObject.GetEndTime() - hitObject.StartTime); + + X = (float)hitObject.StartTime; + + RelativePositionAxes = Axes.X; + RelativeSizeAxes = Axes.X; + + AddInternal(new Circle + { + Size = new Vector2(16), + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.X, + AlwaysPresent = true, + Colour = Color4.White, + }); + + if (hitObject is IHasEndTime) + { + AddInternal(new Box + { + Size = new Vector2(1, 5), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativePositionAxes = Axes.X, + RelativeSizeAxes = Axes.X, + Colour = Color4.White, + }); + } + } + } + } +} From 3ebbf62b2ab11d58fd372a091fe179eb7f827188 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Dec 2019 19:53:31 +0900 Subject: [PATCH 2578/2815] Initial game-wide update with the new SliderPath --- .../TestSceneSliderSelectionBlueprint.cs | 2 +- .../Components/PathControlPointPiece.cs | 28 +++-------- .../Components/PathControlPointVisualiser.cs | 24 +++++---- .../Sliders/SliderPlacementBlueprint.cs | 18 +++---- .../Sliders/SliderSelectionBlueprint.cs | 25 +++------- osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs | 7 +-- .../Objects/Drawables/DrawableSlider.cs | 6 +-- .../Objects/Drawables/DrawableSliderHead.cs | 6 +-- .../Objects/Drawables/DrawableSliderTail.cs | 6 +-- osu.Game.Rulesets.Osu/Objects/Slider.cs | 14 +----- .../Objects/SliderTailCircle.cs | 6 +-- .../Legacy/Catch/ConvertHitObjectParser.cs | 4 +- .../Objects/Legacy/ConvertHitObjectParser.cs | 50 +++++++++++++++---- .../Legacy/Mania/ConvertHitObjectParser.cs | 4 +- .../Legacy/Osu/ConvertHitObjectParser.cs | 4 +- .../Legacy/Taiko/ConvertHitObjectParser.cs | 4 +- 16 files changed, 97 insertions(+), 111 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs index dde2aa53e0..013920684c 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs @@ -196,7 +196,7 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep($"move mouse to control point {index}", () => { - Vector2 position = slider.Position + slider.Path.ControlPoints[index]; + Vector2 position = slider.Position + slider.Path.ControlPoints[index].Position.Value; InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position)); }); } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 0ccf020300..159916f16f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { base.Update(); - Position = slider.StackedPosition + slider.Path.ControlPoints[Index]; + Position = slider.StackedPosition + slider.Path.ControlPoints[Index].Position.Value; updateMarkerDisplay(); updateConnectingPath(); @@ -116,10 +116,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { path.ClearVertices(); - if (Index != slider.Path.ControlPoints.Length - 1) + if (Index != slider.Path.ControlPoints.Count - 1) { path.AddVertex(Vector2.Zero); - path.AddVertex(slider.Path.ControlPoints[Index + 1] - slider.Path.ControlPoints[Index]); + path.AddVertex(slider.Path.ControlPoints[Index + 1].Position.Value - slider.Path.ControlPoints[Index].Position.Value); } path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero); @@ -156,8 +156,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components protected override bool OnDrag(DragEvent e) { - var newControlPoints = slider.Path.ControlPoints.ToArray(); - if (Index == 0) { // Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account @@ -168,29 +166,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components slider.StartTime = snappedTime; // Since control points are relative to the position of the slider, they all need to be offset backwards by the delta - for (int i = 1; i < newControlPoints.Length; i++) - newControlPoints[i] -= movementDelta; + for (int i = 1; i < slider.Path.ControlPoints.Count; i++) + slider.Path.ControlPoints[i].Position.Value -= movementDelta; } else - newControlPoints[Index] += e.Delta; - - if (isSegmentSeparatorWithNext) - newControlPoints[Index + 1] = newControlPoints[Index]; - - if (isSegmentSeparatorWithPrevious) - newControlPoints[Index - 1] = newControlPoints[Index]; - - ControlPointsChanged?.Invoke(newControlPoints); + slider.Path.ControlPoints[Index].Position.Value += e.Delta; return true; } protected override bool OnDragEnd(DragEndEvent e) => true; - private bool isSegmentSeparator => isSegmentSeparatorWithNext || isSegmentSeparatorWithPrevious; - - private bool isSegmentSeparatorWithNext => Index < slider.Path.ControlPoints.Length - 1 && slider.Path.ControlPoints[Index + 1] == slider.Path.ControlPoints[Index]; - - private bool isSegmentSeparatorWithPrevious => Index > 0 && slider.Path.ControlPoints[Index - 1] == slider.Path.ControlPoints[Index]; + private bool isSegmentSeparator => slider.Path.ControlPoints[Index].Type.Value.HasValue; } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index cdca48490e..c47e4d7d4a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { base.Update(); - while (slider.Path.ControlPoints.Length > Pieces.Count) + while (slider.Path.ControlPoints.Count > Pieces.Count) { var piece = new PathControlPointPiece(slider, Pieces.Count) { @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components Pieces.Add(piece); } - while (slider.Path.ControlPoints.Length < Pieces.Count) + while (slider.Path.ControlPoints.Count < Pieces.Count) Pieces.Remove(Pieces[Pieces.Count - 1]); } @@ -105,29 +105,32 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private bool deleteSelected() { - var newControlPoints = new List(); + int countDeleted = 0; foreach (var piece in Pieces) { - if (!piece.IsSelected.Value) - newControlPoints.Add(slider.Path.ControlPoints[piece.Index]); + if (piece.IsSelected.Value) + { + slider.Path.ControlPoints.RemoveAt(piece.Index); + countDeleted++; + } } // Ensure that there are any points to be deleted - if (newControlPoints.Count == slider.Path.ControlPoints.Length) + if (countDeleted == 0) return false; // If there are 0 remaining control points, treat the slider as being deleted - if (newControlPoints.Count == 0) + if (slider.Path.ControlPoints.Count == 0) { placementHandler?.Delete(slider); return true; } // Make control points relative - Vector2 first = newControlPoints[0]; - for (int i = 0; i < newControlPoints.Count; i++) - newControlPoints[i] = newControlPoints[i] - first; + Vector2 first = slider.Path.ControlPoints[0].Position.Value; + for (int i = 0; i < slider.Path.ControlPoints.Count; i++) + slider.Path.ControlPoints[i].Position.Value = slider.Path.ControlPoints[i].Position.Value - first; // The slider's position defines the position of the first control point, and all further control points are relative to that point slider.Position += first; @@ -136,7 +139,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components foreach (var piece in Pieces) piece.IsSelected.Value = false; - ControlPointsChanged?.Invoke(newControlPoints.ToArray()); return true; } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 9c0afada29..62a22dc858 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -27,8 +27,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private HitCirclePiece headCirclePiece; private HitCirclePiece tailCirclePiece; - private readonly List segments = new List(); - private Vector2 cursor; private InputManager inputManager; private PlacementState state; @@ -40,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders : base(new Objects.Slider()) { RelativeSizeAxes = Axes.Both; - segments.Add(new Segment(Vector2.Zero)); + HitObject.Path.ControlPoints.Add(new PathControlPoint { Position = { Value = Vector2.Zero } }); } [BackgroundDependencyLoader] @@ -74,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders case PlacementState.Body: // The given screen-space position may have been externally snapped, but the unsnapped position from the input manager // is used instead since snapping control points doesn't make much sense - cursor = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position; + HitObject.Path.ControlPoints[HitObject.Path.ControlPoints.Count - 1].Position.Value = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position; break; } } @@ -91,7 +89,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders switch (e.Button) { case MouseButton.Left: - segments.Last().ControlPoints.Add(cursor); + HitObject.Path.ControlPoints.Add(new PathControlPoint { Position = { Value = HitObject.Path.ControlPoints[HitObject.Path.ControlPoints.Count - 1].Position.Value } }); break; } @@ -110,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected override bool OnDoubleClick(DoubleClickEvent e) { - segments.Add(new Segment(segments[segments.Count - 1].ControlPoints.Last())); + HitObject.Path.ControlPoints[HitObject.Path.ControlPoints.Count - 2].Type.Value = PathType.Bezier; return true; } @@ -134,12 +132,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void updateSlider() { - Vector2[] newControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray(); - - var unsnappedPath = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints); - var snappedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance; - - HitObject.Path = new SliderPath(unsnappedPath.Type, newControlPoints, snappedDistance); + HitObject.Path.ExpectedDistance.Value = null; + HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.Distance) ?? (float)HitObject.Path.Distance; bodyPiece.UpdateFrom(HitObject); headCirclePiece.UpdateFrom(HitObject.HeadCircle); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 820d6c92d7..ab52c906e8 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -77,12 +77,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { Debug.Assert(placementControlPointIndex != null); - Vector2 position = e.MousePosition - HitObject.Position; - - var controlPoints = HitObject.Path.ControlPoints.ToArray(); - controlPoints[placementControlPointIndex.Value] = position; - - onNewControlPoints(controlPoints); + HitObject.Path.ControlPoints[placementControlPointIndex.Value].Position.Value = e.MousePosition - HitObject.Position; return true; } @@ -97,15 +92,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { position -= HitObject.Position; - var controlPoints = new Vector2[HitObject.Path.ControlPoints.Length + 1]; - HitObject.Path.ControlPoints.CopyTo(controlPoints); - int insertionIndex = 0; float minDistance = float.MaxValue; - for (int i = 0; i < controlPoints.Length - 2; i++) + for (int i = 0; i < HitObject.Path.ControlPoints.Count - 2; i++) { - float dist = new Line(controlPoints[i], controlPoints[i + 1]).DistanceToPoint(position); + float dist = new Line(HitObject.Path.ControlPoints[i].Position.Value, HitObject.Path.ControlPoints[i + 1].Position.Value).DistanceToPoint(position); if (dist < minDistance) { @@ -115,20 +107,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders } // Move the control points from the insertion index onwards to make room for the insertion - Array.Copy(controlPoints, insertionIndex, controlPoints, insertionIndex + 1, controlPoints.Length - insertionIndex - 1); - controlPoints[insertionIndex] = position; - - onNewControlPoints(controlPoints); + HitObject.Path.ControlPoints.Insert(insertionIndex, new PathControlPoint { Position = { Value = position } }); return insertionIndex; } private void onNewControlPoints(Vector2[] controlPoints) { - var unsnappedPath = new SliderPath(controlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, controlPoints); - var snappedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance; - - HitObject.Path = new SliderPath(unsnappedPath.Type, controlPoints, snappedDistance); + HitObject.Path.ExpectedDistance.Value = null; + HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.Distance) ?? (float)HitObject.Path.Distance; UpdateHitObject(); } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs index 3d566362ae..bc5f79331f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs @@ -28,11 +28,8 @@ namespace osu.Game.Rulesets.Osu.Mods slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); - var newControlPoints = new Vector2[slider.Path.ControlPoints.Length]; - for (int i = 0; i < slider.Path.ControlPoints.Length; i++) - newControlPoints[i] = new Vector2(slider.Path.ControlPoints[i].X, -slider.Path.ControlPoints[i].Y); - - slider.Path = new SliderPath(slider.Path.Type, newControlPoints, slider.Path.ExpectedDistance); + foreach (var point in slider.Path.ControlPoints) + point.Position.Value = new Vector2(point.Position.Value.X, -point.Position.Value.Y); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 69189758a6..1e0402d492 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly IBindable positionBindable = new Bindable(); private readonly IBindable stackHeightBindable = new Bindable(); private readonly IBindable scaleBindable = new Bindable(); - private readonly IBindable pathBindable = new Bindable(); + private readonly IBindable pathVersion = new Bindable(); [Resolved(CanBeNull = true)] private OsuRulesetConfigManager config { get; set; } @@ -84,9 +84,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables positionBindable.BindTo(HitObject.PositionBindable); stackHeightBindable.BindTo(HitObject.StackHeightBindable); scaleBindable.BindTo(HitObject.ScaleBindable); - pathBindable.BindTo(slider.PathBindable); + pathVersion.BindTo(slider.Path.Version); - pathBindable.BindValueChanged(_ => Body.Refresh()); + pathVersion.BindValueChanged(_ => Body.Refresh()); AccentColour.BindValueChanged(colour => { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index a10c66d1df..166defbc41 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public class DrawableSliderHead : DrawableHitCircle { private readonly IBindable positionBindable = new Bindable(); - private readonly IBindable pathBindable = new Bindable(); + private readonly IBindable pathVersion = new Bindable(); private readonly Slider slider; @@ -27,10 +27,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private void load() { positionBindable.BindTo(HitObject.PositionBindable); - pathBindable.BindTo(slider.PathBindable); + pathVersion.BindTo(slider.Path.Version); positionBindable.BindValueChanged(_ => updatePosition()); - pathBindable.BindValueChanged(_ => updatePosition(), true); + pathVersion.BindValueChanged(_ => updatePosition(), true); } protected override void Update() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 42bf5e4d21..8e2f6ffa66 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public bool Tracking { get; set; } private readonly IBindable positionBindable = new Bindable(); - private readonly IBindable pathBindable = new Bindable(); + private readonly IBindable pathVersion = new Bindable(); public DrawableSliderTail(Slider slider, SliderTailCircle hitCircle) : base(hitCircle) @@ -36,10 +36,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables AlwaysPresent = true; positionBindable.BindTo(hitCircle.PositionBindable); - pathBindable.BindTo(slider.PathBindable); + pathVersion.BindTo(slider.Path.Version); positionBindable.BindValueChanged(_ => updatePosition()); - pathBindable.BindValueChanged(_ => updatePosition(), true); + pathVersion.BindValueChanged(_ => updatePosition(), true); // TODO: This has no drawable content. Support for skins should be added. } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index c6f5a075e0..b68595c67e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -28,19 +28,7 @@ namespace osu.Game.Rulesets.Osu.Objects public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t); - public readonly Bindable PathBindable = new Bindable(); - - public SliderPath Path - { - get => PathBindable.Value; - set - { - PathBindable.Value = value; - endPositionCache.Invalidate(); - - updateNestedPositions(); - } - } + public SliderPath Path { get; set; } = new SliderPath(new[] { new PathControlPoint { Type = { Value = PathType.Bezier } } }); public double Distance => Path.Distance; diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index 14c3369967..c17d2275b8 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -15,12 +15,12 @@ namespace osu.Game.Rulesets.Osu.Objects /// public class SliderTailCircle : SliderCircle { - private readonly IBindable pathBindable = new Bindable(); + private readonly IBindable pathVersion = new Bindable(); public SliderTailCircle(Slider slider) { - pathBindable.BindTo(slider.PathBindable); - pathBindable.BindValueChanged(_ => Position = slider.EndPosition); + pathVersion.BindTo(slider.Path.Version); + pathVersion.BindValueChanged(_ => Position = slider.EndPosition); } public override Judgement CreateJudgement() => new OsuSliderTailJudgement(); diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index 545cfe07f8..afa1f2996e 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount, + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, List> nodeSamples) { newCombo |= forceNewCombo; @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch X = position.X, NewCombo = FirstObject || newCombo, ComboOffset = comboOffset, - Path = new SliderPath(pathType, controlPoints, length), + Path = new SliderPath(controlPoints, length), NodeSamples = nodeSamples, RepeatCount = repeatCount }; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 5348ff1f02..1ac7284772 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -115,12 +115,6 @@ namespace osu.Game.Rulesets.Objects.Legacy points[pointIndex++] = new Vector2((int)Parsing.ParseDouble(temp[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseDouble(temp[1], Parsing.MAX_COORDINATE_VALUE)) - pos; } - // osu-stable special-cased colinear perfect curves to a CurveType.Linear - static bool isLinear(Vector2[] p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) - (p[1].X - p[0].X) * (p[2].Y - p[0].Y)); - - if (points.Length == 3 && pathType == PathType.PerfectCurve && isLinear(points)) - pathType = PathType.Linear; - int repeatCount = Parsing.ParseInt(split[6]); if (repeatCount > 9000) @@ -187,7 +181,7 @@ namespace osu.Game.Rulesets.Objects.Legacy for (int i = 0; i < nodes; i++) nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); - result = CreateSlider(pos, combo, comboOffset, points, length, pathType, repeatCount, nodeSamples); + result = CreateSlider(pos, combo, comboOffset, convertControlPoints(points, pathType), length, repeatCount, nodeSamples); // The samples are played when the slider ends, which is the last node result.Samples = nodeSamples[nodeSamples.Count - 1]; @@ -259,6 +253,45 @@ namespace osu.Game.Rulesets.Objects.Legacy bankInfo.Filename = split.Length > 4 ? split[4] : null; } + private PathControlPoint[] convertControlPoints(Vector2[] vertices, PathType type) + { + if (type == PathType.PerfectCurve) + { + if (vertices.Length == 3) + { + // osu-stable special-cased colinear perfect curves to a linear path + if (isLinear(vertices)) + type = PathType.Linear; + } + else + type = PathType.Bezier; + } + + var points = new List(vertices.Length) + { + new PathControlPoint + { + Position = { Value = vertices[0] }, + Type = { Value = type } + } + }; + + for (int i = 1; i < vertices.Length; i++) + { + if (vertices[i] == vertices[i - 1]) + { + points[points.Count - 1].Type.Value = type; + continue; + } + + points.Add(new PathControlPoint { Position = { Value = vertices[i] } }); + } + + return points.ToArray(); + + static bool isLinear(Vector2[] p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) - (p[1].X - p[0].X) * (p[2].Y - p[0].Y)); + } + /// /// Creates a legacy Hit-type hit object. /// @@ -276,11 +309,10 @@ namespace osu.Game.Rulesets.Objects.Legacy /// When starting a new combo, the offset of the new combo relative to the current one. /// The slider control points. /// The slider length. - /// The slider curve type. /// The slider repeat count. /// The samples to be played when the slider nodes are hit. This includes the head and tail of the slider. /// The hit object. - protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount, + protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, List> nodeSamples); /// diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs index 8012b4230f..d1a8adc2b7 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs @@ -26,13 +26,13 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount, + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, List> nodeSamples) { return new ConvertSlider { X = position.X, - Path = new SliderPath(pathType, controlPoints, length), + Path = new SliderPath(controlPoints, length), NodeSamples = nodeSamples, RepeatCount = repeatCount }; diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs index 99872e630d..6628f7d059 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount, + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, List> nodeSamples) { newCombo |= forceNewCombo; @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu Position = position, NewCombo = FirstObject || newCombo, ComboOffset = comboOffset, - Path = new SliderPath(pathType, controlPoints, length), + Path = new SliderPath(controlPoints, length), NodeSamples = nodeSamples, RepeatCount = repeatCount }; diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs index 9dc0c01932..7b1d64b19f 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs @@ -23,12 +23,12 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko return new ConvertHit(); } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount, + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, List> nodeSamples) { return new ConvertSlider { - Path = new SliderPath(pathType, controlPoints, length), + Path = new SliderPath(controlPoints, length), NodeSamples = nodeSamples, RepeatCount = repeatCount }; From d8620a70fbbb7bbc9f983126a98478447fba9dc5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Dec 2019 20:12:25 +0900 Subject: [PATCH 2579/2815] Make work in editor --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 8 ++++++-- .../Timelines/Summary/Parts/TimelinePart.cs | 6 +++--- .../Compose/Components/Timeline/Timeline.cs | 4 ---- .../Components/Timeline/TimelineArea.cs | 10 +++++++--- .../Timeline/TimelineHitObjectDisplay.cs | 20 ++++++++++++------- .../Screens/Edit/Compose/ComposeScreen.cs | 13 ++++++++++-- .../Screens/Edit/EditorScreenWithTimeline.cs | 10 ++++++++-- osu.Game/Screens/Edit/IEditorBeatmap.cs | 5 +++++ 8 files changed, 53 insertions(+), 23 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 805fc2b46f..368520ba43 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -34,7 +34,9 @@ namespace osu.Game.Rulesets.Edit where TObject : HitObject { protected IRulesetConfigManager Config { get; private set; } - protected EditorBeatmap EditorBeatmap { get; private set; } + + protected new EditorBeatmap EditorBeatmap { get; private set; } + protected readonly Ruleset Ruleset; [Resolved] @@ -148,7 +150,7 @@ namespace osu.Game.Rulesets.Edit beatmapProcessor = Ruleset.CreateBeatmapProcessor(playableBeatmap); - EditorBeatmap = new EditorBeatmap(playableBeatmap); + base.EditorBeatmap = EditorBeatmap = new EditorBeatmap(playableBeatmap); EditorBeatmap.HitObjectAdded += addHitObject; EditorBeatmap.HitObjectRemoved += removeHitObject; EditorBeatmap.StartTimeChanged += UpdateHitObject; @@ -333,6 +335,8 @@ namespace osu.Game.Rulesets.Edit /// public abstract IEnumerable HitObjects { get; } + public IEditorBeatmap EditorBeatmap { get; protected set; } + /// /// Whether the user's cursor is currently in an area of the that is valid for placement. /// diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs index 26d9614631..7706e33179 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs @@ -14,12 +14,14 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts /// /// Represents a part of the summary timeline.. /// - public abstract class TimelinePart : CompositeDrawable + public abstract class TimelinePart : Container { protected readonly IBindable Beatmap = new Bindable(); private readonly Container timeline; + protected override Container Content => timeline; + protected TimelinePart() { AddInternal(timeline = new Container { RelativeSizeAxes = Axes.Both }); @@ -50,8 +52,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts timeline.RelativeChildSize = new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1); } - protected void Add(Drawable visualisation) => timeline.Add(visualisation); - protected virtual void LoadBeatmap(WorkingBeatmap beatmap) { timeline.Clear(); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 1f8c134d84..2a565d028f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -47,10 +47,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline HighColour = colours.BlueDarker, Depth = float.MaxValue }, - new TimelineHitObjectDisplay - { - RelativeSizeAxes = Axes.Both, - }, }; // We don't want the centre marker to scroll diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index 863a120fc3..93dac059e3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.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.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -11,11 +12,14 @@ using osuTK; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { - public class TimelineArea : CompositeDrawable + public class TimelineArea : Container { - private readonly Timeline timeline; + private Timeline timeline; - public TimelineArea() + protected override Container Content => timeline; + + [BackgroundDependencyLoader] + private void load() { Masking = true; CornerRadius = 5; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs index 8bc0bf1a86..fc16d29639 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs @@ -16,8 +16,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { internal class TimelineHitObjectDisplay : TimelinePart { - [Resolved] - private IEditorBeatmap beatmap { get; set; } + private IEditorBeatmap beatmap { get; } + + public TimelineHitObjectDisplay(IEditorBeatmap beatmap) + { + this.beatmap = beatmap; + } [BackgroundDependencyLoader] private void load() @@ -27,18 +31,20 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline beatmap.HitObjectAdded += add; beatmap.HitObjectRemoved += remove; + beatmap.StartTimeChanged += h => + { + remove(h); + add(h); + }; } private void remove(HitObject h) { - foreach (var d in InternalChildren.OfType().Where(c => c.HitObject == h)) + foreach (var d in Children.OfType().Where(c => c.HitObject == h)) d.Expire(); } - private void add(HitObject h) - { - Add(new TimelineHitObjectRepresentation(h)); - } + private void add(HitObject h) => Add(new TimelineHitObjectRepresentation(h)); private class TimelineHitObjectRepresentation : CompositeDrawable { diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 2e9094ebe6..8511bc3242 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -3,17 +3,21 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Edit; +using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Skinning; namespace osu.Game.Screens.Edit.Compose { public class ComposeScreen : EditorScreenWithTimeline { + private HitObjectComposer composer; + protected override Drawable CreateMainContent() { var ruleset = Beatmap.Value.BeatmapInfo.Ruleset?.CreateInstance(); - var composer = ruleset?.CreateHitObjectComposer(); + composer = ruleset?.CreateHitObjectComposer(); if (composer != null) { @@ -25,10 +29,15 @@ namespace osu.Game.Screens.Edit.Compose // 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. - return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(ruleset.CreateHitObjectComposer())); + return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(composer)); } return new ScreenWhiteBox.UnderConstructionMessage(ruleset == null ? "This beatmap" : $"{ruleset.Description}'s composer"); } + + protected override Drawable CreateTimelineContent() => new TimelineHitObjectDisplay(composer.EditorBeatmap) + { + RelativeSizeAxes = Axes.Both, + }; } } diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index 752356e8c4..1b9d1be4f8 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -20,6 +20,8 @@ namespace osu.Game.Screens.Edit private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); + private TimelineArea timelineArea; + [BackgroundDependencyLoader(true)] private void load([CanBeNull] BindableBeatDivisor beatDivisor) { @@ -64,7 +66,7 @@ namespace osu.Game.Screens.Edit { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Right = 5 }, - Child = CreateTimeline() + Child = timelineArea = CreateTimelineArea() }, new BeatDivisorControl(beatDivisor) { RelativeSizeAxes = Axes.Both } }, @@ -97,11 +99,15 @@ namespace osu.Game.Screens.Edit { mainContent.Add(content); content.FadeInFromZero(300, Easing.OutQuint); + + LoadComponentAsync(CreateTimelineContent(), timelineArea.Add); }); } protected abstract Drawable CreateMainContent(); - protected virtual Drawable CreateTimeline() => new TimelineArea { RelativeSizeAxes = Axes.Both }; + protected virtual Drawable CreateTimelineContent() => new Container { }; + + protected TimelineArea CreateTimelineArea() => new TimelineArea { RelativeSizeAxes = Axes.Both }; } } diff --git a/osu.Game/Screens/Edit/IEditorBeatmap.cs b/osu.Game/Screens/Edit/IEditorBeatmap.cs index 2f250ba446..3e3418ef79 100644 --- a/osu.Game/Screens/Edit/IEditorBeatmap.cs +++ b/osu.Game/Screens/Edit/IEditorBeatmap.cs @@ -23,6 +23,11 @@ namespace osu.Game.Screens.Edit /// Invoked when a is removed from this . /// event Action HitObjectRemoved; + + /// + /// Invoked when the start time of a in this was changed. + /// + event Action StartTimeChanged; } /// From e76f8bdd6414542de669ea0301bebf921cf0be06 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Dec 2019 23:31:21 +0900 Subject: [PATCH 2580/2815] Fix warnings --- .../Editor/TestSceneEditorComposeTimeline.cs | 16 +++---- .../Timeline/TimelineHitObjectDisplay.cs | 43 ++++++++++++------- .../Screens/Edit/EditorScreenWithTimeline.cs | 2 +- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs index b67ea798a0..2b46418659 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs @@ -35,20 +35,14 @@ namespace osu.Game.Tests.Visual.Editor typeof(CentreMarker) }; - [Cached(typeof(IEditorBeatmap))] - private readonly EditorBeatmap editorBeatmap; - - public TestSceneEditorComposeTimeline() - { - editorBeatmap = new EditorBeatmap( - (Beatmap) - CreateWorkingBeatmap(new OsuRuleset().RulesetInfo).GetPlayableBeatmap(new OsuRuleset().RulesetInfo, new Mod[] { }) - ); - } - [BackgroundDependencyLoader] private void load(AudioManager audio) { + var editorBeatmap = new EditorBeatmap( + (Beatmap) + CreateWorkingBeatmap(new OsuRuleset().RulesetInfo).GetPlayableBeatmap(new OsuRuleset().RulesetInfo, new Mod[] { }) + ); + Beatmap.Value = new WaveformTestBeatmap(audio); Children = new Drawable[] diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs index fc16d29639..337fd9a6b2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs @@ -44,7 +44,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline d.Expire(); } - private void add(HitObject h) => Add(new TimelineHitObjectRepresentation(h)); + private void add(HitObject h) + { + var yOffset = Children.Count(d => d.X == h.StartTime); + + Add(new TimelineHitObjectRepresentation(h) { Y = -yOffset * 4 }); + } private class TimelineHitObjectRepresentation : CompositeDrawable { @@ -52,7 +57,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public TimelineHitObjectRepresentation(HitObject hitObject) { - this.HitObject = hitObject; + HitObject = hitObject; Anchor = Anchor.CentreLeft; Origin = Anchor.CentreLeft; @@ -63,6 +68,25 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativePositionAxes = Axes.X; RelativeSizeAxes = Axes.X; + if (hitObject is IHasEndTime) + { + AddInternal(new Container + { + CornerRadius = 2, + Masking = true, + Size = new Vector2(1, 3), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativePositionAxes = Axes.X, + RelativeSizeAxes = Axes.X, + Colour = Color4.Black, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + } + }); + } + AddInternal(new Circle { Size = new Vector2(16), @@ -71,20 +95,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativePositionAxes = Axes.X, AlwaysPresent = true, Colour = Color4.White, + BorderColour = Color4.Black, + BorderThickness = 3, }); - - if (hitObject is IHasEndTime) - { - AddInternal(new Box - { - Size = new Vector2(1, 5), - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativePositionAxes = Axes.X, - RelativeSizeAxes = Axes.X, - Colour = Color4.White, - }); - } } } } diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index 1b9d1be4f8..aa8d99b517 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -106,7 +106,7 @@ namespace osu.Game.Screens.Edit protected abstract Drawable CreateMainContent(); - protected virtual Drawable CreateTimelineContent() => new Container { }; + protected virtual Drawable CreateTimelineContent() => new Container(); protected TimelineArea CreateTimelineArea() => new TimelineArea { RelativeSizeAxes = Axes.Both }; } From 12a98438353fbec5966e234dad10ab8d7f4a1dc9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 10:48:18 +0900 Subject: [PATCH 2581/2815] Move thickness to a constant --- .../Components/Timeline/TimelineHitObjectDisplay.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs index 337fd9a6b2..7d6d8fd729 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs @@ -48,11 +48,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { var yOffset = Children.Count(d => d.X == h.StartTime); - Add(new TimelineHitObjectRepresentation(h) { Y = -yOffset * 4 }); + Add(new TimelineHitObjectRepresentation(h) { Y = -yOffset * TimelineHitObjectRepresentation.THICKNESS }); } private class TimelineHitObjectRepresentation : CompositeDrawable { + public const float THICKNESS = 3; + public readonly HitObject HitObject; public TimelineHitObjectRepresentation(HitObject hitObject) @@ -74,7 +76,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { CornerRadius = 2, Masking = true, - Size = new Vector2(1, 3), + Size = new Vector2(1, THICKNESS), Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, RelativePositionAxes = Axes.X, @@ -96,7 +98,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline AlwaysPresent = true, Colour = Color4.White, BorderColour = Color4.Black, - BorderThickness = 3, + BorderThickness = THICKNESS, }); } } From 28400aa865d4727eb885d50ffe0b0a1a553cff6c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 11:26:50 +0900 Subject: [PATCH 2582/2815] Update test scene --- .../Editor/TestSceneEditorComposeTimeline.cs | 12 ++++-------- .../Compose/Components/Timeline/Timeline.cs | 19 ++++++++----------- .../Components/Timeline/TimelineArea.cs | 4 ++-- .../Timeline/TimelineHitObjectDisplay.cs | 2 ++ .../Screens/Edit/Compose/ComposeScreen.cs | 5 +---- 5 files changed, 17 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs index 2b46418659..e618256c03 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs @@ -13,9 +13,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; @@ -38,13 +36,10 @@ namespace osu.Game.Tests.Visual.Editor [BackgroundDependencyLoader] private void load(AudioManager audio) { - var editorBeatmap = new EditorBeatmap( - (Beatmap) - CreateWorkingBeatmap(new OsuRuleset().RulesetInfo).GetPlayableBeatmap(new OsuRuleset().RulesetInfo, new Mod[] { }) - ); - Beatmap.Value = new WaveformTestBeatmap(audio); + var editorBeatmap = new EditorBeatmap((Beatmap)Beatmap.Value.Beatmap); + Children = new Drawable[] { new FillFlowContainer @@ -60,6 +55,7 @@ namespace osu.Game.Tests.Visual.Editor }, new TimelineArea { + Child = new TimelineHitObjectDisplay(editorBeatmap), Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 2a565d028f..b4f3b1f610 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -36,18 +36,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { this.adjustableClock = adjustableClock; - Children = new Drawable[] + Add(waveform = new WaveformGraph { - waveform = new WaveformGraph - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Blue.Opacity(0.2f), - LowColour = colours.BlueLighter, - MidColour = colours.BlueDark, - HighColour = colours.BlueDarker, - Depth = float.MaxValue - }, - }; + RelativeSizeAxes = Axes.Both, + Colour = colours.Blue.Opacity(0.2f), + LowColour = colours.BlueLighter, + MidColour = colours.BlueDark, + HighColour = colours.BlueDarker, + Depth = float.MaxValue + }); // We don't want the centre marker to scroll AddInternal(new CentreMarker()); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index 93dac059e3..8909b3d422 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class TimelineArea : Container { - private Timeline timeline; + private readonly Timeline timeline = new Timeline { RelativeSizeAxes = Axes.Both }; protected override Container Content => timeline; @@ -111,7 +111,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } }, - timeline = new Timeline { RelativeSizeAxes = Axes.Both } + timeline }, }, ColumnDimensions = new[] diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs index 7d6d8fd729..db4aca75e5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs @@ -20,6 +20,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public TimelineHitObjectDisplay(IEditorBeatmap beatmap) { + RelativeSizeAxes = Axes.Both; + this.beatmap = beatmap; } diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 8511bc3242..cb8f2b2caa 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -35,9 +35,6 @@ namespace osu.Game.Screens.Edit.Compose return new ScreenWhiteBox.UnderConstructionMessage(ruleset == null ? "This beatmap" : $"{ruleset.Description}'s composer"); } - protected override Drawable CreateTimelineContent() => new TimelineHitObjectDisplay(composer.EditorBeatmap) - { - RelativeSizeAxes = Axes.Both, - }; + protected override Drawable CreateTimelineContent() => new TimelineHitObjectDisplay(composer.EditorBeatmap); } } From 23c7132c4fdc452ab2e3b1b7d60fd179f0ea4082 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 6 Dec 2019 11:53:22 +0900 Subject: [PATCH 2583/2815] Add missing license header --- osu.Game/Rulesets/Objects/PathControlPoint.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/Objects/PathControlPoint.cs b/osu.Game/Rulesets/Objects/PathControlPoint.cs index d68ef4112b..83436b7a36 100644 --- a/osu.Game/Rulesets/Objects/PathControlPoint.cs +++ b/osu.Game/Rulesets/Objects/PathControlPoint.cs @@ -1,3 +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.Bindables; using osu.Game.Rulesets.Objects.Types; From 8be6abf6076224b1a3a5a7aa1490793911ab7426 Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Fri, 6 Dec 2019 10:07:16 +0700 Subject: [PATCH 2584/2815] Add param to let function return lighter colour for some diff --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 2 +- osu.Game/Graphics/OsuColour.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 025b7e11fd..7bd40af512 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -168,7 +168,7 @@ namespace osu.Game.Beatmaps.Drawables difficultyName.Text = beatmap.Version; starRating.Text = $"{beatmap.StarDifficulty:0.##}"; - difficultyFlow.Colour = beatmap.DifficultyRating == DifficultyRating.ExpertPlus ? colours.Gray9 : colours.ForDifficultyRating(beatmap.DifficultyRating); + difficultyFlow.Colour = colours.ForDifficultyRating(beatmap.DifficultyRating, true); return true; } diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index af66f57f14..2dc12b3e67 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -38,7 +38,7 @@ namespace osu.Game.Graphics } } - public Color4 ForDifficultyRating(DifficultyRating difficulty) + public Color4 ForDifficultyRating(DifficultyRating difficulty, bool useLighterColour = false) { switch (difficulty) { @@ -56,10 +56,10 @@ namespace osu.Game.Graphics return Pink; case DifficultyRating.Expert: - return Purple; + return useLighterColour ? PurpleLight : Purple; case DifficultyRating.ExpertPlus: - return Gray0; + return useLighterColour ? Gray9 : Gray0; } } From 9f4c8db39508f52fe377d07d58cee38682dc1087 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 12:17:41 +0900 Subject: [PATCH 2585/2815] Add xmldoc --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 368520ba43..9ac967ef74 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -335,6 +335,9 @@ namespace osu.Game.Rulesets.Edit /// public abstract IEnumerable HitObjects { get; } + /// + /// An editor-specific beatmap, exposing mutation events. + /// public IEditorBeatmap EditorBeatmap { get; protected set; } /// From 9248fbe881f81a0d0d0516ad1e770179fb71a5d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 12:22:44 +0900 Subject: [PATCH 2586/2815] Remove extra checkboxes for now --- .../Edit/Compose/Components/Timeline/TimelineArea.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index 8909b3d422..02e5db306d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -24,8 +24,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Masking = true; CornerRadius = 5; - OsuCheckbox hitObjectsCheckbox; - OsuCheckbox hitSoundsCheckbox; OsuCheckbox waveformCheckbox; InternalChildren = new Drawable[] @@ -64,8 +62,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Spacing = new Vector2(0, 4), Children = new[] { - hitObjectsCheckbox = new OsuCheckbox { LabelText = "Hit objects" }, - hitSoundsCheckbox = new OsuCheckbox { LabelText = "Hit sounds" }, waveformCheckbox = new OsuCheckbox { LabelText = "Waveform" } } } @@ -123,8 +119,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } }; - hitObjectsCheckbox.Current.Value = true; - hitSoundsCheckbox.Current.Value = true; waveformCheckbox.Current.Value = true; timeline.WaveformVisible.BindTo(waveformCheckbox.Current); From 247609388ff71a2321baae9171bd29d364c3b397 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 6 Dec 2019 12:31:22 +0900 Subject: [PATCH 2587/2815] Clean up unused/unnecessary properties --- .../Blueprints/Sliders/Components/PathControlPointPiece.cs | 5 +---- .../Sliders/Components/PathControlPointVisualiser.cs | 7 +------ .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 +- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 159916f16f..4fe02135c4 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -21,7 +21,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public class PathControlPointPiece : BlueprintPiece { public Action RequestSelection; - public Action ControlPointsChanged; public readonly BindableBool IsSelected = new BindableBool(); public readonly int Index; @@ -103,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { markerRing.Alpha = IsSelected.Value ? 1 : 0; - Color4 colour = isSegmentSeparator ? colours.Red : colours.Yellow; + Color4 colour = slider.Path.ControlPoints[Index].Type.Value.HasValue ? colours.Red : colours.Yellow; if (IsHovered || IsSelected.Value) colour = Color4.White; marker.Colour = colour; @@ -176,7 +175,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } protected override bool OnDragEnd(DragEndEvent e) => true; - - private bool isSegmentSeparator => slider.Path.ControlPoints[Index].Type.Value.HasValue; } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index c47e4d7d4a..d599ebd893 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -23,8 +23,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler, IHasContextMenu { - public Action ControlPointsChanged; - internal readonly Container Pieces; private readonly Slider slider; private readonly bool allowSelection; @@ -57,10 +55,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components while (slider.Path.ControlPoints.Count > Pieces.Count) { - var piece = new PathControlPointPiece(slider, Pieces.Count) - { - ControlPointsChanged = c => ControlPointsChanged?.Invoke(c), - }; + var piece = new PathControlPointPiece(slider, Pieces.Count); if (allowSelection) piece.RequestSelection = selectPiece; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 62a22dc858..341db249cc 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders bodyPiece = new SliderBodyPiece(), headCirclePiece = new HitCirclePiece(), tailCirclePiece = new HitCirclePiece(), - new PathControlPointVisualiser(HitObject, false) { ControlPointsChanged = _ => updateSlider() }, + new PathControlPointVisualiser(HitObject, false) }; setState(PlacementState.Initial); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index ab52c906e8..8388d78c42 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders BodyPiece = new SliderBodyPiece(), HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start), TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End), - ControlPointVisualiser = new PathControlPointVisualiser(sliderObject, true) { ControlPointsChanged = onNewControlPoints }, + ControlPointVisualiser = new PathControlPointVisualiser(sliderObject, true) }; } From b1426d1b22fe9789dd99df497ae2f315b1ac2c2a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 12:51:43 +0900 Subject: [PATCH 2588/2815] Full impossible nullref --- .../Screens/Edit/Compose/ComposeScreen.cs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index cb8f2b2caa..6984716a2c 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -16,23 +16,20 @@ namespace osu.Game.Screens.Edit.Compose protected override Drawable CreateMainContent() { var ruleset = Beatmap.Value.BeatmapInfo.Ruleset?.CreateInstance(); - composer = ruleset?.CreateHitObjectComposer(); - if (composer != null) - { - var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin); + if (ruleset == null || composer == null) + return new ScreenWhiteBox.UnderConstructionMessage(ruleset == null ? "This beatmap" : $"{ruleset.Description}'s composer"); - // 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 beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin); - // 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. - return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(composer)); - } + // 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)); - return new ScreenWhiteBox.UnderConstructionMessage(ruleset == null ? "This beatmap" : $"{ruleset.Description}'s composer"); + // 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. + return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(composer)); } protected override Drawable CreateTimelineContent() => new TimelineHitObjectDisplay(composer.EditorBeatmap); From a89a23fe08ea1ec85749a665717c94c6d7b7cc9b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 6 Dec 2019 13:08:28 +0900 Subject: [PATCH 2589/2815] Use linq to simplify some expressions --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 341db249cc..6f53a9e4b1 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders case PlacementState.Body: // The given screen-space position may have been externally snapped, but the unsnapped position from the input manager // is used instead since snapping control points doesn't make much sense - HitObject.Path.ControlPoints[HitObject.Path.ControlPoints.Count - 1].Position.Value = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position; + HitObject.Path.ControlPoints.Last().Position.Value = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position; break; } } @@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders switch (e.Button) { case MouseButton.Left: - HitObject.Path.ControlPoints.Add(new PathControlPoint { Position = { Value = HitObject.Path.ControlPoints[HitObject.Path.ControlPoints.Count - 1].Position.Value } }); + HitObject.Path.ControlPoints.Add(new PathControlPoint { Position = { Value = HitObject.Path.ControlPoints.Last().Position.Value } }); break; } @@ -108,6 +108,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected override bool OnDoubleClick(DoubleClickEvent e) { + // At the point of a double click, there's guaranteed to be at least two points - one from the click, and one from the cursor HitObject.Path.ControlPoints[HitObject.Path.ControlPoints.Count - 2].Type.Value = PathType.Bezier; return true; } From de23364608a8f6568f7bb1c46080c8c6ef6fee60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 13:47:34 +0900 Subject: [PATCH 2590/2815] Add failing test --- .../Visual/Gameplay/TestScenePlayerLoader.cs | 26 +++++++++++++++++++ osu.Game/Screens/Play/PlayerLoader.cs | 10 ++++--- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 74ae641bfe..5142b98898 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; +using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -19,6 +20,7 @@ using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens; @@ -55,6 +57,9 @@ namespace osu.Game.Tests.Visual.Gameplay beforeLoadAction?.Invoke(); Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + foreach (var mod in Mods.Value.OfType()) + mod.ApplyToClock(Beatmap.Value.Track); + InputManager.Child = container = new TestPlayerLoaderContainer( loader = new TestPlayerLoader(() => { @@ -63,6 +68,25 @@ namespace osu.Game.Tests.Visual.Gameplay })); } + /// + /// When exits early, it has to wait for the player load task + /// to complete before running disposal on player. This previously caused an issue where mod + /// speed adjustments were undone too late, causing cross-screen pollution. + /// + [Test] + public void TestEarlyExit() + { + AddStep("apply mods", () => Mods.Value = new[] { new OsuModNightcore() }); + AddStep("load dummy beatmap", () => ResetPlayer(false)); + AddUntilStep("wait for current", () => loader.IsCurrentScreen()); + AddAssert("mod rate applied", () => Beatmap.Value.Track.Rate != 1); + AddStep("exit loader", () => loader.Exit()); + AddUntilStep("wait for not current", () => !loader.IsCurrentScreen()); + AddAssert("player did not load", () => !player.IsLoaded); + AddUntilStep("player disposed", () => loader.DisposalTask?.IsCompleted == true); + AddAssert("mod rate still applied", () => Beatmap.Value.Track.Rate != 1); + } + [Test] public void TestBlockLoadViaMouseMovement() { @@ -196,6 +220,8 @@ namespace osu.Game.Tests.Visual.Gameplay { public new VisualSettings VisualSettings => base.VisualSettings; + public new Task DisposalTask => base.DisposalTask; + public TestPlayerLoader(Func createPlayer) : base(createPlayer) { diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 87d902b547..774ca02be1 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -55,7 +55,9 @@ namespace osu.Game.Screens.Play protected override bool PlayResumeSound => false; - private Task loadTask; + protected Task LoadTask; + + protected Task DisposalTask; private InputManager inputManager; private IdleTracker idleTracker; @@ -159,7 +161,7 @@ namespace osu.Game.Screens.Play player.RestartCount = restartCount; player.RestartRequested = restartRequested; - loadTask = LoadComponentAsync(player, _ => info.Loading = false); + LoadTask = LoadComponentAsync(player, _ => info.Loading = false); } private void contentIn() @@ -250,7 +252,7 @@ namespace osu.Game.Screens.Play { if (!this.IsCurrentScreen()) return; - loadTask = null; + LoadTask = null; //By default, we want to load the player and never be returned to. //Note that this may change if the player we load requested a re-run. @@ -301,7 +303,7 @@ namespace osu.Game.Screens.Play if (isDisposing) { // if the player never got pushed, we should explicitly dispose it. - loadTask?.ContinueWith(_ => player.Dispose()); + DisposalTask = LoadTask?.ContinueWith(_ => player.Dispose()); } } From 48c6279e8bb9d9544afae76e9cb5d1598b62627b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 13:49:01 +0900 Subject: [PATCH 2591/2815] Only undo adjustments in GameplayClockContainer if applied at least once --- osu.Game/Screens/Play/GameplayClockContainer.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index ff78d85bf0..58c9a6a784 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -214,10 +214,13 @@ namespace osu.Game.Screens.Play base.Update(); } + private bool speedAdjustmentsApplied; + private void updateRate() { if (sourceClock == null) return; + speedAdjustmentsApplied = true; sourceClock.ResetSpeedAdjustments(); if (sourceClock is IHasTempoAdjust tempo) @@ -239,7 +242,12 @@ namespace osu.Game.Screens.Play private void removeSourceClockAdjustments() { - sourceClock.ResetSpeedAdjustments(); + if (speedAdjustmentsApplied) + { + sourceClock.ResetSpeedAdjustments(); + speedAdjustmentsApplied = false; + } + (sourceClock as IAdjustableAudioComponent)?.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); } } From 21ceb7f85dbbffb771c443f394a98385a3b75b87 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 14:40:45 +0900 Subject: [PATCH 2592/2815] Always display skins at native sizes for now --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 4 +++- osu.Game/Skinning/SkinnableDrawable.cs | 4 ++-- osu.Game/Skinning/SkinnableSprite.cs | 2 +- osu.Game/Skinning/SkinnableSpriteText.cs | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 479c250eab..ec9792d59d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -49,7 +49,9 @@ namespace osu.Game.Rulesets.Osu.Skinning return this.GetAnimation(component.LookupName, true, false); case OsuSkinComponents.SliderFollowCircle: - return this.GetAnimation("sliderfollowcircle", true, true); + var followCircle = this.GetAnimation("sliderfollowcircle", true, true); + followCircle.Scale *= 0.5f; + return followCircle; case OsuSkinComponents.SliderBall: var sliderBallContent = this.GetAnimation("sliderb", true, true, ""); diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs index 9ca5d60cb0..fda031e6cb 100644 --- a/osu.Game/Skinning/SkinnableDrawable.cs +++ b/osu.Game/Skinning/SkinnableDrawable.cs @@ -29,13 +29,13 @@ namespace osu.Game.Skinning /// A function to create the default skin implementation of this element. /// A conditional to decide whether to allow fallback to the default implementation if a skinned element is not present. /// How (if at all) the should be resize to fit within our own bounds. - public SkinnableDrawable(ISkinComponent component, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) + public SkinnableDrawable(ISkinComponent component, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling) : this(component, allowFallback, confineMode) { createDefault = defaultImplementation; } - protected SkinnableDrawable(ISkinComponent component, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) + protected SkinnableDrawable(ISkinComponent component, Func allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling) : base(allowFallback) { this.component = component; diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs index e225bfc490..5352928ec6 100644 --- a/osu.Game/Skinning/SkinnableSprite.cs +++ b/osu.Game/Skinning/SkinnableSprite.cs @@ -19,7 +19,7 @@ namespace osu.Game.Skinning [Resolved] private TextureStore textures { get; set; } - public SkinnableSprite(string textureName, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) + public SkinnableSprite(string textureName, Func allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling) : base(new SpriteComponent(textureName), allowFallback, confineMode) { } diff --git a/osu.Game/Skinning/SkinnableSpriteText.cs b/osu.Game/Skinning/SkinnableSpriteText.cs index e72f9c9811..567dd348e1 100644 --- a/osu.Game/Skinning/SkinnableSpriteText.cs +++ b/osu.Game/Skinning/SkinnableSpriteText.cs @@ -8,7 +8,7 @@ namespace osu.Game.Skinning { public class SkinnableSpriteText : SkinnableDrawable, IHasText { - public SkinnableSpriteText(ISkinComponent component, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) + public SkinnableSpriteText(ISkinComponent component, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling) : base(component, defaultImplementation, allowFallback, confineMode) { } From 27dd12a66d885d12b74ddc1a5e0ead595b2f0d30 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 6 Dec 2019 15:06:31 +0900 Subject: [PATCH 2593/2815] Rewrite slider length calculation for readability --- osu.Game/Rulesets/Objects/SliderPath.cs | 63 +++++++++++++------------ 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 6ad5d2f1c3..6c24ee6878 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -40,6 +40,7 @@ namespace osu.Game.Rulesets.Objects private readonly List calculatedPath = new List(); private readonly List cumulativeLength = new List(); + private double calculatedLength; /// /// Creates a new . @@ -158,7 +159,7 @@ namespace osu.Game.Rulesets.Objects return; calculatePath(); - calculateCumulativeLength(); + calculateLength(); pathCache.Validate(); } @@ -227,46 +228,48 @@ namespace osu.Game.Rulesets.Objects } } - private void calculateCumulativeLength() + private void calculateLength() { - double l = 0; - + calculatedLength = 0; cumulativeLength.Clear(); - cumulativeLength.Add(l); + cumulativeLength.Add(0); - double? expectedDistance = ExpectedDistance.Value; - - for (int i = 0; i < calculatedPath.Count - 1; ++i) + for (int i = 0; i < calculatedPath.Count - 1; i++) { Vector2 diff = calculatedPath[i + 1] - calculatedPath[i]; - double d = diff.Length; - - // Shorted slider paths that are too long compared to the expected distance - if (expectedDistance.HasValue && expectedDistance - l < d) - { - calculatedPath[i + 1] = calculatedPath[i] + diff * (float)((expectedDistance - l) / d); - calculatedPath.RemoveRange(i + 2, calculatedPath.Count - 2 - i); - - l = expectedDistance.Value; - cumulativeLength.Add(l); - break; - } - - l += d; - cumulativeLength.Add(l); + calculatedLength += diff.Length; + cumulativeLength.Add(calculatedLength); } - // Lengthen slider paths that are too short compared to the expected distance - if (expectedDistance.HasValue && l < expectedDistance && calculatedPath.Count > 1) + if (ExpectedDistance.Value is double expectedDistance && calculatedLength != expectedDistance) { - Vector2 diff = calculatedPath[calculatedPath.Count - 1] - calculatedPath[calculatedPath.Count - 2]; - double d = diff.Length; + // The last length is always incorrect + cumulativeLength.RemoveAt(cumulativeLength.Count - 1); - if (d <= 0) + int pathEndIndex = calculatedPath.Count - 1; + + if (calculatedLength > expectedDistance) + { + // The path will be shortened further, in which case we should trim any more unnecessary lengths and their associated path segments + while (cumulativeLength.Count > 0 && cumulativeLength[cumulativeLength.Count - 1] > expectedDistance) + { + cumulativeLength.RemoveAt(cumulativeLength.Count - 1); + calculatedPath.RemoveAt(pathEndIndex--); + } + } + + if (pathEndIndex <= 0) + { + // The expected distance is negative or zero + // TODO: Perhaps negative path lengths should be disallowed altogether return; + } - calculatedPath[calculatedPath.Count - 1] += diff * (float)((expectedDistance - l) / d); - cumulativeLength[calculatedPath.Count - 1] = expectedDistance.Value; + // The direction of the segment to shorten or lengthen + Vector2 dir = (calculatedPath[pathEndIndex] - calculatedPath[pathEndIndex - 1]).Normalized(); + + calculatedPath[pathEndIndex] = calculatedPath[pathEndIndex - 1] + dir * (float)(expectedDistance - cumulativeLength[cumulativeLength.Count - 1]); + cumulativeLength.Add(expectedDistance); } } From 12f1c9e088c307ac86a1411fd7e80d6feba7fefd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 15:29:02 +0900 Subject: [PATCH 2594/2815] Fix test failure --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 5142b98898..dbea8d28a6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -76,8 +76,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestEarlyExit() { - AddStep("apply mods", () => Mods.Value = new[] { new OsuModNightcore() }); - AddStep("load dummy beatmap", () => ResetPlayer(false)); + AddStep("load dummy beatmap", () => ResetPlayer(false, () => Mods.Value = new[] { new OsuModNightcore() })); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); AddAssert("mod rate applied", () => Beatmap.Value.Track.Rate != 1); AddStep("exit loader", () => loader.Exit()); From 2654710d917e47b44f80f38d3600203d14355a0c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 6 Dec 2019 15:37:00 +0900 Subject: [PATCH 2595/2815] Add tests and fix negative expected distances --- .../Visual/Gameplay/TestSceneSliderPath.cs | 34 +++++++++++++++++++ osu.Game/Rulesets/Objects/SliderPath.cs | 1 + 2 files changed, 35 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs index fe2cc188a5..27d39d0f17 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs @@ -146,7 +146,41 @@ namespace osu.Game.Tests.Visual.Gameplay break; } }); + } + [Test] + public void TestLengthenLastSegment() + { + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("lengthen last segment", () => path.ExpectedDistance.Value = 300); + } + + [Test] + public void TestShortenLastSegment() + { + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("shorten last segment", () => path.ExpectedDistance.Value = 150); + } + + [Test] + public void TestShortenFirstSegment() + { + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("shorten first segment", () => path.ExpectedDistance.Value = 50); + } + + [Test] + public void TestShortenToZeroLength() + { + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("shorten to 0 length", () => path.ExpectedDistance.Value = 0); + } + + [Test] + public void TestShortenToNegativeLength() + { + AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); + AddStep("shorten to -10 length", () => path.ExpectedDistance.Value = -10); } private List createSegment(PathType type, params Vector2[] controlPoints) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 6c24ee6878..095353e3f3 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -262,6 +262,7 @@ namespace osu.Game.Rulesets.Objects { // The expected distance is negative or zero // TODO: Perhaps negative path lengths should be disallowed altogether + cumulativeLength.Add(0); return; } From af35df4077015c5b45ebb5b8f304e9bdc58702f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 15:42:11 +0900 Subject: [PATCH 2596/2815] Add multiple mod testing and update test code style --- .../TestSceneModCustomizationSettings.cs | 56 +++++++++++-------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs index 83d77cccdb..f789e60252 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.UserInterface Anchor = Anchor.BottomCentre, }); - var testMod = new TestModCustomizable(); + var testMod = new TestModCustomizable1(); AddStep("open", modSelect.Show); AddAssert("button disabled", () => !modSelect.CustomizeButton.Enabled.Value); @@ -44,64 +44,74 @@ namespace osu.Game.Tests.Visual.UserInterface public new TriangleButton CustomizeButton => base.CustomizeButton; public void SelectMod(Mod mod) => - ModSectionsContainer.Children.Single((s) => s.ModType == mod.Type) - .ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())).SelectNext(1); + ModSectionsContainer.Children.Single(s => s.ModType == mod.Type) + .ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())).SelectNext(1); public ModControlSection GetControlSection(Mod mod) => - ModSettingsContent.Children.FirstOrDefault((s) => s.Mod == mod); + ModSettingsContent.Children.FirstOrDefault(s => s.Mod == mod); protected override void LoadComplete() { base.LoadComplete(); foreach (var section in ModSectionsContainer) + { if (section.ModType == ModType.Conversion) - section.Mods = new Mod[] { new TestModCustomizable() }; + { + section.Mods = new Mod[] + { + new TestModCustomizable1(), + new TestModCustomizable2() + }; + } else section.Mods = new Mod[] { }; + } } } - private class TestModCustomizable : Mod, IModHasSettings + private class TestModCustomizable1 : TestModCustomizable { - public override string Name => "Customizable Mod"; + public override string Name => "Customizable Mod 1"; - public override string Acronym => "CM"; + public override string Acronym => "CM1"; + } + private class TestModCustomizable2 : TestModCustomizable + { + public override string Name => "Customizable Mod 2"; + + public override string Acronym => "CM2"; + } + + private abstract class TestModCustomizable : Mod, IModHasSettings + { public override double ScoreMultiplier => 1.0; public override ModType Type => ModType.Conversion; - public readonly BindableFloat sliderBindable = new BindableFloat + public readonly BindableFloat SliderBindable = new BindableFloat { MinValue = 0, MaxValue = 10, }; - public readonly BindableBool tickBindable = new BindableBool(); + public readonly BindableBool TickBindable = new BindableBool(); - public Drawable[] CreateControls() - { - BindableFloat sliderControl = new BindableFloat(); - BindableBool tickControl = new BindableBool(); - - sliderControl.BindTo(sliderBindable); - tickControl.BindTo(tickBindable); - - return new Drawable[] + public Drawable[] CreateControls() => + new Drawable[] { new SettingsSlider { LabelText = "Slider", - Bindable = sliderControl + Bindable = SliderBindable }, new SettingsCheckbox { LabelText = "Checkbox", - Bindable = tickControl + Bindable = TickBindable } }; - } } } -} \ No newline at end of file +} diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index fc5ed1b3f3..d7d1135e81 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -345,7 +345,7 @@ namespace osu.Game.Overlays.Mods if (added is IModHasSettings) ModSettingsContent.Add(new ModControlSection(added)); else if (removed is IModHasSettings) - ModSettingsContent.Remove(ModSettingsContent.Children.Where(section => section.Mod == removed).Single()); + ModSettingsContent.Remove(ModSettingsContent.Children.Single(section => section.Mod == removed)); CustomizeButton.Enabled.Value = ModSettingsContent.Children.Count > 0; From 3358ab9f8abde79becfd4bca2a56361170aef2e1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 6 Dec 2019 15:53:01 +0900 Subject: [PATCH 2597/2815] Adjust diffcalc test expected value The difference is caused by the reworked calculateLength() of SliderPath. This comes as a result of the increased accuracy of path lengthenings due to calculating the final position relative to the second-to-last point, rather than relative to the last point. --- osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index 693faee3b7..85a41137d4 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; - [TestCase(6.931145117263422, "diffcalc-test")] + [TestCase(6.9311451172608853d, "diffcalc-test")] [TestCase(1.0736587013228804d, "zero-length-sliders")] public void Test(double expected, string name) => base.Test(expected, name); From b4e1b5fa983fdb826ec8693b3fabdcd66e03e2c1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 6 Dec 2019 15:53:19 +0900 Subject: [PATCH 2598/2815] Explose + use the full calculated length of the path --- .../Blueprints/Sliders/SliderPlacementBlueprint.cs | 3 +-- .../Blueprints/Sliders/SliderSelectionBlueprint.cs | 3 +-- osu.Game/Rulesets/Objects/SliderPath.cs | 14 +++++++++++++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 6f53a9e4b1..bbdc43e16f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -133,8 +133,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void updateSlider() { - HitObject.Path.ExpectedDistance.Value = null; - HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.Distance) ?? (float)HitObject.Path.Distance; + HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; bodyPiece.UpdateFrom(HitObject); headCirclePiece.UpdateFrom(HitObject.HeadCircle); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 8388d78c42..7b3ca29e35 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -114,8 +114,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void onNewControlPoints(Vector2[] controlPoints) { - HitObject.Path.ExpectedDistance.Value = null; - HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.Distance) ?? (float)HitObject.Path.Distance; + HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; UpdateHitObject(); } diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 095353e3f3..f6e7c40e12 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -103,6 +103,18 @@ namespace osu.Game.Rulesets.Objects } } + /// + /// The distance of the path prior to lengthening/shortening to account for . + /// + public double CalculatedDistance + { + get + { + ensureValid(); + return calculatedLength; + } + } + /// /// Computes the slider path until a given progress that ranges from 0 (beginning of the slider) /// to 1 (end of the slider) and stores the generated path in the given list. @@ -251,7 +263,7 @@ namespace osu.Game.Rulesets.Objects if (calculatedLength > expectedDistance) { // The path will be shortened further, in which case we should trim any more unnecessary lengths and their associated path segments - while (cumulativeLength.Count > 0 && cumulativeLength[cumulativeLength.Count - 1] > expectedDistance) + while (cumulativeLength.Count > 0 && cumulativeLength[cumulativeLength.Count - 1] >= expectedDistance) { cumulativeLength.RemoveAt(cumulativeLength.Count - 1); calculatedPath.RemoveAt(pathEndIndex--); From d29ccdc25e4ea515c54a3de945fd9724271bf3e7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 6 Dec 2019 16:36:08 +0900 Subject: [PATCH 2599/2815] Fix selection blueprint not re-snapping the path --- .../Blueprints/Sliders/SliderSelectionBlueprint.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 7b3ca29e35..9a504445f4 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; @@ -44,6 +45,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders }; } + private IBindable pathVersion; + + protected override void LoadComplete() + { + base.LoadComplete(); + + pathVersion = HitObject.Path.Version.GetBoundCopy(); + pathVersion.BindValueChanged(_ => updatePath()); + } + protected override void Update() { base.Update(); @@ -112,10 +123,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders return insertionIndex; } - private void onNewControlPoints(Vector2[] controlPoints) + private void updatePath() { HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; - UpdateHitObject(); } From 2b5f9515de1b8fe386a76e6f2c1398912005dd5c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 6 Dec 2019 17:03:54 +0900 Subject: [PATCH 2600/2815] Fix multiple control point deletions --- .../Components/PathControlPointVisualiser.cs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index d599ebd893..cbc2a20328 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -14,6 +14,7 @@ using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose; using osuTK; @@ -100,21 +101,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private bool deleteSelected() { - int countDeleted = 0; - - foreach (var piece in Pieces) - { - if (piece.IsSelected.Value) - { - slider.Path.ControlPoints.RemoveAt(piece.Index); - countDeleted++; - } - } + List toRemove = Pieces.Where(p => p.IsSelected.Value).Select(p => p.Index).Select(i => slider.Path.ControlPoints[i]).ToList(); // Ensure that there are any points to be deleted - if (countDeleted == 0) + if (toRemove.Count == 0) return false; + foreach (var c in toRemove) + slider.Path.ControlPoints.Remove(c); + // If there are 0 remaining control points, treat the slider as being deleted if (slider.Path.ControlPoints.Count == 0) { From 23e47530c50a38bd42874f85ec4c698b967f9e86 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 17:09:24 +0900 Subject: [PATCH 2601/2815] Add a method for getting settings UI components automatically from a target class --- .../Configuration/SettingSourceAttribute.cs | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 osu.Game/Configuration/SettingSourceAttribute.cs diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs new file mode 100644 index 0000000000..fe582b1461 --- /dev/null +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -0,0 +1,85 @@ +// 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 System.Reflection; +using JetBrains.Annotations; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Configuration +{ + /// + /// An attribute to mark a bindable as being exposed to the user via settings controls. + /// Can be used in conjunction with to automatically create UI controls. + /// + [MeansImplicitUse] + [AttributeUsage(AttributeTargets.Property)] + public class SettingSourceAttribute : Attribute + { + public string Label { get; } + + public string Description { get; } + + public SettingSourceAttribute(string label, string description = null) + { + Label = label ?? string.Empty; + Description = description ?? string.Empty; + } + } + + public static class SettingSourceExtensions + { + public static IEnumerable CreateSettingsControls(this object obj) + { + var configProperties = obj.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance).Where(p => p.GetCustomAttribute(true) != null); + + foreach (var property in configProperties) + { + var attr = property.GetCustomAttribute(true); + + switch (property.GetValue(obj)) + { + case BindableNumber bNumber: + yield return new SettingsSlider + { + LabelText = attr.Label, + Bindable = bNumber + }; + + break; + + case BindableNumber bNumber: + yield return new SettingsSlider + { + LabelText = attr.Label, + Bindable = bNumber + }; + + break; + + case BindableNumber bNumber: + yield return new SettingsSlider + { + LabelText = attr.Label, + Bindable = bNumber + }; + + break; + + case Bindable bBool: + yield return new SettingsCheckbox + { + LabelText = attr.Label, + Bindable = bBool + }; + + break; + } + } + } + } +} From a5d5099868f2c7d1a8672dc266030065366592c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 17:09:48 +0900 Subject: [PATCH 2602/2815] Use SettingsSource for mod cusomisation --- .../TestSceneModCustomizationSettings.cs | 30 +++++-------------- osu.Game/Overlays/Mods/ModControlSection.cs | 6 ++-- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 24 ++++++++------- osu.Game/Rulesets/Mods/IModHasSettings.cs | 15 ---------- 4 files changed, 25 insertions(+), 50 deletions(-) delete mode 100644 osu.Game/Rulesets/Mods/IModHasSettings.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs index f789e60252..8227fe8f7a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs @@ -6,9 +6,9 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; -using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mods; namespace osu.Game.Tests.Visual.UserInterface @@ -47,9 +47,6 @@ namespace osu.Game.Tests.Visual.UserInterface ModSectionsContainer.Children.Single(s => s.ModType == mod.Type) .ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())).SelectNext(1); - public ModControlSection GetControlSection(Mod mod) => - ModSettingsContent.Children.FirstOrDefault(s => s.Mod == mod); - protected override void LoadComplete() { base.LoadComplete(); @@ -84,34 +81,23 @@ namespace osu.Game.Tests.Visual.UserInterface public override string Acronym => "CM2"; } - private abstract class TestModCustomizable : Mod, IModHasSettings + private abstract class TestModCustomizable : Mod, IApplicableMod { public override double ScoreMultiplier => 1.0; public override ModType Type => ModType.Conversion; - public readonly BindableFloat SliderBindable = new BindableFloat + [SettingSource("Sample float", "Change something for a mod")] + public BindableFloat SliderBindable { get; } = new BindableFloat { MinValue = 0, MaxValue = 10, + Default = 5, + Value = 7 }; - public readonly BindableBool TickBindable = new BindableBool(); - - public Drawable[] CreateControls() => - new Drawable[] - { - new SettingsSlider - { - LabelText = "Slider", - Bindable = SliderBindable - }, - new SettingsCheckbox - { - LabelText = "Checkbox", - Bindable = TickBindable - } - }; + [SettingSource("Sample bool", "Clicking this changes a setting")] + public BindableBool TickBindable { get; } = new BindableBool(); } } } diff --git a/osu.Game/Overlays/Mods/ModControlSection.cs b/osu.Game/Overlays/Mods/ModControlSection.cs index df6f4d15b0..f4b588ddb3 100644 --- a/osu.Game/Overlays/Mods/ModControlSection.cs +++ b/osu.Game/Overlays/Mods/ModControlSection.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; @@ -34,8 +35,7 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.X, }; - if (Mod is IModHasSettings modHasSettings) - AddRange(modHasSettings.CreateControls()); + AddRange(Mod.CreateSettingsControls()); } [BackgroundDependencyLoader] @@ -53,4 +53,4 @@ namespace osu.Game.Overlays.Mods }); } } -} \ No newline at end of file +} diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index d7d1135e81..cfad0126eb 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; @@ -236,6 +237,7 @@ namespace osu.Game.Overlays.Mods Width = 180, Text = "Customization", Action = () => ModSettingsContainer.Alpha = ModSettingsContainer.Alpha == 1 ? 0 : 1, + Enabled = { Value = false }, Margin = new MarginPadding { Right = 20 @@ -331,7 +333,6 @@ namespace osu.Game.Overlays.Mods SelectedMods.ValueChanged += updateModSettings; Ruleset.ValueChanged += _ => ModSettingsContent.Clear(); - CustomizeButton.Enabled.Value = false; sampleOn = audio.Samples.Get(@"UI/check-on"); sampleOff = audio.Samples.Get(@"UI/check-off"); @@ -339,18 +340,21 @@ namespace osu.Game.Overlays.Mods private void updateModSettings(ValueChangedEvent> selectedMods) { - var added = selectedMods.NewValue.Except(selectedMods.OldValue).FirstOrDefault(); - var removed = selectedMods.OldValue.Except(selectedMods.NewValue).FirstOrDefault(); + foreach (var added in selectedMods.NewValue.Except(selectedMods.OldValue)) + { + var controls = added.CreateSettingsControls().ToList(); + if (controls.Count > 0) + ModSettingsContent.Add(new ModControlSection(added) { Children = controls }); + } - if (added is IModHasSettings) - ModSettingsContent.Add(new ModControlSection(added)); - else if (removed is IModHasSettings) - ModSettingsContent.Remove(ModSettingsContent.Children.Single(section => section.Mod == removed)); + foreach (var removed in selectedMods.OldValue.Except(selectedMods.NewValue)) + ModSettingsContent.RemoveAll(section => section.Mod == removed); - CustomizeButton.Enabled.Value = ModSettingsContent.Children.Count > 0; + bool hasSettings = ModSettingsContent.Children.Count > 0; + CustomizeButton.Enabled.Value = hasSettings; - if (ModSettingsContainer.Alpha == 1 && ModSettingsContent.Children.Count == 0) - ModSettingsContainer.Alpha = 0; + if (!hasSettings) + ModSettingsContainer.Hide(); } public void DeselectAll() diff --git a/osu.Game/Rulesets/Mods/IModHasSettings.cs b/osu.Game/Rulesets/Mods/IModHasSettings.cs deleted file mode 100644 index b5058de82b..0000000000 --- a/osu.Game/Rulesets/Mods/IModHasSettings.cs +++ /dev/null @@ -1,15 +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.Graphics; - -namespace osu.Game.Rulesets.Mods -{ - /// - /// An interface for mods that allows user control over it's properties. - /// - public interface IModHasSettings : IApplicableMod - { - Drawable[] CreateControls(); - } -} From 9de032e35f1394571e3fcc05b4caa94a81b3e27d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 17:10:06 +0900 Subject: [PATCH 2603/2815] Fix SettingsItem bindable logic --- osu.Game/Overlays/Settings/SettingsItem.cs | 29 ++++++++-------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 8863e43cca..212ef5545d 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -53,27 +53,10 @@ namespace osu.Game.Overlays.Settings } } - // hold a reference to the provided bindable so we don't have to in every settings section. - private Bindable bindable; - public virtual Bindable Bindable { - get => bindable; - - set - { - if (bindable != null) - controlWithCurrent?.Current.UnbindFrom(bindable); - - bindable = value; - controlWithCurrent?.Current.BindTo(bindable); - - if (ShowsDefaultIndicator) - { - restoreDefaultButton.Bindable = bindable.GetBoundCopy(); - restoreDefaultButton.Bindable.TriggerChange(); - } - } + get => controlWithCurrent.Current; + set => controlWithCurrent.Current = value; } public virtual IEnumerable FilterTerms => Keywords == null ? new[] { LabelText } : new List(Keywords) { LabelText }.ToArray(); @@ -110,7 +93,15 @@ namespace osu.Game.Overlays.Settings private void load() { if (controlWithCurrent != null) + { controlWithCurrent.Current.DisabledChanged += disabled => { Colour = disabled ? Color4.Gray : Color4.White; }; + + if (ShowsDefaultIndicator) + { + restoreDefaultButton.Bindable = controlWithCurrent.Current; + restoreDefaultButton.Bindable.TriggerChange(); + } + } } private class RestoreDefaultValueButton : Container, IHasTooltip From 901eb5d99639edc7a21caa1f2c7577507415e42a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 18:03:50 +0900 Subject: [PATCH 2604/2815] Fix incorrect trigger logic --- osu.Game/Overlays/Settings/SettingsItem.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 212ef5545d..9c390c34ec 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -97,10 +97,7 @@ namespace osu.Game.Overlays.Settings controlWithCurrent.Current.DisabledChanged += disabled => { Colour = disabled ? Color4.Gray : Color4.White; }; if (ShowsDefaultIndicator) - { restoreDefaultButton.Bindable = controlWithCurrent.Current; - restoreDefaultButton.Bindable.TriggerChange(); - } } } @@ -116,6 +113,7 @@ namespace osu.Game.Overlays.Settings bindable = value; bindable.ValueChanged += _ => UpdateState(); bindable.DisabledChanged += _ => UpdateState(); + UpdateState(); } } From ed6d1ccd958622bbca51c8cab54f0c3dfd4a6296 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 17:09:24 +0900 Subject: [PATCH 2605/2815] Add a method for getting settings UI components automatically from a target class --- .../Configuration/SettingSourceAttribute.cs | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 osu.Game/Configuration/SettingSourceAttribute.cs diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs new file mode 100644 index 0000000000..fe582b1461 --- /dev/null +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -0,0 +1,85 @@ +// 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 System.Reflection; +using JetBrains.Annotations; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Configuration +{ + /// + /// An attribute to mark a bindable as being exposed to the user via settings controls. + /// Can be used in conjunction with to automatically create UI controls. + /// + [MeansImplicitUse] + [AttributeUsage(AttributeTargets.Property)] + public class SettingSourceAttribute : Attribute + { + public string Label { get; } + + public string Description { get; } + + public SettingSourceAttribute(string label, string description = null) + { + Label = label ?? string.Empty; + Description = description ?? string.Empty; + } + } + + public static class SettingSourceExtensions + { + public static IEnumerable CreateSettingsControls(this object obj) + { + var configProperties = obj.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance).Where(p => p.GetCustomAttribute(true) != null); + + foreach (var property in configProperties) + { + var attr = property.GetCustomAttribute(true); + + switch (property.GetValue(obj)) + { + case BindableNumber bNumber: + yield return new SettingsSlider + { + LabelText = attr.Label, + Bindable = bNumber + }; + + break; + + case BindableNumber bNumber: + yield return new SettingsSlider + { + LabelText = attr.Label, + Bindable = bNumber + }; + + break; + + case BindableNumber bNumber: + yield return new SettingsSlider + { + LabelText = attr.Label, + Bindable = bNumber + }; + + break; + + case Bindable bBool: + yield return new SettingsCheckbox + { + LabelText = attr.Label, + Bindable = bBool + }; + + break; + } + } + } + } +} From 2fa0b30fa27801b00927b49025134cf8c8ee8ce1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 17:49:41 +0900 Subject: [PATCH 2606/2815] Add textbox and dropdown support --- .../Configuration/SettingSourceAttribute.cs | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index fe582b1461..dceef1cb7d 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -8,6 +8,7 @@ using System.Reflection; using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.UserInterface; using osu.Game.Overlays.Settings; namespace osu.Game.Configuration @@ -40,8 +41,9 @@ namespace osu.Game.Configuration foreach (var property in configProperties) { var attr = property.GetCustomAttribute(true); + var prop = property.GetValue(obj); - switch (property.GetValue(obj)) + switch (prop) { case BindableNumber bNumber: yield return new SettingsSlider @@ -78,6 +80,30 @@ namespace osu.Game.Configuration }; break; + + case Bindable bString: + yield return new SettingsTextBox + { + LabelText = attr.Label, + Bindable = bString + }; + + break; + + case IBindable bindable: + + var dropdownType = typeof(SettingsEnumDropdown<>).MakeGenericType(bindable.GetType().GetGenericArguments()[0]); + + var dropdown = (Drawable)Activator.CreateInstance(dropdownType); + + dropdown.GetType().GetProperty(nameof(IHasCurrentValue.Current))?.SetValue(dropdown, obj); + + yield return dropdown; + + break; + + default: + throw new InvalidOperationException($"{nameof(SettingSourceAttribute)} was attached to an unsupported type ({prop})"); } } } From f84705ab9605776f579f745ac6164b856bf5903e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 17:49:52 +0900 Subject: [PATCH 2607/2815] Add tests --- .../Settings/TestSceneSettingsSource.cs | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs new file mode 100644 index 0000000000..e3dae9c27e --- /dev/null +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs @@ -0,0 +1,65 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Configuration; +using osuTK; + +namespace osu.Game.Tests.Visual.Settings +{ + [TestFixture] + public class TestSceneSettingsSource : OsuTestScene + { + public TestSceneSettingsSource() + { + Children = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(20), + Width = 0.5f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Padding = new MarginPadding(50), + ChildrenEnumerable = new TestTargetClass().CreateSettingsControls() + }, + }; + } + + private class TestTargetClass + { + [SettingSource("Sample bool", "Clicking this changes a setting")] + public BindableBool TickBindable { get; } = new BindableBool(); + + [SettingSource("Sample float", "Change something for a mod")] + public BindableFloat SliderBindable { get; } = new BindableFloat + { + MinValue = 0, + MaxValue = 10, + Default = 5, + Value = 7 + }; + + [SettingSource("Sample enum", "Change something for a mod")] + public Bindable EnumBindable { get; } = new Bindable + { + Default = TestEnum.Value1, + Value = TestEnum.Value2 + }; + + [SettingSource("Sample string", "Change something for a mod")] + public Bindable StringBindable { get; } = new Bindable(); + } + + private enum TestEnum + { + Value1, + Value2 + } + } +} From 16f8341a0228ebb5fb92c8070918537b980352cb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 6 Dec 2019 18:49:01 +0900 Subject: [PATCH 2608/2815] Handle control point positional updates within SliderPath --- .../Components/PathControlPointVisualiser.cs | 8 -------- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 ++ osu.Game/Rulesets/Objects/SliderPath.cs | 20 +++++++++++++++++++ 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index cbc2a20328..974b611533 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -117,14 +117,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return true; } - // Make control points relative - Vector2 first = slider.Path.ControlPoints[0].Position.Value; - for (int i = 0; i < slider.Path.ControlPoints.Count; i++) - slider.Path.ControlPoints[i].Position.Value = slider.Path.ControlPoints[i].Position.Value - first; - - // The slider's position defines the position of the first control point, and all further control points are relative to that point - slider.Position += first; - // Since pieces are re-used, they will not point to the deleted control points while remaining selected foreach (var piece in Pieces) piece.IsSelected.Value = false; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index b68595c67e..09657b2d47 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -100,6 +100,8 @@ namespace osu.Game.Rulesets.Osu.Objects { SamplesBindable.ItemsAdded += _ => updateNestedSamples(); SamplesBindable.ItemsRemoved += _ => updateNestedSamples(); + + Path.OffsetChanged += offset => Position += offset; } protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index f6e7c40e12..969cdcc463 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -16,6 +16,12 @@ namespace osu.Game.Rulesets.Objects { public class SliderPath { + /// + /// Invoked when the offset of the path changes. + /// The provided value indicates the offset, and should be used to re-calculate the position of the containing drawable. + /// + public event Action OffsetChanged; + /// /// The current version of this . Updated when any change to the path occurs. /// @@ -62,6 +68,20 @@ namespace osu.Game.Rulesets.Objects foreach (var c in items) c.Changed -= invalidate; + // Make all control points relative to the first one + if (ControlPoints.Count > 0) + { + Vector2 first = ControlPoints[0].Position.Value; + + if (first != Vector2.Zero) + { + foreach (var c in ControlPoints) + c.Position.Value -= first; + + OffsetChanged?.Invoke(first); + } + } + invalidate(); }; } From 52dd7bf716fa277fc157397a2cdcebe0c134e180 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 6 Dec 2019 18:49:14 +0900 Subject: [PATCH 2609/2815] Fix deleting the first control point not working --- .../Sliders/Components/PathControlPointVisualiser.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 974b611533..ee4a37a4f2 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -108,7 +108,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return false; foreach (var c in toRemove) + { + // The first control point in the slider must have a type, so take it from the previous "first" one + // Todo: Should be handled within SliderPath itself + if (c == slider.Path.ControlPoints[0] && slider.Path.ControlPoints.Count > 1 && slider.Path.ControlPoints[1].Type.Value == null) + slider.Path.ControlPoints[1].Type.Value = slider.Path.ControlPoints[0].Type.Value; + slider.Path.ControlPoints.Remove(c); + } // If there are 0 remaining control points, treat the slider as being deleted if (slider.Path.ControlPoints.Count == 0) From 680b2653aeedbaa02cf359f6079347931db267c6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 18:49:03 +0900 Subject: [PATCH 2610/2815] Improve animation of popup dialog buttons --- .../UserInterface/TestScenePopupDialog.cs | 22 ++++++++++++++----- .../Graphics/UserInterface/DialogButton.cs | 13 ++++++++--- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs index 3d39bb7003..7207506ccd 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs @@ -1,9 +1,12 @@ // 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.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Dialog; namespace osu.Game.Tests.Visual.UserInterface @@ -11,13 +14,22 @@ namespace osu.Game.Tests.Visual.UserInterface [TestFixture] public class TestScenePopupDialog : OsuTestScene { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(PopupDialogOkButton), + typeof(PopupDialogCancelButton), + typeof(PopupDialogButton), + typeof(DialogButton), + }; + public TestScenePopupDialog() { - Add(new TestPopupDialog - { - RelativeSizeAxes = Axes.Both, - State = { Value = Framework.Graphics.Containers.Visibility.Visible }, - }); + AddStep("new popup", () => + Add(new TestPopupDialog + { + RelativeSizeAxes = Axes.Both, + State = { Value = Framework.Graphics.Containers.Visibility.Visible }, + })); } private class TestPopupDialog : PopupDialog diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index 927ad13829..15c09b10b4 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -20,6 +20,7 @@ namespace osu.Game.Graphics.UserInterface { public class DialogButton : OsuClickableContainer { + private const float idle_width = 0.8f; private const float hover_width = 0.9f; private const float hover_duration = 500; private const float glow_fade_duration = 250; @@ -99,7 +100,7 @@ namespace osu.Game.Graphics.UserInterface RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, Anchor = Anchor.Centre, - Width = 0.8f, + Width = idle_width, Masking = true, MaskingSmoothness = 2, EdgeEffect = new EdgeEffectParameters @@ -199,14 +200,18 @@ namespace osu.Game.Graphics.UserInterface public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => backgroundContainer.ReceivePositionalInputAt(screenSpacePos); + private bool clicked; + protected override bool OnClick(ClickEvent e) { + clicked = true; colourContainer.ResizeTo(new Vector2(1.5f, 1f), click_duration, Easing.In); flash(); this.Delay(click_duration).Schedule(delegate { - colourContainer.ResizeTo(new Vector2(0.8f, 1f)); + clicked = false; + colourContainer.ResizeTo(new Vector2(idle_width, 1f)); spriteText.Spacing = Vector2.Zero; glowContainer.FadeOut(); }); @@ -230,6 +235,8 @@ namespace osu.Game.Graphics.UserInterface private void selectionChanged(ValueChangedEvent args) { + if (clicked) return; + if (args.NewValue) { spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutElastic); @@ -238,7 +245,7 @@ namespace osu.Game.Graphics.UserInterface } else { - colourContainer.ResizeTo(new Vector2(0.8f, 1f), hover_duration, Easing.OutElastic); + colourContainer.ResizeTo(new Vector2(idle_width, 1f), hover_duration, Easing.OutElastic); spriteText.TransformSpacingTo(Vector2.Zero, hover_duration, Easing.OutElastic); glowContainer.FadeOut(glow_fade_duration, Easing.Out); } From af2305bb77491397b87aebc7f9f3d1ed49de31bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 18:53:30 +0900 Subject: [PATCH 2611/2815] Add null check --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index ec9792d59d..fa701a3c41 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -50,7 +50,8 @@ namespace osu.Game.Rulesets.Osu.Skinning case OsuSkinComponents.SliderFollowCircle: var followCircle = this.GetAnimation("sliderfollowcircle", true, true); - followCircle.Scale *= 0.5f; + if (followCircle != null) + followCircle.Scale *= 0.5f; return followCircle; case OsuSkinComponents.SliderBall: From f958485be1c268d53967522ccb3481716e21d3a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 18:54:54 +0900 Subject: [PATCH 2612/2815] Add comment about size change --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index fa701a3c41..f5b7d9166f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -51,6 +51,7 @@ namespace osu.Game.Rulesets.Osu.Skinning case OsuSkinComponents.SliderFollowCircle: var followCircle = this.GetAnimation("sliderfollowcircle", true, true); if (followCircle != null) + // follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x followCircle.Scale *= 0.5f; return followCircle; From 46d055604a6f049735a6d2b0ed12d33ef5287dab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 18:57:11 +0900 Subject: [PATCH 2613/2815] Customize -> Customise --- ...ionSettings.cs => TestSceneModSettings.cs} | 26 +++++++++---------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 6 ++--- 2 files changed, 16 insertions(+), 16 deletions(-) rename osu.Game.Tests/Visual/UserInterface/{TestSceneModCustomizationSettings.cs => TestSceneModSettings.cs} (78%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs similarity index 78% rename from osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index 8227fe8f7a..7a11ba5294 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomizationSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -13,7 +13,7 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Tests.Visual.UserInterface { - public class TestSceneModCustomizationSettings : OsuTestScene + public class TestSceneModSettings : OsuTestScene { private TestModSelectOverlay modSelect; @@ -27,13 +27,13 @@ namespace osu.Game.Tests.Visual.UserInterface Anchor = Anchor.BottomCentre, }); - var testMod = new TestModCustomizable1(); + var testMod = new TestModCustomisable1(); AddStep("open", modSelect.Show); - AddAssert("button disabled", () => !modSelect.CustomizeButton.Enabled.Value); + AddAssert("button disabled", () => !modSelect.CustomiseButton.Enabled.Value); AddStep("select mod", () => modSelect.SelectMod(testMod)); - AddAssert("button enabled", () => modSelect.CustomizeButton.Enabled.Value); - AddStep("open customization", () => modSelect.CustomizeButton.Click()); + AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value); + AddStep("open Customisation", () => modSelect.CustomiseButton.Click()); AddStep("deselect mod", () => modSelect.SelectMod(testMod)); AddAssert("controls hidden", () => modSelect.ModSettingsContainer.Alpha == 0); } @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.UserInterface private class TestModSelectOverlay : ModSelectOverlay { public new Container ModSettingsContainer => base.ModSettingsContainer; - public new TriangleButton CustomizeButton => base.CustomizeButton; + public new TriangleButton CustomiseButton => base.CustomiseButton; public void SelectMod(Mod mod) => ModSectionsContainer.Children.Single(s => s.ModType == mod.Type) @@ -57,8 +57,8 @@ namespace osu.Game.Tests.Visual.UserInterface { section.Mods = new Mod[] { - new TestModCustomizable1(), - new TestModCustomizable2() + new TestModCustomisable1(), + new TestModCustomisable2() }; } else @@ -67,21 +67,21 @@ namespace osu.Game.Tests.Visual.UserInterface } } - private class TestModCustomizable1 : TestModCustomizable + private class TestModCustomisable1 : TestModCustomisable { - public override string Name => "Customizable Mod 1"; + public override string Name => "Customisable Mod 1"; public override string Acronym => "CM1"; } - private class TestModCustomizable2 : TestModCustomizable + private class TestModCustomisable2 : TestModCustomisable { - public override string Name => "Customizable Mod 2"; + public override string Name => "Customisable Mod 2"; public override string Acronym => "CM2"; } - private abstract class TestModCustomizable : Mod, IApplicableMod + private abstract class TestModCustomisable : Mod, IApplicableMod { public override double ScoreMultiplier => 1.0; diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index cfad0126eb..e860463b23 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Mods public class ModSelectOverlay : WaveOverlayContainer { protected readonly TriangleButton DeselectAllButton; - protected readonly TriangleButton CustomizeButton; + protected readonly TriangleButton CustomiseButton; protected readonly TriangleButton CloseButton; protected readonly OsuSpriteText MultiplierLabel; @@ -232,7 +232,7 @@ namespace osu.Game.Overlays.Mods Right = 20 } }, - CustomizeButton = new TriangleButton + CustomiseButton = new TriangleButton { Width = 180, Text = "Customization", @@ -351,7 +351,7 @@ namespace osu.Game.Overlays.Mods ModSettingsContent.RemoveAll(section => section.Mod == removed); bool hasSettings = ModSettingsContent.Children.Count > 0; - CustomizeButton.Enabled.Value = hasSettings; + CustomiseButton.Enabled.Value = hasSettings; if (!hasSettings) ModSettingsContainer.Hide(); From 347373a3ba3ef852dabd35306721ff689b47b0af Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 19:04:55 +0900 Subject: [PATCH 2614/2815] Fix test failures --- .../UserInterface/TestSceneModSettings.cs | 3 +++ osu.Game/Overlays/Mods/ModSection.cs | 22 +++++++++---------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index 7a11ba5294..435dfd92be 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -31,6 +31,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("open", modSelect.Show); AddAssert("button disabled", () => !modSelect.CustomiseButton.Enabled.Value); + AddUntilStep("wait for button load", () => modSelect.ButtonsLoaded); AddStep("select mod", () => modSelect.SelectMod(testMod)); AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value); AddStep("open Customisation", () => modSelect.CustomiseButton.Click()); @@ -43,6 +44,8 @@ namespace osu.Game.Tests.Visual.UserInterface public new Container ModSettingsContainer => base.ModSettingsContainer; public new TriangleButton CustomiseButton => base.CustomiseButton; + public bool ButtonsLoaded => ModSectionsContainer.Children.All(c => c.ModIconsLoaded); + public void SelectMod(Mod mod) => ModSectionsContainer.Children.Single(s => s.ModType == mod.Type) .ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())).SelectNext(1); diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 9c0a164ad6..c55d1d8f70 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -57,6 +57,15 @@ namespace osu.Game.Overlays.Mods }).ToArray(); modsLoadCts?.Cancel(); + + if (modContainers.Length == 0) + { + ModIconsLoaded = true; + headerLabel.Hide(); + Hide(); + return; + } + ModIconsLoaded = false; LoadComponentsAsync(modContainers, c => @@ -67,17 +76,8 @@ namespace osu.Game.Overlays.Mods buttons = modContainers.OfType().ToArray(); - if (value.Any()) - { - headerLabel.FadeIn(200); - this.FadeIn(200); - } - else - { - // transition here looks weird as mods instantly disappear. - headerLabel.Hide(); - Hide(); - } + headerLabel.FadeIn(200); + this.FadeIn(200); } } From 41437242a29c4fdd7dbae5055dd679fd4763e149 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 6 Dec 2019 19:39:25 +0900 Subject: [PATCH 2615/2815] Add initial path type progression support --- .../Sliders/SliderPlacementBlueprint.cs | 51 +++++++++++++++++-- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index bbdc43e16f..cd93aa0074 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -30,6 +30,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private InputManager inputManager; private PlacementState state; + private PathControlPoint segmentStart; + private PathControlPoint cursor; + private int currentSegmentLength = 1; [Resolved(CanBeNull = true)] private HitObjectComposer composer { get; set; } @@ -38,7 +41,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders : base(new Objects.Slider()) { RelativeSizeAxes = Axes.Both; - HitObject.Path.ControlPoints.Add(new PathControlPoint { Position = { Value = Vector2.Zero } }); + + segmentStart = HitObject.Path.ControlPoints[0]; + } [BackgroundDependencyLoader] @@ -70,9 +75,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders break; case PlacementState.Body: + ensureCursor(); + // The given screen-space position may have been externally snapped, but the unsnapped position from the input manager // is used instead since snapping control points doesn't make much sense - HitObject.Path.ControlPoints.Last().Position.Value = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position; + cursor.Position.Value = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position; break; } } @@ -89,7 +96,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders switch (e.Button) { case MouseButton.Left: - HitObject.Path.ControlPoints.Add(new PathControlPoint { Position = { Value = HitObject.Path.ControlPoints.Last().Position.Value } }); + ensureCursor(); + + // Detatch the cursor + cursor = null; break; } @@ -108,8 +118,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected override bool OnDoubleClick(DoubleClickEvent e) { - // At the point of a double click, there's guaranteed to be at least two points - one from the click, and one from the cursor - HitObject.Path.ControlPoints[HitObject.Path.ControlPoints.Count - 2].Type.Value = PathType.Bezier; + // Todo: This should all not occur on double click, but rather if the previous control point is hovered. + segmentStart = HitObject.Path.ControlPoints[HitObject.Path.ControlPoints.Count - 1]; + segmentStart.Type.Value = PathType.Linear; + + currentSegmentLength = 1; return true; } @@ -131,6 +144,34 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders updateSlider(); } + private void updatePathType() + { + switch (currentSegmentLength) + { + case 1: + case 2: + segmentStart.Type.Value = PathType.Linear; + break; + case 3: + segmentStart.Type.Value = PathType.PerfectCurve; + break; + default: + segmentStart.Type.Value = PathType.Bezier; + break; + } + } + + private void ensureCursor() + { + if (cursor == null) + { + HitObject.Path.ControlPoints.Add(cursor = new PathControlPoint { Position = { Value = Vector2.Zero } }); + currentSegmentLength++; + + updatePathType(); + } + } + private void updateSlider() { HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; From 5347e7c4a2aadd5694722786f57b45b8f2ab65d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 20:14:06 +0900 Subject: [PATCH 2616/2815] Apply code quality improvements --- osu.Game/Configuration/SettingSourceAttribute.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index dceef1cb7d..056fa8bcc0 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Reflection; using JetBrains.Annotations; using osu.Framework.Bindables; @@ -36,11 +35,13 @@ namespace osu.Game.Configuration { public static IEnumerable CreateSettingsControls(this object obj) { - var configProperties = obj.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance).Where(p => p.GetCustomAttribute(true) != null); - - foreach (var property in configProperties) + foreach (var property in obj.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance)) { var attr = property.GetCustomAttribute(true); + + if (attr == null) + continue; + var prop = property.GetValue(obj); switch (prop) @@ -91,9 +92,7 @@ namespace osu.Game.Configuration break; case IBindable bindable: - var dropdownType = typeof(SettingsEnumDropdown<>).MakeGenericType(bindable.GetType().GetGenericArguments()[0]); - var dropdown = (Drawable)Activator.CreateInstance(dropdownType); dropdown.GetType().GetProperty(nameof(IHasCurrentValue.Current))?.SetValue(dropdown, obj); From f65866648e3e914a358ec4a1639d24d4862c01c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 20:14:56 +0900 Subject: [PATCH 2617/2815] Use Array.Empty --- osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index 435dfd92be..fc44c5f595 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.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; @@ -65,7 +66,7 @@ namespace osu.Game.Tests.Visual.UserInterface }; } else - section.Mods = new Mod[] { }; + section.Mods = Array.Empty(); } } } From b9d12e5fe4ba3446e168355220bdf9f35ccf36ec Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 6 Dec 2019 20:53:40 +0900 Subject: [PATCH 2618/2815] Fix nested hitobjects not updating --- .../Objects/JuiceStream.cs | 18 ++++++++++++++- osu.Game.Rulesets.Osu/Objects/Slider.cs | 23 ++++++++++++++++--- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 33780427b6..366f10d61d 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -116,7 +116,23 @@ namespace osu.Game.Rulesets.Catch.Objects public double Duration => EndTime - StartTime; - public SliderPath Path { get; set; } + private readonly SliderPath path = new SliderPath(new[] { new PathControlPoint { Type = { Value = PathType.Linear } } }); + + public SliderPath Path + { + get => path; + set + { + path.ControlPoints.Clear(); + path.ExpectedDistance.Value = null; + + if (value != null) + { + path.ControlPoints.AddRange(value.ControlPoints); + path.ExpectedDistance.Value = value.ExpectedDistance.Value; + } + } + } public double Distance => Path.Distance; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 09657b2d47..4ba5265d17 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -28,7 +28,23 @@ namespace osu.Game.Rulesets.Osu.Objects public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t); - public SliderPath Path { get; set; } = new SliderPath(new[] { new PathControlPoint { Type = { Value = PathType.Bezier } } }); + private readonly SliderPath path = new SliderPath(new[] { new PathControlPoint { Type = { Value = PathType.Linear } } }); + + public SliderPath Path + { + get => path; + set + { + path.ControlPoints.Clear(); + path.ExpectedDistance.Value = null; + + if (value != null) + { + path.ControlPoints.AddRange(value.ControlPoints); + path.ExpectedDistance.Value = value.ExpectedDistance.Value; + } + } + } public double Distance => Path.Distance; @@ -38,8 +54,6 @@ namespace osu.Game.Rulesets.Osu.Objects set { base.Position = value; - endPositionCache.Invalidate(); - updateNestedPositions(); } } @@ -102,6 +116,7 @@ namespace osu.Game.Rulesets.Osu.Objects SamplesBindable.ItemsRemoved += _ => updateNestedSamples(); Path.OffsetChanged += offset => Position += offset; + Path.Version.ValueChanged += _ => updateNestedPositions(); } protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) @@ -179,6 +194,8 @@ namespace osu.Game.Rulesets.Osu.Objects private void updateNestedPositions() { + endPositionCache.Invalidate(); + if (HeadCircle != null) HeadCircle.Position = Position; From 54798b134e99fb136fc50a5749a8d38a2b201892 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 8 Dec 2019 03:16:41 +0900 Subject: [PATCH 2619/2815] Add accessibility modifiers --- osu.Game/Screens/Play/PlayerLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 774ca02be1..57021dfc68 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -55,9 +55,9 @@ namespace osu.Game.Screens.Play protected override bool PlayResumeSound => false; - protected Task LoadTask; + protected Task LoadTask { get; private set; } - protected Task DisposalTask; + protected Task DisposalTask { get; private set; } private InputManager inputManager; private IdleTracker idleTracker; From ff8544597c3bd5d0fa1dc26b19f469dcbbdc0632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 7 Dec 2019 19:51:54 +0100 Subject: [PATCH 2620/2815] Add explicit app manifest to desktop project After the .NET Core bump to version 3.0 in the 2019.1011.0 release, reports popped up of the game not starting any more on some computers using Intel graphics cards (HD 3000 in particular). After investigation the auto-generated application manifest changed in .NET Core 3.0. In particular this seems to be a root cause for the failed start-ups on Intel cards, due to a Windows version compatibility section appearing. The section in turn affects some WinAPI calls like GetVersionEx, which will return major version 10 instead of 6 if compatibility with Windows 10 is declared. This combined with a broken check in the Intel OpenGL driver caused the crashes. To resolve this without having to patch binaries, add an explicit application manifest to the desktop project with the compatibility section removed. --- osu.Desktop/app.manifest | 20 ++++++++++++++++++++ osu.Desktop/osu.Desktop.csproj | 1 + 2 files changed, 21 insertions(+) create mode 100644 osu.Desktop/app.manifest diff --git a/osu.Desktop/app.manifest b/osu.Desktop/app.manifest new file mode 100644 index 0000000000..2e9127bf44 --- /dev/null +++ b/osu.Desktop/app.manifest @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + true + + + \ No newline at end of file diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 453cf6f94d..01e4ada2f1 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -8,6 +8,7 @@ osu!lazer osu!lazer lazer.ico + app.manifest 0.0.0 0.0.0 From 929be3e9e7e32954385f36f348f9cea8d9a7595c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 8 Dec 2019 12:34:07 +0300 Subject: [PATCH 2621/2815] Highlight own score in BeatmapSetOverlay --- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 2 +- .../Scores/ScoreTableRowBackground.cs | 24 +++++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 58f5f02956..f6723839b2 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -63,7 +63,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores return; for (int i = 0; i < value.Count; i++) - backgroundFlow.Add(new ScoreTableRowBackground(i)); + backgroundFlow.Add(new ScoreTableRowBackground(i, value[i])); Columns = createHeaders(value[0]); Content = value.Select((s, i) => createContent(i, s)).ToArray().ToRectangular(); diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs index d820f4d89d..1e10c200ab 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs @@ -7,6 +7,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics; +using osu.Game.Online.API; +using osu.Game.Scoring; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -17,8 +19,14 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly Box hoveredBackground; private readonly Box background; - public ScoreTableRowBackground(int index) + private readonly int index; + private readonly ScoreInfo score; + + public ScoreTableRowBackground(int index, ScoreInfo score) { + this.index = index; + this.score = score; + RelativeSizeAxes = Axes.X; Height = 25; @@ -37,16 +45,18 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Alpha = 0, }, }; - - if (index % 2 != 0) - background.Alpha = 0; } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, IAPIProvider api) { - hoveredBackground.Colour = colours.Gray4; - background.Colour = colours.Gray3; + var isOwnScore = api.LocalUser.Value.Id == score.UserID; + + if (index % 2 != 0 && !isOwnScore) + background.Alpha = 0; + + hoveredBackground.Colour = isOwnScore ? colours.GreenDark : colours.Gray4; + background.Colour = isOwnScore ? colours.GreenDarker : colours.Gray3; } protected override bool OnHover(HoverEvent e) From 13b891f3f4ed6eae1c572737238a005e199efbd1 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sun, 8 Dec 2019 20:05:02 +0800 Subject: [PATCH 2622/2815] Crude legacy cursor rotation support --- osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs index 470ba3acae..6bef90fbaa 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs @@ -19,6 +19,8 @@ namespace osu.Game.Rulesets.Osu.Skinning Origin = Anchor.Centre; } + private NonPlayfieldSprite cursor; + [BackgroundDependencyLoader] private void load(ISkinSource skin) { @@ -30,13 +32,14 @@ namespace osu.Game.Rulesets.Osu.Skinning Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - new NonPlayfieldSprite + cursor = new NonPlayfieldSprite { Texture = skin.GetTexture("cursor"), Anchor = Anchor.Centre, Origin = Anchor.Centre, } }; + cursor.Spin(10000, RotationDirection.Clockwise); } } } From 4cd0dd7856e8b553a9698d86dc114f0ca0768a92 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sun, 8 Dec 2019 20:47:28 +0800 Subject: [PATCH 2623/2815] Move transformation to LoadComplete --- osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs index 6bef90fbaa..11039227c6 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs @@ -39,6 +39,10 @@ namespace osu.Game.Rulesets.Osu.Skinning Origin = Anchor.Centre, } }; + } + + protected override void LoadComplete() + { cursor.Spin(10000, RotationDirection.Clockwise); } } From 8956768fe0d49a9d433b7ff88b601f4d6dd90e1e Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 8 Dec 2019 08:55:45 -0800 Subject: [PATCH 2624/2815] Fix mod buttons being selected when drag scrolling overlay --- osu.Game/Overlays/Mods/ModButton.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index c6b4787ff1..ce623dc182 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -167,10 +167,6 @@ namespace osu.Game.Overlays.Mods { switch (e.Button) { - case MouseButton.Left: - SelectNext(1); - break; - case MouseButton.Right: SelectNext(-1); break; @@ -180,6 +176,15 @@ namespace osu.Game.Overlays.Mods return true; } + protected override bool OnClick(ClickEvent e) + { + scaleContainer.ScaleTo(1, 500, Easing.OutElastic); + + SelectNext(1); + + return base.OnClick(e); + } + /// /// Select the next available mod in a specified direction. /// From 463b6c00300b1686fe4eb66742ca0f63ea373e03 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 8 Dec 2019 09:04:34 -0800 Subject: [PATCH 2625/2815] Remove whitespace --- osu.Game/Overlays/Mods/ModButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index ce623dc182..5af3ebab3b 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -179,7 +179,7 @@ namespace osu.Game.Overlays.Mods protected override bool OnClick(ClickEvent e) { scaleContainer.ScaleTo(1, 500, Easing.OutElastic); - + SelectNext(1); return base.OnClick(e); From e394b28799f4df5e7e01e87dbed07a8bc50127a2 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 8 Dec 2019 09:12:32 -0800 Subject: [PATCH 2626/2815] Remove redundant transform --- osu.Game/Overlays/Mods/ModButton.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 5af3ebab3b..69a4a4181a 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -178,11 +178,9 @@ namespace osu.Game.Overlays.Mods protected override bool OnClick(ClickEvent e) { - scaleContainer.ScaleTo(1, 500, Easing.OutElastic); - SelectNext(1); - return base.OnClick(e); + return true; } /// From c2a40c574dabf83556a23272e869768759b966d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Dec 2019 02:13:47 +0900 Subject: [PATCH 2627/2815] Split out if statement for readability --- .../Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs index 1e10c200ab..724a7f8b55 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs @@ -52,11 +52,14 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { var isOwnScore = api.LocalUser.Value.Id == score.UserID; - if (index % 2 != 0 && !isOwnScore) + if (isOwnScore) + background.Colour = colours.GreenDarker; + else if (index % 2 == 0) + background.Colour = colours.Gray3; + else background.Alpha = 0; hoveredBackground.Colour = isOwnScore ? colours.GreenDark : colours.Gray4; - background.Colour = isOwnScore ? colours.GreenDarker : colours.Gray3; } protected override bool OnHover(HoverEvent e) From 5cf35869e956ffcf0fc2671705bf3cb7c33cab2c Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 8 Dec 2019 10:40:39 -0800 Subject: [PATCH 2628/2815] Fix key config search not clearing after pressing escape --- osu.Game/Overlays/SettingsSubPanel.cs | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/osu.Game/Overlays/SettingsSubPanel.cs b/osu.Game/Overlays/SettingsSubPanel.cs index 5000156e97..2235f9f338 100644 --- a/osu.Game/Overlays/SettingsSubPanel.cs +++ b/osu.Game/Overlays/SettingsSubPanel.cs @@ -4,12 +4,10 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Input.Bindings; using osu.Game.Overlays.Settings; using osu.Game.Screens.Ranking; using osuTK; @@ -36,7 +34,7 @@ namespace osu.Game.Overlays protected override bool DimMainContent => false; // dimming is handled by main overlay - private class BackButton : OsuClickableContainer, IKeyBindingHandler + private class BackButton : OsuClickableContainer { private AspectContainer aspect; @@ -85,20 +83,6 @@ namespace osu.Game.Overlays aspect.ScaleTo(1, 1000, Easing.OutElastic); return base.OnMouseUp(e); } - - public bool OnPressed(GlobalAction action) - { - switch (action) - { - case GlobalAction.Back: - Click(); - return true; - } - - return false; - } - - public bool OnReleased(GlobalAction action) => false; } } } From 9974fff5cc3d52d36e0ebaf2f57cc3af5eddc4ea Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 8 Dec 2019 10:51:25 -0800 Subject: [PATCH 2629/2815] Make sub panel back button inherit osu button --- osu.Game/Overlays/SettingsSubPanel.cs | 32 +++++++++------------------ 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/osu.Game/Overlays/SettingsSubPanel.cs b/osu.Game/Overlays/SettingsSubPanel.cs index 2235f9f338..1fa233d9d4 100644 --- a/osu.Game/Overlays/SettingsSubPanel.cs +++ b/osu.Game/Overlays/SettingsSubPanel.cs @@ -3,14 +3,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; -using osu.Game.Screens.Ranking; using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays { @@ -34,21 +34,21 @@ namespace osu.Game.Overlays protected override bool DimMainContent => false; // dimming is handled by main overlay - private class BackButton : OsuClickableContainer + private class BackButton : OsuButton { - private AspectContainer aspect; - [BackgroundDependencyLoader] private void load() { Size = new Vector2(Sidebar.DEFAULT_WIDTH); - Children = new Drawable[] + + BackgroundColour = Color4.Black; + + AddRange(new Drawable[] { - aspect = new AspectContainer + new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, Children = new Drawable[] { new SpriteIcon @@ -69,19 +69,7 @@ namespace osu.Game.Overlays }, } } - }; - } - - protected override bool OnMouseDown(MouseDownEvent e) - { - aspect.ScaleTo(0.75f, 2000, Easing.OutQuint); - return base.OnMouseDown(e); - } - - protected override bool OnMouseUp(MouseUpEvent e) - { - aspect.ScaleTo(1, 1000, Easing.OutElastic); - return base.OnMouseUp(e); + }); } } } From b2b252a1cc446f96c306129fe6d6011b07dafe66 Mon Sep 17 00:00:00 2001 From: mcendu Date: Mon, 9 Dec 2019 08:36:07 +0800 Subject: [PATCH 2630/2815] Allow skin to disable spin --- osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs | 8 ++++++-- .../Skinning/OsuLegacySkinTransformer.cs | 4 +++- osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs index 11039227c6..68cf99caf1 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs @@ -11,15 +11,18 @@ namespace osu.Game.Rulesets.Osu.Skinning { public class LegacyCursor : CompositeDrawable { - public LegacyCursor() + public LegacyCursor(bool spin = true) { Size = new Vector2(50); Anchor = Anchor.Centre; Origin = Anchor.Centre; + + rotate = spin; } private NonPlayfieldSprite cursor; + private bool rotate; [BackgroundDependencyLoader] private void load(ISkinSource skin) @@ -43,7 +46,8 @@ namespace osu.Game.Rulesets.Osu.Skinning protected override void LoadComplete() { - cursor.Spin(10000, RotationDirection.Clockwise); + if (rotate) + cursor.Spin(10000, RotationDirection.Clockwise); } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index f5b7d9166f..f58b96844e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -81,7 +81,9 @@ namespace osu.Game.Rulesets.Osu.Skinning case OsuSkinComponents.Cursor: if (source.GetTexture("cursor") != null) - return new LegacyCursor(); + { + return new LegacyCursor(GetConfig(OsuSkinConfiguration.CursorRotate)?.Value ?? true); + } return null; diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs index 98219cafe8..5d99960f10 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs @@ -11,5 +11,6 @@ namespace osu.Game.Rulesets.Osu.Skinning SliderPathRadius, AllowSliderBallTint, CursorExpand, + CursorRotate } } From 1cf81c49066dbf3fdafc3d029c187ad012b26d0d Mon Sep 17 00:00:00 2001 From: mcendu Date: Mon, 9 Dec 2019 08:37:32 +0800 Subject: [PATCH 2631/2815] rm unnecessary curlies --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index f58b96844e..56c8e74509 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -81,9 +81,7 @@ namespace osu.Game.Rulesets.Osu.Skinning case OsuSkinComponents.Cursor: if (source.GetTexture("cursor") != null) - { return new LegacyCursor(GetConfig(OsuSkinConfiguration.CursorRotate)?.Value ?? true); - } return null; From eb065286ae5b17081910d708fc1deb2421c969bd Mon Sep 17 00:00:00 2001 From: mcendu Date: Mon, 9 Dec 2019 08:49:44 +0800 Subject: [PATCH 2632/2815] fix ci --- osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs index 68cf99caf1..85ae9d0fc4 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Skinning } private NonPlayfieldSprite cursor; - private bool rotate; + private readonly bool rotate; [BackgroundDependencyLoader] private void load(ISkinSource skin) From 76aabdd2971effca216074ee33e7399b8e8b92b0 Mon Sep 17 00:00:00 2001 From: mcendu Date: Mon, 9 Dec 2019 12:11:04 +0800 Subject: [PATCH 2633/2815] rename field rotate to spin --- osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs index 85ae9d0fc4..d0d11b9157 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs @@ -18,11 +18,11 @@ namespace osu.Game.Rulesets.Osu.Skinning Anchor = Anchor.Centre; Origin = Anchor.Centre; - rotate = spin; + this.spin = spin; } private NonPlayfieldSprite cursor; - private readonly bool rotate; + private readonly bool spin; [BackgroundDependencyLoader] private void load(ISkinSource skin) @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Skinning protected override void LoadComplete() { - if (rotate) + if (spin) cursor.Spin(10000, RotationDirection.Clockwise); } } From 4905709ea497799416c71fbcdba485612c60b67e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 13:19:21 +0900 Subject: [PATCH 2634/2815] Remove unused usings --- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 2 -- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 -- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 -- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs | 1 - osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs | 1 - osu.Game.Rulesets.Osu/Objects/Slider.cs | 1 - .../Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs | 1 - .../Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs | 1 - osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs | 1 - .../Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs | 1 - 10 files changed, 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index ee4a37a4f2..a9616026f5 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.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; @@ -17,7 +16,6 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose; -using osuTK; using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index cd93aa0074..639e18681b 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -2,9 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Events; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 9a504445f4..7431972673 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.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.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -12,7 +11,6 @@ using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index 166defbc41..c5609b01e0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -4,7 +4,6 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osuTK; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 8e2f6ffa66..21a3a0d236 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -3,7 +3,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osuTK; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 4ba5265d17..4c299fd7c2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -6,7 +6,6 @@ using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; using osu.Game.Rulesets.Objects; using System.Linq; -using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Game.Audio; using osu.Game.Beatmaps; diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index afa1f2996e..43e8d01297 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -3,7 +3,6 @@ using osuTK; using osu.Game.Audio; -using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; namespace osu.Game.Rulesets.Objects.Legacy.Catch diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs index d1a8adc2b7..f94c4aaa75 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs @@ -3,7 +3,6 @@ using osuTK; using osu.Game.Audio; -using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; namespace osu.Game.Rulesets.Objects.Legacy.Mania diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs index 6628f7d059..b95ec703b6 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osuTK; -using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; using osu.Game.Audio; diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs index 7b1d64b19f..db65a61c90 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osuTK; -using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; using osu.Game.Audio; From 69deb0ca96f2ef4d22233fd8333311c290b965b0 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 9 Dec 2019 07:19:55 +0300 Subject: [PATCH 2635/2815] Fix unavailable replays can be accessed via leaderboard --- osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs index 79ce04ed66..b941cd8973 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs @@ -34,7 +34,7 @@ namespace osu.Game.Online.API.Requests.Responses PP = PP, Beatmap = Beatmap, RulesetID = OnlineRulesetID, - Hash = "online", // todo: temporary? + Hash = Replay ? "online" : string.Empty, // todo: temporary? Rank = Rank, Ruleset = ruleset, Mods = mods, From 03d18186c23ab92eddda00a04f958d592ca9abf5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Dec 2019 14:11:44 +0900 Subject: [PATCH 2636/2815] Fix broken merge --- .../Graphics/UserInterface/DialogButton.cs | 73 ++++++++++--------- 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index 15c09b10b4..aed07e56ee 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -22,8 +22,8 @@ namespace osu.Game.Graphics.UserInterface { private const float idle_width = 0.8f; private const float hover_width = 0.9f; + private const float hover_duration = 500; - private const float glow_fade_duration = 250; private const float click_duration = 200; public readonly BindableBool Selected = new BindableBool(); @@ -200,30 +200,50 @@ namespace osu.Game.Graphics.UserInterface public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => backgroundContainer.ReceivePositionalInputAt(screenSpacePos); - private bool clicked; + private bool clickAnimating; protected override bool OnClick(ClickEvent e) { - clicked = true; - colourContainer.ResizeTo(new Vector2(1.5f, 1f), click_duration, Easing.In); - flash(); - - this.Delay(click_duration).Schedule(delegate + var flash = new Box { - clicked = false; - colourContainer.ResizeTo(new Vector2(idle_width, 1f)); - spriteText.Spacing = Vector2.Zero; - glowContainer.FadeOut(); - }); + RelativeSizeAxes = Axes.Both, + Colour = ButtonColour, + Blending = BlendingParameters.Additive, + Alpha = 0.05f + }; + + colourContainer.Add(flash); + flash.FadeOutFromOne(100).Expire(); + + clickAnimating = true; + colourContainer.ResizeWidthTo(colourContainer.Width * 1.05f, 100, Easing.OutQuint) + .OnComplete(_ => + { + clickAnimating = false; + Selected.TriggerChange(); + }); return base.OnClick(e); } + protected override bool OnMouseDown(MouseDownEvent e) + { + colourContainer.ResizeWidthTo(hover_width * 0.98f, click_duration * 4, Easing.OutQuad); + return base.OnMouseDown(e); + } + + protected override bool OnMouseUp(MouseUpEvent e) + { + if (Selected.Value) + colourContainer.ResizeWidthTo(hover_width, click_duration, Easing.In); + return base.OnMouseUp(e); + } + protected override bool OnHover(HoverEvent e) { base.OnHover(e); - Selected.Value = true; + return true; } @@ -235,38 +255,23 @@ namespace osu.Game.Graphics.UserInterface private void selectionChanged(ValueChangedEvent args) { - if (clicked) return; + if (clickAnimating) + return; if (args.NewValue) { spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutElastic); - colourContainer.ResizeTo(new Vector2(hover_width, 1f), hover_duration, Easing.OutElastic); - glowContainer.FadeIn(glow_fade_duration, Easing.Out); + colourContainer.ResizeWidthTo(hover_width, hover_duration, Easing.OutElastic); + glowContainer.FadeIn(hover_duration, Easing.OutQuint); } else { - colourContainer.ResizeTo(new Vector2(idle_width, 1f), hover_duration, Easing.OutElastic); + colourContainer.ResizeWidthTo(idle_width, hover_duration, Easing.OutElastic); spriteText.TransformSpacingTo(Vector2.Zero, hover_duration, Easing.OutElastic); - glowContainer.FadeOut(glow_fade_duration, Easing.Out); + glowContainer.FadeOut(hover_duration, Easing.OutQuint); } } - private void flash() - { - var flash = new Box - { - RelativeSizeAxes = Axes.Both - }; - - colourContainer.Add(flash); - - flash.Colour = ButtonColour; - flash.Blending = BlendingParameters.Additive; - flash.Alpha = 0.3f; - flash.FadeOutFromOne(click_duration); - flash.Expire(); - } - private void updateGlow() { leftGlow.Colour = ColourInfo.GradientHorizontal(new Color4(ButtonColour.R, ButtonColour.G, ButtonColour.B, 0f), ButtonColour); From aff1b93a0790672311926a9e0f6bec711c1ce011 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 14:43:23 +0900 Subject: [PATCH 2637/2815] Move config retrieval into LegacySliderBall --- osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs | 12 ++++++------ .../Skinning/OsuLegacySkinTransformer.cs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs index d0d11b9157..05b38ae195 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs @@ -11,22 +11,22 @@ namespace osu.Game.Rulesets.Osu.Skinning { public class LegacyCursor : CompositeDrawable { - public LegacyCursor(bool spin = true) + private NonPlayfieldSprite cursor; + private bool spin; + + public LegacyCursor() { Size = new Vector2(50); Anchor = Anchor.Centre; Origin = Anchor.Centre; - - this.spin = spin; } - private NonPlayfieldSprite cursor; - private readonly bool spin; - [BackgroundDependencyLoader] private void load(ISkinSource skin) { + spin = skin.GetConfig(OsuSkinConfiguration.CursorRotate)?.Value ?? true; + InternalChildren = new Drawable[] { new NonPlayfieldSprite diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 56c8e74509..f5b7d9166f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Osu.Skinning case OsuSkinComponents.Cursor: if (source.GetTexture("cursor") != null) - return new LegacyCursor(GetConfig(OsuSkinConfiguration.CursorRotate)?.Value ?? true); + return new LegacyCursor(); return null; From 12cfb7dedb3a8e631abdeb73c18d2169d7dd8255 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2019 07:28:11 +0000 Subject: [PATCH 2638/2815] Bump System.ComponentModel.Annotations from 4.6.0 to 4.7.0 Bumps [System.ComponentModel.Annotations](https://github.com/dotnet/corefx) from 4.6.0 to 4.7.0. - [Release notes](https://github.com/dotnet/corefx/releases) - [Commits](https://github.com/dotnet/corefx/commits) 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 086359ee41..ef16738908 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -27,6 +27,6 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 0eb8d98a63..5090190f28 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -86,7 +86,7 @@ - + From 53f7c753fb83cec669a56a12b9a4a19e096e9390 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 16:44:19 +0900 Subject: [PATCH 2639/2815] General cleanups --- .../Sliders/Components/PathControlPointVisualiser.cs | 2 +- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 3 ++- osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index a9616026f5..f0888b34fe 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private bool deleteSelected() { - List toRemove = Pieces.Where(p => p.IsSelected.Value).Select(p => p.Index).Select(i => slider.Path.ControlPoints[i]).ToList(); + List toRemove = Pieces.Where(p => p.IsSelected.Value).Select(p => slider.Path.ControlPoints[p.Index]).ToList(); // Ensure that there are any points to be deleted if (toRemove.Count == 0) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 639e18681b..7dd2017e48 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -41,7 +41,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders RelativeSizeAxes = Axes.Both; segmentStart = HitObject.Path.ControlPoints[0]; - } [BackgroundDependencyLoader] @@ -150,9 +149,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders case 2: segmentStart.Type.Value = PathType.Linear; break; + case 3: segmentStart.Type.Value = PathType.PerfectCurve; break; + default: segmentStart.Type.Value = PathType.Bezier; break; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs index 27d39d0f17..606395c289 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs @@ -6,7 +6,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Lines; -using osu.Framework.MathUtils; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osuTK; @@ -141,6 +140,7 @@ namespace osu.Game.Tests.Visual.Gameplay case 2: path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(0, 100))); break; + case 4: path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); break; From b764a74919e5da3ac88b576e7b954b4a7bab6463 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2019 07:52:22 +0000 Subject: [PATCH 2640/2815] Bump Microsoft.Win32.Registry from 4.6.0 to 4.7.0 Bumps [Microsoft.Win32.Registry](https://github.com/dotnet/corefx) from 4.6.0 to 4.7.0. - [Release notes](https://github.com/dotnet/corefx/releases) - [Commits](https://github.com/dotnet/corefx/commits) Signed-off-by: dependabot-preview[bot] --- osu.Desktop/osu.Desktop.csproj | 2 +- osu.Game.Tournament/osu.Game.Tournament.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 01e4ada2f1..63aa999a97 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -28,7 +28,7 @@ - + diff --git a/osu.Game.Tournament/osu.Game.Tournament.csproj b/osu.Game.Tournament/osu.Game.Tournament.csproj index 8e881fdd9c..9cce40c9d3 100644 --- a/osu.Game.Tournament/osu.Game.Tournament.csproj +++ b/osu.Game.Tournament/osu.Game.Tournament.csproj @@ -9,6 +9,6 @@ - + \ No newline at end of file From 3861abce826e0f533f97cc678b5333046409b55f Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2019 07:52:23 +0000 Subject: [PATCH 2641/2815] Bump System.IO.Packaging from 4.6.0 to 4.7.0 Bumps [System.IO.Packaging](https://github.com/dotnet/corefx) from 4.6.0 to 4.7.0. - [Release notes](https://github.com/dotnet/corefx/releases) - [Commits](https://github.com/dotnet/corefx/commits) Signed-off-by: dependabot-preview[bot] --- osu.Desktop/osu.Desktop.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 01e4ada2f1..70b9d5c184 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -24,7 +24,7 @@ - + From 0c7e5a2e3b0cf886eae6f2828c33b793c2501cc0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 19:15:49 +0900 Subject: [PATCH 2642/2815] Add bindable adjustments for DT/HT rate --- osu.Game/Rulesets/Mods/ModDoubleTime.cs | 14 +++++++++++++- osu.Game/Rulesets/Mods/ModHalfTime.cs | 14 +++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index a5e76e32b1..1a6831d974 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -3,7 +3,9 @@ using System; using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods @@ -19,6 +21,16 @@ namespace osu.Game.Rulesets.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModHalfTime)).ToArray(); - protected override double RateAdjust => 1.5; + [SettingSource("Speed increase", "The actual increase to apply")] + public BindableNumber SpeedChange { get; } = new BindableDouble + { + MinValue = 1.01, + MaxValue = 2, + Default = 1.5, + Value = 1.5, + Precision = 0.01, + }; + + protected override double RateAdjust => SpeedChange.Value; } } diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 27369f4c30..4acf771fa8 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -3,7 +3,9 @@ using System; using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods @@ -19,6 +21,16 @@ namespace osu.Game.Rulesets.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModDoubleTime)).ToArray(); - protected override double RateAdjust => 0.75; + [SettingSource("Speed decrease", "The actual decrease to apply")] + public BindableNumber SpeedChange { get; } = new BindableDouble + { + MinValue = 0.5, + MaxValue = 0.99, + Default = 0.75, + Value = 0.75, + Precision = 0.01, + }; + + protected override double RateAdjust => SpeedChange.Value; } } From eb074b7058cc6586f447d308131db79b8af5724e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Dec 2019 17:34:04 +0900 Subject: [PATCH 2643/2815] Allow mods to apply to track, not clock --- .../Visual/Gameplay/TestScenePlayerLoader.cs | 4 +- osu.Game/Overlays/MusicController.cs | 4 +- .../Difficulty/DifficultyCalculator.cs | 8 ++-- .../Difficulty/PerformanceCalculator.cs | 8 ++-- ...icableToClock.cs => IApplicableToTrack.cs} | 6 +-- osu.Game/Rulesets/Mods/ModDaycore.cs | 10 ++--- osu.Game/Rulesets/Mods/ModDoubleTime.cs | 2 +- osu.Game/Rulesets/Mods/ModHalfTime.cs | 2 +- osu.Game/Rulesets/Mods/ModNightcore.cs | 10 ++--- osu.Game/Rulesets/Mods/ModTimeAdjust.cs | 12 ++---- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 32 ++++------------ .../Screens/Play/GameplayClockContainer.cs | 37 +++++++++---------- 12 files changed, 52 insertions(+), 83 deletions(-) rename osu.Game/Rulesets/Mods/{IApplicableToClock.cs => IApplicableToTrack.cs} (69%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index dbea8d28a6..f02361e685 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -57,8 +57,8 @@ namespace osu.Game.Tests.Visual.Gameplay beforeLoadAction?.Invoke(); Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); - foreach (var mod in Mods.Value.OfType()) - mod.ApplyToClock(Beatmap.Value.Track); + foreach (var mod in Mods.Value.OfType()) + mod.ApplyToTrack(Beatmap.Value.Track); InputManager.Child = container = new TestPlayerLoaderContainer( loader = new TestPlayerLoader(() => diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 5e0a67c2f7..bafdad3508 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -261,8 +261,8 @@ namespace osu.Game.Overlays if (allowRateAdjustments) { - foreach (var mod in mods.Value.OfType()) - mod.ApplyToClock(track); + foreach (var mod in mods.Value.OfType()) + mod.ApplyToTrack(track); } } diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index e31c963403..1902de5bda 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -4,8 +4,8 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Audio.Track; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; @@ -41,10 +41,10 @@ namespace osu.Game.Rulesets.Difficulty IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods); - var clock = new StopwatchClock(); - mods.OfType().ForEach(m => m.ApplyToClock(clock)); + var track = new TrackVirtual(10000); + mods.OfType().ForEach(m => m.ApplyToTrack(track)); - return calculate(playableBeatmap, mods, clock.Rate); + return calculate(playableBeatmap, mods, track.Rate); } /// diff --git a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs index 9ab81b9580..ac3b817840 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Audio.Track; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; @@ -35,9 +35,9 @@ namespace osu.Game.Rulesets.Difficulty protected virtual void ApplyMods(Mod[] mods) { - var clock = new StopwatchClock(); - mods.OfType().ForEach(m => m.ApplyToClock(clock)); - TimeRate = clock.Rate; + var track = new TrackVirtual(10000); + mods.OfType().ForEach(m => m.ApplyToTrack(track)); + TimeRate = track.Rate; } public abstract double Calculate(Dictionary categoryDifficulty = null); diff --git a/osu.Game/Rulesets/Mods/IApplicableToClock.cs b/osu.Game/Rulesets/Mods/IApplicableToTrack.cs similarity index 69% rename from osu.Game/Rulesets/Mods/IApplicableToClock.cs rename to osu.Game/Rulesets/Mods/IApplicableToTrack.cs index e5767b5fbf..4d6d958e82 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToClock.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToTrack.cs @@ -1,15 +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 osu.Framework.Timing; +using osu.Framework.Audio.Track; namespace osu.Game.Rulesets.Mods { /// /// An interface for mods that make adjustments to the track. /// - public interface IApplicableToClock : IApplicableMod + public interface IApplicableToTrack : IApplicableMod { - void ApplyToClock(IAdjustableClock clock); + void ApplyToTrack(Track track); } } diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index 7e6d959119..dcb3cb5597 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -1,9 +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 osu.Framework.Audio; +using osu.Framework.Audio.Track; using osu.Framework.Graphics.Sprites; -using osu.Framework.Timing; namespace osu.Game.Rulesets.Mods { @@ -14,12 +13,9 @@ namespace osu.Game.Rulesets.Mods public override IconUsage Icon => FontAwesome.Solid.Question; public override string Description => "Whoaaaaa..."; - public override void ApplyToClock(IAdjustableClock clock) + public override void ApplyToTrack(Track track) { - if (clock is IHasPitchAdjust pitchAdjust) - pitchAdjust.PitchAdjust *= RateAdjust; - else - base.ApplyToClock(clock); + track.Frequency.Value *= RateAdjust; } } } diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index a5e76e32b1..5e685b040e 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -8,7 +8,7 @@ using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods { - public abstract class ModDoubleTime : ModTimeAdjust, IApplicableToClock + public abstract class ModDoubleTime : ModTimeAdjust { public override string Name => "Double Time"; public override string Acronym => "DT"; diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 27369f4c30..d17ddd2253 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -8,7 +8,7 @@ using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods { - public abstract class ModHalfTime : ModTimeAdjust, IApplicableToClock + public abstract class ModHalfTime : ModTimeAdjust { public override string Name => "Half Time"; public override string Acronym => "HT"; diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index dc0fc33088..a4f1ef5a72 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -1,9 +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 osu.Framework.Audio; +using osu.Framework.Audio.Track; using osu.Framework.Graphics.Sprites; -using osu.Framework.Timing; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods @@ -15,12 +14,9 @@ namespace osu.Game.Rulesets.Mods public override IconUsage Icon => OsuIcon.ModNightcore; public override string Description => "Uguuuuuuuu..."; - public override void ApplyToClock(IAdjustableClock clock) + public override void ApplyToTrack(Track track) { - if (clock is IHasPitchAdjust pitchAdjust) - pitchAdjust.PitchAdjust *= RateAdjust; - else - base.ApplyToClock(clock); + track.Frequency.Value *= RateAdjust; } } } diff --git a/osu.Game/Rulesets/Mods/ModTimeAdjust.cs b/osu.Game/Rulesets/Mods/ModTimeAdjust.cs index 513883f552..f137a75ed8 100644 --- a/osu.Game/Rulesets/Mods/ModTimeAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModTimeAdjust.cs @@ -2,23 +2,19 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Audio; -using osu.Framework.Timing; +using osu.Framework.Audio.Track; namespace osu.Game.Rulesets.Mods { - public abstract class ModTimeAdjust : Mod + public abstract class ModTimeAdjust : Mod, IApplicableToTrack { public override Type[] IncompatibleMods => new[] { typeof(ModTimeRamp) }; protected abstract double RateAdjust { get; } - public virtual void ApplyToClock(IAdjustableClock clock) + public virtual void ApplyToTrack(Track track) { - if (clock is IHasTempoAdjust tempo) - tempo.TempoAdjust *= RateAdjust; - else - clock.Rate *= RateAdjust; + track.TempoAdjust *= RateAdjust; } } } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index e231225e3c..d95d354487 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -3,15 +3,14 @@ using System; using System.Linq; -using osu.Framework.Audio; -using osu.Framework.Timing; +using osu.Framework.Audio.Track; using osu.Game.Beatmaps; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mods { - public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToClock, IApplicableToBeatmap + public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToTrack, IApplicableToBeatmap { /// /// The point in the beatmap at which the final ramping rate should be reached. @@ -24,11 +23,11 @@ namespace osu.Game.Rulesets.Mods private double finalRateTime; private double beginRampTime; - private IAdjustableClock clock; + private Track track; - public virtual void ApplyToClock(IAdjustableClock clock) + public virtual void ApplyToTrack(Track track) { - this.clock = clock; + this.track = track; lastAdjust = 1; @@ -46,7 +45,7 @@ namespace osu.Game.Rulesets.Mods public virtual void Update(Playfield playfield) { - applyAdjustment((clock.CurrentTime - beginRampTime) / finalRateTime); + applyAdjustment((track.CurrentTime - beginRampTime) / finalRateTime); } private double lastAdjust = 1; @@ -59,23 +58,8 @@ namespace osu.Game.Rulesets.Mods { double adjust = 1 + (Math.Sign(FinalRateAdjustment) * Math.Clamp(amount, 0, 1) * Math.Abs(FinalRateAdjustment)); - switch (clock) - { - case IHasPitchAdjust pitch: - pitch.PitchAdjust /= lastAdjust; - pitch.PitchAdjust *= adjust; - break; - - case IHasTempoAdjust tempo: - tempo.TempoAdjust /= lastAdjust; - tempo.TempoAdjust *= adjust; - break; - - default: - clock.Rate /= lastAdjust; - clock.Rate *= adjust; - break; - } + track.Tempo.Value /= lastAdjust; + track.Tempo.Value *= lastAdjust; lastAdjust = adjust; } diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 58c9a6a784..1508758c87 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -28,9 +28,9 @@ namespace osu.Game.Screens.Play private readonly IReadOnlyList mods; /// - /// The original source (usually a 's track). + /// The 's track. /// - private IAdjustableClock sourceClock; + private Track track; public readonly BindableBool IsPaused = new BindableBool(); @@ -72,8 +72,8 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both; - sourceClock = (IAdjustableClock)beatmap.Track ?? new StopwatchClock(); - (sourceClock as IAdjustableAudioComponent)?.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); + track = beatmap.Track; + track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; @@ -127,11 +127,11 @@ namespace osu.Game.Screens.Play { Task.Run(() => { - sourceClock.Reset(); + track.Reset(); Schedule(() => { - adjustableClock.ChangeSource(sourceClock); + adjustableClock.ChangeSource(track); updateRate(); if (!IsPaused.Value) @@ -197,13 +197,13 @@ namespace osu.Game.Screens.Play /// public void StopUsingBeatmapClock() { - if (sourceClock != beatmap.Track) + if (track != beatmap.Track) return; removeSourceClockAdjustments(); - sourceClock = new TrackVirtual(beatmap.Track.Length); - adjustableClock.ChangeSource(sourceClock); + track = new TrackVirtual(beatmap.Track.Length); + adjustableClock.ChangeSource(track); } protected override void Update() @@ -218,18 +218,15 @@ namespace osu.Game.Screens.Play private void updateRate() { - if (sourceClock == null) return; + if (track == null) return; speedAdjustmentsApplied = true; - sourceClock.ResetSpeedAdjustments(); + track.ResetSpeedAdjustments(); - if (sourceClock is IHasTempoAdjust tempo) - tempo.TempoAdjust = UserPlaybackRate.Value; - else - sourceClock.Rate = UserPlaybackRate.Value; + track.Tempo.Value = UserPlaybackRate.Value; - foreach (var mod in mods.OfType()) - mod.ApplyToClock(sourceClock); + foreach (var mod in mods.OfType()) + mod.ApplyToTrack(track); } protected override void Dispose(bool isDisposing) @@ -237,18 +234,18 @@ namespace osu.Game.Screens.Play base.Dispose(isDisposing); removeSourceClockAdjustments(); - sourceClock = null; + track = null; } private void removeSourceClockAdjustments() { if (speedAdjustmentsApplied) { - sourceClock.ResetSpeedAdjustments(); + track.ResetSpeedAdjustments(); speedAdjustmentsApplied = false; } - (sourceClock as IAdjustableAudioComponent)?.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); + (track as IAdjustableAudioComponent)?.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); } } } From 5a093c039cedd3b214827871a77c3ef6c69852c7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 17:45:08 +0900 Subject: [PATCH 2644/2815] Simplify path/point construction --- osu.Game/Rulesets/Objects/PathControlPoint.cs | 15 ++++++++++++++- osu.Game/Rulesets/Objects/SliderPath.cs | 15 ++++----------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/osu.Game/Rulesets/Objects/PathControlPoint.cs b/osu.Game/Rulesets/Objects/PathControlPoint.cs index 83436b7a36..de40c24060 100644 --- a/osu.Game/Rulesets/Objects/PathControlPoint.cs +++ b/osu.Game/Rulesets/Objects/PathControlPoint.cs @@ -26,12 +26,25 @@ namespace osu.Game.Rulesets.Objects /// internal event Action Changed; + /// + /// Creates a new . + /// public PathControlPoint() + : this(Vector2.Zero, null) + { + } + + /// + /// Creates a new with a provided position and type. + /// + /// The initial position. + /// The initial type. + public PathControlPoint(Vector2 position, PathType? type = null) { Position.ValueChanged += _ => Changed?.Invoke(); Type.ValueChanged += _ => Changed?.Invoke(); } - public bool Equals(PathControlPoint other) => Position.Value == other.Position.Value && Type.Value == other.Type.Value; + public bool Equals(PathControlPoint other) => Position.Value == other?.Position.Value && Type.Value == other.Type.Value; } } diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 969cdcc463..dbd236107c 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -40,12 +40,10 @@ namespace osu.Game.Rulesets.Objects /// public readonly BindableList ControlPoints = new BindableList(); - public readonly List Test = new List(); - - private readonly Cached pathCache = new Cached(); - private readonly List calculatedPath = new List(); private readonly List cumulativeLength = new List(); + private readonly Cached pathCache = new Cached(); + private double calculatedLength; /// @@ -87,7 +85,7 @@ namespace osu.Game.Rulesets.Objects } /// - /// Creates a new . + /// Creates a new initialised with a list of control points. /// /// An optional set of s to initialise the path with. /// A user-set distance of the path that may be shorter or longer than the true distance between all control points. @@ -101,13 +99,8 @@ namespace osu.Game.Rulesets.Objects } public SliderPath(PathType type, Vector2[] controlPoints, double? expectedDistance = null) - : this() + : this(controlPoints.Select((c, i) => new PathControlPoint(c, i == 0 ? (PathType?)type : null)).ToArray(), expectedDistance) { - foreach (var c in controlPoints) - ControlPoints.Add(new PathControlPoint { Position = { Value = c } }); - ControlPoints[0].Type.Value = type; - - ExpectedDistance.Value = expectedDistance; } /// From 9cb649436c838047fdddbc91b87f08f07e94afd2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 17:47:05 +0900 Subject: [PATCH 2645/2815] Default to linear control point type --- osu.Game/Rulesets/Objects/SliderPath.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index dbd236107c..e323054234 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -196,9 +196,6 @@ namespace osu.Game.Rulesets.Objects if (ControlPoints.Count == 0) return; - if (ControlPoints[0].Type.Value == null) - throw new InvalidOperationException($"The first control point in a {nameof(SliderPath)} must have a non-null type."); - Vector2[] vertices = new Vector2[ControlPoints.Count]; for (int i = 0; i < ControlPoints.Count; i++) vertices[i] = ControlPoints[i].Position.Value; @@ -214,7 +211,7 @@ namespace osu.Game.Rulesets.Objects // The current vertex ends the segment var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); - var segmentType = ControlPoints[start].Type.Value.Value; + var segmentType = ControlPoints[start].Type.Value ?? PathType.Linear; foreach (Vector2 t in computeSubPath(segmentVertices, segmentType)) { From fa1468325e504a22f7a6e7f9dff5c34dd8878d41 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 17:48:27 +0900 Subject: [PATCH 2646/2815] Refactor hitobjects to remove default control point --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 366f10d61d..d5d99640af 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Catch.Objects public double Duration => EndTime - StartTime; - private readonly SliderPath path = new SliderPath(new[] { new PathControlPoint { Type = { Value = PathType.Linear } } }); + private readonly SliderPath path = new SliderPath(); public SliderPath Path { diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 4c299fd7c2..134576316a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Objects public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t); - private readonly SliderPath path = new SliderPath(new[] { new PathControlPoint { Type = { Value = PathType.Linear } } }); + private readonly SliderPath path = new SliderPath(); public SliderPath Path { From 883d5bc11d54cfd72e58f2e7d42c21b8fea1141b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 17:54:19 +0900 Subject: [PATCH 2647/2815] Remove automatic slider path offsetting --- .../Components/PathControlPointVisualiser.cs | 8 ++++++++ osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 -- osu.Game/Rulesets/Objects/SliderPath.cs | 20 ------------------- 3 files changed, 8 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index f0888b34fe..434e74ddeb 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -16,6 +16,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose; +using osuTK; using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components @@ -122,6 +123,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return true; } + // The path will have a non-zero offset if the head is removed, but sliders don't support this behaviour since the head is positioned at the slider's position + // So the slider needs to be offset by this amount instead, and all control points offset backwards such that the path is re-positioned at (0, 0) + Vector2 first = slider.Path.ControlPoints[0].Position.Value; + foreach (var c in slider.Path.ControlPoints) + c.Position.Value -= first; + slider.Position += first; + // Since pieces are re-used, they will not point to the deleted control points while remaining selected foreach (var piece in Pieces) piece.IsSelected.Value = false; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 134576316a..34e5a7f3cd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -113,8 +113,6 @@ namespace osu.Game.Rulesets.Osu.Objects { SamplesBindable.ItemsAdded += _ => updateNestedSamples(); SamplesBindable.ItemsRemoved += _ => updateNestedSamples(); - - Path.OffsetChanged += offset => Position += offset; Path.Version.ValueChanged += _ => updateNestedPositions(); } diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index e323054234..d868ee27f0 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -16,12 +16,6 @@ namespace osu.Game.Rulesets.Objects { public class SliderPath { - /// - /// Invoked when the offset of the path changes. - /// The provided value indicates the offset, and should be used to re-calculate the position of the containing drawable. - /// - public event Action OffsetChanged; - /// /// The current version of this . Updated when any change to the path occurs. /// @@ -66,20 +60,6 @@ namespace osu.Game.Rulesets.Objects foreach (var c in items) c.Changed -= invalidate; - // Make all control points relative to the first one - if (ControlPoints.Count > 0) - { - Vector2 first = ControlPoints[0].Position.Value; - - if (first != Vector2.Zero) - { - foreach (var c in ControlPoints) - c.Position.Value -= first; - - OffsetChanged?.Invoke(first); - } - } - invalidate(); }; } From bfbb9aa18e09640549f580748be35a5e7690d82b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 18:01:02 +0900 Subject: [PATCH 2648/2815] Remove outdated assert --- osu.Game/Rulesets/Objects/SliderPath.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index d868ee27f0..d5ae6f471d 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -187,8 +187,6 @@ namespace osu.Game.Rulesets.Objects if (ControlPoints[i].Type.Value == null && i < ControlPoints.Count - 1) continue; - Debug.Assert(ControlPoints[start].Type.Value.HasValue); - // The current vertex ends the segment var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); var segmentType = ControlPoints[start].Type.Value ?? PathType.Linear; From a1798fd38d29e8cc25a0919025b78bd4e1f24e39 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 18:01:13 +0900 Subject: [PATCH 2649/2815] Fix bad ctor implementation --- osu.Game/Rulesets/Objects/PathControlPoint.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/Objects/PathControlPoint.cs b/osu.Game/Rulesets/Objects/PathControlPoint.cs index de40c24060..5737d3f618 100644 --- a/osu.Game/Rulesets/Objects/PathControlPoint.cs +++ b/osu.Game/Rulesets/Objects/PathControlPoint.cs @@ -41,6 +41,9 @@ namespace osu.Game.Rulesets.Objects /// The initial type. public PathControlPoint(Vector2 position, PathType? type = null) { + Position.Value = position; + Type.Value = type; + Position.ValueChanged += _ => Changed?.Invoke(); Type.ValueChanged += _ => Changed?.Invoke(); } From d650bfb6d6d3c56439c3a56e1c1afd940d9d989b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Dec 2019 18:05:14 +0900 Subject: [PATCH 2650/2815] Remove unnecessary cast --- osu.Game/Screens/Play/GameplayClockContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 1508758c87..2cc03ae453 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -245,7 +245,7 @@ namespace osu.Game.Screens.Play speedAdjustmentsApplied = false; } - (track as IAdjustableAudioComponent)?.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); + track.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); } } } From 2dbf94f3abc054504d74a831fc8e885640710d1b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 18:10:33 +0900 Subject: [PATCH 2651/2815] Make placement blueprint add an initial segment --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 7dd2017e48..c004b6db28 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private PlacementState state; private PathControlPoint segmentStart; private PathControlPoint cursor; - private int currentSegmentLength = 1; + private int currentSegmentLength; [Resolved(CanBeNull = true)] private HitObjectComposer composer { get; set; } @@ -40,7 +40,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { RelativeSizeAxes = Axes.Both; - segmentStart = HitObject.Path.ControlPoints[0]; + HitObject.Path.ControlPoints.Add(segmentStart = new PathControlPoint(Vector2.Zero, PathType.Linear)); + currentSegmentLength = 1; } [BackgroundDependencyLoader] From b6e2738236da446fd99de6f44fa6cb5930572a3f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 18:15:00 +0900 Subject: [PATCH 2652/2815] Remove unused using --- osu.Game/Rulesets/Objects/SliderPath.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index d5ae6f471d..d8c6320c6d 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using Newtonsoft.Json; using osu.Framework.Bindables; From 6d9cd0fafe6b405d48fe74061c529bcdf0aaeeac Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 18:25:13 +0900 Subject: [PATCH 2653/2815] Split out complex method --- osu.Game/Rulesets/Objects/SliderPath.cs | 38 ++++++++++++------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index d8c6320c6d..6a151d7d33 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Objects var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); var segmentType = ControlPoints[start].Type.Value ?? PathType.Linear; - foreach (Vector2 t in computeSubPath(segmentVertices, segmentType)) + foreach (Vector2 t in calculateSubPath(segmentVertices, segmentType)) { if (calculatedPath.Count == 0 || calculatedPath.Last() != t) calculatedPath.Add(t); @@ -199,32 +199,32 @@ namespace osu.Game.Rulesets.Objects // Start the new segment at the current vertex start = i; } + } - static List computeSubPath(ReadOnlySpan subControlPoints, PathType type) + private List calculateSubPath(ReadOnlySpan subControlPoints, PathType type) + { + switch (type) { - switch (type) - { - case PathType.Linear: - return PathApproximator.ApproximateLinear(subControlPoints); + case PathType.Linear: + return PathApproximator.ApproximateLinear(subControlPoints); - case PathType.PerfectCurve: - if (subControlPoints.Length != 3) - break; + case PathType.PerfectCurve: + if (subControlPoints.Length != 3) + break; - List subpath = PathApproximator.ApproximateCircularArc(subControlPoints); + List subpath = PathApproximator.ApproximateCircularArc(subControlPoints); - // If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation. - if (subpath.Count == 0) - break; + // If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation. + if (subpath.Count == 0) + break; - return subpath; + return subpath; - case PathType.Catmull: - return PathApproximator.ApproximateCatmull(subControlPoints); - } - - return PathApproximator.ApproximateBezier(subControlPoints); + case PathType.Catmull: + return PathApproximator.ApproximateCatmull(subControlPoints); } + + return PathApproximator.ApproximateBezier(subControlPoints); } private void calculateLength() From 04b3297a0515c9a204109b387bf4e5e3008cabe8 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Fri, 6 Dec 2019 21:32:31 +0800 Subject: [PATCH 2654/2815] Constrain configuration lookup as enum. --- osu.Game/Configuration/DatabasedConfigManager.cs | 7 ++++--- osu.Game/Configuration/InMemoryConfigManager.cs | 5 +++-- osu.Game/Rulesets/Configuration/RulesetConfigManager.cs | 5 +++-- osu.Game/Skinning/SkinConfigManager.cs | 3 ++- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/osu.Game/Configuration/DatabasedConfigManager.cs b/osu.Game/Configuration/DatabasedConfigManager.cs index 1ef4c2527a..b3783b45a8 100644 --- a/osu.Game/Configuration/DatabasedConfigManager.cs +++ b/osu.Game/Configuration/DatabasedConfigManager.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 osu.Framework.Bindables; @@ -9,8 +10,8 @@ using osu.Game.Rulesets; namespace osu.Game.Configuration { - public abstract class DatabasedConfigManager : ConfigManager - where T : struct + public abstract class DatabasedConfigManager : ConfigManager + where TLookup : struct, Enum { private readonly SettingsStore settings; @@ -53,7 +54,7 @@ namespace osu.Game.Configuration private readonly List dirtySettings = new List(); - protected override void AddBindable(T lookup, Bindable bindable) + protected override void AddBindable(TLookup lookup, Bindable bindable) { base.AddBindable(lookup, bindable); diff --git a/osu.Game/Configuration/InMemoryConfigManager.cs b/osu.Game/Configuration/InMemoryConfigManager.cs index b0dc6b0e9c..ccf697f680 100644 --- a/osu.Game/Configuration/InMemoryConfigManager.cs +++ b/osu.Game/Configuration/InMemoryConfigManager.cs @@ -1,12 +1,13 @@ // 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; namespace osu.Game.Configuration { - public class InMemoryConfigManager : ConfigManager - where T : struct + public class InMemoryConfigManager : ConfigManager + where TLookup : struct, Enum { public InMemoryConfigManager() { diff --git a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs index ed5fdf9809..0ff3455f00 100644 --- a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs @@ -1,12 +1,13 @@ // 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.Configuration; namespace osu.Game.Rulesets.Configuration { - public abstract class RulesetConfigManager : DatabasedConfigManager, IRulesetConfigManager - where T : struct + public abstract class RulesetConfigManager : DatabasedConfigManager, IRulesetConfigManager + where TLookup : struct, Enum { protected RulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null) : base(settings, ruleset, variant) diff --git a/osu.Game/Skinning/SkinConfigManager.cs b/osu.Game/Skinning/SkinConfigManager.cs index 896444d1d2..682138a2e9 100644 --- a/osu.Game/Skinning/SkinConfigManager.cs +++ b/osu.Game/Skinning/SkinConfigManager.cs @@ -1,11 +1,12 @@ // 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; namespace osu.Game.Skinning { - public class SkinConfigManager : ConfigManager where T : struct + public class SkinConfigManager : ConfigManager where TLookup : struct, Enum { protected override void PerformLoad() { From 40a5c1fd96bfd197131f15bc6c9eececba82c437 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 7 Dec 2019 19:49:52 +0800 Subject: [PATCH 2655/2815] Constrain transformable with class. --- osu.Game/Graphics/IHasAccentColour.cs | 2 +- osu.Game/Storyboards/Drawables/IFlippable.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/IHasAccentColour.cs b/osu.Game/Graphics/IHasAccentColour.cs index 1a66819379..af497da70f 100644 --- a/osu.Game/Graphics/IHasAccentColour.cs +++ b/osu.Game/Graphics/IHasAccentColour.cs @@ -24,7 +24,7 @@ namespace osu.Game.Graphics /// /// A to which further transforms can be added. public static TransformSequence FadeAccent(this T accentedDrawable, Color4 newColour, double duration = 0, Easing easing = Easing.None) - where T : IHasAccentColour + where T : class, IHasAccentColour => accentedDrawable.TransformTo(nameof(accentedDrawable.AccentColour), newColour, duration, easing); /// diff --git a/osu.Game/Storyboards/Drawables/IFlippable.cs b/osu.Game/Storyboards/Drawables/IFlippable.cs index 9e12de5833..1c4cdde22d 100644 --- a/osu.Game/Storyboards/Drawables/IFlippable.cs +++ b/osu.Game/Storyboards/Drawables/IFlippable.cs @@ -41,7 +41,7 @@ namespace osu.Game.Storyboards.Drawables /// /// A to which further transforms can be added. public static TransformSequence TransformFlipH(this T flippable, bool newValue, double delay = 0) - where T : IFlippable + where T : class, IFlippable => flippable.TransformTo(flippable.PopulateTransform(new TransformFlipH(), newValue, delay)); /// @@ -49,7 +49,7 @@ namespace osu.Game.Storyboards.Drawables /// /// A to which further transforms can be added. public static TransformSequence TransformFlipV(this T flippable, bool newValue, double delay = 0) - where T : IFlippable + where T : class, IFlippable => flippable.TransformTo(flippable.PopulateTransform(new TransformFlipV(), newValue, delay)); } } From c3518a2b9491960e573e5de0e3860d66a178a0c7 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 7 Dec 2019 19:56:56 +0800 Subject: [PATCH 2656/2815] Enum constraint for enum dropdown. --- .../Graphics/UserInterface/OsuEnumDropdown.cs | 4 +--- .../SearchableList/DisplayStyleControl.cs | 2 ++ .../SearchableListFilterControl.cs | 19 +++++++++---------- .../SearchableList/SearchableListHeader.cs | 4 +--- .../SearchableList/SearchableListOverlay.cs | 14 +++++++++----- .../SearchableList/SlimEnumDropdown.cs | 2 ++ .../Overlays/Settings/SettingsEnumDropdown.cs | 2 ++ 7 files changed, 26 insertions(+), 21 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs b/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs index e132027787..528d7d60f8 100644 --- a/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs @@ -6,12 +6,10 @@ using System; namespace osu.Game.Graphics.UserInterface { public class OsuEnumDropdown : OsuDropdown + where T : struct, Enum { public OsuEnumDropdown() { - if (!typeof(T).IsEnum) - throw new InvalidOperationException("OsuEnumDropdown only supports enums as the generic type argument"); - Items = (T[])Enum.GetValues(typeof(T)); } } diff --git a/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs b/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs index 0808cc8fcc..a33f4eb30d 100644 --- a/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs +++ b/osu.Game/Overlays/SearchableList/DisplayStyleControl.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 osuTK; using osu.Framework.Graphics; @@ -11,6 +12,7 @@ using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.SearchableList { public class DisplayStyleControl : Container + where T : struct, Enum { public readonly SlimEnumDropdown Dropdown; public readonly Bindable DisplayStyle = new Bindable(); diff --git a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs index 372da94b37..117f905de4 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs @@ -13,7 +13,9 @@ using osu.Framework.Graphics.Shapes; namespace osu.Game.Overlays.SearchableList { - public abstract class SearchableListFilterControl : Container + public abstract class SearchableListFilterControl : Container + where TTab : struct, Enum + where TCategory : struct, Enum { private const float padding = 10; @@ -21,12 +23,12 @@ namespace osu.Game.Overlays.SearchableList private readonly Box tabStrip; public readonly SearchTextBox Search; - public readonly PageTabControl Tabs; - public readonly DisplayStyleControl DisplayStyleControl; + public readonly PageTabControl Tabs; + public readonly DisplayStyleControl DisplayStyleControl; protected abstract Color4 BackgroundColour { get; } - protected abstract T DefaultTab { get; } - protected abstract U DefaultCategory { get; } + protected abstract TTab DefaultTab { get; } + protected abstract TCategory DefaultCategory { get; } protected virtual Drawable CreateSupplementaryControls() => null; /// @@ -36,9 +38,6 @@ namespace osu.Game.Overlays.SearchableList protected SearchableListFilterControl() { - if (!typeof(T).IsEnum) - throw new InvalidOperationException("SearchableListFilterControl's sort tabs only support enums as the generic type argument"); - RelativeSizeAxes = Axes.X; var controls = CreateSupplementaryControls(); @@ -90,7 +89,7 @@ namespace osu.Game.Overlays.SearchableList RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Right = 225 }, - Child = Tabs = new PageTabControl + Child = Tabs = new PageTabControl { RelativeSizeAxes = Axes.X, }, @@ -105,7 +104,7 @@ namespace osu.Game.Overlays.SearchableList }, }, }, - DisplayStyleControl = new DisplayStyleControl + DisplayStyleControl = new DisplayStyleControl { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, diff --git a/osu.Game/Overlays/SearchableList/SearchableListHeader.cs b/osu.Game/Overlays/SearchableList/SearchableListHeader.cs index 73dca956d1..66fedf0a56 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListHeader.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListHeader.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.SearchableList { public abstract class SearchableListHeader : Container + where T : struct, Enum { public readonly HeaderTabControl Tabs; @@ -24,9 +25,6 @@ namespace osu.Game.Overlays.SearchableList protected SearchableListHeader() { - if (!typeof(T).IsEnum) - throw new InvalidOperationException("BrowseHeader only supports enums as the generic type argument"); - RelativeSizeAxes = Axes.X; Height = 90; diff --git a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs index fb0c1d9808..37478d902b 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListOverlay.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 osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -16,19 +17,22 @@ namespace osu.Game.Overlays.SearchableList public const float WIDTH_PADDING = 80; } - public abstract class SearchableListOverlay : SearchableListOverlay + public abstract class SearchableListOverlay : SearchableListOverlay + where THeader : struct, Enum + where TTab : struct, Enum + where TCategory : struct, Enum { private readonly Container scrollContainer; - protected readonly SearchableListHeader Header; - protected readonly SearchableListFilterControl Filter; + protected readonly SearchableListHeader Header; + protected readonly SearchableListFilterControl Filter; protected readonly FillFlowContainer ScrollFlow; protected abstract Color4 BackgroundColour { get; } protected abstract Color4 TrianglesColourLight { get; } protected abstract Color4 TrianglesColourDark { get; } - protected abstract SearchableListHeader CreateHeader(); - protected abstract SearchableListFilterControl CreateFilterControl(); + protected abstract SearchableListHeader CreateHeader(); + protected abstract SearchableListFilterControl CreateFilterControl(); protected SearchableListOverlay() { diff --git a/osu.Game/Overlays/SearchableList/SlimEnumDropdown.cs b/osu.Game/Overlays/SearchableList/SlimEnumDropdown.cs index f320ef1344..9e7ff1205f 100644 --- a/osu.Game/Overlays/SearchableList/SlimEnumDropdown.cs +++ b/osu.Game/Overlays/SearchableList/SlimEnumDropdown.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 osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -11,6 +12,7 @@ using osuTK; namespace osu.Game.Overlays.SearchableList { public class SlimEnumDropdown : OsuEnumDropdown + where T : struct, Enum { protected override DropdownHeader CreateHeader() => new SlimDropdownHeader(); diff --git a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs index 9f09f251c2..c77d14632b 100644 --- a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs +++ b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs @@ -1,12 +1,14 @@ // 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.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings { public class SettingsEnumDropdown : SettingsDropdown + where T : struct, Enum { protected override OsuDropdown CreateDropdown() => new DropdownControl(); From ad1fb3bda2b75bb357d4bd2b8930af7d533e3384 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 9 Dec 2019 17:48:41 +0800 Subject: [PATCH 2657/2815] Remove IComparable in constraint. --- osu.Game/Screens/Select/FilterCriteria.cs | 2 +- osu.Game/Screens/Select/FilterQueryParser.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index abcb1f2171..e3ad76ac35 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Select } public struct OptionalRange : IEquatable> - where T : struct, IComparable + where T : struct { public bool HasFilter => Max != null || Min != null; diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index ffe1258168..89afc729fe 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -170,7 +170,7 @@ namespace osu.Game.Screens.Select } private static void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, T value) - where T : struct, IComparable + where T : struct { switch (op) { From 1e49078c526f2050a137f087cf0000f678f3484e Mon Sep 17 00:00:00 2001 From: mcendu Date: Mon, 9 Dec 2019 17:51:44 +0800 Subject: [PATCH 2658/2815] Add OsuCursorSprite --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs new file mode 100644 index 0000000000..56c04ce1fc --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs @@ -0,0 +1,13 @@ +// 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.Containers; + +namespace osu.Game.Rulesets.Osu.UI.Cursor +{ + public class OsuCursorSprite : CompositeDrawable + { + public Drawable ExpandTarget { get; protected set; } + } +} From 22f2a4bed2284ab2fa81f7cfbb4a6a17f56ecb27 Mon Sep 17 00:00:00 2001 From: mcendu Date: Mon, 9 Dec 2019 17:53:16 +0800 Subject: [PATCH 2659/2815] Fix LegacyCursor's cursormiddle expanding --- osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs | 8 ++++---- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 18 +++++++++++------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs index 05b38ae195..cf133f54ea 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs @@ -5,13 +5,13 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Skinning; +using osu.Game.Rulesets.Osu.UI.Cursor; using osuTK; namespace osu.Game.Rulesets.Osu.Skinning { - public class LegacyCursor : CompositeDrawable + public class LegacyCursor : OsuCursorSprite { - private NonPlayfieldSprite cursor; private bool spin; public LegacyCursor() @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Skinning Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - cursor = new NonPlayfieldSprite + ExpandTarget = new NonPlayfieldSprite { Texture = skin.GetTexture("cursor"), Anchor = Anchor.Centre, @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Skinning protected override void LoadComplete() { if (spin) - cursor.Spin(10000, RotationDirection.Clockwise); + ExpandTarget.Spin(10000, RotationDirection.Clockwise); } } } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index 0aa8661fd3..4a67d23f57 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private bool cursorExpand; - private Container expandTarget; + private OsuCursorSprite cursorSprite; public OsuCursor() { @@ -37,17 +37,19 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor [BackgroundDependencyLoader] private void load() { - InternalChild = expandTarget = new Container + SkinnableDrawable cursor; + InternalChild = new Container { RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, Anchor = Anchor.Centre, - Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling) + Child = cursor = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling) { Origin = Anchor.Centre, Anchor = Anchor.Centre, } }; + cursorSprite = cursor.Drawable as OsuCursorSprite; } private const float pressed_scale = 1.2f; @@ -57,12 +59,12 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { if (!cursorExpand) return; - expandTarget.ScaleTo(released_scale).ScaleTo(pressed_scale, 100, Easing.OutQuad); + cursorSprite.ExpandTarget.ScaleTo(released_scale).ScaleTo(pressed_scale, 100, Easing.OutQuad); } - public void Contract() => expandTarget.ScaleTo(released_scale, 100, Easing.OutQuad); + public void Contract() => cursorSprite.ExpandTarget.ScaleTo(released_scale, 100, Easing.OutQuad); - private class DefaultCursor : CompositeDrawable + private class DefaultCursor : OsuCursorSprite { public DefaultCursor() { @@ -73,8 +75,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor InternalChildren = new Drawable[] { - new CircularContainer + ExpandTarget = new CircularContainer { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, Masking = true, BorderThickness = size / 6, From 6b667daf0965ec6386632bca78415cfe8bca1365 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Dec 2019 19:41:18 +0900 Subject: [PATCH 2660/2815] Update bindable types in line with framework --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 14 +++++++++----- .../Tests/Visual/RateAdjustedBeatmapTestScene.cs | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 96275c1274..c15b5d4786 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -511,15 +511,17 @@ namespace osu.Game.Rulesets.UI public IEnumerable GetAvailableResources() => throw new NotImplementedException(); - public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => throw new NotImplementedException(); + public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotImplementedException(); - public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => throw new NotImplementedException(); + public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotImplementedException(); - public BindableDouble Volume => throw new NotImplementedException(); + public BindableNumber Volume => throw new NotImplementedException(); - public BindableDouble Balance => throw new NotImplementedException(); + public BindableNumber Balance => throw new NotImplementedException(); - public BindableDouble Frequency => throw new NotImplementedException(); + public BindableNumber Frequency => throw new NotImplementedException(); + + public BindableNumber Tempo => throw new NotImplementedException(); public IBindable AggregateVolume => throw new NotImplementedException(); @@ -527,6 +529,8 @@ namespace osu.Game.Rulesets.UI public IBindable AggregateFrequency => throw new NotImplementedException(); + public IBindable AggregateTempo => throw new NotImplementedException(); + public int PlaybackConcurrency { get => throw new NotImplementedException(); diff --git a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs index 921a1d9789..ad24ffc7b8 100644 --- a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs +++ b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs @@ -13,7 +13,7 @@ namespace osu.Game.Tests.Visual base.Update(); // note that this will override any mod rate application - Beatmap.Value.Track.TempoAdjust = Clock.Rate; + Beatmap.Value.Track.Tempo.Value = Clock.Rate; } } } From 7fd52c21f56cdf6fdc2f9004e6b9f449e26b9bba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Dec 2019 19:41:31 +0900 Subject: [PATCH 2661/2815] Update mods and user adjust to use adjustments --- osu.Game/Rulesets/Mods/ModDaycore.cs | 3 +- osu.Game/Rulesets/Mods/ModDoubleTime.cs | 6 ++-- osu.Game/Rulesets/Mods/ModHalfTime.cs | 6 ++-- osu.Game/Rulesets/Mods/ModNightcore.cs | 3 +- .../{ModTimeAdjust.cs => ModRateAdjust.cs} | 8 +++-- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 35 +++++++++---------- .../Screens/Play/GameplayClockContainer.cs | 9 ++--- 7 files changed, 32 insertions(+), 38 deletions(-) rename osu.Game/Rulesets/Mods/{ModTimeAdjust.cs => ModRateAdjust.cs} (60%) diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index dcb3cb5597..f7da1e86db 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.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.Audio; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Sprites; @@ -15,7 +16,7 @@ namespace osu.Game.Rulesets.Mods public override void ApplyToTrack(Track track) { - track.Frequency.Value *= RateAdjust; + track.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); } } } diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index b9faad8cbd..7015460c51 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -10,7 +10,7 @@ using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods { - public abstract class ModDoubleTime : ModTimeAdjust + public abstract class ModDoubleTime : ModRateAdjust { public override string Name => "Double Time"; public override string Acronym => "DT"; @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModHalfTime)).ToArray(); [SettingSource("Speed increase", "The actual increase to apply")] - public BindableNumber SpeedChange { get; } = new BindableDouble + public override BindableNumber SpeedChange { get; } = new BindableDouble { MinValue = 1.01, MaxValue = 2, @@ -30,7 +30,5 @@ namespace osu.Game.Rulesets.Mods Value = 1.5, Precision = 0.01, }; - - protected override double RateAdjust => SpeedChange.Value; } } diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 255c5b7065..15f7afa312 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -10,7 +10,7 @@ using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods { - public abstract class ModHalfTime : ModTimeAdjust + public abstract class ModHalfTime : ModRateAdjust { public override string Name => "Half Time"; public override string Acronym => "HT"; @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModDoubleTime)).ToArray(); [SettingSource("Speed decrease", "The actual decrease to apply")] - public BindableNumber SpeedChange { get; } = new BindableDouble + public override BindableNumber SpeedChange { get; } = new BindableDouble { MinValue = 0.5, MaxValue = 0.99, @@ -30,7 +30,5 @@ namespace osu.Game.Rulesets.Mods Value = 0.75, Precision = 0.01, }; - - protected override double RateAdjust => SpeedChange.Value; } } diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index a4f1ef5a72..e41f1415ab 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.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.Audio; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; @@ -16,7 +17,7 @@ namespace osu.Game.Rulesets.Mods public override void ApplyToTrack(Track track) { - track.Frequency.Value *= RateAdjust; + track.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); } } } diff --git a/osu.Game/Rulesets/Mods/ModTimeAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs similarity index 60% rename from osu.Game/Rulesets/Mods/ModTimeAdjust.cs rename to osu.Game/Rulesets/Mods/ModRateAdjust.cs index f137a75ed8..5aa3e09fee 100644 --- a/osu.Game/Rulesets/Mods/ModTimeAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -2,19 +2,21 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Audio; using osu.Framework.Audio.Track; +using osu.Framework.Bindables; namespace osu.Game.Rulesets.Mods { - public abstract class ModTimeAdjust : Mod, IApplicableToTrack + public abstract class ModRateAdjust : Mod, IApplicableToTrack { public override Type[] IncompatibleMods => new[] { typeof(ModTimeRamp) }; - protected abstract double RateAdjust { get; } + public abstract BindableNumber SpeedChange { get; } public virtual void ApplyToTrack(Track track) { - track.TempoAdjust *= RateAdjust; + track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange); } } } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index d95d354487..36de546707 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -3,36 +3,42 @@ using System; using System.Linq; +using osu.Framework.Audio; using osu.Framework.Audio.Track; +using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mods { - public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToTrack, IApplicableToBeatmap + public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToBeatmap, IApplicableToTrack { /// /// The point in the beatmap at which the final ramping rate should be reached. /// private const double final_rate_progress = 0.75f; - public override Type[] IncompatibleMods => new[] { typeof(ModTimeAdjust) }; + public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust) }; protected abstract double FinalRateAdjustment { get; } private double finalRateTime; private double beginRampTime; + + public BindableNumber SpeedChange { get; } = new BindableDouble + { + Default = 1, + Value = 1, + Precision = 0.01, + }; + private Track track; - public virtual void ApplyToTrack(Track track) + public void ApplyToTrack(Track track) { this.track = track; - - lastAdjust = 1; - - // for preview purposes. during gameplay, Update will overwrite this setting. - applyAdjustment(1); + track.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); } public virtual void ApplyToBeatmap(IBeatmap beatmap) @@ -48,20 +54,11 @@ namespace osu.Game.Rulesets.Mods applyAdjustment((track.CurrentTime - beginRampTime) / finalRateTime); } - private double lastAdjust = 1; - /// /// Adjust the rate along the specified ramp /// /// The amount of adjustment to apply (from 0..1). - private void applyAdjustment(double amount) - { - double adjust = 1 + (Math.Sign(FinalRateAdjustment) * Math.Clamp(amount, 0, 1) * Math.Abs(FinalRateAdjustment)); - - track.Tempo.Value /= lastAdjust; - track.Tempo.Value *= lastAdjust; - - lastAdjust = adjust; - } + private void applyAdjustment(double amount) => + SpeedChange.Value = 1 + (Math.Sign(FinalRateAdjustment) * Math.Clamp(amount, 0, 1) * Math.Abs(FinalRateAdjustment)); } } diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 2cc03ae453..9f46fddc5e 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Play private readonly double firstHitObjectTime; - public readonly Bindable UserPlaybackRate = new BindableDouble(1) + public readonly BindableNumber UserPlaybackRate = new BindableDouble(1) { Default = 1, MinValue = 0.5, @@ -73,7 +73,6 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both; track = beatmap.Track; - track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; @@ -120,7 +119,6 @@ namespace osu.Game.Screens.Play Seek(startTime); adjustableClock.ProcessFrame(); - UserPlaybackRate.ValueChanged += _ => updateRate(); } public void Restart() @@ -223,7 +221,8 @@ namespace osu.Game.Screens.Play speedAdjustmentsApplied = true; track.ResetSpeedAdjustments(); - track.Tempo.Value = UserPlaybackRate.Value; + track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); + track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); foreach (var mod in mods.OfType()) mod.ApplyToTrack(track); @@ -244,8 +243,6 @@ namespace osu.Game.Screens.Play track.ResetSpeedAdjustments(); speedAdjustmentsApplied = false; } - - track.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); } } } From edc82205543106251bba5a9b8a2c92ea3b81e7ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Dec 2019 19:58:51 +0900 Subject: [PATCH 2662/2815] Add time ramp settings --- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 8 ++++++-- osu.Game/Rulesets/Mods/ModWindDown.cs | 12 +++++++++++- osu.Game/Rulesets/Mods/ModWindUp.cs | 12 +++++++++++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 36de546707..bffe4f7b70 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -7,6 +7,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Objects; @@ -21,7 +22,8 @@ namespace osu.Game.Rulesets.Mods public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust) }; - protected abstract double FinalRateAdjustment { get; } + [SettingSource("Final rate", "The final speed to ramp to")] + public abstract BindableNumber FinalRate { get; } private double finalRateTime; private double beginRampTime; @@ -45,6 +47,8 @@ namespace osu.Game.Rulesets.Mods { HitObject lastObject = beatmap.HitObjects.LastOrDefault(); + SpeedChange.SetDefault(); + beginRampTime = beatmap.HitObjects.FirstOrDefault()?.StartTime ?? 0; finalRateTime = final_rate_progress * (lastObject?.GetEndTime() ?? 0); } @@ -59,6 +63,6 @@ namespace osu.Game.Rulesets.Mods /// /// The amount of adjustment to apply (from 0..1). private void applyAdjustment(double amount) => - SpeedChange.Value = 1 + (Math.Sign(FinalRateAdjustment) * Math.Clamp(amount, 0, 1) * Math.Abs(FinalRateAdjustment)); + SpeedChange.Value = 1 + (Math.Sign(FinalRate.Value) * Math.Clamp(amount, 0, 1) * Math.Abs(FinalRate.Value)); } } diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs index b2e3abb59d..680c1a10fb 100644 --- a/osu.Game/Rulesets/Mods/ModWindDown.cs +++ b/osu.Game/Rulesets/Mods/ModWindDown.cs @@ -3,7 +3,9 @@ using System; using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; namespace osu.Game.Rulesets.Mods { @@ -15,7 +17,15 @@ namespace osu.Game.Rulesets.Mods public override IconUsage Icon => FontAwesome.Solid.ChevronCircleDown; public override double ScoreMultiplier => 1.0; - protected override double FinalRateAdjustment => -0.25; + [SettingSource("Final rate", "The speed increase to ramp towards")] + public override BindableNumber FinalRate { get; } = new BindableDouble + { + MinValue = -0.5, + MaxValue = -0.01, + Default = -0.25, + Value = -0.25, + Precision = 0.01, + }; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindUp)).ToArray(); } diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs index 8df35a1de2..ca9ce0ea3e 100644 --- a/osu.Game/Rulesets/Mods/ModWindUp.cs +++ b/osu.Game/Rulesets/Mods/ModWindUp.cs @@ -3,7 +3,9 @@ using System; using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; namespace osu.Game.Rulesets.Mods { @@ -15,7 +17,15 @@ namespace osu.Game.Rulesets.Mods public override IconUsage Icon => FontAwesome.Solid.ChevronCircleUp; public override double ScoreMultiplier => 1.0; - protected override double FinalRateAdjustment => 0.5; + [SettingSource("Final rate", "The speed increase to ramp towards")] + public override BindableNumber FinalRate { get; } = new BindableDouble + { + MinValue = 0.01, + MaxValue = 2, + Default = 1.5, + Value = 1.5, + Precision = 0.01, + }; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindDown)).ToArray(); } From 47f3c4a596a3b0b8f54e492dd723644e1665241f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 20:18:18 +0900 Subject: [PATCH 2663/2815] Don't serialise path version --- osu.Game/Rulesets/Objects/SliderPath.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 6a151d7d33..86deba3b93 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -18,6 +18,7 @@ namespace osu.Game.Rulesets.Objects /// /// The current version of this . Updated when any change to the path occurs. /// + [JsonIgnore] public IBindable Version => version; private readonly Bindable version = new Bindable(); From 12bdb1dafd98d09db5af10de7961b8ee17c94a05 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Dec 2019 20:40:38 +0900 Subject: [PATCH 2664/2815] Pin DC/NC pitch --- osu.Game/Rulesets/Mods/ModDaycore.cs | 13 ++++++++++++- osu.Game/Rulesets/Mods/ModNightcore.cs | 13 ++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index f7da1e86db..474e793dd1 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -3,6 +3,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Track; +using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; namespace osu.Game.Rulesets.Mods @@ -14,9 +15,19 @@ namespace osu.Game.Rulesets.Mods public override IconUsage Icon => FontAwesome.Solid.Question; public override string Description => "Whoaaaaa..."; + private readonly BindableNumber tempoAdjust = new BindableDouble(1); + private readonly BindableNumber freqAdjust = new BindableDouble(1); + public override void ApplyToTrack(Track track) { - track.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); + track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); + track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); + + SpeedChange.BindValueChanged(val => + { + freqAdjust.Value = SpeedChange.Default; + tempoAdjust.Value = val.NewValue / SpeedChange.Default; + }, true); } } } diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index e41f1415ab..401814d18b 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -3,6 +3,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Track; +using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; @@ -15,9 +16,19 @@ namespace osu.Game.Rulesets.Mods public override IconUsage Icon => OsuIcon.ModNightcore; public override string Description => "Uguuuuuuuu..."; + private readonly BindableNumber tempoAdjust = new BindableDouble(1); + private readonly BindableNumber freqAdjust = new BindableDouble(1); + public override void ApplyToTrack(Track track) { - track.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); + track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); + track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); + + SpeedChange.BindValueChanged(val => + { + freqAdjust.Value = SpeedChange.Default; + tempoAdjust.Value = val.NewValue / SpeedChange.Default; + }, true); } } } From af90b45c407a0cbad892224204e83e5c6cfe0778 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 20:49:59 +0900 Subject: [PATCH 2665/2815] Refactor path visualisers to use bindables --- .../Components/PathControlPointPiece.cs | 116 ++++++++++++------ .../Components/PathControlPointVisualiser.cs | 34 +++-- 2 files changed, 98 insertions(+), 52 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 4fe02135c4..38da5939b6 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -11,6 +11,8 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osuTK; using osuTK.Graphics; @@ -20,10 +22,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { public class PathControlPointPiece : BlueprintPiece { - public Action RequestSelection; + public Action RequestSelection; public readonly BindableBool IsSelected = new BindableBool(); - public readonly int Index; + + public readonly PathControlPoint ControlPoint; private readonly Slider slider; private readonly Path path; @@ -36,10 +39,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components [Resolved] private OsuColour colours { get; set; } - public PathControlPointPiece(Slider slider, int index) + private IBindable sliderPosition; + private IBindable pathVersion; + + public PathControlPointPiece(Slider slider, PathControlPoint controlPoint) { this.slider = slider; - Index = index; + + ControlPoint = controlPoint; Origin = Anchor.Centre; AutoSizeAxes = Axes.Both; @@ -85,48 +92,41 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components }; } - protected override void Update() + protected override void LoadComplete() { - base.Update(); + base.LoadComplete(); - Position = slider.StackedPosition + slider.Path.ControlPoints[Index].Position.Value; + sliderPosition = slider.PositionBindable.GetBoundCopy(); + sliderPosition.BindValueChanged(_ => updateDisplay()); + pathVersion = slider.Path.Version.GetBoundCopy(); + pathVersion.BindValueChanged(_ => updateDisplay()); + + IsSelected.BindValueChanged(_ => updateMarkerDisplay()); + + updateDisplay(); + } + + private void updateDisplay() + { updateMarkerDisplay(); updateConnectingPath(); } - /// - /// Updates the state of the circular control point marker. - /// - private void updateMarkerDisplay() - { - markerRing.Alpha = IsSelected.Value ? 1 : 0; - - Color4 colour = slider.Path.ControlPoints[Index].Type.Value.HasValue ? colours.Red : colours.Yellow; - if (IsHovered || IsSelected.Value) - colour = Color4.White; - marker.Colour = colour; - } - - /// - /// Updates the path connecting this control point to the previous one. - /// - private void updateConnectingPath() - { - path.ClearVertices(); - - if (Index != slider.Path.ControlPoints.Count - 1) - { - path.AddVertex(Vector2.Zero); - path.AddVertex(slider.Path.ControlPoints[Index + 1].Position.Value - slider.Path.ControlPoints[Index].Position.Value); - } - - path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero); - } - // The connecting path is excluded from positional input public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => marker.ReceivePositionalInputAt(screenSpacePos); + protected override bool OnHover(HoverEvent e) + { + updateMarkerDisplay(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateMarkerDisplay(); + } + protected override bool OnMouseDown(MouseDownEvent e) { if (RequestSelection == null) @@ -135,12 +135,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components switch (e.Button) { case MouseButton.Left: - RequestSelection.Invoke(Index, e); + RequestSelection.Invoke(this, e); return true; case MouseButton.Right: if (!IsSelected.Value) - RequestSelection.Invoke(Index, e); + RequestSelection.Invoke(this, e); return false; // Allow context menu to show } @@ -155,7 +155,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components protected override bool OnDrag(DragEvent e) { - if (Index == 0) + if (ControlPoint == slider.Path.ControlPoints[0]) { // Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account (Vector2 snappedPosition, double snappedTime) = snapProvider?.GetSnappedPosition(e.MousePosition, slider.StartTime) ?? (e.MousePosition, slider.StartTime); @@ -169,11 +169,47 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components slider.Path.ControlPoints[i].Position.Value -= movementDelta; } else - slider.Path.ControlPoints[Index].Position.Value += e.Delta; + ControlPoint.Position.Value += e.Delta; return true; } protected override bool OnDragEnd(DragEndEvent e) => true; + + /// + /// Updates the state of the circular control point marker. + /// + private void updateMarkerDisplay() + { + Position = slider.StackedPosition + ControlPoint.Position.Value; + + markerRing.Alpha = IsSelected.Value ? 1 : 0; + + Color4 colour = ControlPoint.Type.Value != null ? colours.Red : colours.Yellow; + if (IsHovered || IsSelected.Value) + colour = Color4.White; + marker.Colour = colour; + } + + /// + /// Updates the path connecting this control point to the previous one. + /// + private void updateConnectingPath() + { + path.ClearVertices(); + + int index = slider.Path.ControlPoints.IndexOf(ControlPoint); + + if (index == -1) + return; + + if (++index != slider.Path.ControlPoints.Count) + { + path.AddVertex(Vector2.Zero); + path.AddVertex(slider.Path.ControlPoints[index].Position.Value - ControlPoint.Position.Value); + } + + path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero); + } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 434e74ddeb..eb6e3c01e1 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using Humanizer; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -32,6 +33,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components [Resolved(CanBeNull = true)] private IPlacementHandler placementHandler { get; set; } + private IBindableList controlPoints; + public PathControlPointVisualiser(Slider slider, bool allowSelection) { this.slider = slider; @@ -47,24 +50,31 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components base.LoadComplete(); inputManager = GetContainingInputManager(); + + controlPoints = slider.Path.ControlPoints.GetBoundCopy(); + controlPoints.ItemsAdded += addControlPoints; + controlPoints.ItemsRemoved += removeControlPoints; + + addControlPoints(controlPoints); } - protected override void Update() + private void addControlPoints(IEnumerable controlPoints) { - base.Update(); - - while (slider.Path.ControlPoints.Count > Pieces.Count) + foreach (var point in controlPoints) { - var piece = new PathControlPointPiece(slider, Pieces.Count); + var piece = new PathControlPointPiece(slider, point); if (allowSelection) piece.RequestSelection = selectPiece; Pieces.Add(piece); } + } - while (slider.Path.ControlPoints.Count < Pieces.Count) - Pieces.Remove(Pieces[Pieces.Count - 1]); + private void removeControlPoints(IEnumerable controlPoints) + { + foreach (var point in controlPoints) + Pieces.RemoveAll(p => p.ControlPoint == point); } protected override bool OnClick(ClickEvent e) @@ -87,20 +97,20 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public bool OnReleased(PlatformAction action) => action.ActionMethod == PlatformActionMethod.Delete; - private void selectPiece(int index, MouseButtonEvent e) + private void selectPiece(PathControlPointPiece piece, MouseButtonEvent e) { if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed) - Pieces[index].IsSelected.Toggle(); + piece.IsSelected.Toggle(); else { - foreach (var piece in Pieces) - piece.IsSelected.Value = piece.Index == index; + foreach (var p in Pieces) + p.IsSelected.Value = p == piece; } } private bool deleteSelected() { - List toRemove = Pieces.Where(p => p.IsSelected.Value).Select(p => slider.Path.ControlPoints[p.Index]).ToList(); + List toRemove = Pieces.Where(p => p.IsSelected.Value).Select(p => p.ControlPoint).ToList(); // Ensure that there are any points to be deleted if (toRemove.Count == 0) From bd2b0af2695048a1f5baae9cbde9f80a18e28957 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 22:35:19 +0900 Subject: [PATCH 2666/2815] Consider having only 1 control point as being deleted --- .../Sliders/Components/PathControlPointVisualiser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 434e74ddeb..f7692c64f4 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -116,8 +116,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components slider.Path.ControlPoints.Remove(c); } - // If there are 0 remaining control points, treat the slider as being deleted - if (slider.Path.ControlPoints.Count == 0) + // If there are 0 or 1 remaining control points, the slider is in a degenerate (single point) form and should be deleted + if (slider.Path.ControlPoints.Count <= 1) { placementHandler?.Delete(slider); return true; From 2c4c190f15238574285407bd7fd5925b22c1c768 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 22:44:47 +0900 Subject: [PATCH 2667/2815] Fix control points not adding to last segment --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 7431972673..68873093a6 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders int insertionIndex = 0; float minDistance = float.MaxValue; - for (int i = 0; i < HitObject.Path.ControlPoints.Count - 2; i++) + for (int i = 0; i < HitObject.Path.ControlPoints.Count - 1; i++) { float dist = new Line(HitObject.Path.ControlPoints[i].Position.Value, HitObject.Path.ControlPoints[i + 1].Position.Value).DistanceToPoint(position); From 0ee303f7d6417130079bd7862938e78c403c0e19 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 9 Dec 2019 22:45:44 +0900 Subject: [PATCH 2668/2815] Remove unused using --- .../Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 38da5939b6..7c66f38854 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -12,7 +12,6 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osuTK; using osuTK.Graphics; From 9b318d2869a99a97f7e213946b7993fac71cf972 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 10 Dec 2019 00:07:07 +0900 Subject: [PATCH 2669/2815] Add right-click menu item to change path type --- .../Components/PathControlPointPiece.cs | 2 +- .../Components/PathControlPointVisualiser.cs | 45 ++++++++++++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 7c66f38854..c2aefac587 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components protected override bool OnHover(HoverEvent e) { updateMarkerDisplay(); - return true; + return false; } protected override void OnHoverLost(HoverLostEvent e) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index eb6e3c01e1..352bb56030 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.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; @@ -15,6 +16,7 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose; using osuTK; @@ -161,9 +163,50 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return new MenuItem[] { - new OsuMenuItem($"Delete {"control point".ToQuantity(selectedPoints)}", MenuItemType.Destructive, () => deleteSelected()) + new OsuMenuItem($"Delete {"control point".ToQuantity(selectedPoints)}", MenuItemType.Destructive, () => deleteSelected()), + new OsuMenuItem("Type") + { + Items = new[] + { + createMenuItemForPathType(null), + createMenuItemForPathType(PathType.Linear), + createMenuItemForPathType(PathType.PerfectCurve), + createMenuItemForPathType(PathType.Catmull) + } + } }; } } + + private MenuItem createMenuItemForPathType(PathType? type) + { + int totalCount = Pieces.Count(p => p.IsSelected.Value); + int countOfState = Pieces.Where(p => p.IsSelected.Value).Count(p => p.ControlPoint.Type.Value == type); + + var item = new PathTypeMenuItem(type, () => + { + foreach (var p in Pieces.Where(p => p.IsSelected.Value)) + p.ControlPoint.Type.Value = type; + }); + + if (countOfState == totalCount) + item.State.Value = TernaryState.True; + else if (countOfState > 0) + item.State.Value = TernaryState.Indeterminate; + else + item.State.Value = TernaryState.False; + + return item; + } + + private class PathTypeMenuItem : TernaryStateMenuItem + { + public PathTypeMenuItem(PathType? type, Action action) + : base(type == null ? "Inherit" : type.ToString().Humanize(), changeState, MenuItemType.Standard, _ => action?.Invoke()) + { + } + + private static TernaryState changeState(TernaryState state) => TernaryState.True; + } } } From 1b14b0e5b6cecdbfacbf44024f2ff0fa42252d93 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 10 Dec 2019 00:08:38 +0900 Subject: [PATCH 2670/2815] Fix pieces blocking context menu --- .../Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 7c66f38854..c2aefac587 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components protected override bool OnHover(HoverEvent e) { updateMarkerDisplay(); - return true; + return false; } protected override void OnHoverLost(HoverLostEvent e) From 1e71681916b3fe6473da6f3fda1e2fb35f9dc7fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2019 02:00:39 +0900 Subject: [PATCH 2671/2815] Fix osu!catch catcher not scaling down correctly --- osu.Game.Rulesets.Catch/UI/CatcherSprite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs index e3c6c93d01..025fa9c56e 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.UI [BackgroundDependencyLoader] private void load() { - InternalChild = new SkinnableSprite("Gameplay/catch/fruit-catcher-idle") + InternalChild = new SkinnableSprite("Gameplay/catch/fruit-catcher-idle", confineMode: ConfineMode.ScaleDownToFit) { RelativeSizeAxes = Axes.Both, Anchor = Anchor.TopCentre, From dc9775742ca0dfc20f65e9de2d043c714a6f1a61 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2019 02:23:17 +0900 Subject: [PATCH 2672/2815] Fix incorrect code migration 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/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 d95d354487..839b2ae36e 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Mods double adjust = 1 + (Math.Sign(FinalRateAdjustment) * Math.Clamp(amount, 0, 1) * Math.Abs(FinalRateAdjustment)); track.Tempo.Value /= lastAdjust; - track.Tempo.Value *= lastAdjust; + track.Tempo.Value *= adjust; lastAdjust = adjust; } From cdde5d1d690adf16a02cc8b48cdb85467f68fb61 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2019 02:30:23 +0900 Subject: [PATCH 2673/2815] Fix song select filters not reapplied if in a child screen Closes https://github.com/ppy/osu/issues/6980. --- .../SongSelect/TestScenePlaySongSelect.cs | 36 +++++++++++++++++++ .../Visual/TestSceneOsuScreenStack.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 8 ++--- osu.Game/Tests/Visual/ScreenTestScene.cs | 10 +++--- 4 files changed, 46 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index a4b8d1a24a..5dd02c1ddd 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -95,6 +95,42 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("filter count is 1", () => songSelect.FilterCount == 1); } + [Test] + public void TestNoFilterOnSimpleResume() + { + addRulesetImportStep(0); + addRulesetImportStep(0); + + createSongSelect(); + + AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child"))); + AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); + + AddStep("return", () => songSelect.MakeCurrent()); + AddUntilStep("wait for current", () => songSelect.IsCurrentScreen()); + AddAssert("filter count is 1", () => songSelect.FilterCount == 1); + } + + [Test] + public void TestFilterOnResumeAfterChange() + { + addRulesetImportStep(0); + addRulesetImportStep(0); + + AddStep("change convert setting", () => config.Set(OsuSetting.ShowConvertedBeatmaps, false)); + + createSongSelect(); + + AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child"))); + AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); + + AddStep("change convert setting", () => config.Set(OsuSetting.ShowConvertedBeatmaps, true)); + + AddStep("return", () => songSelect.MakeCurrent()); + AddUntilStep("wait for current", () => songSelect.IsCurrentScreen()); + AddAssert("filter count is 2", () => songSelect.FilterCount == 2); + } + [Test] public void TestAudioResuming() { diff --git a/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs b/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs index a68fd0ef40..c55988d1bb 100644 --- a/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs +++ b/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual AddAssert("Parallax is off", () => stack.ParallaxAmount == 0); } - private class TestScreen : ScreenWithBeatmapBackground + public class TestScreen : ScreenWithBeatmapBackground { private readonly string screenText; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index a52edb70db..8f7ad2022d 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -262,8 +262,10 @@ namespace osu.Game.Screens.Select protected virtual void ApplyFilterToCarousel(FilterCriteria criteria) { - if (this.IsCurrentScreen()) - Carousel.Filter(criteria); + // if not the current screen, we want to get carousel in a good presentation state before displaying (resume or enter). + bool shouldDebounce = this.IsCurrentScreen(); + + Schedule(() => Carousel.Filter(criteria, shouldDebounce)); } private DependencyContainer dependencies; @@ -437,8 +439,6 @@ namespace osu.Game.Screens.Select { base.OnEntering(last); - Carousel.Filter(FilterControl.CreateCriteria(), false); - this.FadeInFromZero(250); FilterControl.Activate(); } diff --git a/osu.Game/Tests/Visual/ScreenTestScene.cs b/osu.Game/Tests/Visual/ScreenTestScene.cs index 23f45e0d0f..707aa61283 100644 --- a/osu.Game/Tests/Visual/ScreenTestScene.cs +++ b/osu.Game/Tests/Visual/ScreenTestScene.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tests.Visual /// public abstract class ScreenTestScene : ManualInputManagerTestScene { - private readonly OsuScreenStack stack; + protected readonly OsuScreenStack Stack; private readonly Container content; @@ -22,16 +22,16 @@ namespace osu.Game.Tests.Visual { base.Content.AddRange(new Drawable[] { - stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }, + Stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }, content = new Container { RelativeSizeAxes = Axes.Both } }); } protected void LoadScreen(OsuScreen screen) { - if (stack.CurrentScreen != null) - stack.Exit(); - stack.Push(screen); + if (Stack.CurrentScreen != null) + Stack.Exit(); + Stack.Push(screen); } } } From 1db218f9082749578b8af301d7a329c677b8dfe1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2019 03:29:25 +0900 Subject: [PATCH 2674/2815] Don't show count when deleting only one control point Reads better. --- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index cdca48490e..629604357d 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return new MenuItem[] { - new OsuMenuItem($"Delete {"control point".ToQuantity(selectedPoints)}", MenuItemType.Destructive, () => deleteSelected()) + new OsuMenuItem($"Delete {"control point".ToQuantity(selectedPoints, selectedPoints > 1 ? ShowQuantityAs.Numeric : ShowQuantityAs.None)}", MenuItemType.Destructive, () => deleteSelected()) }; } } From e9ec6591a907209bda53c4dc8fce87805b7d3e67 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 10 Dec 2019 11:20:08 +0900 Subject: [PATCH 2675/2815] Separate path connections from control points --- .../Components/PathControlPointConnection.cs | 72 +++++++++++++++++++ .../Components/PathControlPointPiece.cs | 43 ++--------- .../Components/PathControlPointVisualiser.cs | 22 ++++-- 3 files changed, 92 insertions(+), 45 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnection.cs diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnection.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnection.cs new file mode 100644 index 0000000000..f57299c5a9 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnection.cs @@ -0,0 +1,72 @@ +// 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; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Lines; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components +{ + public class PathControlPointConnection : CompositeDrawable + { + public PathControlPoint ControlPoint; + + private readonly Path path; + private readonly Slider slider; + + private IBindable sliderPosition; + private IBindable pathVersion; + + public PathControlPointConnection(Slider slider, PathControlPoint controlPoint) + { + this.slider = slider; + ControlPoint = controlPoint; + + Origin = Anchor.Centre; + AutoSizeAxes = Axes.Both; + + InternalChild = path = new SmoothPath + { + Anchor = Anchor.Centre, + PathRadius = 1 + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + sliderPosition = slider.PositionBindable.GetBoundCopy(); + sliderPosition.BindValueChanged(_ => updateConnectingPath()); + + pathVersion = slider.Path.Version.GetBoundCopy(); + pathVersion.BindValueChanged(_ => updateConnectingPath()); + + updateConnectingPath(); + } + + /// + /// Updates the path connecting this control point to the next one. + /// + private void updateConnectingPath() + { + Position = slider.StackedPosition + ControlPoint.Position.Value; + + path.ClearVertices(); + + int index = slider.Path.ControlPoints.IndexOf(ControlPoint) + 1; + + if (index == 0 || index == slider.Path.ControlPoints.Count) + return; + + path.AddVertex(Vector2.Zero); + path.AddVertex(slider.Path.ControlPoints[index].Position.Value - ControlPoint.Position.Value); + + path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index c2aefac587..42812ff934 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -6,7 +6,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Lines; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics; @@ -28,7 +27,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public readonly PathControlPoint ControlPoint; private readonly Slider slider; - private readonly Path path; private readonly Container marker; private readonly Drawable markerRing; @@ -39,12 +37,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private OsuColour colours { get; set; } private IBindable sliderPosition; - private IBindable pathVersion; + private IBindable controlPointPosition; public PathControlPointPiece(Slider slider, PathControlPoint controlPoint) { this.slider = slider; - ControlPoint = controlPoint; Origin = Anchor.Centre; @@ -52,11 +49,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components InternalChildren = new Drawable[] { - path = new SmoothPath - { - Anchor = Anchor.Centre, - PathRadius = 1 - }, marker = new Container { Anchor = Anchor.Centre, @@ -96,20 +88,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components base.LoadComplete(); sliderPosition = slider.PositionBindable.GetBoundCopy(); - sliderPosition.BindValueChanged(_ => updateDisplay()); + sliderPosition.BindValueChanged(_ => updateMarkerDisplay()); - pathVersion = slider.Path.Version.GetBoundCopy(); - pathVersion.BindValueChanged(_ => updateDisplay()); + controlPointPosition = ControlPoint.Position.GetBoundCopy(); + controlPointPosition.BindValueChanged(_ => updateMarkerDisplay()); IsSelected.BindValueChanged(_ => updateMarkerDisplay()); - updateDisplay(); - } - - private void updateDisplay() - { updateMarkerDisplay(); - updateConnectingPath(); } // The connecting path is excluded from positional input @@ -189,26 +175,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components colour = Color4.White; marker.Colour = colour; } - - /// - /// Updates the path connecting this control point to the previous one. - /// - private void updateConnectingPath() - { - path.ClearVertices(); - - int index = slider.Path.ControlPoints.IndexOf(ControlPoint); - - if (index == -1) - return; - - if (++index != slider.Path.ControlPoints.Count) - { - path.AddVertex(Vector2.Zero); - path.AddVertex(slider.Path.ControlPoints[index].Position.Value - ControlPoint.Position.Value); - } - - path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero); - } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index eb6e3c01e1..e45dc1d47a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler, IHasContextMenu { internal readonly Container Pieces; + + private readonly Container connections; private readonly Slider slider; private readonly bool allowSelection; @@ -42,7 +44,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components RelativeSizeAxes = Axes.Both; - InternalChild = Pieces = new Container { RelativeSizeAxes = Axes.Both }; + InternalChildren = new Drawable[] + { + connections = new Container { RelativeSizeAxes = Axes.Both }, + Pieces = new Container { RelativeSizeAxes = Axes.Both } + }; } protected override void LoadComplete() @@ -62,19 +68,23 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { foreach (var point in controlPoints) { - var piece = new PathControlPointPiece(slider, point); + Pieces.Add(new PathControlPointPiece(slider, point).With(d => + { + if (allowSelection) + d.RequestSelection = selectPiece; + })); - if (allowSelection) - piece.RequestSelection = selectPiece; - - Pieces.Add(piece); + connections.Add(new PathControlPointConnection(slider, point)); } } private void removeControlPoints(IEnumerable controlPoints) { foreach (var point in controlPoints) + { Pieces.RemoveAll(p => p.ControlPoint == point); + connections.RemoveAll(c => c.ControlPoint == point); + } } protected override bool OnClick(ClickEvent e) From ab0f2e7c6a1c1692a115ca54512344a162ec2230 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 10 Dec 2019 13:12:54 +0900 Subject: [PATCH 2676/2815] Apply suggested refactorings --- .../Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 9 ++++----- osu.Game/Rulesets/Objects/PathControlPoint.cs | 7 +++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 1ac7284772..b5b1e26486 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -257,14 +257,13 @@ namespace osu.Game.Rulesets.Objects.Legacy { if (type == PathType.PerfectCurve) { - if (vertices.Length == 3) + if (vertices.Length != 3) + type = PathType.Bezier; + else if (isLinear(vertices)) { // osu-stable special-cased colinear perfect curves to a linear path - if (isLinear(vertices)) - type = PathType.Linear; + type = PathType.Linear; } - else - type = PathType.Bezier; } var points = new List(vertices.Length) diff --git a/osu.Game/Rulesets/Objects/PathControlPoint.cs b/osu.Game/Rulesets/Objects/PathControlPoint.cs index 5737d3f618..0336f94313 100644 --- a/osu.Game/Rulesets/Objects/PathControlPoint.cs +++ b/osu.Game/Rulesets/Objects/PathControlPoint.cs @@ -30,8 +30,9 @@ namespace osu.Game.Rulesets.Objects /// Creates a new . /// public PathControlPoint() - : this(Vector2.Zero, null) { + Position.ValueChanged += _ => Changed?.Invoke(); + Type.ValueChanged += _ => Changed?.Invoke(); } /// @@ -40,12 +41,10 @@ namespace osu.Game.Rulesets.Objects /// The initial position. /// The initial type. public PathControlPoint(Vector2 position, PathType? type = null) + : this() { Position.Value = position; Type.Value = type; - - Position.ValueChanged += _ => Changed?.Invoke(); - Type.ValueChanged += _ => Changed?.Invoke(); } public bool Equals(PathControlPoint other) => Position.Value == other?.Position.Value && Type.Value == other.Type.Value; From 94a298a82d5e965ff54e63e56b332c78af50f775 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 10 Dec 2019 16:00:09 +0900 Subject: [PATCH 2677/2815] Refactor distance snap grid to not require hitobjects --- .../Edit/OsuDistanceSnapGrid.cs | 5 +- .../Editor/TestSceneDistanceSnapGrid.cs | 29 ++++++----- .../Components/CircularDistanceSnapGrid.cs | 25 +++++----- .../Compose/Components/DistanceSnapGrid.cs | 50 +++++++++---------- 4 files changed, 52 insertions(+), 57 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs index 9b00204d51..bde86a2890 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.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.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; @@ -8,8 +9,8 @@ namespace osu.Game.Rulesets.Osu.Edit { public class OsuDistanceSnapGrid : CircularDistanceSnapGrid { - public OsuDistanceSnapGrid(OsuHitObject hitObject, OsuHitObject nextHitObject) - : base(hitObject, nextHitObject, hitObject.StackedEndPosition) + public OsuDistanceSnapGrid(OsuHitObject hitObject, [CanBeNull] OsuHitObject nextHitObject = null) + : base(hitObject.StackedPosition, hitObject.StartTime, nextHitObject?.StartTime) { Masking = true; } diff --git a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs index e4c987923c..39b4bf7218 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; @@ -44,7 +43,7 @@ namespace osu.Game.Tests.Visual.Editor RelativeSizeAxes = Axes.Both, Colour = Color4.SlateGray }, - new TestDistanceSnapGrid(new HitObject(), grid_position) + new TestDistanceSnapGrid() }; }); @@ -73,7 +72,7 @@ namespace osu.Game.Tests.Visual.Editor RelativeSizeAxes = Axes.Both, Colour = Color4.SlateGray }, - new TestDistanceSnapGrid(new HitObject(), grid_position, new HitObject { StartTime = 100 }) + new TestDistanceSnapGrid(100) }; }); } @@ -82,68 +81,68 @@ namespace osu.Game.Tests.Visual.Editor { public new float DistanceSpacing => base.DistanceSpacing; - public TestDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition, HitObject nextHitObject = null) - : base(hitObject, nextHitObject, centrePosition) + public TestDistanceSnapGrid(double? endTime = null) + : base(grid_position, 0, endTime) { } - protected override void CreateContent(Vector2 centrePosition) + protected override void CreateContent(Vector2 startPosition) { AddInternal(new Circle { Origin = Anchor.Centre, Size = new Vector2(5), - Position = centrePosition + Position = startPosition }); int beatIndex = 0; - for (float s = centrePosition.X + DistanceSpacing; s <= DrawWidth && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++) + for (float s = startPosition.X + DistanceSpacing; s <= DrawWidth && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++) { AddInternal(new Circle { Origin = Anchor.Centre, Size = new Vector2(5, 10), - Position = new Vector2(s, centrePosition.Y), + Position = new Vector2(s, startPosition.Y), Colour = GetColourForBeatIndex(beatIndex) }); } beatIndex = 0; - for (float s = centrePosition.X - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++) + for (float s = startPosition.X - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++) { AddInternal(new Circle { Origin = Anchor.Centre, Size = new Vector2(5, 10), - Position = new Vector2(s, centrePosition.Y), + Position = new Vector2(s, startPosition.Y), Colour = GetColourForBeatIndex(beatIndex) }); } beatIndex = 0; - for (float s = centrePosition.Y + DistanceSpacing; s <= DrawHeight && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++) + for (float s = startPosition.Y + DistanceSpacing; s <= DrawHeight && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++) { AddInternal(new Circle { Origin = Anchor.Centre, Size = new Vector2(10, 5), - Position = new Vector2(centrePosition.X, s), + Position = new Vector2(startPosition.X, s), Colour = GetColourForBeatIndex(beatIndex) }); } beatIndex = 0; - for (float s = centrePosition.Y - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++) + for (float s = startPosition.Y - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++) { AddInternal(new Circle { Origin = Anchor.Centre, Size = new Vector2(10, 5), - Position = new Vector2(centrePosition.X, s), + Position = new Vector2(startPosition.X, s), Colour = GetColourForBeatIndex(beatIndex) }); } diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index 91e19f9cc0..23ed10b92d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -5,19 +5,18 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; -using osu.Game.Rulesets.Objects; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { public abstract class CircularDistanceSnapGrid : DistanceSnapGrid { - protected CircularDistanceSnapGrid(HitObject hitObject, HitObject nextHitObject, Vector2 centrePosition) - : base(hitObject, nextHitObject, centrePosition) + protected CircularDistanceSnapGrid(Vector2 startPosition, double startTime, double? endTime = null) + : base(startPosition, startTime, endTime) { } - protected override void CreateContent(Vector2 centrePosition) + protected override void CreateContent(Vector2 startPosition) { const float crosshair_thickness = 1; const float crosshair_max_size = 10; @@ -27,7 +26,7 @@ namespace osu.Game.Screens.Edit.Compose.Components new Box { Origin = Anchor.Centre, - Position = centrePosition, + Position = startPosition, Width = crosshair_thickness, EdgeSmoothness = new Vector2(1), Height = Math.Min(crosshair_max_size, DistanceSpacing * 2), @@ -35,15 +34,15 @@ namespace osu.Game.Screens.Edit.Compose.Components new Box { Origin = Anchor.Centre, - Position = centrePosition, + Position = startPosition, EdgeSmoothness = new Vector2(1), Width = Math.Min(crosshair_max_size, DistanceSpacing * 2), Height = crosshair_thickness, } }); - float dx = Math.Max(centrePosition.X, DrawWidth - centrePosition.X); - float dy = Math.Max(centrePosition.Y, DrawHeight - centrePosition.Y); + float dx = Math.Max(startPosition.X, DrawWidth - startPosition.X); + float dy = Math.Max(startPosition.Y, DrawHeight - startPosition.Y); float maxDistance = new Vector2(dx, dy).Length; int requiredCircles = Math.Min(MaxIntervals, (int)(maxDistance / DistanceSpacing)); @@ -54,7 +53,7 @@ namespace osu.Game.Screens.Edit.Compose.Components AddInternal(new CircularProgress { Origin = Anchor.Centre, - Position = centrePosition, + Position = startPosition, Current = { Value = 1 }, Size = new Vector2(radius), InnerRadius = 4 * 1f / radius, @@ -66,9 +65,9 @@ namespace osu.Game.Screens.Edit.Compose.Components public override (Vector2 position, double time) GetSnappedPosition(Vector2 position) { if (MaxIntervals == 0) - return (CentrePosition, StartTime); + return (StartPosition, StartTime); - Vector2 direction = position - CentrePosition; + Vector2 direction = position - StartPosition; if (direction == Vector2.Zero) direction = new Vector2(0.001f, 0.001f); @@ -78,9 +77,9 @@ namespace osu.Game.Screens.Edit.Compose.Components int radialCount = Math.Clamp((int)MathF.Round(distance / radius), 1, MaxIntervals); Vector2 normalisedDirection = direction * new Vector2(1f / distance); - Vector2 snappedPosition = CentrePosition + normalisedDirection * radialCount * radius; + Vector2 snappedPosition = StartPosition + normalisedDirection * radialCount * radius; - return (snappedPosition, StartTime + SnapProvider.GetSnappedDurationFromDistance(StartTime, (snappedPosition - CentrePosition).Length)); + return (snappedPosition, StartTime + SnapProvider.GetSnappedDurationFromDistance(StartTime, (snappedPosition - StartPosition).Length)); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 9508a2cdf0..00326d04f7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Caching; using osu.Framework.Graphics; @@ -9,7 +8,6 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components @@ -24,21 +22,21 @@ namespace osu.Game.Screens.Edit.Compose.Components /// protected float DistanceSpacing { get; private set; } - /// - /// The snapping time at . - /// - protected double StartTime { get; private set; } - /// /// The maximum number of distance snapping intervals allowed. /// protected int MaxIntervals { get; private set; } /// - /// The position which the grid is centred on. - /// The first beat snapping tick is located at + in the desired direction. + /// The position which the grid should start. + /// The first beat snapping tick is located at + away from this point. /// - protected readonly Vector2 CentrePosition; + protected readonly Vector2 StartPosition; + + /// + /// The snapping time at . + /// + protected readonly double StartTime; [Resolved] protected OsuColour Colours { get; private set; } @@ -53,25 +51,23 @@ namespace osu.Game.Screens.Edit.Compose.Components private BindableBeatDivisor beatDivisor { get; set; } private readonly Cached gridCache = new Cached(); - private readonly HitObject hitObject; - private readonly HitObject nextHitObject; + private readonly double? endTime; - protected DistanceSnapGrid(HitObject hitObject, [CanBeNull] HitObject nextHitObject, Vector2 centrePosition) + /// + /// Creates a new . + /// + /// The position at which the grid should start. The first tick is located one distance spacing length away from this point. + /// The snapping time at . + /// The time at which the snapping grid should end. If null, the grid will continue until the bounds of the screen are exceeded. + protected DistanceSnapGrid(Vector2 startPosition, double startTime, double? endTime = null) { - this.hitObject = hitObject; - this.nextHitObject = nextHitObject; - - CentrePosition = centrePosition; + this.endTime = endTime; + StartPosition = startPosition; + StartTime = startTime; RelativeSizeAxes = Axes.Both; } - [BackgroundDependencyLoader] - private void load() - { - StartTime = hitObject.GetEndTime(); - } - protected override void LoadComplete() { base.LoadComplete(); @@ -83,12 +79,12 @@ namespace osu.Game.Screens.Edit.Compose.Components { DistanceSpacing = SnapProvider.GetBeatSnapDistanceAt(StartTime); - if (nextHitObject == null) + if (endTime == null) MaxIntervals = int.MaxValue; else { // +1 is added since a snapped hitobject may have its start time slightly less than the snapped time due to floating point errors - double maxDuration = nextHitObject.StartTime - StartTime + 1; + double maxDuration = endTime.Value - StartTime + 1; MaxIntervals = (int)(maxDuration / SnapProvider.DistanceToDuration(StartTime, DistanceSpacing)); } @@ -110,7 +106,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (!gridCache.IsValid) { ClearInternal(); - CreateContent(CentrePosition); + CreateContent(StartPosition); gridCache.Validate(); } } @@ -118,7 +114,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Creates the content which visualises the grid ticks. /// - protected abstract void CreateContent(Vector2 centrePosition); + protected abstract void CreateContent(Vector2 startPosition); /// /// Snaps a position to this grid. From 609c51130964ccb85e00160be1556ec4aa7bec61 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2019 16:43:58 +0900 Subject: [PATCH 2678/2815] 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 301c615ce4..914d352070 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -54,6 +54,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ef16738908..473ce82443 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 5090190f28..d03005012a 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -74,7 +74,7 @@ - + @@ -82,7 +82,7 @@ - + From f7f4a57c5f6b1b3d64bec8c22461e261c05bae97 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Dec 2019 19:41:18 +0900 Subject: [PATCH 2679/2815] Update bindable types in line with framework --- osu.Game/Rulesets/Mods/ModTimeAdjust.cs | 2 +- osu.Game/Rulesets/UI/DrawableRuleset.cs | 16 +++++++++++----- .../Tests/Visual/RateAdjustedBeatmapTestScene.cs | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModTimeAdjust.cs b/osu.Game/Rulesets/Mods/ModTimeAdjust.cs index f137a75ed8..7d0cc2a7c3 100644 --- a/osu.Game/Rulesets/Mods/ModTimeAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModTimeAdjust.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mods public virtual void ApplyToTrack(Track track) { - track.TempoAdjust *= RateAdjust; + track.Tempo.Value *= RateAdjust; } } } diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 96275c1274..a856974292 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -511,15 +511,19 @@ namespace osu.Game.Rulesets.UI public IEnumerable GetAvailableResources() => throw new NotImplementedException(); - public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => throw new NotImplementedException(); + public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotImplementedException(); - public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => throw new NotImplementedException(); + public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotImplementedException(); - public BindableDouble Volume => throw new NotImplementedException(); + public BindableNumber Volume => throw new NotImplementedException(); - public BindableDouble Balance => throw new NotImplementedException(); + public BindableNumber Balance => throw new NotImplementedException(); - public BindableDouble Frequency => throw new NotImplementedException(); + public BindableNumber Frequency => throw new NotImplementedException(); + + public BindableNumber Tempo => throw new NotImplementedException(); + + public IBindable GetAggregate(AdjustableProperty type) => throw new NotImplementedException(); public IBindable AggregateVolume => throw new NotImplementedException(); @@ -527,6 +531,8 @@ namespace osu.Game.Rulesets.UI public IBindable AggregateFrequency => throw new NotImplementedException(); + public IBindable AggregateTempo => throw new NotImplementedException(); + public int PlaybackConcurrency { get => throw new NotImplementedException(); diff --git a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs index 921a1d9789..ad24ffc7b8 100644 --- a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs +++ b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs @@ -13,7 +13,7 @@ namespace osu.Game.Tests.Visual base.Update(); // note that this will override any mod rate application - Beatmap.Value.Track.TempoAdjust = Clock.Rate; + Beatmap.Value.Track.Tempo.Value = Clock.Rate; } } } From 65f2d1f8757f809eb5f33b639abc5e7b338b6060 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2019 17:49:42 +0900 Subject: [PATCH 2680/2815] 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 914d352070..3cd4dc48bf 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -54,6 +54,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 473ce82443..530d62f583 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 d03005012a..fb753b8c6f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -74,7 +74,7 @@ - + @@ -82,7 +82,7 @@ - + From 55c938e5daa07a0a471d313645c172d5372ba73c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2019 18:08:11 +0900 Subject: [PATCH 2681/2815] Fix bindable usage --- osu.Game/Online/DownloadTrackingComposite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs index dcec17788a..9a0e112727 100644 --- a/osu.Game/Online/DownloadTrackingComposite.cs +++ b/osu.Game/Online/DownloadTrackingComposite.cs @@ -26,7 +26,7 @@ namespace osu.Game.Online /// protected readonly Bindable State = new Bindable(); - protected readonly Bindable Progress = new Bindable(); + protected readonly BindableNumber Progress = new BindableNumber { MinValue = 0, MaxValue = 1 }; protected DownloadTrackingComposite(TModel model = null) { From f593caf0eaa4d8750d314d16f17278c7fdc178d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2019 18:08:51 +0900 Subject: [PATCH 2682/2815] Remove unused class --- .../Blueprints/Sliders/SliderPlacementBlueprint.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index c004b6db28..9b820261ab 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.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.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input; @@ -191,15 +190,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders Initial, Body, } - - private class Segment - { - public readonly List ControlPoints = new List(); - - public Segment(Vector2 offset) - { - ControlPoints.Add(offset); - } - } } } From 48976f5d0e0e14663ac8b5d5c1f106c0fd592f1f Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 9 Dec 2019 21:46:29 +0800 Subject: [PATCH 2683/2815] Add VS launcher profile for tournament client. --- osu.Desktop/Properties/launchSettings.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 osu.Desktop/Properties/launchSettings.json diff --git a/osu.Desktop/Properties/launchSettings.json b/osu.Desktop/Properties/launchSettings.json new file mode 100644 index 0000000000..5e768ec9fa --- /dev/null +++ b/osu.Desktop/Properties/launchSettings.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "osu! Desktop": { + "commandName": "Project" + }, + "osu! Tournament": { + "commandName": "Project", + "commandLineArgs": "--tournament" + } + } +} \ No newline at end of file From 06cde2b0c22feca53459df4213f46eb82ca6d0c1 Mon Sep 17 00:00:00 2001 From: mcendu Date: Tue, 10 Dec 2019 19:30:46 +0800 Subject: [PATCH 2684/2815] remove unused using directive --- osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs index cf133f54ea..4e027c4351 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs @@ -3,7 +3,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Skinning; using osu.Game.Rulesets.Osu.UI.Cursor; using osuTK; From e37369304b7f75f67aafaa59947ca49b214a399e Mon Sep 17 00:00:00 2001 From: mcendu Date: Tue, 10 Dec 2019 19:45:06 +0800 Subject: [PATCH 2685/2815] property-ize expand target --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index 4a67d23f57..07cde5e407 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -20,7 +20,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private bool cursorExpand; - private OsuCursorSprite cursorSprite; + private SkinnableDrawable cursorSprite; + + private Drawable ExpandTarget => (cursorSprite.Drawable as OsuCursorSprite)?.ExpandTarget ?? cursorSprite; public OsuCursor() { @@ -37,19 +39,17 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor [BackgroundDependencyLoader] private void load() { - SkinnableDrawable cursor; InternalChild = new Container { RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, Anchor = Anchor.Centre, - Child = cursor = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling) + Child = cursorSprite = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling) { Origin = Anchor.Centre, Anchor = Anchor.Centre, } }; - cursorSprite = cursor.Drawable as OsuCursorSprite; } private const float pressed_scale = 1.2f; @@ -59,10 +59,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { if (!cursorExpand) return; - cursorSprite.ExpandTarget.ScaleTo(released_scale).ScaleTo(pressed_scale, 100, Easing.OutQuad); + ExpandTarget.ScaleTo(released_scale).ScaleTo(pressed_scale, 100, Easing.OutQuad); } - public void Contract() => cursorSprite.ExpandTarget.ScaleTo(released_scale, 100, Easing.OutQuad); + public void Contract() => ExpandTarget.ScaleTo(released_scale, 100, Easing.OutQuad); private class DefaultCursor : OsuCursorSprite { From 1afeaf31bcad901bceec6766d3ec7a6810b5f82b Mon Sep 17 00:00:00 2001 From: mcendu Date: Tue, 10 Dec 2019 19:58:56 +0800 Subject: [PATCH 2686/2815] make OsuCursorSprite abstract --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs index 56c04ce1fc..909c764b9e 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics.Containers; namespace osu.Game.Rulesets.Osu.UI.Cursor { - public class OsuCursorSprite : CompositeDrawable + public abstract class OsuCursorSprite : CompositeDrawable { public Drawable ExpandTarget { get; protected set; } } From b93bbf81aa4cba9b9ad840a4c804d7419c3a25be Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 10 Dec 2019 15:10:35 +0300 Subject: [PATCH 2687/2815] Add lighten background during breaks setting --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ .../Overlays/Settings/Sections/Gameplay/GeneralSettings.cs | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index b71463841a..947e864a87 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -80,6 +80,7 @@ namespace osu.Game.Configuration // Gameplay Set(OsuSetting.DimLevel, 0.3, 0, 1, 0.01); Set(OsuSetting.BlurLevel, 0, 0, 1, 0.01); + Set(OsuSetting.LightenDuringBreaks, true); Set(OsuSetting.HitLighting, true); @@ -142,6 +143,7 @@ namespace osu.Game.Configuration AutoCursorSize, DimLevel, BlurLevel, + LightenDuringBreaks, ShowStoryboard, ShowVideoBackground, KeyOverlay, diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index f4aa9a0144..3f8bc2b0c7 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -30,6 +30,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay KeyboardStep = 0.01f }, new SettingsCheckbox + { + LabelText = "Lighten playfield during breaks", + Bindable = config.GetBindable(OsuSetting.LightenDuringBreaks) + }, + new SettingsCheckbox { LabelText = "Show score overlay", Bindable = config.GetBindable(OsuSetting.ShowInterface) From bb078c2afc82b34bc1e9fae15cd8b09627d38892 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 10 Dec 2019 15:13:44 +0300 Subject: [PATCH 2688/2815] Lighten user-dim container if on break time --- .../Graphics/Containers/UserDimContainer.cs | 22 ++++++++++++++++--- .../Backgrounds/BackgroundScreenBeatmap.cs | 3 +++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 7683bbcd63..20f5a4fd83 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -1,11 +1,13 @@ // 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.Game.Configuration; +using osu.Game.Screens.Play; namespace osu.Game.Graphics.Containers { @@ -14,7 +16,9 @@ namespace osu.Game.Graphics.Containers /// public abstract class UserDimContainer : Container { - protected const float BACKGROUND_FADE_DURATION = 800; + private const float break_lighten_amount = 0.3f; + + protected const double BACKGROUND_FADE_DURATION = 800; /// /// Whether or not user-configured dim levels should be applied to the container. @@ -26,6 +30,12 @@ namespace osu.Game.Graphics.Containers /// public readonly Bindable StoryboardReplacesBackground = new Bindable(); + /// + /// Whether player is in break time. + /// Must be bound to to allow for dim adjustments in gameplay. + /// + internal readonly IBindable IsBreakTime = new Bindable(); + /// /// Whether the content of this container is currently being displayed. /// @@ -33,11 +43,14 @@ namespace osu.Game.Graphics.Containers protected Bindable UserDimLevel { get; private set; } + protected Bindable LightenDuringBreaks { get; private set; } + protected Bindable ShowStoryboard { get; private set; } protected Bindable ShowVideo { get; private set; } - protected double DimLevel => EnableUserDim.Value ? UserDimLevel.Value : 0; + private float breakLightening => LightenDuringBreaks.Value && IsBreakTime.Value ? break_lighten_amount : 0; + protected float DimLevel => Math.Max(EnableUserDim.Value ? (float)UserDimLevel.Value - breakLightening : 0, 0); protected override Container Content => dimContent; @@ -55,11 +68,14 @@ namespace osu.Game.Graphics.Containers private void load(OsuConfigManager config) { 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(); @@ -84,7 +100,7 @@ namespace osu.Game.Graphics.Containers ContentDisplayed = ShowDimContent; dimContent.FadeTo(ContentDisplayed ? 1 : 0, BACKGROUND_FADE_DURATION, Easing.OutQuint); - dimContent.FadeColour(OsuColour.Gray(1 - (float)DimLevel), BACKGROUND_FADE_DURATION, Easing.OutQuint); + dimContent.FadeColour(OsuColour.Gray(1f - DimLevel), BACKGROUND_FADE_DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 7b68460e6b..1ab3a5b533 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -38,6 +38,8 @@ namespace osu.Game.Screens.Backgrounds /// public readonly Bindable BlurAmount = new Bindable(); + internal readonly IBindable IsBreakTime = new Bindable(); + private readonly DimmableBackground dimmable; protected virtual DimmableBackground CreateFadeContainer() => new DimmableBackground { RelativeSizeAxes = Axes.Both }; @@ -48,6 +50,7 @@ namespace osu.Game.Screens.Backgrounds InternalChild = dimmable = CreateFadeContainer(); dimmable.EnableUserDim.BindTo(EnableUserDim); + dimmable.IsBreakTime.BindTo(IsBreakTime); dimmable.BlurAmount.BindTo(BlurAmount); } From 38f1a8bc170d11a3a4edacc4d058f7ef791fbd32 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 10 Dec 2019 15:14:47 +0300 Subject: [PATCH 2689/2815] Bind UserDimContainer.IsBreakTime from Player --- osu.Game/Screens/Play/Player.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index d40c448452..9b372c3791 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -140,6 +140,11 @@ namespace osu.Game.Screens.Play // bind clock into components that require it DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); + // bind break into components that require it. + Background.IsBreakTime.BindTo(breakOverlay.IsBreakTime); + DimmableStoryboard.IsBreakTime.BindTo(breakOverlay.IsBreakTime); + DimmableVideo.IsBreakTime.BindTo(breakOverlay.IsBreakTime); + // Bind ScoreProcessor to ourselves ScoreProcessor.AllJudged += onCompletion; ScoreProcessor.Failed += onFail; From 63f66aa5fa9fa2c178910e6bb9f1a68de5e0a372 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 10 Dec 2019 15:25:03 +0300 Subject: [PATCH 2690/2815] Check by UserDimContainer.DimLevel instead --- .../Visual/Background/TestSceneUserDimContainer.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index 8f71584b4d..910c462718 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -314,7 +314,7 @@ namespace osu.Game.Tests.Visual.Background config.BindWith(OsuSetting.BlurLevel, BlurLevel); } - public bool IsBackgroundDimmed() => ((FadeAccessibleBackground)Background).CurrentColour == OsuColour.Gray(1 - (float)DimLevel.Value); + public bool IsBackgroundDimmed() => ((FadeAccessibleBackground)Background).CurrentColour == OsuColour.Gray(1f - ((FadeAccessibleBackground)Background).CurrentDim); public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).CurrentColour == Color4.White; @@ -404,6 +404,8 @@ namespace osu.Game.Tests.Visual.Background public float CurrentAlpha => dimmable.CurrentAlpha; + public float CurrentDim => dimmable.DimLevel; + public Vector2 CurrentBlur => Background.BlurSigma; private TestDimmableBackground dimmable; @@ -418,6 +420,8 @@ namespace osu.Game.Tests.Visual.Background { public Color4 CurrentColour => Content.Colour; public float CurrentAlpha => Content.Alpha; + + public new float DimLevel => base.DimLevel; } } } From dbe46c6cf7df3dc77eb66bdfd88679d7d88add3c Mon Sep 17 00:00:00 2001 From: mcendu Date: Tue, 10 Dec 2019 20:40:10 +0800 Subject: [PATCH 2691/2815] conform to coding styles --- osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs | 2 +- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs index 4e027c4351..02152fa51e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Skinning { spin = skin.GetConfig(OsuSkinConfiguration.CursorRotate)?.Value ?? true; - InternalChildren = new Drawable[] + InternalChildren = new[] { new NonPlayfieldSprite { diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index 07cde5e407..4f3d07f208 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private SkinnableDrawable cursorSprite; - private Drawable ExpandTarget => (cursorSprite.Drawable as OsuCursorSprite)?.ExpandTarget ?? cursorSprite; + private Drawable expandTarget => (cursorSprite.Drawable as OsuCursorSprite)?.ExpandTarget ?? cursorSprite; public OsuCursor() { @@ -59,10 +59,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { if (!cursorExpand) return; - ExpandTarget.ScaleTo(released_scale).ScaleTo(pressed_scale, 100, Easing.OutQuad); + expandTarget.ScaleTo(released_scale).ScaleTo(pressed_scale, 100, Easing.OutQuad); } - public void Contract() => ExpandTarget.ScaleTo(released_scale, 100, Easing.OutQuad); + public void Contract() => expandTarget.ScaleTo(released_scale, 100, Easing.OutQuad); private class DefaultCursor : OsuCursorSprite { @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor Anchor = Anchor.Centre; Origin = Anchor.Centre; - InternalChildren = new Drawable[] + InternalChildren = new[] { ExpandTarget = new CircularContainer { From 6c1ae3bc8ac77bd1babbd0f0395071fd583a905a Mon Sep 17 00:00:00 2001 From: Albie Date: Tue, 10 Dec 2019 16:59:31 +0000 Subject: [PATCH 2692/2815] add tests --- .../Background/TestSceneUserDimContainer.cs | 17 +++++++++++++++++ .../Graphics/Containers/UserDimContainer.cs | 9 +++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index f858174ff2..ecea80d6cc 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -165,6 +165,21 @@ namespace osu.Game.Tests.Visual.Background AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); } + /// + /// Ensure is able to disable user-defined display settings. + /// + [Test] + public void DisableUserDisplaySettingsTest() + { + performFullSetup(); + AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true); + AddStep("Set user dim to max", () => player.DimmableStoryboard.) + waitForDim(); + AddStep("Turn on IgnoreUserSettings", () => player.DimmableStoryboard.IgnoreUserSettings.Value = true); + waitForDim(); + AddAssert("Check the background is undimmed", () => player.IsBackgroundUndimmed()); + } + /// /// Ensure is properly accepting user-defined visual changes for a storyboard. /// @@ -352,6 +367,8 @@ namespace osu.Game.Tests.Visual.Background public new DimmableStoryboard DimmableStoryboard => base.DimmableStoryboard; + public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).Colour == Color4.White; + // Whether or not the player should be allowed to load. public bool BlockLoad; diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 42a25a79b1..74d922704e 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Configuration; +using osuTK.Graphics; namespace osu.Game.Graphics.Containers { @@ -22,7 +23,7 @@ namespace osu.Game.Graphics.Containers public readonly Bindable EnableUserDim = new Bindable(true); /// - /// Whether or not user-configured settings relating to visibility of elements should be ignored + /// Whether or not user-configured settings relating to brightness of elements should be ignored /// public readonly Bindable IgnoreUserSettings = new Bindable(); @@ -36,14 +37,14 @@ namespace osu.Game.Graphics.Containers /// public bool ContentDisplayed { get; private set; } + public double DimLevel => EnableUserDim.Value && !IgnoreUserSettings.Value ? UserDimLevel.Value : 0; + protected Bindable UserDimLevel { get; private set; } protected Bindable ShowStoryboard { get; private set; } protected Bindable ShowVideo { get; private set; } - protected double DimLevel => EnableUserDim.Value ? UserDimLevel.Value : 0; - protected override Container Content => dimContent; private Container dimContent { get; } @@ -90,7 +91,7 @@ namespace osu.Game.Graphics.Containers ContentDisplayed = ShowDimContent; dimContent.FadeTo(ContentDisplayed ? 1 : 0, BACKGROUND_FADE_DURATION, Easing.OutQuint); - dimContent.FadeColour(OsuColour.Gray(1 - (float)DimLevel), BACKGROUND_FADE_DURATION, Easing.OutQuint); + dimContent.FadeColour(IgnoreUserSettings.Value ? OsuColour.Gray(1 - (float)DimLevel) : Color4.White, BACKGROUND_FADE_DURATION, Easing.OutQuint); } } } From 479acdcb5b670b0644a2755cd3b93396b3a95764 Mon Sep 17 00:00:00 2001 From: Albie Date: Tue, 10 Dec 2019 17:06:34 +0000 Subject: [PATCH 2693/2815] fix build bug --- osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index ecea80d6cc..8c7948ebef 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -173,7 +173,6 @@ namespace osu.Game.Tests.Visual.Background { performFullSetup(); AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true); - AddStep("Set user dim to max", () => player.DimmableStoryboard.) waitForDim(); AddStep("Turn on IgnoreUserSettings", () => player.DimmableStoryboard.IgnoreUserSettings.Value = true); waitForDim(); From 12fb17572c1ce4ad6cb8f1e37b6755c11c13dc8f Mon Sep 17 00:00:00 2001 From: Albie Date: Tue, 10 Dec 2019 18:16:34 +0000 Subject: [PATCH 2694/2815] cleanup test logic --- .../Background/TestSceneUserDimContainer.cs | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index 5b7900bad4..28cc7a8532 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -148,6 +148,20 @@ namespace osu.Game.Tests.Visual.Background AddAssert("Background is visible", () => songSelect.IsBackgroundVisible()); } + /// + /// Ensure is able to disable user-defined display settings. + /// + [Test] + public void DisableUserDisplaySettingsTest() + { + performFullSetup(); + AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true); + waitForDim(); + AddStep("Turn on IgnoreUserSettings", () => player.DimmableStoryboard.IgnoreUserSettings.Value = true); + waitForDim(); + AddAssert("Check the background is undimmed", () => player.IsBackgroundUndimmed()); + } + /// /// Ensure is properly accepting user-defined visual changes for a background. /// @@ -165,20 +179,6 @@ namespace osu.Game.Tests.Visual.Background AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); } - /// - /// Ensure is able to disable user-defined display settings. - /// - [Test] - public void DisableUserDisplaySettingsTest() - { - performFullSetup(); - AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true); - waitForDim(); - AddStep("Turn on IgnoreUserSettings", () => player.DimmableStoryboard.IgnoreUserSettings.Value = true); - waitForDim(); - AddAssert("Check the background is undimmed", () => player.IsBackgroundUndimmed()); - } - /// /// Ensure is properly accepting user-defined visual changes for a storyboard. /// @@ -367,7 +367,7 @@ namespace osu.Game.Tests.Visual.Background public new DimmableStoryboard DimmableStoryboard => base.DimmableStoryboard; - public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).Colour == Color4.White; + public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).CurrentColour == Color4.White; // Whether or not the player should be allowed to load. public bool BlockLoad; From 53daa37eaa97e3575261c333f67b68cd6ab5ad92 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 10 Dec 2019 23:06:13 +0300 Subject: [PATCH 2695/2815] Fix failing tests --- osu.Game/Screens/Play/Player.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 9b372c3791..8684798632 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -140,11 +140,6 @@ namespace osu.Game.Screens.Play // bind clock into components that require it DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); - // bind break into components that require it. - Background.IsBreakTime.BindTo(breakOverlay.IsBreakTime); - DimmableStoryboard.IsBreakTime.BindTo(breakOverlay.IsBreakTime); - DimmableVideo.IsBreakTime.BindTo(breakOverlay.IsBreakTime); - // Bind ScoreProcessor to ourselves ScoreProcessor.AllJudged += onCompletion; ScoreProcessor.Failed += onFail; @@ -506,6 +501,11 @@ namespace osu.Game.Screens.Play Background.EnableUserDim.Value = true; Background.BlurAmount.Value = 0; + // 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); From a37af311d010a1122d64ff5b8ca1c025a2ec3931 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 13:19:02 +0900 Subject: [PATCH 2696/2815] Simplify settings update logic --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 47 +++++++++++----------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index e860463b23..ec5f99dc31 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -331,32 +331,10 @@ namespace osu.Game.Overlays.Mods Ruleset.BindTo(ruleset); if (mods != null) SelectedMods.BindTo(mods); - SelectedMods.ValueChanged += updateModSettings; - Ruleset.ValueChanged += _ => ModSettingsContent.Clear(); - sampleOn = audio.Samples.Get(@"UI/check-on"); sampleOff = audio.Samples.Get(@"UI/check-off"); } - private void updateModSettings(ValueChangedEvent> selectedMods) - { - foreach (var added in selectedMods.NewValue.Except(selectedMods.OldValue)) - { - var controls = added.CreateSettingsControls().ToList(); - if (controls.Count > 0) - ModSettingsContent.Add(new ModControlSection(added) { Children = controls }); - } - - foreach (var removed in selectedMods.OldValue.Except(selectedMods.NewValue)) - ModSettingsContent.RemoveAll(section => section.Mod == removed); - - bool hasSettings = ModSettingsContent.Children.Count > 0; - CustomiseButton.Enabled.Value = hasSettings; - - if (!hasSettings) - ModSettingsContainer.Hide(); - } - public void DeselectAll() { foreach (var section in ModSectionsContainer.Children) @@ -450,12 +428,14 @@ namespace osu.Game.Overlays.Mods refreshSelectedMods(); } - private void selectedModsChanged(ValueChangedEvent> e) + private void selectedModsChanged(ValueChangedEvent> mods) { foreach (var section in ModSectionsContainer.Children) - section.SelectTypes(e.NewValue.Select(m => m.GetType()).ToList()); + section.SelectTypes(mods.NewValue.Select(m => m.GetType()).ToList()); updateMods(); + + updateModSettings(mods); } private void updateMods() @@ -480,6 +460,25 @@ namespace osu.Game.Overlays.Mods UnrankedLabel.FadeTo(ranked ? 0 : 1, 200); } + private void updateModSettings(ValueChangedEvent> selectedMods) + { + foreach (var added in selectedMods.NewValue.Except(selectedMods.OldValue)) + { + var controls = added.CreateSettingsControls().ToList(); + if (controls.Count > 0) + ModSettingsContent.Add(new ModControlSection(added) { Children = controls }); + } + + foreach (var removed in selectedMods.OldValue.Except(selectedMods.NewValue)) + ModSettingsContent.RemoveAll(section => section.Mod == removed); + + bool hasSettings = ModSettingsContent.Children.Count > 0; + CustomiseButton.Enabled.Value = hasSettings; + + if (!hasSettings) + ModSettingsContainer.Hide(); + } + private void modButtonPressed(Mod selectedMod) { if (selectedMod != null) From 5624b9fd3f981451db3c52a7da0c94bc56971a20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 13:19:13 +0900 Subject: [PATCH 2697/2815] Fix US english --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index ec5f99dc31..e8ea43e3f2 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -235,7 +235,7 @@ namespace osu.Game.Overlays.Mods CustomiseButton = new TriangleButton { Width = 180, - Text = "Customization", + Text = "Customisation", Action = () => ModSettingsContainer.Alpha = ModSettingsContainer.Alpha == 1 ? 0 : 1, Enabled = { Value = false }, Margin = new MarginPadding From 77b9989e115fa5681e17236415cf6c01f939b863 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 14:10:35 +0900 Subject: [PATCH 2698/2815] Fix some weird private field names --- .../MathUtils/FastRandom.cs | 20 +-- .../Components/ScrollingTeamContainer.cs | 121 +++++++++--------- osu.Game/Online/Multiplayer/PlaylistItem.cs | 22 ++-- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 34 +++-- 4 files changed, 94 insertions(+), 103 deletions(-) diff --git a/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs b/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs index c721ff862a..46e427e1b7 100644 --- a/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs +++ b/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs @@ -12,14 +12,14 @@ namespace osu.Game.Rulesets.Catch.MathUtils { private const double int_to_real = 1.0 / (int.MaxValue + 1.0); private const uint int_mask = 0x7FFFFFFF; - private const uint y = 842502087; - private const uint z = 3579807591; - private const uint w = 273326509; - private uint _x, _y = y, _z = z, _w = w; + private const uint y_initial = 842502087; + private const uint z_initial = 3579807591; + private const uint w_initial = 273326509; + private uint x, y = y_initial, z = z_initial, w = w_initial; public FastRandom(int seed) { - _x = (uint)seed; + x = (uint)seed; } public FastRandom() @@ -33,11 +33,11 @@ namespace osu.Game.Rulesets.Catch.MathUtils /// The random value. public uint NextUInt() { - uint t = _x ^ (_x << 11); - _x = _y; - _y = _z; - _z = _w; - return _w = _w ^ (_w >> 19) ^ t ^ (t >> 8); + uint t = x ^ (x << 11); + x = y; + y = z; + z = w; + return w = w ^ (w >> 19) ^ t ^ (t >> 8); } /// diff --git a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs index a345f93896..3ff4718b75 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs @@ -83,88 +83,81 @@ namespace osu.Game.Tournament.Screens.Drawings.Components }; } - private ScrollState _scrollState; + private ScrollState scrollState; - private ScrollState scrollState + private void setScrollState(ScrollState newstate) { - get => _scrollState; + if (scrollState == newstate) + return; - set + delayedStateChangeDelegate?.Cancel(); + + switch (scrollState = newstate) { - if (_scrollState == value) - return; + case ScrollState.Scrolling: + resetSelected(); - _scrollState = value; + OnScrollStarted?.Invoke(); - delayedStateChangeDelegate?.Cancel(); + speedTo(1000f, 200); + tracker.FadeOut(100); + break; - switch (value) - { - case ScrollState.Scrolling: - resetSelected(); + case ScrollState.Stopping: + speedTo(0f, 2000); + tracker.FadeIn(200); - OnScrollStarted?.Invoke(); + delayedStateChangeDelegate = Scheduler.AddDelayed(() => setScrollState(ScrollState.Stopped), 2300); + break; - speedTo(1000f, 200); - tracker.FadeOut(100); + case ScrollState.Stopped: + // Find closest to center + if (!Children.Any()) break; - case ScrollState.Stopping: - speedTo(0f, 2000); - tracker.FadeIn(200); + ScrollingTeam closest = null; - delayedStateChangeDelegate = Scheduler.AddDelayed(() => scrollState = ScrollState.Stopped, 2300); - break; + foreach (var c in Children) + { + if (!(c is ScrollingTeam stc)) + continue; - case ScrollState.Stopped: - // Find closest to center - if (!Children.Any()) - break; - - ScrollingTeam closest = null; - - foreach (var c in Children) + if (closest == null) { - if (!(c is ScrollingTeam stc)) - continue; - - if (closest == null) - { - closest = stc; - continue; - } - - float o = Math.Abs(c.Position.X + c.DrawWidth / 2f - DrawWidth / 2f); - float lastOffset = Math.Abs(closest.Position.X + closest.DrawWidth / 2f - DrawWidth / 2f); - - if (o < lastOffset) - closest = stc; + closest = stc; + continue; } - Trace.Assert(closest != null, "closest != null"); + float o = Math.Abs(c.Position.X + c.DrawWidth / 2f - DrawWidth / 2f); + float lastOffset = Math.Abs(closest.Position.X + closest.DrawWidth / 2f - DrawWidth / 2f); - // ReSharper disable once PossibleNullReferenceException - offset += DrawWidth / 2f - (closest.Position.X + closest.DrawWidth / 2f); + if (o < lastOffset) + closest = stc; + } - ScrollingTeam st = closest; + Trace.Assert(closest != null, "closest != null"); - availableTeams.RemoveAll(at => at == st.Team); + // ReSharper disable once PossibleNullReferenceException + offset += DrawWidth / 2f - (closest.Position.X + closest.DrawWidth / 2f); - st.Selected = true; - OnSelected?.Invoke(st.Team); + ScrollingTeam st = closest; - delayedStateChangeDelegate = Scheduler.AddDelayed(() => scrollState = ScrollState.Idle, 10000); - break; + availableTeams.RemoveAll(at => at == st.Team); - case ScrollState.Idle: - resetSelected(); + st.Selected = true; + OnSelected?.Invoke(st.Team); - OnScrollStarted?.Invoke(); + delayedStateChangeDelegate = Scheduler.AddDelayed(() => setScrollState(ScrollState.Idle), 10000); + break; - speedTo(40f, 200); - tracker.FadeOut(100); - break; - } + case ScrollState.Idle: + resetSelected(); + + OnScrollStarted?.Invoke(); + + speedTo(40f, 200); + tracker.FadeOut(100); + break; } } @@ -176,7 +169,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components availableTeams.Add(team); RemoveAll(c => c is ScrollingTeam); - scrollState = ScrollState.Idle; + setScrollState(ScrollState.Idle); } public void AddTeams(IEnumerable teams) @@ -192,7 +185,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components { availableTeams.Clear(); RemoveAll(c => c is ScrollingTeam); - scrollState = ScrollState.Idle; + setScrollState(ScrollState.Idle); } public void RemoveTeam(TournamentTeam team) @@ -217,7 +210,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components if (availableTeams.Count == 0) return; - scrollState = ScrollState.Scrolling; + setScrollState(ScrollState.Scrolling); } public void StopScrolling() @@ -232,13 +225,13 @@ namespace osu.Game.Tournament.Screens.Drawings.Components return; } - scrollState = ScrollState.Stopping; + setScrollState(ScrollState.Stopping); } protected override void LoadComplete() { base.LoadComplete(); - scrollState = ScrollState.Idle; + setScrollState(ScrollState.Idle); } protected override void UpdateAfterChildren() @@ -305,7 +298,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components private void speedTo(float value, double duration = 0, Easing easing = Easing.None) => this.TransformTo(nameof(speed), value, duration, easing); - private enum ScrollState + protected enum ScrollState { None, Idle, diff --git a/osu.Game/Online/Multiplayer/PlaylistItem.cs b/osu.Game/Online/Multiplayer/PlaylistItem.cs index e47d497d94..d13e8b31e6 100644 --- a/osu.Game/Online/Multiplayer/PlaylistItem.cs +++ b/osu.Game/Online/Multiplayer/PlaylistItem.cs @@ -45,23 +45,25 @@ namespace osu.Game.Online.Multiplayer [JsonProperty("beatmap")] private APIBeatmap apiBeatmap { get; set; } + private APIMod[] allowedModsBacking; + [JsonProperty("allowed_mods")] private APIMod[] allowedMods { get => AllowedMods.Select(m => new APIMod { Acronym = m.Acronym }).ToArray(); - set => _allowedMods = value; + set => allowedModsBacking = value; } + private APIMod[] requiredModsBacking; + [JsonProperty("required_mods")] private APIMod[] requiredMods { get => RequiredMods.Select(m => new APIMod { Acronym = m.Acronym }).ToArray(); - set => _requiredMods = value; + set => requiredModsBacking = value; } private BeatmapInfo beatmap; - private APIMod[] _allowedMods; - private APIMod[] _requiredMods; public void MapObjects(BeatmapManager beatmaps, RulesetStore rulesets) { @@ -70,20 +72,20 @@ namespace osu.Game.Online.Multiplayer Beatmap = apiBeatmap == null ? beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == BeatmapID) : apiBeatmap.ToBeatmap(rulesets); Ruleset = rulesets.GetRuleset(RulesetID); - if (_allowedMods != null) + if (allowedModsBacking != null) { AllowedMods.Clear(); - AllowedMods.AddRange(Ruleset.CreateInstance().GetAllMods().Where(mod => _allowedMods.Any(m => m.Acronym == mod.Acronym))); + AllowedMods.AddRange(Ruleset.CreateInstance().GetAllMods().Where(mod => allowedModsBacking.Any(m => m.Acronym == mod.Acronym))); - _allowedMods = null; + allowedModsBacking = null; } - if (_requiredMods != null) + if (requiredModsBacking != null) { RequiredMods.Clear(); - RequiredMods.AddRange(Ruleset.CreateInstance().GetAllMods().Where(mod => _requiredMods.Any(m => m.Acronym == mod.Acronym))); + RequiredMods.AddRange(Ruleset.CreateInstance().GetAllMods().Where(mod => requiredModsBacking.Any(m => m.Acronym == mod.Acronym))); - _requiredMods = null; + requiredModsBacking = null; } } diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index f54d638584..2fa5098890 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -188,26 +188,22 @@ namespace osu.Game.Screens.Play InternalButtons.Add(button); } - private int _selectionIndex = -1; + private int selectionIndex = -1; - private int selectionIndex + private void setSelected(int value) { - get => _selectionIndex; - set - { - if (_selectionIndex == value) - return; + if (selectionIndex == value) + return; - // Deselect the previously-selected button - if (_selectionIndex != -1) - InternalButtons[_selectionIndex].Selected.Value = false; + // Deselect the previously-selected button + if (selectionIndex != -1) + InternalButtons[selectionIndex].Selected.Value = false; - _selectionIndex = value; + selectionIndex = value; - // Select the newly-selected button - if (_selectionIndex != -1) - InternalButtons[_selectionIndex].Selected.Value = true; - } + // Select the newly-selected button + if (selectionIndex != -1) + InternalButtons[selectionIndex].Selected.Value = true; } protected override bool OnKeyDown(KeyDownEvent e) @@ -218,16 +214,16 @@ namespace osu.Game.Screens.Play { case Key.Up: if (selectionIndex == -1 || selectionIndex == 0) - selectionIndex = InternalButtons.Count - 1; + setSelected(InternalButtons.Count - 1); else - selectionIndex--; + setSelected(selectionIndex--); return true; case Key.Down: if (selectionIndex == -1 || selectionIndex == InternalButtons.Count - 1) - selectionIndex = 0; + setSelected(0); else - selectionIndex++; + setSelected(selectionIndex++); return true; } } From 9ebad16436cf7a079586c62216964f9f5648f20f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 14:34:17 +0900 Subject: [PATCH 2699/2815] Fix logic regression --- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 2fa5098890..adfbe977a4 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -216,14 +216,14 @@ namespace osu.Game.Screens.Play if (selectionIndex == -1 || selectionIndex == 0) setSelected(InternalButtons.Count - 1); else - setSelected(selectionIndex--); + setSelected(selectionIndex - 1); return true; case Key.Down: if (selectionIndex == -1 || selectionIndex == InternalButtons.Count - 1) setSelected(0); else - setSelected(selectionIndex++); + setSelected(selectionIndex + 1); return true; } } @@ -262,9 +262,9 @@ namespace osu.Game.Screens.Play private void buttonSelectionChanged(DialogButton button, bool isSelected) { if (!isSelected) - selectionIndex = -1; + setSelected(-1); else - selectionIndex = InternalButtons.IndexOf(button); + setSelected(InternalButtons.IndexOf(button)); } private void updateRetryCount() From 6c8f325063affe692a31ba0d3c1cd1688b3456c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 15:24:06 +0900 Subject: [PATCH 2700/2815] Add failing test --- .../Visual/Gameplay/TestScenePause.cs | 2 - .../Gameplay/TestScenePauseWhenInactive.cs | 49 +++++++++++++++++++ osu.Game/Tests/Visual/TestPlayer.cs | 7 ++- 3 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 803cab9325..e04315894e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -285,8 +285,6 @@ namespace osu.Game.Tests.Visual.Gameplay protected class PausePlayer : TestPlayer { - public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; - public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; public new HUDOverlay HUDOverlay => base.HUDOverlay; diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs new file mode 100644 index 0000000000..5f29b46fe6 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Platform; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual.Gameplay +{ + [HeadlessTest] // we alter unsafe properties on the game host to test inactive window state. + public class TestScenePauseWhenInactive : PlayerTestScene + { + protected new TestPlayer Player => (TestPlayer)base.Player; + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + var beatmap = (Beatmap)base.CreateBeatmap(ruleset); + + beatmap.HitObjects.RemoveAll(h => h.StartTime < 30000); + + return beatmap; + } + + [Resolved] + private GameHost host { get; set; } + + public TestScenePauseWhenInactive() + : base(new OsuRuleset()) + { + } + + [Test] + public void TestDoesntPauseDuringIntro() + { + AddStep("set inactive", () => ((Bindable)host.IsActive).Value = false); + AddStep("resume player", () => Player.GameplayClockContainer.Start()); + AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value); + AddAssert("time of pause is after gameplay start time", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= Player.DrawableRuleset.GameplayStartTime); + } + + protected override Player CreatePlayer(Ruleset ruleset) => new TestPlayer(true, true, true); + } +} diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index 31f6edadec..8e3821f1a0 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -8,13 +8,16 @@ namespace osu.Game.Tests.Visual { public class TestPlayer : Player { - protected override bool PauseOnFocusLost => false; + protected override bool PauseOnFocusLost { get; } public new DrawableRuleset DrawableRuleset => base.DrawableRuleset; - public TestPlayer(bool allowPause = true, bool showResults = true) + public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; + + public TestPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false) : base(allowPause, showResults) { + PauseOnFocusLost = pauseOnFocusLost; } } } From 75f92506451b47a1815530d00ffed7a74c23f41b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 15:45:50 +0900 Subject: [PATCH 2701/2815] Don't automatically pause when window is inactive if in break time --- osu.Game/Screens/Play/Player.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index d40c448452..9feee82989 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -135,7 +135,7 @@ namespace osu.Game.Screens.Play addGameplayComponents(GameplayClockContainer, working); addOverlayComponents(GameplayClockContainer, working); - DrawableRuleset.HasReplayLoaded.BindValueChanged(e => HUDOverlay.HoldToQuit.PauseOnFocusLost = !e.NewValue && PauseOnFocusLost, true); + DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); // bind clock into components that require it DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); @@ -146,6 +146,7 @@ namespace osu.Game.Screens.Play foreach (var mod in Mods.Value.OfType()) mod.ApplyToScoreProcessor(ScoreProcessor); + breakOverlay.IsBreakTime.ValueChanged += _ => updatePauseOnFocusLostState(); } private void addUnderlayComponents(Container target) @@ -241,6 +242,11 @@ namespace osu.Game.Screens.Play }); } + private void updatePauseOnFocusLostState() => + HUDOverlay.HoldToQuit.PauseOnFocusLost = PauseOnFocusLost + && !DrawableRuleset.HasReplayLoaded.Value + && !breakOverlay.IsBreakTime.Value; + private WorkingBeatmap loadBeatmap() { WorkingBeatmap working = Beatmap.Value; From 274958669c826236e48f681b8edb3c00ea8bf2b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 15:47:34 +0900 Subject: [PATCH 2702/2815] Add early assert as sanity check --- osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs index 5f29b46fe6..3513b6c25a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs @@ -39,7 +39,9 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestDoesntPauseDuringIntro() { AddStep("set inactive", () => ((Bindable)host.IsActive).Value = false); + AddStep("resume player", () => Player.GameplayClockContainer.Start()); + AddAssert("ensure not paused", () => !Player.GameplayClockContainer.IsPaused.Value); AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value); AddAssert("time of pause is after gameplay start time", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= Player.DrawableRuleset.GameplayStartTime); } From 83b2e0525e9055dae18b7eb7396a5804dbd21d39 Mon Sep 17 00:00:00 2001 From: Albie Date: Wed, 11 Dec 2019 07:02:51 +0000 Subject: [PATCH 2703/2815] further fixes, not perfect yet --- .../Visual/Background/TestSceneUserDimContainer.cs | 6 ++---- osu.Game/Graphics/Containers/UserDimContainer.cs | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index 28cc7a8532..f867b98fda 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -155,9 +155,7 @@ namespace osu.Game.Tests.Visual.Background public void DisableUserDisplaySettingsTest() { performFullSetup(); - AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true); - waitForDim(); - AddStep("Turn on IgnoreUserSettings", () => player.DimmableStoryboard.IgnoreUserSettings.Value = true); + AddStep("Start ignoring user settings", () => player.DimmableStoryboard.IgnoreUserSettings.Value = true); waitForDim(); AddAssert("Check the background is undimmed", () => player.IsBackgroundUndimmed()); } @@ -367,7 +365,7 @@ namespace osu.Game.Tests.Visual.Background public new DimmableStoryboard DimmableStoryboard => base.DimmableStoryboard; - public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).CurrentColour == Color4.White; + public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).CurrentColour == OsuColour.Gray(1); // Whether or not the player should be allowed to load. public bool BlockLoad; diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 74d922704e..bddbbca0ea 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -91,7 +91,7 @@ namespace osu.Game.Graphics.Containers ContentDisplayed = ShowDimContent; dimContent.FadeTo(ContentDisplayed ? 1 : 0, BACKGROUND_FADE_DURATION, Easing.OutQuint); - dimContent.FadeColour(IgnoreUserSettings.Value ? OsuColour.Gray(1 - (float)DimLevel) : Color4.White, BACKGROUND_FADE_DURATION, Easing.OutQuint); + dimContent.FadeColour(IgnoreUserSettings.Value ? Color4.White : OsuColour.Gray(1 - (float)DimLevel), BACKGROUND_FADE_DURATION, Easing.OutQuint); } } } From d8cebd20edac75973ad00c65b35421fcab97a9e7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 16:06:20 +0900 Subject: [PATCH 2704/2815] Add xmldoc --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs index 909c764b9e..573c408a78 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs @@ -8,6 +8,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { public abstract class OsuCursorSprite : CompositeDrawable { + /// + /// The an optional piece of the cursor to expand when in a clicked state. + /// If null, the whole cursor will be affected by expansion. + /// public Drawable ExpandTarget { get; protected set; } } } From 6b3c7c842198b1fe6e24314c6bad453cfd73d37b Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 19 Nov 2019 20:34:35 +0800 Subject: [PATCH 2705/2815] Remove usages of FileSafety class. --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 8 ++++---- osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs | 4 ++-- osu.Game/Beatmaps/WorkingBeatmap.cs | 7 +++++-- osu.Game/Database/ArchiveModelManager.cs | 3 +-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 838b1c2f07..bffe999896 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using osu.Framework.IO.File; +using osu.Framework.Extensions; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Beatmaps.ControlPoints; @@ -112,7 +112,7 @@ namespace osu.Game.Beatmaps.Formats switch (pair.Key) { case @"AudioFilename": - metadata.AudioFile = FileSafety.PathStandardise(pair.Value); + metadata.AudioFile = pair.Value.PathStandardise(); break; case @"AudioLeadIn": @@ -300,12 +300,12 @@ namespace osu.Game.Beatmaps.Formats { case EventType.Background: string bgFilename = split[2].Trim('"'); - beatmap.BeatmapInfo.Metadata.BackgroundFile = FileSafety.PathStandardise(bgFilename); + beatmap.BeatmapInfo.Metadata.BackgroundFile = bgFilename.PathStandardise(); break; case EventType.Video: string videoFilename = split[2].Trim('"'); - beatmap.BeatmapInfo.Metadata.VideoFile = FileSafety.PathStandardise(videoFilename); + beatmap.BeatmapInfo.Metadata.VideoFile = videoFilename.PathStandardise(); break; case EventType.Break: diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index f94ab3f27b..d79c0f7fa8 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -8,8 +8,8 @@ using System.IO; using System.Linq; using osuTK; using osuTK.Graphics; +using osu.Framework.Extensions; using osu.Framework.Graphics; -using osu.Framework.IO.File; using osu.Game.IO; using osu.Game.Storyboards; @@ -335,6 +335,6 @@ namespace osu.Game.Beatmaps.Formats } } - private string cleanFilename(string path) => FileSafety.PathStandardise(path.Trim('"')); + private string cleanFilename(string path) => path.Trim('"').PathStandardise(); } } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 44d6d33cef..f23669579a 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -7,7 +7,6 @@ using osu.Game.Rulesets.Mods; using System; using System.Collections.Generic; using osu.Game.Storyboards; -using osu.Framework.IO.File; using System.IO; using System.Linq; using System.Threading; @@ -83,7 +82,11 @@ namespace osu.Game.Beatmaps /// The absolute path of the output file. public string Save() { - var path = FileSafety.GetTempPath(Guid.NewGuid().ToString().Replace("-", string.Empty) + ".json"); + // copied from osu.Framework.IO.File.FileSafety.GetTempPath + string directory = Path.Combine(Path.GetTempPath(), @"osu!"); + Directory.CreateDirectory(directory); + + var path = Path.Combine(directory, Guid.NewGuid().ToString().Replace("-", string.Empty) + ".json"); using (var sw = new StreamWriter(path)) sw.WriteLine(Beatmap.Serialize()); return path; diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 7cce2fb92f..5cd2b947fe 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -13,7 +13,6 @@ using Microsoft.EntityFrameworkCore; using osu.Framework; using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.IO.File; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Threading; @@ -493,7 +492,7 @@ namespace osu.Game.Database { fileInfos.Add(new TFileModel { - Filename = FileSafety.PathStandardise(file.Substring(prefix.Length)), + Filename = file.Substring(prefix.Length).PathStandardise(), FileInfo = files.Add(s) }); } From b86a3dbfabbc1db04314b6e6282cf88343e50eeb Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 11 Dec 2019 16:06:56 +0800 Subject: [PATCH 2706/2815] PathStandardise -> ToStandardisedPath --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 6 +++--- osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs | 2 +- osu.Game/Database/ArchiveModelManager.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index bffe999896..f8275ec4f6 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -112,7 +112,7 @@ namespace osu.Game.Beatmaps.Formats switch (pair.Key) { case @"AudioFilename": - metadata.AudioFile = pair.Value.PathStandardise(); + metadata.AudioFile = pair.Value.ToStandardisedPath(); break; case @"AudioLeadIn": @@ -300,12 +300,12 @@ namespace osu.Game.Beatmaps.Formats { case EventType.Background: string bgFilename = split[2].Trim('"'); - beatmap.BeatmapInfo.Metadata.BackgroundFile = bgFilename.PathStandardise(); + beatmap.BeatmapInfo.Metadata.BackgroundFile = bgFilename.ToStandardisedPath(); break; case EventType.Video: string videoFilename = split[2].Trim('"'); - beatmap.BeatmapInfo.Metadata.VideoFile = videoFilename.PathStandardise(); + beatmap.BeatmapInfo.Metadata.VideoFile = videoFilename.ToStandardisedPath(); break; case EventType.Break: diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index d79c0f7fa8..ccd46ab559 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -335,6 +335,6 @@ namespace osu.Game.Beatmaps.Formats } } - private string cleanFilename(string path) => path.Trim('"').PathStandardise(); + private string cleanFilename(string path) => path.Trim('"').ToStandardisedPath(); } } diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 5cd2b947fe..fd455d7cd5 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -492,7 +492,7 @@ namespace osu.Game.Database { fileInfos.Add(new TFileModel { - Filename = file.Substring(prefix.Length).PathStandardise(), + Filename = file.Substring(prefix.Length).ToStandardisedPath(), FileInfo = files.Add(s) }); } From cffeceb2290c4ce9b6ce37dacdd7d766e00d216e Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 11 Dec 2019 16:24:22 +0800 Subject: [PATCH 2707/2815] Remove unnecessary comment. --- osu.Game/Beatmaps/WorkingBeatmap.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index f23669579a..4452d26fcd 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -82,7 +82,6 @@ namespace osu.Game.Beatmaps /// The absolute path of the output file. public string Save() { - // copied from osu.Framework.IO.File.FileSafety.GetTempPath string directory = Path.Combine(Path.GetTempPath(), @"osu!"); Directory.CreateDirectory(directory); From 48f1dad4aa4546402ed6c0bd860ae54598c7365d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 11 Dec 2019 17:25:06 +0900 Subject: [PATCH 2708/2815] Remove abstract ScoreProcessor class --- .../Scoring/CatchScoreProcessor.cs | 10 +- .../UI/DrawableCatchRuleset.cs | 2 +- .../Scoring/ManiaScoreProcessor.cs | 12 +- .../UI/DrawableManiaRuleset.cs | 2 +- .../Scoring/OsuScoreProcessor.cs | 10 +- .../UI/DrawableOsuRuleset.cs | 2 +- .../Scoring/TaikoScoreProcessor.cs | 12 +- .../UI/DrawableTaikoRuleset.cs | 2 +- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 276 ++++++++---------- osu.Game/Rulesets/UI/DrawableRuleset.cs | 26 +- osu.Game/Screens/Play/Player.cs | 3 + 11 files changed, 165 insertions(+), 192 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 18785d65ea..f67ca1213e 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -2,23 +2,21 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; -using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Catch.Scoring { - public class CatchScoreProcessor : ScoreProcessor + public class CatchScoreProcessor : ScoreProcessor { - public CatchScoreProcessor(DrawableRuleset drawableRuleset) - : base(drawableRuleset) + public CatchScoreProcessor(IBeatmap beatmap) + : base(beatmap) { } private float hpDrainRate; - protected override void ApplyBeatmap(Beatmap beatmap) + protected override void ApplyBeatmap(IBeatmap beatmap) { base.ApplyBeatmap(beatmap); diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index 6b7f00c5d0..f5bddeb2cb 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 TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450); } - public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this); + public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(Beatmap); protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 49894a644c..a678ef60e7 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -3,13 +3,11 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mania.Scoring { - internal class ManiaScoreProcessor : ScoreProcessor + internal class ManiaScoreProcessor : ScoreProcessor { /// /// The hit HP multiplier at OD = 0. @@ -51,12 +49,12 @@ namespace osu.Game.Rulesets.Mania.Scoring /// private double hpMultiplier = 1; - public ManiaScoreProcessor(DrawableRuleset drawableRuleset) - : base(drawableRuleset) + public ManiaScoreProcessor(IBeatmap beatmap) + : base(beatmap) { } - protected override void ApplyBeatmap(Beatmap beatmap) + protected override void ApplyBeatmap(IBeatmap beatmap) { base.ApplyBeatmap(beatmap); @@ -65,7 +63,7 @@ namespace osu.Game.Rulesets.Mania.Scoring hpMissMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_miss_min, hp_multiplier_miss_mid, hp_multiplier_miss_max); } - protected override void SimulateAutoplay(Beatmap beatmap) + protected override void SimulateAutoplay(IBeatmap beatmap) { while (true) { diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index d371c1f7a8..0607bf0abd 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Mania.UI protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages); - public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this); + public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(Beatmap); public override int Variant => (int)(Beatmap.Stages.Count == 1 ? PlayfieldType.Single : PlayfieldType.Dual) + Beatmap.TotalColumns; diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index affe18a30d..6779271cb3 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -5,22 +5,20 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; -using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.Scoring { - internal class OsuScoreProcessor : ScoreProcessor + internal class OsuScoreProcessor : ScoreProcessor { - public OsuScoreProcessor(DrawableRuleset drawableRuleset) - : base(drawableRuleset) + public OsuScoreProcessor(IBeatmap beatmap) + : base(beatmap) { } private float hpDrainRate; - protected override void ApplyBeatmap(Beatmap beatmap) + protected override void ApplyBeatmap(IBeatmap beatmap) { base.ApplyBeatmap(beatmap); diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index 49aea52902..5bb728a9b0 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.UI public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // always show the gameplay cursor - public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(this); + public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(Beatmap); protected override Playfield CreatePlayfield() => new OsuPlayfield(); diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index 75a27ff639..ae593d2e3a 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -1,15 +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.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Taiko.Scoring { - internal class TaikoScoreProcessor : ScoreProcessor + internal class TaikoScoreProcessor : ScoreProcessor { /// /// A value used for calculating . @@ -31,16 +31,16 @@ namespace osu.Game.Rulesets.Taiko.Scoring /// private double hpMissMultiplier; - public TaikoScoreProcessor(DrawableRuleset drawableRuleset) - : base(drawableRuleset) + public TaikoScoreProcessor(IBeatmap beatmap) + : base(beatmap) { } - protected override void ApplyBeatmap(Beatmap beatmap) + protected override void ApplyBeatmap(IBeatmap beatmap) { base.ApplyBeatmap(beatmap); - hpMultiplier = 1 / (object_count_factor * beatmap.HitObjects.FindAll(o => o is Hit).Count * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98)); + hpMultiplier = 1 / (object_count_factor * beatmap.HitObjects.OfType().Count() * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98)); hpMissMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.0018, 0.0075, 0.0120); } diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index fc109bf6a6..d4ea9a043a 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.UI new BarLineGenerator(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar.Major ? new DrawableBarLineMajor(bar) : new DrawableBarLine(bar))); } - public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this); + public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(Beatmap); public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer(); diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 18c2a2ca01..a8a2294498 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -13,13 +13,16 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.UI; using osu.Game.Scoring; namespace osu.Game.Rulesets.Scoring { - public abstract class ScoreProcessor + public class ScoreProcessor { + private const double base_portion = 0.3; + private const double combo_portion = 0.7; + private const double max_score = 1000000; + /// /// Invoked when the is in a failed state. /// This may occur regardless of whether an event is invoked. @@ -67,11 +70,6 @@ namespace osu.Game.Rulesets.Scoring /// public readonly Bindable> Mods = new Bindable>(Array.Empty()); - /// - /// Create a for this processor. - /// - public virtual HitWindows CreateHitWindows() => new HitWindows(); - /// /// The current rank. /// @@ -90,132 +88,23 @@ namespace osu.Game.Rulesets.Scoring /// /// Whether all s have been processed. /// - public virtual bool HasCompleted => false; - - /// - /// The total number of judged s at the current point in time. - /// - public int JudgedHits { get; protected set; } + public bool HasCompleted => JudgedHits == MaxHits; /// /// Whether this ScoreProcessor has already triggered the failed state. /// - public virtual bool HasFailed { get; private set; } + public bool HasFailed { get; private set; } /// - /// The default conditions for failing. + /// The maximum number of hits that can be judged. /// - protected virtual bool DefaultFailCondition => Precision.AlmostBigger(Health.MinValue, Health.Value); - - protected ScoreProcessor() - { - Combo.ValueChanged += delegate { HighestCombo.Value = Math.Max(HighestCombo.Value, Combo.Value); }; - Accuracy.ValueChanged += delegate - { - Rank.Value = rankFrom(Accuracy.Value); - foreach (var mod in Mods.Value.OfType()) - Rank.Value = mod.AdjustRank(Rank.Value, Accuracy.Value); - }; - } - - private ScoreRank rankFrom(double acc) - { - if (acc == 1) - return ScoreRank.X; - if (acc > 0.95) - return ScoreRank.S; - if (acc > 0.9) - return ScoreRank.A; - if (acc > 0.8) - return ScoreRank.B; - if (acc > 0.7) - return ScoreRank.C; - - return ScoreRank.D; - } - - /// - /// Resets this ScoreProcessor to a default state. - /// - /// Whether to store the current state of the for future use. - protected virtual void Reset(bool storeResults) - { - TotalScore.Value = 0; - Accuracy.Value = 1; - Health.Value = 1; - Combo.Value = 0; - Rank.Value = ScoreRank.X; - HighestCombo.Value = 0; - - JudgedHits = 0; - - HasFailed = false; - } - - /// - /// Checks if the score is in a failed state and notifies subscribers. - /// - /// This can only ever notify subscribers once. - /// - /// - protected void UpdateFailed(JudgementResult result) - { - if (HasFailed) - return; - - if (!DefaultFailCondition && FailConditions?.Invoke(this, result) != true) - return; - - if (Failed?.Invoke() != false) - HasFailed = true; - } - - /// - /// Notifies subscribers of that a new judgement has occurred. - /// - /// The judgement scoring result to notify subscribers of. - protected void NotifyNewJudgement(JudgementResult result) - { - NewJudgement?.Invoke(result); - - if (HasCompleted) - AllJudged?.Invoke(); - } - - /// - /// Retrieve a score populated with data for the current play this processor is responsible for. - /// - public virtual void PopulateScore(ScoreInfo score) - { - score.TotalScore = (long)Math.Round(TotalScore.Value); - score.Combo = Combo.Value; - score.MaxCombo = HighestCombo.Value; - score.Accuracy = Math.Round(Accuracy.Value, 4); - score.Rank = Rank.Value; - score.Date = DateTimeOffset.Now; - - var hitWindows = CreateHitWindows(); - - foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r))) - score.Statistics[result] = GetStatistic(result); - } - - public abstract int GetStatistic(HitResult result); - - public abstract double GetStandardisedScore(); - } - - public class ScoreProcessor : ScoreProcessor - where TObject : HitObject - { - private const double base_portion = 0.3; - private const double combo_portion = 0.7; - private const double max_score = 1000000; - - public sealed override bool HasCompleted => JudgedHits == MaxHits; - protected int MaxHits { get; private set; } + /// + /// The total number of judged s at the current point in time. + /// + public int JudgedHits { get; private set; } + private double maxHighestCombo; private double maxBaseScore; @@ -225,17 +114,22 @@ namespace osu.Game.Rulesets.Scoring private double scoreMultiplier = 1; - public ScoreProcessor(DrawableRuleset drawableRuleset) + public ScoreProcessor(IBeatmap beatmap) { Debug.Assert(base_portion + combo_portion == 1.0); - drawableRuleset.OnNewResult += applyResult; - drawableRuleset.OnRevertResult += revertResult; + Combo.ValueChanged += combo => HighestCombo.Value = Math.Max(HighestCombo.Value, combo.NewValue); + Accuracy.ValueChanged += accuracy => + { + Rank.Value = rankFrom(accuracy.NewValue); + foreach (var mod in Mods.Value.OfType()) + Rank.Value = mod.AdjustRank(Rank.Value, accuracy.NewValue); + }; - ApplyBeatmap(drawableRuleset.Beatmap); + ApplyBeatmap(beatmap); Reset(false); - SimulateAutoplay(drawableRuleset.Beatmap); + SimulateAutoplay(beatmap); Reset(true); if (maxBaseScore == 0 || maxHighestCombo == 0) @@ -257,19 +151,19 @@ namespace osu.Game.Rulesets.Scoring } /// - /// Applies any properties of the which affect scoring to this . + /// Applies any properties of the which affect scoring to this . /// - /// The to read properties from. - protected virtual void ApplyBeatmap(Beatmap beatmap) + /// The to read properties from. + protected virtual void ApplyBeatmap(IBeatmap beatmap) { } /// - /// Simulates an autoplay of the to determine scoring values. + /// Simulates an autoplay of the to determine scoring values. /// /// This provided temporarily. DO NOT USE. - /// The to simulate. - protected virtual void SimulateAutoplay(Beatmap beatmap) + /// The to simulate. + protected virtual void SimulateAutoplay(IBeatmap beatmap) { foreach (var obj in beatmap.HitObjects) simulate(obj); @@ -289,7 +183,7 @@ namespace osu.Game.Rulesets.Scoring result.Type = judgement.MaxResult; - applyResult(result); + ApplyResult(result); } } @@ -297,22 +191,26 @@ namespace osu.Game.Rulesets.Scoring /// Applies the score change of a to this . /// /// The to apply. - private void applyResult(JudgementResult result) + public void ApplyResult(JudgementResult result) { - ApplyResult(result); - updateScore(); + ApplyResultInternal(result); - UpdateFailed(result); - NotifyNewJudgement(result); + updateScore(); + updateFailed(result); + + NewJudgement?.Invoke(result); + + if (HasCompleted) + AllJudged?.Invoke(); } /// /// Reverts the score change of a that was applied to this . /// /// The judgement scoring result. - private void revertResult(JudgementResult result) + public void RevertResult(JudgementResult result) { - RevertResult(result); + RevertResultInternal(result); updateScore(); } @@ -322,10 +220,10 @@ namespace osu.Game.Rulesets.Scoring /// Applies the score change of a to this . /// /// - /// Any changes applied via this method can be reverted via . + /// Any changes applied via this method can be reverted via . /// /// The to apply. - protected virtual void ApplyResult(JudgementResult result) + protected virtual void ApplyResultInternal(JudgementResult result) { result.ComboAtJudgement = Combo.Value; result.HighestComboAtJudgement = HighestCombo.Value; @@ -372,10 +270,10 @@ namespace osu.Game.Rulesets.Scoring } /// - /// Reverts the score change of a that was applied to this via . + /// Reverts the score change of a that was applied to this via . /// /// The judgement scoring result. - protected virtual void RevertResult(JudgementResult result) + protected virtual void RevertResultInternal(JudgementResult result) { Combo.Value = result.ComboAtJudgement; HighestCombo.Value = result.HighestComboAtJudgement; @@ -432,11 +330,49 @@ namespace osu.Game.Rulesets.Scoring } } - public override int GetStatistic(HitResult result) => scoreResultCounts.GetOrDefault(result); + /// + /// Checks if the score is in a failed state and notifies subscribers. + /// + /// This can only ever notify subscribers once. + /// + /// + private void updateFailed(JudgementResult result) + { + if (HasFailed) + return; - public override double GetStandardisedScore() => getScore(ScoringMode.Standardised); + if (!DefaultFailCondition && FailConditions?.Invoke(this, result) != true) + return; - protected override void Reset(bool storeResults) + if (Failed?.Invoke() != false) + HasFailed = true; + } + + private ScoreRank rankFrom(double acc) + { + if (acc == 1) + return ScoreRank.X; + if (acc > 0.95) + return ScoreRank.S; + if (acc > 0.9) + return ScoreRank.A; + if (acc > 0.8) + return ScoreRank.B; + if (acc > 0.7) + return ScoreRank.C; + + return ScoreRank.D; + } + + public int GetStatistic(HitResult result) => scoreResultCounts.GetOrDefault(result); + + public double GetStandardisedScore() => getScore(ScoringMode.Standardised); + + /// + /// Resets this ScoreProcessor to a default state. + /// + /// Whether to store the current state of the for future use. + protected virtual void Reset(bool storeResults) { scoreResultCounts.Clear(); @@ -447,13 +383,49 @@ namespace osu.Game.Rulesets.Scoring maxBaseScore = baseScore; } - base.Reset(storeResults); - + JudgedHits = 0; baseScore = 0; rollingMaxBaseScore = 0; bonusScore = 0; + + TotalScore.Value = 0; + Accuracy.Value = 1; + Health.Value = 1; + Combo.Value = 0; + Rank.Value = ScoreRank.X; + HighestCombo.Value = 0; + + HasFailed = false; } + /// + /// Retrieve a score populated with data for the current play this processor is responsible for. + /// + public virtual void PopulateScore(ScoreInfo score) + { + score.TotalScore = (long)Math.Round(TotalScore.Value); + score.Combo = Combo.Value; + score.MaxCombo = HighestCombo.Value; + score.Accuracy = Math.Round(Accuracy.Value, 4); + score.Rank = Rank.Value; + score.Date = DateTimeOffset.Now; + + var hitWindows = CreateHitWindows(); + + foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r))) + score.Statistics[result] = GetStatistic(result); + } + + /// + /// The default conditions for failing. + /// + protected virtual bool DefaultFailCondition => Precision.AlmostBigger(Health.MinValue, Health.Value); + + /// + /// Create a for this processor. + /// + public virtual HitWindows CreateHitWindows() => new HitWindows(); + /// /// Creates the that represents the scoring result for a . /// diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index a856974292..10657f6b39 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -45,6 +45,10 @@ namespace osu.Game.Rulesets.UI public abstract class DrawableRuleset : DrawableRuleset, IProvideCursor, ICanAttachKeyCounter where TObject : HitObject { + public override event Action OnNewResult; + + public override event Action OnRevertResult; + /// /// The selected variant. /// @@ -91,16 +95,6 @@ namespace osu.Game.Rulesets.UI } } - /// - /// Invoked when a has been applied by a . - /// - public event Action OnNewResult; - - /// - /// Invoked when a is being reverted by a . - /// - public event Action OnRevertResult; - /// /// The beatmap. /// @@ -309,7 +303,7 @@ namespace osu.Game.Rulesets.UI /// The Playfield. protected abstract Playfield CreatePlayfield(); - public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this); + public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(Beatmap); /// /// Applies the active mods to this DrawableRuleset. @@ -366,6 +360,16 @@ namespace osu.Game.Rulesets.UI /// public abstract class DrawableRuleset : CompositeDrawable { + /// + /// Invoked when a has been applied by a . + /// + public abstract event Action OnNewResult; + + /// + /// Invoked when a is being reverted by a . + /// + public abstract event Action OnRevertResult; + /// /// Whether a replay is currently loaded. /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index d40c448452..d3df137328 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -140,6 +140,9 @@ namespace osu.Game.Screens.Play // bind clock into components that require it DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); + DrawableRuleset.OnNewResult += ScoreProcessor.ApplyResult; + DrawableRuleset.OnRevertResult += ScoreProcessor.RevertResult; + // Bind ScoreProcessor to ourselves ScoreProcessor.AllJudged += onCompletion; ScoreProcessor.Failed += onFail; From 97ca2e2753151ec527264d0226a5e0b2459f6cd9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 17:58:59 +0900 Subject: [PATCH 2709/2815] Add missing bezier option to menu --- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index d706fb51d5..ab6064602b 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -171,6 +171,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components createMenuItemForPathType(null), createMenuItemForPathType(PathType.Linear), createMenuItemForPathType(PathType.PerfectCurve), + createMenuItemForPathType(PathType.Bezier), createMenuItemForPathType(PathType.Catmull) } } From 03040d175092313cd9a5657fa1f9915917d22927 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 18:18:16 +0900 Subject: [PATCH 2710/2815] Don't show inherit menu item when first control point is selected --- .../Components/PathControlPointVisualiser.cs | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index ab6064602b..e406bb6426 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -27,7 +27,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler, IHasContextMenu { internal readonly Container Pieces; + private readonly Slider slider; + private readonly bool allowSelection; private InputManager inputManager; @@ -82,7 +84,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components protected override bool OnClick(ClickEvent e) { foreach (var piece in Pieces) + { piece.IsSelected.Value = false; + } + return false; } @@ -156,24 +161,29 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (!Pieces.Any(p => p.IsHovered)) return null; - int selectedPoints = Pieces.Count(p => p.IsSelected.Value); + var selectedPieces = Pieces.Where(p => p.IsSelected.Value).ToList(); + int count = selectedPieces.Count; - if (selectedPoints == 0) + if (count == 0) return null; + List items = new List(); + + if (!selectedPieces.Contains(Pieces[0])) + items.Add(createMenuItemForPathType(null)); + + // todo: hide/disable items which aren't valid for selected points + items.Add(createMenuItemForPathType(PathType.Linear)); + items.Add(createMenuItemForPathType(PathType.PerfectCurve)); + items.Add(createMenuItemForPathType(PathType.Bezier)); + items.Add(createMenuItemForPathType(PathType.Catmull)); + return new MenuItem[] { - new OsuMenuItem($"Delete {"control point".ToQuantity(selectedPoints, selectedPoints > 1 ? ShowQuantityAs.Numeric : ShowQuantityAs.None)}", MenuItemType.Destructive, () => deleteSelected()), + new OsuMenuItem($"Delete {"control point".ToQuantity(count, count > 1 ? ShowQuantityAs.Numeric : ShowQuantityAs.None)}", MenuItemType.Destructive, () => deleteSelected()), new OsuMenuItem("Type") { - Items = new[] - { - createMenuItemForPathType(null), - createMenuItemForPathType(PathType.Linear), - createMenuItemForPathType(PathType.PerfectCurve), - createMenuItemForPathType(PathType.Bezier), - createMenuItemForPathType(PathType.Catmull) - } + Items = items } }; } From d82ba3e7f7ebb69b29a7529299dbe3aaa800a373 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 18:20:28 +0900 Subject: [PATCH 2711/2815] Curve -> Curve type --- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index e406bb6426..22155ab7af 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return new MenuItem[] { new OsuMenuItem($"Delete {"control point".ToQuantity(count, count > 1 ? ShowQuantityAs.Numeric : ShowQuantityAs.None)}", MenuItemType.Destructive, () => deleteSelected()), - new OsuMenuItem("Type") + new OsuMenuItem("Curve type") { Items = items } From 23959f3a3ccb462a21c52460163b70bc474e8fa1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 18:52:38 +0900 Subject: [PATCH 2712/2815] Move control point removal to SliderSelectionBlueprint --- .../Components/PathControlPointVisualiser.cs | 32 ++----------- .../Sliders/SliderSelectionBlueprint.cs | 47 +++++++++++++++++-- 2 files changed, 47 insertions(+), 32 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 22155ab7af..cd19653a2e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using Humanizer; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -18,8 +17,6 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Screens.Edit.Compose; -using osuTK; using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components @@ -34,11 +31,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private InputManager inputManager; - [Resolved(CanBeNull = true)] - private IPlacementHandler placementHandler { get; set; } - private IBindableList controlPoints; + public Action> RemoveControlPointsRequested; + public PathControlPointVisualiser(Slider slider, bool allowSelection) { this.slider = slider; @@ -123,29 +119,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (toRemove.Count == 0) return false; - foreach (var c in toRemove) - { - // The first control point in the slider must have a type, so take it from the previous "first" one - // Todo: Should be handled within SliderPath itself - if (c == slider.Path.ControlPoints[0] && slider.Path.ControlPoints.Count > 1 && slider.Path.ControlPoints[1].Type.Value == null) - slider.Path.ControlPoints[1].Type.Value = slider.Path.ControlPoints[0].Type.Value; - - slider.Path.ControlPoints.Remove(c); - } - - // If there are 0 or 1 remaining control points, the slider is in a degenerate (single point) form and should be deleted - if (slider.Path.ControlPoints.Count <= 1) - { - placementHandler?.Delete(slider); - return true; - } - - // The path will have a non-zero offset if the head is removed, but sliders don't support this behaviour since the head is positioned at the slider's position - // So the slider needs to be offset by this amount instead, and all control points offset backwards such that the path is re-positioned at (0, 0) - Vector2 first = slider.Path.ControlPoints[0].Position.Value; - foreach (var c in slider.Path.ControlPoints) - c.Position.Value -= first; - slider.Position += first; + RemoveControlPointsRequested?.Invoke(toRemove); // Since pieces are re-used, they will not point to the deleted control points while remaining selected foreach (var piece in Pieces) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 68873093a6..3165c441fb 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.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.Collections.Generic; using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -14,6 +15,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Screens.Edit.Compose; using osuTK; using osuTK.Input; @@ -29,6 +31,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders [Resolved(CanBeNull = true)] private HitObjectComposer composer { get; set; } + [Resolved(CanBeNull = true)] + private IPlacementHandler placementHandler { get; set; } + public SliderSelectionBlueprint(DrawableSlider slider) : base(slider) { @@ -40,6 +45,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start), TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End), ControlPointVisualiser = new PathControlPointVisualiser(sliderObject, true) + { + RemoveControlPointsRequested = removeControlPoints + } }; } @@ -97,6 +105,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders return true; } + private BindableList controlPoints => HitObject.Path.ControlPoints; + private int addControlPoint(Vector2 position) { position -= HitObject.Position; @@ -104,9 +114,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders int insertionIndex = 0; float minDistance = float.MaxValue; - for (int i = 0; i < HitObject.Path.ControlPoints.Count - 1; i++) + for (int i = 0; i < controlPoints.Count - 1; i++) { - float dist = new Line(HitObject.Path.ControlPoints[i].Position.Value, HitObject.Path.ControlPoints[i + 1].Position.Value).DistanceToPoint(position); + float dist = new Line(controlPoints[i].Position.Value, controlPoints[i + 1].Position.Value).DistanceToPoint(position); if (dist < minDistance) { @@ -116,11 +126,42 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders } // Move the control points from the insertion index onwards to make room for the insertion - HitObject.Path.ControlPoints.Insert(insertionIndex, new PathControlPoint { Position = { Value = position } }); + controlPoints.Insert(insertionIndex, new PathControlPoint { Position = { Value = position } }); return insertionIndex; } + private void removeControlPoints(List toRemove) + { + // Ensure that there are any points to be deleted + if (toRemove.Count == 0) + return; + + foreach (var c in toRemove) + { + // The first control point in the slider must have a type, so take it from the previous "first" one + // Todo: Should be handled within SliderPath itself + if (c == controlPoints[0] && controlPoints.Count > 1 && controlPoints[1].Type.Value == null) + controlPoints[1].Type.Value = controlPoints[0].Type.Value; + + controlPoints.Remove(c); + } + + // If there are 0 or 1 remaining control points, the slider is in a degenerate (single point) form and should be deleted + if (controlPoints.Count <= 1) + { + placementHandler?.Delete(HitObject); + return; + } + + // The path will have a non-zero offset if the head is removed, but sliders don't support this behaviour since the head is positioned at the slider's position + // So the slider needs to be offset by this amount instead, and all control points offset backwards such that the path is re-positioned at (0, 0) + Vector2 first = controlPoints[0].Position.Value; + foreach (var c in controlPoints) + c.Position.Value -= first; + HitObject.Position += first; + } + private void updatePath() { HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; From 9963d18d17c00777a68d36768429c5ee9ebd4006 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 11 Dec 2019 19:13:04 +0900 Subject: [PATCH 2713/2815] Add whitespace --- osu.Game/Overlays/RankingsOverlay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index e7c8b94a10..c8874ef891 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -106,6 +106,7 @@ namespace osu.Game.Overlays Scheduler.AddOnce(loadNewContent); }, true); + Scope.BindValueChanged(_ => { // country filtering is only valid for performance scope. From 404d3207ffc0c963dedde39d10b27d7de5f890fd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 11 Dec 2019 19:43:32 +0900 Subject: [PATCH 2714/2815] Refactor ModNightcore/ModDaycore --- osu.Game/Rulesets/Mods/ModDaycore.cs | 12 ++++++++---- osu.Game/Rulesets/Mods/ModNightcore.cs | 12 ++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index 474e793dd1..9445895fb0 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -18,16 +18,20 @@ namespace osu.Game.Rulesets.Mods private readonly BindableNumber tempoAdjust = new BindableDouble(1); private readonly BindableNumber freqAdjust = new BindableDouble(1); - public override void ApplyToTrack(Track track) + public ModDaycore() { - track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); - track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); - SpeedChange.BindValueChanged(val => { freqAdjust.Value = SpeedChange.Default; tempoAdjust.Value = val.NewValue / SpeedChange.Default; }, true); } + + public override void ApplyToTrack(Track track) + { + // base.ApplyToTrack() intentionally not called (different tempo adjustment is applied) + track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); + track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); + } } } diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index 401814d18b..9b27925693 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -19,16 +19,20 @@ namespace osu.Game.Rulesets.Mods private readonly BindableNumber tempoAdjust = new BindableDouble(1); private readonly BindableNumber freqAdjust = new BindableDouble(1); - public override void ApplyToTrack(Track track) + public ModNightcore() { - track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); - track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); - SpeedChange.BindValueChanged(val => { freqAdjust.Value = SpeedChange.Default; tempoAdjust.Value = val.NewValue / SpeedChange.Default; }, true); } + + public override void ApplyToTrack(Track track) + { + // base.ApplyToTrack() intentionally not called (different tempo adjustment is applied) + track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); + track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); + } } } From c34b6b59eda2e6a62c5e1b1aca36b9e9c45f88c1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 11 Dec 2019 19:48:57 +0900 Subject: [PATCH 2715/2815] Remove time ramp and rate adjust mod incompatibility --- osu.Game/Rulesets/Mods/ModRateAdjust.cs | 2 -- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index 5aa3e09fee..9143836fb7 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -10,8 +10,6 @@ namespace osu.Game.Rulesets.Mods { public abstract class ModRateAdjust : Mod, IApplicableToTrack { - public override Type[] IncompatibleMods => new[] { typeof(ModTimeRamp) }; - public abstract BindableNumber SpeedChange { get; } public virtual void ApplyToTrack(Track track) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index bffe4f7b70..e10afa7d7c 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -20,8 +20,6 @@ namespace osu.Game.Rulesets.Mods /// private const double final_rate_progress = 0.75f; - public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust) }; - [SettingSource("Final rate", "The final speed to ramp to")] public abstract BindableNumber FinalRate { get; } From 40f918dce6a65a77589bd6a5800f510f82d1ff53 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 11 Dec 2019 19:49:32 +0900 Subject: [PATCH 2716/2815] Remove unused using --- osu.Game/Rulesets/Mods/ModRateAdjust.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index 9143836fb7..1739524bcd 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.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.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; From 47b6b0173913ad78ac3beaf702676b0e929c8515 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 20:11:48 +0900 Subject: [PATCH 2717/2815] Rename class to signify it is a drawable --- ...ointConnection.cs => PathControlPointConnectionPiece.cs} | 4 ++-- .../Sliders/Components/PathControlPointVisualiser.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) rename osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/{PathControlPointConnection.cs => PathControlPointConnectionPiece.cs} (92%) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnection.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs similarity index 92% rename from osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnection.cs rename to osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs index f57299c5a9..4dfe7834fd 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs @@ -11,7 +11,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { - public class PathControlPointConnection : CompositeDrawable + public class PathControlPointConnectionPiece : CompositeDrawable { public PathControlPoint ControlPoint; @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private IBindable sliderPosition; private IBindable pathVersion; - public PathControlPointConnection(Slider slider, PathControlPoint controlPoint) + public PathControlPointConnectionPiece(Slider slider, PathControlPoint controlPoint) { this.slider = slider; ControlPoint = controlPoint; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 64a8faa02a..a97c0b4a72 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { internal readonly Container Pieces; - private readonly Container connections; + private readonly Container connections; private readonly Slider slider; @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components InternalChildren = new Drawable[] { - connections = new Container { RelativeSizeAxes = Axes.Both }, + connections = new Container { RelativeSizeAxes = Axes.Both }, Pieces = new Container { RelativeSizeAxes = Axes.Both } }; } @@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components d.RequestSelection = selectPiece; })); - connections.Add(new PathControlPointConnection(slider, point)); + connections.Add(new PathControlPointConnectionPiece(slider, point)); } } From 50377e728637b48c96769e1917cc628e4e70a1c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 20:14:16 +0900 Subject: [PATCH 2718/2815] Add summary xmldoc --- .../Sliders/Components/PathControlPointConnectionPiece.cs | 3 +++ .../Blueprints/Sliders/Components/PathControlPointPiece.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs index 4dfe7834fd..0fc441fec6 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs @@ -11,6 +11,9 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { + /// + /// A visualisation of the line between two s. + /// public class PathControlPointConnectionPiece : CompositeDrawable { public PathControlPoint ControlPoint; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 42812ff934..6a0730db91 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -18,6 +18,9 @@ using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { + /// + /// A visualisation of a single in a . + /// public class PathControlPointPiece : BlueprintPiece { public Action RequestSelection; From caa9286a9091f2482ba4ba5b6879f4894cc675ad Mon Sep 17 00:00:00 2001 From: Albie Date: Wed, 11 Dec 2019 17:39:40 +0000 Subject: [PATCH 2719/2815] update tests, change binding and reduce lines in cinema mod --- .../Background/TestSceneUserDimContainer.cs | 15 +++++++++++---- osu.Game/Graphics/Containers/UserDimContainer.cs | 14 +++++++++++--- osu.Game/Rulesets/Mods/ModCinema.cs | 2 -- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index f867b98fda..fdfde9cc2f 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -155,9 +155,18 @@ namespace osu.Game.Tests.Visual.Background public void DisableUserDisplaySettingsTest() { performFullSetup(); - AddStep("Start ignoring user settings", () => player.DimmableStoryboard.IgnoreUserSettings.Value = true); + createFakeStoryboard(); + AddStep("Enable Storyboard", () => + { + player.ReplacesBackground.Value = true; + player.StoryboardEnabled.Value = true; + }); + AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true); + AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f); waitForDim(); - AddAssert("Check the background is undimmed", () => player.IsBackgroundUndimmed()); + AddAssert("Ignore User Settings", () => player.DimmableStoryboard.IgnoreUserSettings.Value = true); + waitForDim(); + AddAssert("User dim settings ignored", () => !player.DimmableStoryboard.EnableUserDim.Value && player.DimmableStoryboard.DimLevel == 0); } /// @@ -365,8 +374,6 @@ namespace osu.Game.Tests.Visual.Background public new DimmableStoryboard DimmableStoryboard => base.DimmableStoryboard; - public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).CurrentColour == OsuColour.Gray(1); - // Whether or not the player should be allowed to load. public bool BlockLoad; diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index bddbbca0ea..ae4e5557be 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -37,7 +37,7 @@ namespace osu.Game.Graphics.Containers /// public bool ContentDisplayed { get; private set; } - public double DimLevel => EnableUserDim.Value && !IgnoreUserSettings.Value ? UserDimLevel.Value : 0; + public double DimLevel => EnableUserDim.Value ? UserDimLevel.Value : 0; protected Bindable UserDimLevel { get; private set; } @@ -69,7 +69,7 @@ namespace osu.Game.Graphics.Containers ShowStoryboard.ValueChanged += _ => UpdateVisuals(); ShowVideo.ValueChanged += _ => UpdateVisuals(); StoryboardReplacesBackground.ValueChanged += _ => UpdateVisuals(); - IgnoreUserSettings.ValueChanged += _ => UpdateVisuals(); + IgnoreUserSettings.ValueChanged += _ => updateSettings(); } protected override void LoadComplete() @@ -91,7 +91,15 @@ namespace osu.Game.Graphics.Containers ContentDisplayed = ShowDimContent; dimContent.FadeTo(ContentDisplayed ? 1 : 0, BACKGROUND_FADE_DURATION, Easing.OutQuint); - dimContent.FadeColour(IgnoreUserSettings.Value ? Color4.White : OsuColour.Gray(1 - (float)DimLevel), BACKGROUND_FADE_DURATION, Easing.OutQuint); + dimContent.FadeColour(OsuColour.Gray(1 - (float)DimLevel), BACKGROUND_FADE_DURATION, Easing.OutQuint); + } + + /// + /// Invoked when the IgnoreUserSettings bindable is changed + /// + private void updateSettings() + { + EnableUserDim.Value = !IgnoreUserSettings.Value; } } } diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 77bf80b149..5faa2f4f3e 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -37,8 +37,6 @@ namespace osu.Game.Rulesets.Mods public void ApplyToPlayer(Player player) { player.Background.EnableUserDim.Value = false; - player.DimmableVideo.EnableUserDim.Value = false; - player.DimmableStoryboard.EnableUserDim.Value = false; player.DimmableVideo.IgnoreUserSettings.Value = true; player.DimmableStoryboard.IgnoreUserSettings.Value = true; From 2ca722423b76898ca121bf3dee1444f552f03d4a Mon Sep 17 00:00:00 2001 From: Albie Date: Wed, 11 Dec 2019 18:58:14 +0000 Subject: [PATCH 2720/2815] remove uneccesary using statement --- osu.Game/Graphics/Containers/UserDimContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index ae4e5557be..f83b85e023 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -6,7 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Configuration; -using osuTK.Graphics; namespace osu.Game.Graphics.Containers { From 663405d17d4ffeb6e74063db2fddd33f6715bc72 Mon Sep 17 00:00:00 2001 From: Albie Date: Wed, 11 Dec 2019 19:55:45 +0000 Subject: [PATCH 2721/2815] reduce test length and fix the poorly worded description --- .../Background/TestSceneUserDimContainer.cs | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index fdfde9cc2f..52f8fe0a2e 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -119,11 +119,7 @@ namespace osu.Game.Tests.Visual.Background { performFullSetup(); createFakeStoryboard(); - AddStep("Enable Storyboard", () => - { - player.ReplacesBackground.Value = true; - player.StoryboardEnabled.Value = true; - }); + enableStoryboard(); waitForDim(); AddAssert("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible); AddStep("Disable Storyboard", () => @@ -149,22 +145,17 @@ namespace osu.Game.Tests.Visual.Background } /// - /// Ensure is able to disable user-defined display settings. + /// Ensure can disable user-defined display settings. /// [Test] public void DisableUserDisplaySettingsTest() { performFullSetup(); createFakeStoryboard(); - AddStep("Enable Storyboard", () => - { - player.ReplacesBackground.Value = true; - player.StoryboardEnabled.Value = true; - }); + enableStoryboard(); AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true); - AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f); waitForDim(); - AddAssert("Ignore User Settings", () => player.DimmableStoryboard.IgnoreUserSettings.Value = true); + AddAssert("Ignore user settings", () => player.DimmableStoryboard.IgnoreUserSettings.Value = true); waitForDim(); AddAssert("User dim settings ignored", () => !player.DimmableStoryboard.EnableUserDim.Value && player.DimmableStoryboard.DimLevel == 0); } @@ -194,11 +185,7 @@ namespace osu.Game.Tests.Visual.Background { performFullSetup(); createFakeStoryboard(); - AddStep("Enable Storyboard", () => - { - player.ReplacesBackground.Value = true; - player.StoryboardEnabled.Value = true; - }); + enableStoryboard(); AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true); AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f); waitForDim(); @@ -285,6 +272,12 @@ namespace osu.Game.Tests.Visual.Background }); }); + private void enableStoryboard() => AddStep("Enable Storyboard", () => + { + player.ReplacesBackground.Value = true; + player.StoryboardEnabled.Value = true; + }); + private void performFullSetup(bool allowPause = false) { setupUserSettings(); From bc02cfc2e24cdaa26832f004b8ad8910b5b8342c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 12 Dec 2019 00:30:16 +0300 Subject: [PATCH 2722/2815] TestSceneUserDimContainer -> TestSceneUserDimBackgrounds --- ...tSceneUserDimContainer.cs => TestSceneUserDimBackgrounds.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Tests/Visual/Background/{TestSceneUserDimContainer.cs => TestSceneUserDimBackgrounds.cs} (99%) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs similarity index 99% rename from osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs rename to osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 910c462718..2d2726bbd3 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -36,7 +36,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Background { [TestFixture] - public class TestSceneUserDimContainer : ManualInputManagerTestScene + public class TestSceneUserDimBackgrounds : ManualInputManagerTestScene { public override IReadOnlyList RequiredTypes => new[] { From 4f6b85e5ea19381ab19a2ebdab80336bcf4a59e0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 12 Dec 2019 00:32:39 +0300 Subject: [PATCH 2723/2815] Add test ensuring correct break lightening behaviour --- .../Background/TestSceneUserDimContainer.cs | 82 +++++++++++++++++++ .../Graphics/Containers/UserDimContainer.cs | 5 +- 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs new file mode 100644 index 0000000000..0aad9bed75 --- /dev/null +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Background +{ + public class TestSceneUserDimContainer : OsuTestScene + { + private readonly TestUserDimContainer container; + private readonly BindableBool isBreakTime = new BindableBool(); + private readonly Bindable lightenDuringBreaks = new Bindable(); + + public TestSceneUserDimContainer() + { + Add(container = new TestUserDimContainer + { + RelativeSizeAxes = Axes.Both, + Child = new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + }, + }); + + container.IsBreakTime.BindTo(isBreakTime); + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + config.BindWith(OsuSetting.LightenDuringBreaks, lightenDuringBreaks); + } + + [SetUp] + public void SetUp() + { + isBreakTime.Value = false; + lightenDuringBreaks.Value = false; + } + + [TestCase(0.6f, 0.3f)] + [TestCase(0.2f, 0.0f)] + [TestCase(0.0f, 0.0f)] + public void TestBreakLightening(float userDim, float expectedBreakDim) + { + AddStep($"set dim level {userDim}", () => container.UserDimLevel.Value = userDim); + + AddStep("set break", () => isBreakTime.Value = true); + AddWaitStep("wait for dim", 3); + AddAssert($"is current dim {userDim}", () => container.DimEqual(userDim)); + + AddStep("set lighten during break", () => lightenDuringBreaks.Value = true); + AddWaitStep("wait for dim", 3); + AddAssert($"is current dim {expectedBreakDim}", () => container.DimEqual(expectedBreakDim)); + + AddStep("clear lighten during break", () => lightenDuringBreaks.Value = false); + AddWaitStep("wait for dim", 3); + AddAssert($"is current dim {userDim}", () => container.DimEqual(userDim)); + + AddStep("clear break", () => isBreakTime.Value = false); + AddStep("set lighten during break", () => lightenDuringBreaks.Value = true); + AddWaitStep("wait for dim", 3); + AddAssert($"is current dim {userDim}", () => container.DimEqual(userDim)); + } + + private class TestUserDimContainer : UserDimContainer + { + public bool DimEqual(float expectedDimLevel) => Content.Colour == OsuColour.Gray(1f - expectedDimLevel); + + public new Bindable UserDimLevel => base.UserDimLevel; + } + } +} diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 20f5a4fd83..dcc8a52e9d 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -16,6 +16,9 @@ namespace osu.Game.Graphics.Containers /// public abstract class UserDimContainer : Container { + /// + /// Amount of lightening to apply to current dim level during break times. + /// private const float break_lighten_amount = 0.3f; protected const double BACKGROUND_FADE_DURATION = 800; @@ -34,7 +37,7 @@ namespace osu.Game.Graphics.Containers /// Whether player is in break time. /// Must be bound to to allow for dim adjustments in gameplay. /// - internal readonly IBindable IsBreakTime = new Bindable(); + public readonly IBindable IsBreakTime = new Bindable(); /// /// Whether the content of this container is currently being displayed. From 61265ed452d14b2353bc5957ae5af6b25e442a2b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 12 Dec 2019 00:52:33 +0300 Subject: [PATCH 2724/2815] Increase the waiting steps --- .../Visual/Background/TestSceneUserDimContainer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index 0aad9bed75..bdbfacaea2 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -55,20 +55,20 @@ namespace osu.Game.Tests.Visual.Background AddStep($"set dim level {userDim}", () => container.UserDimLevel.Value = userDim); AddStep("set break", () => isBreakTime.Value = true); - AddWaitStep("wait for dim", 3); + AddWaitStep("wait for dim", 5); AddAssert($"is current dim {userDim}", () => container.DimEqual(userDim)); AddStep("set lighten during break", () => lightenDuringBreaks.Value = true); - AddWaitStep("wait for dim", 3); + AddWaitStep("wait for dim", 5); AddAssert($"is current dim {expectedBreakDim}", () => container.DimEqual(expectedBreakDim)); AddStep("clear lighten during break", () => lightenDuringBreaks.Value = false); - AddWaitStep("wait for dim", 3); + AddWaitStep("wait for dim", 5); AddAssert($"is current dim {userDim}", () => container.DimEqual(userDim)); AddStep("clear break", () => isBreakTime.Value = false); AddStep("set lighten during break", () => lightenDuringBreaks.Value = true); - AddWaitStep("wait for dim", 3); + AddWaitStep("wait for dim", 5); AddAssert($"is current dim {userDim}", () => container.DimEqual(userDim)); } From 035a53cb9e2edb0f232369aa933989b2c0b3fdb8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 12 Dec 2019 01:10:43 +0300 Subject: [PATCH 2725/2815] Schedule SetUp() --- osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index bdbfacaea2..25c11d2292 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -41,11 +41,11 @@ namespace osu.Game.Tests.Visual.Background } [SetUp] - public void SetUp() + public void SetUp() => Schedule(() => { isBreakTime.Value = false; lightenDuringBreaks.Value = false; - } + }); [TestCase(0.6f, 0.3f)] [TestCase(0.2f, 0.0f)] From 606bd33aa664fa4d50d15c0b23340ddb52c64274 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 13:04:32 +0900 Subject: [PATCH 2726/2815] Use beatmap background in editor --- osu.Game/Screens/Edit/Editor.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 33a4c48d28..1b4964c068 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -4,7 +4,6 @@ using System; using osuTK.Graphics; using osu.Framework.Screens; -using osu.Game.Screens.Backgrounds; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -29,14 +28,13 @@ using osu.Game.Input.Bindings; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Edit.Timing; +using osu.Game.Screens.Play; using osu.Game.Users; namespace osu.Game.Screens.Edit { - public class Editor : OsuScreen, IKeyBindingHandler + public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler { - protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg4"); - public override float BackgroundParallaxAmount => 0.1f; public override bool AllowBackButton => false; @@ -250,8 +248,12 @@ namespace osu.Game.Screens.Edit { base.OnEntering(last); + // todo: temporary. we want to be applying dim using the UserDimContainer eventually. Background.FadeColour(Color4.DarkGray, 500); + Background.EnableUserDim.Value = false; + Background.BlurAmount.Value = 0; + resetTrack(true); } From b6c86d512a5c7c5ffcb87b362e956c6457b8fd1d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 13:28:27 +0900 Subject: [PATCH 2727/2815] 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 3cd4dc48bf..252570a150 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -54,6 +54,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 530d62f583..a07348b57c 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 fb753b8c6f..544bba3963 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -74,7 +74,7 @@ - + @@ -82,7 +82,7 @@ - + From 76a7e9cde82d8a37670802b6ea2d1583b79b7c1c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 14:04:57 +0900 Subject: [PATCH 2728/2815] Catch file exception in test reset --- osu.Game/Database/DatabaseContextFactory.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index bb6bef1c50..1ed5fb3268 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -149,7 +149,15 @@ namespace osu.Game.Database lock (writeLock) { recycleThreadContexts(); - storage.DeleteDatabase(database_name); + + try + { + storage.DeleteDatabase(database_name); + } + catch + { + // for now we are not sure why file handles are kept open by EF, but this is generally only used in testing + } } } } From ad2528d4d2e03a1c355c84934dd73104b1afb81e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 15:05:29 +0900 Subject: [PATCH 2729/2815] Hide key counter along with other hud elements Also tidies up HUD hide logic and protects against incorrect hiding. --- osu.Game/Screens/Play/HUDOverlay.cs | 60 ++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 0f9edf5606..35157fca58 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.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.Graphics.Containers; using osu.Framework.Input.Events; @@ -43,8 +44,15 @@ namespace osu.Game.Screens.Play private readonly DrawableRuleset drawableRuleset; private readonly IReadOnlyList mods; - private Bindable showHud; + /// + /// Whether the elements that can optionally be hidden should be visible. + /// + public Bindable ShowHud { get; } = new BindableBool(); + + private Bindable configShowHud; + private readonly Container visibilityContainer; + private readonly BindableBool replayLoaded = new BindableBool(); private static bool hasShownNotificationOnce; @@ -53,6 +61,8 @@ namespace osu.Game.Screens.Play private readonly Container topScoreContainer; + private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter }; + public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods) { this.scoreProcessor = scoreProcessor; @@ -73,8 +83,6 @@ namespace osu.Game.Screens.Play Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, AutoSizeAxes = Axes.Both, - AutoSizeDuration = 200, - AutoSizeEasing = Easing.Out, Children = new Drawable[] { AccuracyCounter = CreateAccuracyCounter(), @@ -95,6 +103,8 @@ namespace osu.Game.Screens.Play Origin = Anchor.BottomRight, Position = -new Vector2(5, TwoLayerButton.SIZE_RETRACTED.Y), AutoSizeAxes = Axes.Both, + AutoSizeDuration = 150, + AutoSizeEasing = Easing.OutQuint, Direction = FillDirection.Vertical, Children = new Drawable[] { @@ -118,8 +128,29 @@ namespace osu.Game.Screens.Play ModDisplay.Current.Value = mods; - showHud = config.GetBindable(OsuSetting.ShowInterface); - showHud.BindValueChanged(visible => visibilityContainer.FadeTo(visible.NewValue ? 1 : 0, duration, easing), true); + configShowHud = config.GetBindable(OsuSetting.ShowInterface); + + if (!configShowHud.Value && !hasShownNotificationOnce) + { + hasShownNotificationOnce = true; + + notificationOverlay?.Post(new SimpleNotification + { + Text = @"The score overlay is currently disabled. You can toggle this by pressing Shift+Tab." + }); + } + + // start all elements hidden + hideTargets.ForEach(d => d.Hide()); + } + + public override void Hide() => throw new InvalidOperationException($"{nameof(HUDOverlay)} should not be hidden as it will remove the ability of a user to quit. Use {nameof(ShowHud)} instead."); + + protected override void LoadComplete() + { + base.LoadComplete(); + + ShowHud.BindValueChanged(visible => hideTargets.ForEach(d => d.FadeTo(visible.NewValue ? 1 : 0, duration, easing))); ShowHealthbar.BindValueChanged(healthBar => { @@ -135,20 +166,11 @@ namespace osu.Game.Screens.Play } }, true); - if (!showHud.Value && !hasShownNotificationOnce) + configShowHud.BindValueChanged(visible => { - hasShownNotificationOnce = true; - - notificationOverlay?.Post(new SimpleNotification - { - Text = @"The score overlay is currently disabled. You can toggle this by pressing Shift+Tab." - }); - } - } - - protected override void LoadComplete() - { - base.LoadComplete(); + if (!ShowHud.Disabled) + ShowHud.Value = visible.NewValue; + }, true); replayLoaded.BindValueChanged(replayLoadedValueChanged, true); } @@ -189,7 +211,7 @@ namespace osu.Game.Screens.Play switch (e.Key) { case Key.Tab: - showHud.Value = !showHud.Value; + configShowHud.Value = !configShowHud.Value; return true; } } From ffb5cdc6aef7056a8430cd76f79299d5ee087d84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 14:19:04 +0900 Subject: [PATCH 2730/2815] Hide settings overlay along with other HUD-hidden content --- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 35157fca58..64c3cddf2a 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -94,9 +94,9 @@ namespace osu.Game.Screens.Play Progress = CreateProgress(), ModDisplay = CreateModsContainer(), HitErrorDisplay = CreateHitErrorDisplayOverlay(), + PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), } }, - PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), new FillFlowContainer { Anchor = Anchor.BottomRight, From 6a539e307a2bdab3517ee840ee542dcf462c162c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 12 Dec 2019 09:20:56 +0300 Subject: [PATCH 2731/2815] Split into small tests and add more cases --- .../Background/TestSceneUserDimContainer.cs | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index 25c11d2292..03206402c3 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -53,23 +53,37 @@ namespace osu.Game.Tests.Visual.Background public void TestBreakLightening(float userDim, float expectedBreakDim) { AddStep($"set dim level {userDim}", () => container.UserDimLevel.Value = userDim); + AddStep("set lighten during break", () => lightenDuringBreaks.Value = true); AddStep("set break", () => isBreakTime.Value = true); - AddWaitStep("wait for dim", 5); - AddAssert($"is current dim {userDim}", () => container.DimEqual(userDim)); - - AddStep("set lighten during break", () => lightenDuringBreaks.Value = true); - AddWaitStep("wait for dim", 5); - AddAssert($"is current dim {expectedBreakDim}", () => container.DimEqual(expectedBreakDim)); - - AddStep("clear lighten during break", () => lightenDuringBreaks.Value = false); - AddWaitStep("wait for dim", 5); - AddAssert($"is current dim {userDim}", () => container.DimEqual(userDim)); - + AddUntilStep("has lightened", () => container.DimEqual(expectedBreakDim)); AddStep("clear break", () => isBreakTime.Value = false); + AddUntilStep("not lightened", () => container.DimEqual(userDim)); + } + + [Test] + public void TestEnableSettingDuringBreak() + { + AddStep("set dim level 0.6", () => container.UserDimLevel.Value = 0.6f); + + AddStep("set break", () => isBreakTime.Value = true); + AddUntilStep("not lightened", () => container.DimEqual(0.6f)); AddStep("set lighten during break", () => lightenDuringBreaks.Value = true); - AddWaitStep("wait for dim", 5); - AddAssert($"is current dim {userDim}", () => container.DimEqual(userDim)); + AddUntilStep("has lightened", () => container.DimEqual(0.3f)); + } + + [Test] + public void TestDisableSettingDuringBreak() + { + AddStep("set dim level 0.6", () => container.UserDimLevel.Value = 0.6f); + AddStep("set lighten during break", () => lightenDuringBreaks.Value = true); + + AddStep("set break", () => isBreakTime.Value = true); + AddUntilStep("has lightened", () => container.DimEqual(0.3f)); + AddStep("clear lighten during break", () => lightenDuringBreaks.Value = false); + AddUntilStep("not lightened", () => container.DimEqual(0.6f)); + AddStep("clear break", () => isBreakTime.Value = false); + AddUntilStep("not lightened", () => container.DimEqual(0.6f)); } private class TestUserDimContainer : UserDimContainer From 4c4199269c267c32ee651369e183c0e4d62a477a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 15:25:37 +0900 Subject: [PATCH 2732/2815] Use protected constructors --- osu.Game/Rulesets/Mods/ModDaycore.cs | 2 +- osu.Game/Rulesets/Mods/ModNightcore.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index 9445895fb0..71a666414f 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mods private readonly BindableNumber tempoAdjust = new BindableDouble(1); private readonly BindableNumber freqAdjust = new BindableDouble(1); - public ModDaycore() + protected ModDaycore() { SpeedChange.BindValueChanged(val => { diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index 9b27925693..c14e02e64d 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mods private readonly BindableNumber tempoAdjust = new BindableDouble(1); private readonly BindableNumber freqAdjust = new BindableDouble(1); - public ModNightcore() + protected ModNightcore() { SpeedChange.BindValueChanged(val => { From 5861eca80d291bbfd6261ca732d4101012a86713 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 12 Dec 2019 15:58:11 +0900 Subject: [PATCH 2733/2815] Make DrawableRuleset take a converted beatmap --- .../TestSceneDrawableHitObjects.cs | 2 +- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- .../UI/DrawableCatchRuleset.cs | 4 +- .../Edit/DrawableManiaEditRuleset.cs | 2 +- .../Edit/ManiaHitObjectComposer.cs | 2 +- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- .../UI/DrawableManiaRuleset.cs | 2 +- .../Edit/DrawableOsuEditRuleset.cs | 2 +- .../Edit/OsuHitObjectComposer.cs | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- .../UI/DrawableOsuRuleset.cs | 2 +- .../TestSceneTaikoPlayfield.cs | 3 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- .../UI/DrawableTaikoRuleset.cs | 2 +- .../TestSceneDrawableScrollingRuleset.cs | 6 +-- osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 2 +- osu.Game/Beatmaps/IWorkingBeatmap.cs | 2 +- osu.Game/Beatmaps/WorkingBeatmap.cs | 4 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 8 ++-- osu.Game/Rulesets/Ruleset.cs | 2 +- osu.Game/Rulesets/UI/DrawableRuleset.cs | 18 ++++---- .../UI/Scrolling/DrawableScrollingRuleset.cs | 2 +- osu.Game/Screens/Play/Player.cs | 41 ++++++++++--------- 23 files changed, 59 insertions(+), 57 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs index 0369b6db4e..02a017ce45 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Catch.Tests RelativeSizeAxes = Axes.Both, Children = new[] { - drawableRuleset = new DrawableCatchRuleset(new CatchRuleset(), beatmap, Array.Empty()) + drawableRuleset = new DrawableCatchRuleset(new CatchRuleset(), beatmap.GetPlayableBeatmap(new CatchRuleset().RulesetInfo)) } }); diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 71d68ace94..506fa23fa9 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Catch { public class CatchRuleset : Ruleset { - public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableCatchRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableCatchRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap); diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index f5bddeb2cb..278ff97195 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -25,11 +25,11 @@ namespace osu.Game.Rulesets.Catch.UI protected override bool UserScrollSpeedAdjustment => false; - public DrawableCatchRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) + public DrawableCatchRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) : base(ruleset, beatmap, mods) { Direction.Value = ScrollingDirection.Down; - TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450); + TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450); } public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(Beatmap); diff --git a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs index 97d8aaa052..445df79f6f 100644 --- a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs +++ b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Edit { public new IScrollingInfo ScrollingInfo => base.ScrollingInfo; - public DrawableManiaEditRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) + public DrawableManiaEditRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 0bfe6f9517..1632b6a583 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.Edit public int TotalColumns => ((ManiaPlayfield)drawableRuleset.Playfield).TotalColumns; - protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) { drawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap, mods); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index c74a292331..a96c79b40b 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mania { public class ManiaRuleset : Ruleset { - public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableManiaRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableManiaRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score); diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 0607bf0abd..cf1970c28b 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.UI private readonly Bindable configDirection = new Bindable(); - public DrawableManiaRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) + public DrawableManiaRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) : base(ruleset, beatmap, mods) { BarLines = new BarLineGenerator(Beatmap).BarLines; diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index 3437af8c1e..22b4c3e82e 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Edit /// private const double editor_hit_object_fade_out_extension = 500; - public DrawableOsuEditRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) + public DrawableOsuEditRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 812afaaa24..675b09fc6d 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Edit { } - protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableOsuEditRuleset(ruleset, beatmap, mods); protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index fa69cec78d..2f43909332 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu { public class OsuRuleset : Ruleset { - public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableOsuRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableOsuRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap); diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index 5bb728a9b0..039d38e4fd 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.UI { protected new OsuRulesetConfigManager Config => (OsuRulesetConfigManager)base.Config; - public DrawableOsuRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) + public DrawableOsuRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) : base(ruleset, beatmap, mods) { } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs index 8522a42739..b2c8c7feda 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs @@ -11,7 +11,6 @@ using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; @@ -91,7 +90,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, Height = 768, - Children = new[] { drawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap, Array.Empty()) } + Children = new[] { drawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(new TaikoRuleset().RulesetInfo)) } }); } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index b2655f592c..ab9c95159c 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko { public class TaikoRuleset : Ruleset { - public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableTaikoRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableTaikoRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap); public const string SHORT_NAME = "taiko"; diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index d4ea9a043a..2233658428 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool UserScrollSpeedAdjustment => false; - public DrawableTaikoRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) + public DrawableTaikoRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) : base(ruleset, beatmap, mods) { Direction.Value = ScrollingDirection.Left; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index 684e79b3f5..071dc381e0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -172,7 +172,7 @@ namespace osu.Game.Tests.Visual.Gameplay { var ruleset = new TestScrollingRuleset(); - drawableRuleset = (TestDrawableScrollingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap), Array.Empty()); + drawableRuleset = (TestDrawableScrollingRuleset)ruleset.CreateDrawableRulesetWith(beatmap); drawableRuleset.FrameStablePlayback = false; overrideAction?.Invoke(drawableRuleset); @@ -201,7 +201,7 @@ namespace osu.Game.Tests.Visual.Gameplay public override IEnumerable GetModsFor(ModType type) => throw new NotImplementedException(); - public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new TestDrawableScrollingRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new TestDrawableScrollingRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TestBeatmapConverter(beatmap); @@ -222,7 +222,7 @@ namespace osu.Game.Tests.Visual.Gameplay public new Bindable TimeRange => base.TimeRange; - public TestDrawableScrollingRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) + public TestDrawableScrollingRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) : base(ruleset, beatmap, mods) { TimeRange.Value = time_range; diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index a3ab01c886..59a27e3fde 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -57,7 +57,7 @@ namespace osu.Game.Beatmaps { public override IEnumerable GetModsFor(ModType type) => new Mod[] { }; - public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) { throw new NotImplementedException(); } diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index a087a52ada..5f1f0d1e40 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -62,6 +62,6 @@ namespace osu.Game.Beatmaps /// The s to apply to the . /// The converted . /// If could not be converted to . - IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods); + IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null); } } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 4452d26fcd..1255665cf0 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -99,8 +99,10 @@ namespace osu.Game.Beatmaps /// The applicable . protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap); - public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods) + public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null) { + mods ??= Array.Empty(); + var rulesetInstance = ruleset.CreateInstance(); IBeatmapConverter converter = CreateBeatmapConverter(Beatmap, rulesetInstance); diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 9ac967ef74..22d94abcb9 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -48,7 +48,6 @@ namespace osu.Game.Rulesets.Edit [Resolved] private BindableBeatDivisor beatDivisor { get; set; } - private IWorkingBeatmap workingBeatmap; private Beatmap playableBeatmap; private IBeatmapProcessor beatmapProcessor; @@ -71,7 +70,7 @@ namespace osu.Game.Rulesets.Edit { try { - drawableRulesetWrapper = new DrawableEditRulesetWrapper(CreateDrawableRuleset(Ruleset, workingBeatmap, Array.Empty())) + drawableRulesetWrapper = new DrawableEditRulesetWrapper(CreateDrawableRuleset(Ruleset, playableBeatmap)) { Clock = framedClock, ProcessCustomClock = false @@ -145,8 +144,7 @@ namespace osu.Game.Rulesets.Edit { var parentWorkingBeatmap = parent.Get>().Value; - playableBeatmap = (Beatmap)parentWorkingBeatmap.GetPlayableBeatmap(Ruleset.RulesetInfo, Array.Empty()); - workingBeatmap = new EditorWorkingBeatmap(playableBeatmap, parentWorkingBeatmap); + playableBeatmap = (Beatmap)parentWorkingBeatmap.GetPlayableBeatmap(Ruleset.RulesetInfo); beatmapProcessor = Ruleset.CreateBeatmapProcessor(playableBeatmap); @@ -250,7 +248,7 @@ namespace osu.Game.Rulesets.Edit protected abstract IReadOnlyList CompositionTools { get; } - protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods); + protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null); public void BeginPlacement(HitObject hitObject) { diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index dd1b3615c7..45aa904b98 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets /// The s to apply. /// Unable to successfully load the beatmap to be usable with this ruleset. /// - public abstract DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods); + public abstract DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null); /// /// Creates a to convert a to one that is applicable for this . diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 10657f6b39..5033fd0686 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.UI /// /// The beatmap. /// - public Beatmap Beatmap; + public readonly Beatmap Beatmap; public override IEnumerable Objects => Beatmap.HitObjects; @@ -118,20 +118,22 @@ namespace osu.Game.Rulesets.UI /// Creates a ruleset visualisation for the provided ruleset and beatmap. /// /// The ruleset being represented. - /// The beatmap to create the hit renderer for. + /// The beatmap to create the hit renderer for. /// The s to apply. - protected DrawableRuleset(Ruleset ruleset, IWorkingBeatmap workingBeatmap, IReadOnlyList mods) + protected DrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) : base(ruleset) { - if (workingBeatmap == null) - throw new ArgumentException("Beatmap cannot be null.", nameof(workingBeatmap)); + if (beatmap == null) + throw new ArgumentNullException(nameof(beatmap), "Beatmap cannot be null."); - this.mods = mods.ToArray(); + if (!(beatmap is Beatmap tBeatmap)) + throw new ArgumentException($"{GetType()} expected the beatmap to contain hitobjects of type {typeof(TObject)}.", nameof(beatmap)); + + Beatmap = tBeatmap; + this.mods = mods?.ToArray() ?? Array.Empty(); RelativeSizeAxes = Axes.Both; - Beatmap = (Beatmap)workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods); - KeyBindingInputManager = CreateInputManager(); playfield = new Lazy(CreatePlayfield); diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index cf714b5d46..fda1d7c723 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.UI.Scrolling [Cached(Type = typeof(IScrollingInfo))] private readonly LocalScrollingInfo scrollingInfo; - protected DrawableScrollingRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) + protected DrawableScrollingRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) : base(ruleset, beatmap, mods) { scrollingInfo = new LocalScrollingInfo(); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 9f4ca7d817..b65d20dcbb 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -114,26 +114,31 @@ namespace osu.Game.Screens.Play Mods.Value = base.Mods.Value.Select(m => m.CreateCopy()).ToArray(); - WorkingBeatmap working = loadBeatmap(); + if (Beatmap.Value is DummyWorkingBeatmap) + return; - if (working == null) + IBeatmap playableBeatmap = loadPlayableBeatmap(); + + if (playableBeatmap == null) return; sampleRestart = audio.Samples.Get(@"Gameplay/restart"); mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); + DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); + ScoreProcessor = DrawableRuleset.CreateScoreProcessor(); ScoreProcessor.Mods.BindTo(Mods); if (!ScoreProcessor.Mode.Disabled) config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); - InternalChild = GameplayClockContainer = new GameplayClockContainer(working, Mods.Value, DrawableRuleset.GameplayStartTime); + InternalChild = GameplayClockContainer = new GameplayClockContainer(Beatmap.Value, Mods.Value, DrawableRuleset.GameplayStartTime); addUnderlayComponents(GameplayClockContainer); - addGameplayComponents(GameplayClockContainer, working); - addOverlayComponents(GameplayClockContainer, working); + addGameplayComponents(GameplayClockContainer, Beatmap.Value); + addOverlayComponents(GameplayClockContainer, Beatmap.Value); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); @@ -250,36 +255,32 @@ namespace osu.Game.Screens.Play && !DrawableRuleset.HasReplayLoaded.Value && !breakOverlay.IsBreakTime.Value; - private WorkingBeatmap loadBeatmap() + private IBeatmap loadPlayableBeatmap() { - WorkingBeatmap working = Beatmap.Value; - if (working is DummyWorkingBeatmap) - return null; + IBeatmap playable; try { - var beatmap = working.Beatmap; - - if (beatmap == null) + if (Beatmap.Value.Beatmap == null) throw new InvalidOperationException("Beatmap was not loaded"); - rulesetInfo = Ruleset.Value ?? beatmap.BeatmapInfo.Ruleset; + rulesetInfo = Ruleset.Value ?? Beatmap.Value.BeatmapInfo.Ruleset; ruleset = rulesetInfo.CreateInstance(); try { - DrawableRuleset = ruleset.CreateDrawableRulesetWith(working, Mods.Value); + playable = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Mods.Value); } catch (BeatmapInvalidForRulesetException) { - // we may fail to create a DrawableRuleset if the beatmap cannot be loaded with the user's preferred ruleset - // let's try again forcing the beatmap's ruleset. - rulesetInfo = beatmap.BeatmapInfo.Ruleset; + // A playable beatmap may not be creatable with the user's preferred ruleset, so try using the beatmap's default ruleset + rulesetInfo = Beatmap.Value.BeatmapInfo.Ruleset; ruleset = rulesetInfo.CreateInstance(); - DrawableRuleset = ruleset.CreateDrawableRulesetWith(Beatmap.Value, Mods.Value); + + playable = Beatmap.Value.GetPlayableBeatmap(rulesetInfo, Mods.Value); } - if (!DrawableRuleset.Objects.Any()) + if (playable.HitObjects.Count == 0) { Logger.Log("Beatmap contains no hit objects!", level: LogLevel.Error); return null; @@ -292,7 +293,7 @@ namespace osu.Game.Screens.Play return null; } - return working; + return playable; } private void performImmediateExit() From 59345c97e4d3ec412d4d1b519f3021eb3988d70c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 12 Dec 2019 15:58:31 +0900 Subject: [PATCH 2734/2815] Remove now unnecessary editor working beatmap --- osu.Game/Screens/Edit/EditorWorkingBeatmap.cs | 49 ------------------- 1 file changed, 49 deletions(-) delete mode 100644 osu.Game/Screens/Edit/EditorWorkingBeatmap.cs diff --git a/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs b/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs deleted file mode 100644 index 4b8720fe1c..0000000000 --- a/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs +++ /dev/null @@ -1,49 +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 System.Collections.Generic; -using osu.Framework.Audio.Track; -using osu.Framework.Graphics.Textures; -using osu.Framework.Graphics.Video; -using osu.Game.Beatmaps; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; -using osu.Game.Skinning; -using osu.Game.Storyboards; - -namespace osu.Game.Screens.Edit -{ - /// - /// Encapsulates a while providing an overridden . - /// - /// - public class EditorWorkingBeatmap : IWorkingBeatmap - where TObject : HitObject - { - private readonly Beatmap playableBeatmap; - private readonly WorkingBeatmap workingBeatmap; - - public EditorWorkingBeatmap(Beatmap playableBeatmap, WorkingBeatmap workingBeatmap) - { - this.playableBeatmap = playableBeatmap; - this.workingBeatmap = workingBeatmap; - } - - public IBeatmap Beatmap => workingBeatmap.Beatmap; - - public Texture Background => workingBeatmap.Background; - - public VideoSprite Video => workingBeatmap.Video; - - public Track Track => workingBeatmap.Track; - - public Waveform Waveform => workingBeatmap.Waveform; - - public Storyboard Storyboard => workingBeatmap.Storyboard; - - public ISkin Skin => workingBeatmap.Skin; - - public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods) => playableBeatmap; - } -} From 3ccfee64f6ff44c82f3b66270f790b951c745da5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 16:09:42 +0900 Subject: [PATCH 2735/2815] Add HUDOverlay tests --- .../Visual/Gameplay/TestSceneHUDOverlay.cs | 81 +++++++++++++++++++ osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 7 +- osu.Game/Screens/Play/HUDOverlay.cs | 19 +++-- 3 files changed, 98 insertions(+), 9 deletions(-) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs new file mode 100644 index 0000000000..39c42980ab --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.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; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Configuration; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneHUDOverlay : ManualInputManagerTestScene + { + private HUDOverlay hudOverlay; + + private Drawable hideTarget => hudOverlay.KeyCounter; // best way of checking hideTargets without exposing. + + [Resolved] + private OsuConfigManager config { get; set; } + + [Test] + public void TestShownByDefault() + { + createNew(); + + AddAssert("showhud is set", () => hudOverlay.ShowHud.Value); + + AddAssert("hidetarget is visible", () => hideTarget.IsPresent); + AddAssert("pause button is visible", () => hudOverlay.HoldToQuit.IsPresent); + } + + [Test] + public void TestFadesInOnLoadComplete() + { + float? initialAlpha = null; + + createNew(h => h.OnLoadComplete += _ => initialAlpha = hideTarget.Alpha); + AddUntilStep("wait for load", () => hudOverlay.IsAlive); + AddAssert("initial alpha was less than 1", () => initialAlpha != null && initialAlpha < 1); + } + + [Test] + public void TestHideExternally() + { + createNew(); + + AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); + + AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent); + AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent); + } + + [Test] + public void TestExternalHideDoesntAffectConfig() + { + bool originalConfigValue = false; + + createNew(); + + AddStep("get original config value", () => originalConfigValue = config.Get(OsuSetting.ShowInterface)); + + AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); + AddAssert("config unchanged", () => originalConfigValue == config.Get(OsuSetting.ShowInterface)); + + AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true); + AddAssert("config unchanged", () => originalConfigValue == config.Get(OsuSetting.ShowInterface)); + } + + private void createNew(Action action = null) + { + AddStep("create overlay", () => + { + Child = hudOverlay = new HUDOverlay(null, null, Array.Empty()); + + action?.Invoke(hudOverlay); + }); + } + } +} diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 54556f8648..6196ce4026 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -31,7 +31,8 @@ namespace osu.Game.Screens.Play.HUD RelativeSizeAxes = Axes.Both; - processor.NewJudgement += onNewJudgement; + if (processor != null) + processor.NewJudgement += onNewJudgement; } [BackgroundDependencyLoader] @@ -96,7 +97,9 @@ namespace osu.Game.Screens.Play.HUD protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - processor.NewJudgement -= onNewJudgement; + + if (processor != null) + processor.NewJudgement -= onNewJudgement; } } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 64c3cddf2a..7df780b678 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -118,13 +118,18 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader(true)] private void load(OsuConfigManager config, NotificationOverlay notificationOverlay) { - BindProcessor(scoreProcessor); - BindDrawableRuleset(drawableRuleset); + if (scoreProcessor != null) + BindProcessor(scoreProcessor); - Progress.Objects = drawableRuleset.Objects; - Progress.AllowSeeking = drawableRuleset.HasReplayLoaded.Value; - Progress.RequestSeek = time => RequestSeek(time); - Progress.ReferenceClock = drawableRuleset.FrameStableClock; + if (drawableRuleset != null) + { + BindDrawableRuleset(drawableRuleset); + + Progress.Objects = drawableRuleset.Objects; + Progress.AllowSeeking = drawableRuleset.HasReplayLoaded.Value; + Progress.RequestSeek = time => RequestSeek(time); + Progress.ReferenceClock = drawableRuleset.FrameStableClock; + } ModDisplay.Current.Value = mods; @@ -279,7 +284,7 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20, Right = 10 }, }; - protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset.FirstAvailableHitWindows); + protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset?.FirstAvailableHitWindows); protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay(); From 94f3dbb2f65afef1aaff9f63a7070efa11f0985b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 16:09:50 +0900 Subject: [PATCH 2736/2815] Adjust transitions slightly --- osu.Game/Screens/Play/HUDOverlay.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 7df780b678..dc32fc7cd5 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -24,8 +24,8 @@ namespace osu.Game.Screens.Play { public class HUDOverlay : Container { - private const int duration = 250; - private const Easing easing = Easing.OutQuint; + private const int fade_duration = 400; + private const Easing fade_easing = Easing.Out; public readonly KeyCounterDisplay KeyCounter; public readonly RollingCounter ComboCounter; @@ -103,8 +103,8 @@ namespace osu.Game.Screens.Play Origin = Anchor.BottomRight, Position = -new Vector2(5, TwoLayerButton.SIZE_RETRACTED.Y), AutoSizeAxes = Axes.Both, - AutoSizeDuration = 150, - AutoSizeEasing = Easing.OutQuint, + AutoSizeDuration = fade_duration, + AutoSizeEasing = fade_easing, Direction = FillDirection.Vertical, Children = new Drawable[] { @@ -155,19 +155,19 @@ namespace osu.Game.Screens.Play { base.LoadComplete(); - ShowHud.BindValueChanged(visible => hideTargets.ForEach(d => d.FadeTo(visible.NewValue ? 1 : 0, duration, easing))); + ShowHud.BindValueChanged(visible => hideTargets.ForEach(d => d.FadeTo(visible.NewValue ? 1 : 0, fade_duration, fade_easing))); ShowHealthbar.BindValueChanged(healthBar => { if (healthBar.NewValue) { - HealthDisplay.FadeIn(duration, easing); - topScoreContainer.MoveToY(30, duration, easing); + HealthDisplay.FadeIn(fade_duration, fade_easing); + topScoreContainer.MoveToY(30, fade_duration, fade_easing); } else { - HealthDisplay.FadeOut(duration, easing); - topScoreContainer.MoveToY(0, duration, easing); + HealthDisplay.FadeOut(fade_duration, fade_easing); + topScoreContainer.MoveToY(0, fade_duration, fade_easing); } }, true); From e4297ffeaded24ea61f8fd188e7fb92215d70674 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 15:09:55 +0900 Subject: [PATCH 2737/2815] Hide HUD in a better way --- osu.Game/Rulesets/Mods/ModCinema.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 5faa2f4f3e..5262813b08 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -30,8 +30,8 @@ namespace osu.Game.Rulesets.Mods public void ApplyToHUD(HUDOverlay overlay) { - overlay.AlwaysPresent = true; - overlay.Hide(); + overlay.ShowHud.Value = false; + overlay.ShowHud.Disabled = true; } public void ApplyToPlayer(Player player) From 99280db69487c1f0b8542031c8d6037fe875084e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 15:16:34 +0900 Subject: [PATCH 2738/2815] Add note about AlwaysPresent requirement --- osu.Game/Rulesets/Mods/ModCinema.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 5262813b08..1e4cf66711 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Mods { drawableRuleset.SetReplayScore(CreateReplayScore(drawableRuleset.Beatmap)); + // AlwaysPresent required for hitsounds drawableRuleset.Playfield.AlwaysPresent = true; drawableRuleset.Playfield.Hide(); } From d15f49f60f33c5beba335b75f0e0b8d40fc8ee10 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 15:14:59 +0900 Subject: [PATCH 2739/2815] Also hide the break overlay --- osu.Game/Rulesets/Mods/ModCinema.cs | 2 ++ osu.Game/Screens/Play/Player.cs | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 1e4cf66711..3487d49e08 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -41,6 +41,8 @@ namespace osu.Game.Rulesets.Mods player.DimmableVideo.IgnoreUserSettings.Value = true; player.DimmableStoryboard.IgnoreUserSettings.Value = true; + + player.BreakOverlay.Hide(); } } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 7abd60b3c1..1d1252063f 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -69,7 +69,7 @@ namespace osu.Game.Screens.Play private SampleChannel sampleRestart; - private BreakOverlay breakOverlay; + public BreakOverlay BreakOverlay; protected ScoreProcessor ScoreProcessor { get; private set; } protected DrawableRuleset DrawableRuleset { get; private set; } @@ -149,7 +149,7 @@ namespace osu.Game.Screens.Play foreach (var mod in Mods.Value.OfType()) mod.ApplyToScoreProcessor(ScoreProcessor); - breakOverlay.IsBreakTime.ValueChanged += _ => updatePauseOnFocusLostState(); + BreakOverlay.IsBreakTime.ValueChanged += _ => updatePauseOnFocusLostState(); } private void addUnderlayComponents(Container target) @@ -183,7 +183,7 @@ namespace osu.Game.Screens.Play { target.AddRange(new[] { - breakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, DrawableRuleset.GameplayStartTime, ScoreProcessor) + BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, DrawableRuleset.GameplayStartTime, ScoreProcessor) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -248,7 +248,7 @@ namespace osu.Game.Screens.Play private void updatePauseOnFocusLostState() => HUDOverlay.HoldToQuit.PauseOnFocusLost = PauseOnFocusLost && !DrawableRuleset.HasReplayLoaded.Value - && !breakOverlay.IsBreakTime.Value; + && !BreakOverlay.IsBreakTime.Value; private WorkingBeatmap loadBeatmap() { @@ -477,7 +477,7 @@ namespace osu.Game.Screens.Play PauseOverlay.Hide(); // breaks and time-based conditions may allow instant resume. - if (breakOverlay.IsBreakTime.Value) + if (BreakOverlay.IsBreakTime.Value) completeResume(); else DrawableRuleset.RequestResume(completeResume); From 1807fc9b6153e7c02881bae15efd00c5545c4d88 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 12 Dec 2019 16:48:33 +0900 Subject: [PATCH 2740/2815] Fix testcase not converting beatmap --- .../Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index 071dc381e0..c958932730 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -172,7 +172,7 @@ namespace osu.Game.Tests.Visual.Gameplay { var ruleset = new TestScrollingRuleset(); - drawableRuleset = (TestDrawableScrollingRuleset)ruleset.CreateDrawableRulesetWith(beatmap); + drawableRuleset = (TestDrawableScrollingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap).GetPlayableBeatmap(ruleset.RulesetInfo)); drawableRuleset.FrameStablePlayback = false; overrideAction?.Invoke(drawableRuleset); From a0792f82e8bb8031cd97c22cdadd6109a0445e04 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 17:05:47 +0900 Subject: [PATCH 2741/2815] Re-jig mod select logic to reduce event fires --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index e8ea43e3f2..a6f9642a56 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -360,8 +360,8 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); + SelectedMods.BindValueChanged(selectedModsChanged); Ruleset.BindValueChanged(rulesetChanged, true); - SelectedMods.BindValueChanged(selectedModsChanged, true); } protected override void PopOut() From c4bc57484fdee19279ac616d690891494d67e0a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 17:12:01 +0900 Subject: [PATCH 2742/2815] Fix test logic and add regression test --- .../UserInterface/TestSceneModSettings.cs | 84 ++++++++++++++----- 1 file changed, 62 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index fc44c5f595..8117a4ad78 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -2,15 +2,20 @@ // 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 NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; namespace osu.Game.Tests.Visual.UserInterface { @@ -18,28 +23,51 @@ namespace osu.Game.Tests.Visual.UserInterface { private TestModSelectOverlay modSelect; - [BackgroundDependencyLoader] - private void load() + Mod testCustomisableMod = new TestModCustomisable1(); + + [Test] + public void TestButtonShowsOnCustomisableMod() { - Add(modSelect = new TestModSelectOverlay - { - RelativeSizeAxes = Axes.X, - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - }); + createModSelect(); - var testMod = new TestModCustomisable1(); - - AddStep("open", modSelect.Show); + AddStep("open", () => modSelect.Show()); AddAssert("button disabled", () => !modSelect.CustomiseButton.Enabled.Value); AddUntilStep("wait for button load", () => modSelect.ButtonsLoaded); - AddStep("select mod", () => modSelect.SelectMod(testMod)); + AddStep("select mod", () => modSelect.SelectMod(testCustomisableMod)); AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value); AddStep("open Customisation", () => modSelect.CustomiseButton.Click()); - AddStep("deselect mod", () => modSelect.SelectMod(testMod)); + AddStep("deselect mod", () => modSelect.SelectMod(testCustomisableMod)); AddAssert("controls hidden", () => modSelect.ModSettingsContainer.Alpha == 0); } + [Test] + public void TestButtonShowsOnModAlreadyAdded() + { + AddStep("set active mods", () => Mods.Value = new List { testCustomisableMod }); + + createModSelect(); + + AddAssert("mods still active", () => Mods.Value.Count == 1); + + AddStep("open", () => modSelect.Show()); + AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value); + } + + private void createModSelect() + { + AddStep("create mod select", () => + { + Ruleset.Value = new TestRulesetInfo(); + + Child = modSelect = new TestModSelectOverlay + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + }; + }); + } + private class TestModSelectOverlay : ModSelectOverlay { public new Container ModSettingsContainer => base.ModSettingsContainer; @@ -50,24 +78,36 @@ namespace osu.Game.Tests.Visual.UserInterface public void SelectMod(Mod mod) => ModSectionsContainer.Children.Single(s => s.ModType == mod.Type) .ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())).SelectNext(1); + } - protected override void LoadComplete() + public class TestRulesetInfo : RulesetInfo + { + public override Ruleset CreateInstance() => new TestCustomisableModRuleset(); + + public class TestCustomisableModRuleset : Ruleset { - base.LoadComplete(); - - foreach (var section in ModSectionsContainer) + public override IEnumerable GetModsFor(ModType type) { - if (section.ModType == ModType.Conversion) + if (type == ModType.Conversion) { - section.Mods = new Mod[] + return new Mod[] { new TestModCustomisable1(), new TestModCustomisable2() }; } - else - section.Mods = Array.Empty(); + + return Array.Empty(); } + + public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => throw new NotImplementedException(); + + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException(); + + public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException(); + + public override string Description { get; } + public override string ShortName { get; } } } From 623ab1ef3be8f3ef21b71797622894690b21d735 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 17:28:31 +0900 Subject: [PATCH 2743/2815] Update time ramp preview on setting change --- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index e10afa7d7c..5276c196f7 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -35,6 +35,12 @@ namespace osu.Game.Rulesets.Mods private Track track; + public ModTimeRamp() + { + // for preview purpose at song select. eventually we'll want to be able to update every frame. + FinalRate.BindValueChanged(val => applyAdjustment(1), true); + } + public void ApplyToTrack(Track track) { this.track = track; From 5e634c118309cf56ccd14ce43d5681293aff529f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 20:51:58 +0900 Subject: [PATCH 2744/2815] Move test values to constants --- .../Background/TestSceneUserDimContainer.cs | 19 +++++++++++-------- .../Graphics/Containers/UserDimContainer.cs | 4 ++-- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index 03206402c3..8eb256538a 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -47,7 +47,10 @@ namespace osu.Game.Tests.Visual.Background lightenDuringBreaks.Value = false; }); - [TestCase(0.6f, 0.3f)] + private const float test_user_dim = 0.6f; + private const float test_user_dim_lightened = test_user_dim - UserDimContainer.BREAK_LIGHTEN_AMOUNT; + + [TestCase(test_user_dim, test_user_dim_lightened)] [TestCase(0.2f, 0.0f)] [TestCase(0.0f, 0.0f)] public void TestBreakLightening(float userDim, float expectedBreakDim) @@ -64,26 +67,26 @@ namespace osu.Game.Tests.Visual.Background [Test] public void TestEnableSettingDuringBreak() { - AddStep("set dim level 0.6", () => container.UserDimLevel.Value = 0.6f); + AddStep("set dim level 0.6", () => container.UserDimLevel.Value = test_user_dim); AddStep("set break", () => isBreakTime.Value = true); - AddUntilStep("not lightened", () => container.DimEqual(0.6f)); + AddUntilStep("not lightened", () => container.DimEqual(test_user_dim)); AddStep("set lighten during break", () => lightenDuringBreaks.Value = true); - AddUntilStep("has lightened", () => container.DimEqual(0.3f)); + AddUntilStep("has lightened", () => container.DimEqual(test_user_dim_lightened)); } [Test] public void TestDisableSettingDuringBreak() { - AddStep("set dim level 0.6", () => container.UserDimLevel.Value = 0.6f); + AddStep("set dim level 0.6", () => container.UserDimLevel.Value = test_user_dim); AddStep("set lighten during break", () => lightenDuringBreaks.Value = true); AddStep("set break", () => isBreakTime.Value = true); - AddUntilStep("has lightened", () => container.DimEqual(0.3f)); + AddUntilStep("has lightened", () => container.DimEqual(test_user_dim_lightened)); AddStep("clear lighten during break", () => lightenDuringBreaks.Value = false); - AddUntilStep("not lightened", () => container.DimEqual(0.6f)); + AddUntilStep("not lightened", () => container.DimEqual(test_user_dim)); AddStep("clear break", () => isBreakTime.Value = false); - AddUntilStep("not lightened", () => container.DimEqual(0.6f)); + AddUntilStep("not lightened", () => container.DimEqual(test_user_dim)); } private class TestUserDimContainer : UserDimContainer diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index dcc8a52e9d..e67cd94d5c 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -19,7 +19,7 @@ namespace osu.Game.Graphics.Containers /// /// Amount of lightening to apply to current dim level during break times. /// - private const float break_lighten_amount = 0.3f; + public const float BREAK_LIGHTEN_AMOUNT = 0.3f; protected const double BACKGROUND_FADE_DURATION = 800; @@ -52,7 +52,7 @@ namespace osu.Game.Graphics.Containers protected Bindable ShowVideo { get; private set; } - private float breakLightening => LightenDuringBreaks.Value && IsBreakTime.Value ? break_lighten_amount : 0; + private float breakLightening => LightenDuringBreaks.Value && IsBreakTime.Value ? BREAK_LIGHTEN_AMOUNT : 0; protected float DimLevel => Math.Max(EnableUserDim.Value ? (float)UserDimLevel.Value - breakLightening : 0, 0); protected override Container Content => dimContent; From ecc7fdc561a8d98e064fd0b47cc6bc7605dff53e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 21:04:56 +0900 Subject: [PATCH 2745/2815] Ensure a clean run on each test method --- .../Background/TestSceneUserDimContainer.cs | 55 +++++++++---------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index 8eb256538a..472c43096f 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -15,13 +15,22 @@ namespace osu.Game.Tests.Visual.Background { public class TestSceneUserDimContainer : OsuTestScene { - private readonly TestUserDimContainer container; - private readonly BindableBool isBreakTime = new BindableBool(); - private readonly Bindable lightenDuringBreaks = new Bindable(); + private TestUserDimContainer userDimContainer; - public TestSceneUserDimContainer() + private readonly BindableBool isBreakTime = new BindableBool(); + + private Bindable lightenDuringBreaks = new Bindable(); + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) { - Add(container = new TestUserDimContainer + lightenDuringBreaks = config.GetBindable(OsuSetting.LightenDuringBreaks); + } + + [SetUp] + public void SetUp() => Schedule(() => + { + Child = userDimContainer = new TestUserDimContainer { RelativeSizeAxes = Axes.Both, Child = new Box @@ -29,21 +38,11 @@ namespace osu.Game.Tests.Visual.Background Colour = Color4.White, RelativeSizeAxes = Axes.Both, }, - }); + }; - container.IsBreakTime.BindTo(isBreakTime); - } - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - config.BindWith(OsuSetting.LightenDuringBreaks, lightenDuringBreaks); - } - - [SetUp] - public void SetUp() => Schedule(() => - { + userDimContainer.IsBreakTime.BindTo(isBreakTime); isBreakTime.Value = false; + lightenDuringBreaks.Value = false; }); @@ -55,38 +54,38 @@ namespace osu.Game.Tests.Visual.Background [TestCase(0.0f, 0.0f)] public void TestBreakLightening(float userDim, float expectedBreakDim) { - AddStep($"set dim level {userDim}", () => container.UserDimLevel.Value = userDim); + AddStep($"set dim level {userDim}", () => userDimContainer.UserDimLevel.Value = userDim); AddStep("set lighten during break", () => lightenDuringBreaks.Value = true); AddStep("set break", () => isBreakTime.Value = true); - AddUntilStep("has lightened", () => container.DimEqual(expectedBreakDim)); + AddUntilStep("has lightened", () => userDimContainer.DimEqual(expectedBreakDim)); AddStep("clear break", () => isBreakTime.Value = false); - AddUntilStep("not lightened", () => container.DimEqual(userDim)); + AddUntilStep("not lightened", () => userDimContainer.DimEqual(userDim)); } [Test] public void TestEnableSettingDuringBreak() { - AddStep("set dim level 0.6", () => container.UserDimLevel.Value = test_user_dim); + AddStep("set dim level 0.6", () => userDimContainer.UserDimLevel.Value = test_user_dim); AddStep("set break", () => isBreakTime.Value = true); - AddUntilStep("not lightened", () => container.DimEqual(test_user_dim)); + AddUntilStep("not lightened", () => userDimContainer.DimEqual(test_user_dim)); AddStep("set lighten during break", () => lightenDuringBreaks.Value = true); - AddUntilStep("has lightened", () => container.DimEqual(test_user_dim_lightened)); + AddUntilStep("has lightened", () => userDimContainer.DimEqual(test_user_dim_lightened)); } [Test] public void TestDisableSettingDuringBreak() { - AddStep("set dim level 0.6", () => container.UserDimLevel.Value = test_user_dim); + AddStep("set dim level 0.6", () => userDimContainer.UserDimLevel.Value = test_user_dim); AddStep("set lighten during break", () => lightenDuringBreaks.Value = true); AddStep("set break", () => isBreakTime.Value = true); - AddUntilStep("has lightened", () => container.DimEqual(test_user_dim_lightened)); + AddUntilStep("has lightened", () => userDimContainer.DimEqual(test_user_dim_lightened)); AddStep("clear lighten during break", () => lightenDuringBreaks.Value = false); - AddUntilStep("not lightened", () => container.DimEqual(test_user_dim)); + AddUntilStep("not lightened", () => userDimContainer.DimEqual(test_user_dim)); AddStep("clear break", () => isBreakTime.Value = false); - AddUntilStep("not lightened", () => container.DimEqual(test_user_dim)); + AddUntilStep("not lightened", () => userDimContainer.DimEqual(test_user_dim)); } private class TestUserDimContainer : UserDimContainer From c6cbf0f28a968ce42c5ba3f7731f98da822ac7e6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 12 Dec 2019 16:27:11 +0300 Subject: [PATCH 2746/2815] Add 1.0 skin to osu! ruleset test --- .../Resources/old-skin/approachcircle.png | Bin 0 -> 4540 bytes .../Resources/old-skin/hit0.png | Bin 0 -> 12904 bytes .../Resources/old-skin/hit100.png | Bin 0 -> 30853 bytes .../Resources/old-skin/hit300.png | Bin 0 -> 33649 bytes .../Resources/old-skin/hit50.png | Bin 0 -> 27832 bytes .../Resources/old-skin/hitcircle.png | Bin 0 -> 3572 bytes .../Resources/old-skin/hitcircleoverlay.png | Bin 0 -> 7113 bytes .../Resources/old-skin/skin.ini | 2 ++ .../SkinnableTestScene.cs | 5 ++++- 9 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/approachcircle.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit0.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit100.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit300.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit50.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hitcircle.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hitcircleoverlay.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/approachcircle.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/approachcircle.png new file mode 100644 index 0000000000000000000000000000000000000000..ff8b02ce800824f510e8655a1a9d977418bbdeb3 GIT binary patch literal 4540 zcmV;t5ku~YP);J

@NHN+%xmd&%Mk3yL--e&pG%0_n}mDQ`*|v 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�cZa_2kS}f04>ihEu+(-}$68mAH8q>c4Yg}Zw1U5cyjPrzcc;21 zO}$F%L)_@T&0)*iXf-CvhjIO&mqFgsMtO0u9|8C^bCA~uMy{agX#U1Lf3yS=T8V3V zC`|-zcm6mMrruab(wUEJ^lq~9wIU%F(T@Dr`RSvfIRR#VjIn%mnC8%Gu%2`$9~bp< z+maNMH~22(3vM`d=S(0OZ@g$m<3Y_Ge4+FUjoH2rY^OlFHiUDH9u2-Jr!WDfO;0sQ zSH5~hJhf0|pdF`2)~VAW?&z%MnQ=!f&<|VfzMBwInyt$Rx%>X<=SlD4!;UNT>@PlNtt*&nR zy0BzmIP`)3j5bWkjyClEQIg)5FK C#z4*13%nv%sar3_~4ci)7wz%BiUeMi~U~D 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 Dvd!~3 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit300.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit300.png new file mode 100644 index 0000000000000000000000000000000000000000..24945f7d925ee3cd4dbb741907aa1a4e8e90a1c5 GIT binary patch literal 33649 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 PUkRuy>nK&jZ6p5&sNh6p literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hitcircle.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hitcircle.png new file mode 100644 index 0000000000000000000000000000000000000000..a5a3545abf2510e7eec395bf47b73d9082e1fd8d GIT binary patch literal 3572 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 u5 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini new file mode 100644 index 0000000000..5369de24e9 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini @@ -0,0 +1,2 @@ +[General] +Version: 1.0 \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs index 38aac50df6..2fad3bd04f 100644 --- a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs +++ b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs @@ -19,9 +19,10 @@ namespace osu.Game.Rulesets.Osu.Tests private Skin metricsSkin; private Skin defaultSkin; private Skin specialSkin; + private Skin oldSkin; protected SkinnableTestScene() - : base(2, 2) + : base(2, 3) { } @@ -33,6 +34,7 @@ namespace osu.Game.Rulesets.Osu.Tests metricsSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), audio, true); defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info); specialSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore(dllStore, "Resources/special_skin"), audio, true); + oldSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore(dllStore, "Resources/old_skin"), audio, true); } public void SetContents(Func creationFunction) @@ -41,6 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests Cell(1).Child = createProvider(metricsSkin, creationFunction); Cell(2).Child = createProvider(defaultSkin, creationFunction); Cell(3).Child = createProvider(specialSkin, creationFunction); + Cell(4).Child = createProvider(oldSkin, creationFunction); } private Drawable createProvider(Skin skin, Func creationFunction) From 87a7f340e3100b7f1ee1f9a2c4b05abdb194b86b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 13 Dec 2019 18:12:44 +0900 Subject: [PATCH 2747/2815] Fix first sample point being treated as redundant --- osu.Game.Tests/NonVisual/ControlPointInfoTest.cs | 12 ++++++------ osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs index a51b90851c..0535473a3f 100644 --- a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs +++ b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs @@ -60,18 +60,18 @@ namespace osu.Game.Tests.NonVisual { var cpi = new ControlPointInfo(); - cpi.Add(0, new SampleControlPoint()); // is redundant + cpi.Add(0, new SampleControlPoint()); // is *not* redundant, special exception for first sample point cpi.Add(1000, new SampleControlPoint()); // is redundant - Assert.That(cpi.Groups.Count, Is.EqualTo(0)); + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0)); - Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1)); cpi.Add(1000, new SampleControlPoint { SampleVolume = 50 }); // is not redundant - Assert.That(cpi.Groups.Count, Is.EqualTo(1)); - Assert.That(cpi.SamplePoints.Count, Is.EqualTo(1)); - Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1)); + Assert.That(cpi.Groups.Count, Is.EqualTo(2)); + Assert.That(cpi.SamplePoints.Count, Is.EqualTo(2)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2)); } [Test] diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index ce2783004c..6965544292 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -239,7 +239,7 @@ namespace osu.Game.Beatmaps.ControlPoints break; case SampleControlPoint _: - existing = SamplePointAt(time); + existing = binarySearch(SamplePoints, time); break; case DifficultyControlPoint _: From 36f541d8e5ce77a846646063ba10903a93690965 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 13 Dec 2019 18:13:24 +0900 Subject: [PATCH 2748/2815] Fix incorrect asserts --- osu.Game.Tests/NonVisual/ControlPointInfoTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs index 0535473a3f..2782e902fe 100644 --- a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs +++ b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.NonVisual cpi.Add(1000, new DifficultyControlPoint()); // is redundant Assert.That(cpi.Groups.Count, Is.EqualTo(0)); - Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0)); + Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0)); Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0)); cpi.Add(1000, new DifficultyControlPoint { SpeedMultiplier = 2 }); // is not redundant @@ -64,7 +64,7 @@ namespace osu.Game.Tests.NonVisual cpi.Add(1000, new SampleControlPoint()); // is redundant Assert.That(cpi.Groups.Count, Is.EqualTo(1)); - Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0)); + Assert.That(cpi.SamplePoints.Count, Is.EqualTo(1)); Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1)); cpi.Add(1000, new SampleControlPoint { SampleVolume = 50 }); // is not redundant @@ -83,7 +83,7 @@ namespace osu.Game.Tests.NonVisual cpi.Add(1000, new EffectControlPoint()); // is redundant Assert.That(cpi.Groups.Count, Is.EqualTo(0)); - Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0)); + Assert.That(cpi.EffectPoints.Count, Is.EqualTo(0)); Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0)); cpi.Add(1000, new EffectControlPoint { KiaiMode = true }); // is not redundant From 952bc96bbf3223b2415a486c3b8b922e57328ee4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2019 20:05:38 +0900 Subject: [PATCH 2749/2815] Use GameBase data sources for Beatmap/Mods/Ruleset Sourced in via OsuScreenDependencies for management --- osu.Game/Tests/Visual/OsuTestScene.cs | 40 ++++++++++----------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 4ca0ec0f7e..4a561f17a3 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -12,7 +12,6 @@ using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Textures; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Framework.Timing; @@ -21,6 +20,7 @@ using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Screens; using osu.Game.Storyboards; using osu.Game.Tests.Beatmaps; @@ -28,21 +28,13 @@ namespace osu.Game.Tests.Visual { public abstract class OsuTestScene : TestScene { - [Cached(typeof(Bindable))] - [Cached(typeof(IBindable))] - private NonNullableBindable beatmap; + protected Bindable Beatmap { get; private set; } - protected Bindable Beatmap => beatmap; + protected Bindable Ruleset; - [Cached] - [Cached(typeof(IBindable))] - protected readonly Bindable Ruleset = new Bindable(); + protected Bindable> Mods; - [Cached] - [Cached(Type = typeof(IBindable>))] - protected readonly Bindable> Mods = new Bindable>(Array.Empty()); - - protected new DependencyContainer Dependencies { get; private set; } + protected new OsuScreenDependencies Dependencies { get; private set; } private readonly Lazy localStorage; protected Storage LocalStorage => localStorage.Value; @@ -72,18 +64,16 @@ namespace osu.Game.Tests.Visual protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - // This is the earliest we can get OsuGameBase, which is used by the dummy working beatmap to find textures - var working = new DummyWorkingBeatmap(parent.Get(), parent.Get()); + Dependencies = new OsuScreenDependencies(false, base.CreateChildDependencies(parent)); - beatmap = new NonNullableBindable(working) { Default = working }; - beatmap.BindValueChanged(b => ScheduleAfterChildren(() => - { - // compare to last beatmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo) - if (b.OldValue?.TrackLoaded == true && b.OldValue?.Track != b.NewValue?.Track) - b.OldValue.RecycleTrack(); - })); + Beatmap = Dependencies.Beatmap; + Beatmap.SetDefault(); - Dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + Ruleset = Dependencies.Ruleset; + Ruleset.SetDefault(); + + Mods = Dependencies.Mods; + Mods.SetDefault(); if (!UseOnlineAPI) { @@ -135,8 +125,8 @@ namespace osu.Game.Tests.Visual { base.Dispose(isDisposing); - if (beatmap?.Value.TrackLoaded == true) - beatmap.Value.Track.Stop(); + if (Beatmap?.Value.TrackLoaded == true) + Beatmap.Value.Track.Stop(); if (contextFactory.IsValueCreated) contextFactory.Value.ResetDatabase(); From 395b058ff872a28cf74554088ec04f26eff319d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2019 20:05:54 +0900 Subject: [PATCH 2750/2815] Fix OsuScreenDependencies not caching non-leased versions --- osu.Game/Screens/OsuScreenDependencies.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Screens/OsuScreenDependencies.cs b/osu.Game/Screens/OsuScreenDependencies.cs index 115f4b7e1a..8d54829d49 100644 --- a/osu.Game/Screens/OsuScreenDependencies.cs +++ b/osu.Game/Screens/OsuScreenDependencies.cs @@ -26,16 +26,26 @@ namespace osu.Game.Screens Beatmap = parent.Get>()?.GetBoundCopy(); if (Beatmap == null) + { Cache(Beatmap = parent.Get>().BeginLease(false)); + CacheAs(Beatmap); + } Ruleset = parent.Get>()?.GetBoundCopy(); if (Ruleset == null) + { Cache(Ruleset = parent.Get>().BeginLease(true)); + CacheAs(Ruleset); + } Mods = parent.Get>>()?.GetBoundCopy(); + if (Mods == null) + { Cache(Mods = parent.Get>>().BeginLease(true)); + CacheAs(Mods); + } } else { From 3dc2b59d2a066e4827f52aff6c7951cd72cfddd4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2019 20:06:12 +0900 Subject: [PATCH 2751/2815] Move variable above common bindables --- osu.Game/Screens/OsuScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 94165fe4b7..6394fb8d23 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -87,12 +87,12 @@ namespace osu.Game.Screens public virtual float BackgroundParallaxAmount => 1; + public virtual bool AllowRateAdjustments => true; + public Bindable Beatmap { get; private set; } public Bindable Ruleset { get; private set; } - public virtual bool AllowRateAdjustments => true; - public Bindable> Mods { get; private set; } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) From 7033974733537e4d32984f4efeae2c578534fc04 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2019 20:06:56 +0900 Subject: [PATCH 2752/2815] Fix test regressions (incorrect from the start) --- .../TestSceneDrawableHitObjectsHidden.cs | 6 ++++-- osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs | 5 +++-- osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs | 5 +++-- osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs | 5 +++-- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 2 +- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs index f6d26addaa..08107e01eb 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NUnit.Framework; using osu.Game.Rulesets.Catch.Mods; namespace osu.Game.Rulesets.Catch.Tests @@ -12,9 +13,10 @@ namespace osu.Game.Rulesets.Catch.Tests { public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(CatchModHidden) }).ToList(); - public TestSceneDrawableHitObjectsHidden() + [SetUp] + public void SetUp() => Schedule(() => { Mods.Value = new[] { new CatchModHidden() }; - } + }); } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs index 55c6b22146..eaba82a469 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs @@ -14,9 +14,10 @@ namespace osu.Game.Rulesets.Osu.Tests { public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList(); - public TestSceneHitCircleHidden() + [SetUp] + public void SetUp() => Schedule(() => { Mods.Value = new[] { new OsuModHidden() }; - } + }); } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs index 2a9c1d167b..a1795fb877 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs @@ -14,9 +14,10 @@ namespace osu.Game.Rulesets.Osu.Tests { public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList(); - public TestSceneSliderHidden() + [SetUp] + public void SetUp() => Schedule(() => { Mods.Value = new[] { new OsuModHidden() }; - } + }); } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs index a0ab1908d6..2976d4fdce 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs @@ -14,9 +14,10 @@ namespace osu.Game.Rulesets.Osu.Tests { public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList(); - public TestSceneSpinnerHidden() + [SetUp] + public void SetUp() => Schedule(() => { Mods.Value = new[] { new OsuModHidden() }; - } + }); } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index be50200e1c..8cf2d29906 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Visual.UserInterface var easierMods = instance.GetModsFor(ModType.DifficultyReduction); var noFailMod = easierMods.FirstOrDefault(m => m is OsuModNoFail); - AddStep("set mods externally", () => { modDisplay.Current.Value = new[] { noFailMod }; }); + AddStep("set mods externally", () => { Mods.Value = new[] { noFailMod }; }); changeRuleset(rulesetOsu); From a1f8ab1735b755bd98b27697dbf3f99828716f28 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2019 20:13:53 +0900 Subject: [PATCH 2753/2815] Fix unrequired type keyword --- osu.Game/OsuGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index f310da3883..207fb91aab 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -80,7 +80,7 @@ namespace osu.Game // todo: move this to SongSelect once Screen has the ability to unsuspend. [Cached] - [Cached(Type = typeof(IBindable>))] + [Cached(typeof(IBindable>))] protected readonly Bindable> Mods = new Bindable>(Array.Empty()); protected Bindable Beatmap { get; private set; } // cached via load() method From 0bbaf9b7fbe760d4b57ca66bf82c114e952cf824 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2019 20:21:35 +0900 Subject: [PATCH 2754/2815] Fix mod select overlay tests not running individually --- .../TestSceneModSelectOverlay.cs | 101 +++++++++--------- 1 file changed, 52 insertions(+), 49 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 8cf2d29906..5f77116c67 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -8,13 +8,16 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Testing; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; using osu.Game.Overlays.Mods.Sections; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play.HUD; @@ -48,42 +51,48 @@ namespace osu.Game.Tests.Visual.UserInterface private void load(RulesetStore rulesets) { this.rulesets = rulesets; + } - Add(modSelect = new TestModSelectOverlay + [SetUp] + public void SetUp() => Schedule(() => + { + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - }); + modSelect = new TestModSelectOverlay + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + }, - Add(modDisplay = new ModDisplay - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Position = new Vector2(0, 25), - }); + modDisplay = new ModDisplay + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Position = new Vector2(0, 25), + Current = { BindTarget = modSelect.SelectedMods } + } + }; + }); - modDisplay.Current.UnbindBindings(); - modDisplay.Current.BindTo(modSelect.SelectedMods); - - AddStep("Show", modSelect.Show); - AddStep("Toggle", modSelect.ToggleVisibility); - AddStep("Toggle", modSelect.ToggleVisibility); + [SetUpSteps] + public void SetUpSteps() + { + AddStep("show", () => modSelect.Show()); } [Test] public void TestOsuMods() { - var ruleset = rulesets.AvailableRulesets.First(r => r.ID == 0); - changeRuleset(ruleset); + changeRuleset(0); - var instance = ruleset.CreateInstance(); + var osu = new OsuRuleset(); - var easierMods = instance.GetModsFor(ModType.DifficultyReduction); - var harderMods = instance.GetModsFor(ModType.DifficultyIncrease); + var easierMods = osu.GetModsFor(ModType.DifficultyReduction); + var harderMods = osu.GetModsFor(ModType.DifficultyIncrease); - var noFailMod = easierMods.FirstOrDefault(m => m is OsuModNoFail); + 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)); @@ -97,8 +106,8 @@ namespace osu.Game.Tests.Visual.UserInterface testMultiMod(doubleTimeMod); testIncompatibleMods(easy, hardRock); testDeselectAll(easierMods.Where(m => !(m is MultiMod))); - testMultiplierTextColour(noFailMod, modSelect.LowMultiplierColour); - testMultiplierTextColour(hiddenMod, modSelect.HighMultiplierColour); + testMultiplierTextColour(noFailMod, () => modSelect.LowMultiplierColour); + testMultiplierTextColour(hiddenMod, () => modSelect.HighMultiplierColour); testUnimplementedMod(spunOutMod); } @@ -106,37 +115,31 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestManiaMods() { - var ruleset = rulesets.AvailableRulesets.First(r => r.ID == 3); - changeRuleset(ruleset); + changeRuleset(3); - testRankedText(ruleset.CreateInstance().GetModsFor(ModType.Conversion).First(m => m is ManiaModRandom)); + testRankedText(new ManiaRuleset().GetModsFor(ModType.Conversion).First(m => m is ManiaModRandom)); } [Test] public void TestRulesetChanges() { - var rulesetOsu = rulesets.AvailableRulesets.First(r => r.ID == 0); - var rulesetMania = rulesets.AvailableRulesets.First(r => r.ID == 3); + changeRuleset(0); - changeRuleset(null); - - var instance = rulesetOsu.CreateInstance(); - var easierMods = instance.GetModsFor(ModType.DifficultyReduction); - var noFailMod = easierMods.FirstOrDefault(m => m is OsuModNoFail); + var noFailMod = new OsuRuleset().GetModsFor(ModType.DifficultyReduction).FirstOrDefault(m => m is OsuModNoFail); AddStep("set mods externally", () => { Mods.Value = new[] { noFailMod }; }); - changeRuleset(rulesetOsu); + changeRuleset(0); AddAssert("ensure mods still selected", () => modDisplay.Current.Value.Single(m => m is OsuModNoFail) != null); - changeRuleset(rulesetMania); + changeRuleset(3); - AddAssert("ensure mods not selected", () => !modDisplay.Current.Value.Any(m => m is OsuModNoFail)); + AddAssert("ensure mods not selected", () => modDisplay.Current.Value.Count == 0); - changeRuleset(rulesetOsu); + changeRuleset(0); - AddAssert("ensure mods not selected", () => !modDisplay.Current.Value.Any()); + AddAssert("ensure mods not selected", () => modDisplay.Current.Value.Count == 0); } private void testSingleMod(Mod mod) @@ -198,19 +201,19 @@ namespace osu.Game.Tests.Visual.UserInterface selectNext(mod); AddAssert("check for any selection", () => modSelect.SelectedMods.Value.Any()); - AddStep("deselect all", modSelect.DeselectAllButton.Action.Invoke); + AddStep("deselect all", () => modSelect.DeselectAllButton.Action.Invoke()); AddAssert("check for no selection", () => !modSelect.SelectedMods.Value.Any()); } - private void testMultiplierTextColour(Mod mod, Color4 colour) + private void testMultiplierTextColour(Mod mod, Func getCorrectColour) { - checkLabelColor(Color4.White); + checkLabelColor(() => Color4.White); selectNext(mod); AddWaitStep("wait for changing colour", 1); - checkLabelColor(colour); + checkLabelColor(getCorrectColour); selectPrevious(mod); AddWaitStep("wait for changing colour", 1); - checkLabelColor(Color4.White); + checkLabelColor(() => Color4.White); } private void testRankedText(Mod mod) @@ -235,9 +238,9 @@ namespace osu.Game.Tests.Visual.UserInterface }); } - private void changeRuleset(RulesetInfo ruleset) + private void changeRuleset(int? id) { - AddStep($"change ruleset to {ruleset}", () => { Ruleset.Value = ruleset; }); + AddStep($"change ruleset to {(id.ToString() ?? "none")}", () => { Ruleset.Value = rulesets.AvailableRulesets.FirstOrDefault(r => r.ID == id); }); waitForLoad(); } @@ -253,7 +256,7 @@ namespace osu.Game.Tests.Visual.UserInterface }); } - private void checkLabelColor(Color4 color) => AddAssert("check label has expected colour", () => modSelect.MultiplierLabel.Colour.AverageColour == color); + private void checkLabelColor(Func getColour) => AddAssert("check label has expected colour", () => modSelect.MultiplierLabel.Colour.AverageColour == getColour()); private class TestModSelectOverlay : ModSelectOverlay { From 34f67b9cadf7a01e8bae1a31dcc8e0b2435ca40f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2019 20:31:20 +0900 Subject: [PATCH 2755/2815] 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 252570a150..8b266b08ba 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -54,6 +54,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index a07348b57c..9ec833c9ac 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 544bba3963..1829cbe32a 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -74,7 +74,7 @@ - + @@ -82,7 +82,7 @@ - + From f349e7ff78f9f3a7dec1851241c5478209dc8ca2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2019 20:35:34 +0900 Subject: [PATCH 2756/2815] Fix non-null ?? usage --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 5f77116c67..2738b5b8fc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -240,7 +240,7 @@ namespace osu.Game.Tests.Visual.UserInterface private void changeRuleset(int? id) { - AddStep($"change ruleset to {(id.ToString() ?? "none")}", () => { Ruleset.Value = rulesets.AvailableRulesets.FirstOrDefault(r => r.ID == id); }); + AddStep($"change ruleset to {(id?.ToString() ?? "none")}", () => { Ruleset.Value = rulesets.AvailableRulesets.FirstOrDefault(r => r.ID == id); }); waitForLoad(); } From 440a8470e146276a20899dcb8b8513a02b1fa622 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 18:53:25 +0900 Subject: [PATCH 2757/2815] Move available mods to global context This also tidies up ModSelectOverlay and setting creation flow in general. --- osu.Game/OsuGameBase.cs | 17 ++++++++ osu.Game/Overlays/Mods/ModControlSection.cs | 10 ++--- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 43 ++++++++------------- 3 files changed, 37 insertions(+), 33 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 207fb91aab..78a035c5d6 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -83,6 +83,11 @@ namespace osu.Game [Cached(typeof(IBindable>))] protected readonly Bindable> Mods = new Bindable>(Array.Empty()); + ///

+ /// Mods available for the current . + /// + public readonly Bindable>> AvailableMods = new Bindable>>(); + protected Bindable Beatmap { get; private set; } // cached via load() method private Bindable fpsDisplayVisible; @@ -233,6 +238,18 @@ namespace osu.Game PreviewTrackManager previewTrackManager; dependencies.Cache(previewTrackManager = new PreviewTrackManager()); Add(previewTrackManager); + + Ruleset.BindValueChanged(onRulesetChanged); + } + + private void onRulesetChanged(ValueChangedEvent r) + { + var dict = new Dictionary>(); + + foreach (ModType type in Enum.GetValues(typeof(ModType))) dict[type] = r.NewValue?.CreateInstance().GetModsFor(type).ToList(); + + SelectedMods.Value = Array.Empty(); + AvailableMods.Value = dict; } protected virtual Container CreateScalingContainer() => new DrawSizePreservingFillContainer(); diff --git a/osu.Game/Overlays/Mods/ModControlSection.cs b/osu.Game/Overlays/Mods/ModControlSection.cs index f4b588ddb3..10b3bc7c2b 100644 --- a/osu.Game/Overlays/Mods/ModControlSection.cs +++ b/osu.Game/Overlays/Mods/ModControlSection.cs @@ -1,10 +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.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; @@ -12,14 +12,13 @@ using osuTK; namespace osu.Game.Overlays.Mods { - public class ModControlSection : Container + public class ModControlSection : CompositeDrawable { protected FillFlowContainer FlowContent; - protected override Container Content => FlowContent; public readonly Mod Mod; - public ModControlSection(Mod mod) + public ModControlSection(Mod mod, IEnumerable modControls) { Mod = mod; @@ -33,9 +32,8 @@ namespace osu.Game.Overlays.Mods Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, + ChildrenEnumerable = modControls }; - - AddRange(Mod.CreateSettingsControls()); } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index e8ea43e3f2..247f4a2947 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -20,7 +20,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods.Sections; -using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens; using osuTK; @@ -50,7 +49,7 @@ namespace osu.Game.Overlays.Mods protected readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); - protected readonly IBindable Ruleset = new Bindable(); + private Bindable>> availableMods; protected Color4 LowMultiplierColour; protected Color4 HighMultiplierColour; @@ -322,14 +321,14 @@ namespace osu.Game.Overlays.Mods } [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, IBindable ruleset, AudioManager audio, Bindable> mods) + private void load(OsuColour colours, AudioManager audio, Bindable> selectedMods, OsuGameBase osu) { LowMultiplierColour = colours.Red; HighMultiplierColour = colours.Green; UnrankedLabel.Colour = colours.Blue; - Ruleset.BindTo(ruleset); - if (mods != null) SelectedMods.BindTo(mods); + availableMods = osu.AvailableMods.GetBoundCopy(); + SelectedMods.BindTo(selectedMods); sampleOn = audio.Samples.Get(@"UI/check-on"); sampleOff = audio.Samples.Get(@"UI/check-off"); @@ -360,7 +359,7 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - Ruleset.BindValueChanged(rulesetChanged, true); + availableMods.BindValueChanged(availableModsChanged, true); SelectedMods.BindValueChanged(selectedModsChanged, true); } @@ -410,22 +409,12 @@ namespace osu.Game.Overlays.Mods return base.OnKeyDown(e); } - private void rulesetChanged(ValueChangedEvent e) + private void availableModsChanged(ValueChangedEvent>> mods) { - if (e.NewValue == null) return; - - var instance = e.NewValue.CreateInstance(); + if (mods.NewValue == null) return; foreach (var section in ModSectionsContainer.Children) - section.Mods = instance.GetModsFor(section.ModType); - - // attempt to re-select any already selected mods. - // this may be the first time we are receiving the ruleset, in which case they will still match. - selectedModsChanged(new ValueChangedEvent>(SelectedMods.Value, SelectedMods.Value)); - - // write the mods back to the SelectedMods bindable in the case a change was not applicable. - // this generally isn't required as the previous line will perform deselection; just here for safety. - refreshSelectedMods(); + section.Mods = mods.NewValue[section.ModType]; } private void selectedModsChanged(ValueChangedEvent> mods) @@ -462,17 +451,17 @@ namespace osu.Game.Overlays.Mods private void updateModSettings(ValueChangedEvent> selectedMods) { - foreach (var added in selectedMods.NewValue.Except(selectedMods.OldValue)) + ModSettingsContent.Clear(); + + foreach (var mod in selectedMods.NewValue) { - var controls = added.CreateSettingsControls().ToList(); - if (controls.Count > 0) - ModSettingsContent.Add(new ModControlSection(added) { Children = controls }); + var settings = mod.CreateSettingsControls().ToList(); + if (settings.Count > 0) + ModSettingsContent.Add(new ModControlSection(mod, settings)); } - foreach (var removed in selectedMods.OldValue.Except(selectedMods.NewValue)) - ModSettingsContent.RemoveAll(section => section.Mod == removed); + bool hasSettings = ModSettingsContent.Count > 0; - bool hasSettings = ModSettingsContent.Children.Count > 0; CustomiseButton.Enabled.Value = hasSettings; if (!hasSettings) @@ -502,7 +491,7 @@ namespace osu.Game.Overlays.Mods { base.Dispose(isDisposing); - Ruleset.UnbindAll(); + availableMods.UnbindAll(); SelectedMods.UnbindAll(); } From 7fdaf338f3af99870215902b66ff759228d53038 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 17:12:01 +0900 Subject: [PATCH 2758/2815] Fix test logic and add regression test --- .../UserInterface/TestSceneModSettings.cs | 84 ++++++++++++++----- 1 file changed, 62 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index fc44c5f595..8117a4ad78 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -2,15 +2,20 @@ // 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 NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; namespace osu.Game.Tests.Visual.UserInterface { @@ -18,28 +23,51 @@ namespace osu.Game.Tests.Visual.UserInterface { private TestModSelectOverlay modSelect; - [BackgroundDependencyLoader] - private void load() + Mod testCustomisableMod = new TestModCustomisable1(); + + [Test] + public void TestButtonShowsOnCustomisableMod() { - Add(modSelect = new TestModSelectOverlay - { - RelativeSizeAxes = Axes.X, - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - }); + createModSelect(); - var testMod = new TestModCustomisable1(); - - AddStep("open", modSelect.Show); + AddStep("open", () => modSelect.Show()); AddAssert("button disabled", () => !modSelect.CustomiseButton.Enabled.Value); AddUntilStep("wait for button load", () => modSelect.ButtonsLoaded); - AddStep("select mod", () => modSelect.SelectMod(testMod)); + AddStep("select mod", () => modSelect.SelectMod(testCustomisableMod)); AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value); AddStep("open Customisation", () => modSelect.CustomiseButton.Click()); - AddStep("deselect mod", () => modSelect.SelectMod(testMod)); + AddStep("deselect mod", () => modSelect.SelectMod(testCustomisableMod)); AddAssert("controls hidden", () => modSelect.ModSettingsContainer.Alpha == 0); } + [Test] + public void TestButtonShowsOnModAlreadyAdded() + { + AddStep("set active mods", () => Mods.Value = new List { testCustomisableMod }); + + createModSelect(); + + AddAssert("mods still active", () => Mods.Value.Count == 1); + + AddStep("open", () => modSelect.Show()); + AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value); + } + + private void createModSelect() + { + AddStep("create mod select", () => + { + Ruleset.Value = new TestRulesetInfo(); + + Child = modSelect = new TestModSelectOverlay + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + }; + }); + } + private class TestModSelectOverlay : ModSelectOverlay { public new Container ModSettingsContainer => base.ModSettingsContainer; @@ -50,24 +78,36 @@ namespace osu.Game.Tests.Visual.UserInterface public void SelectMod(Mod mod) => ModSectionsContainer.Children.Single(s => s.ModType == mod.Type) .ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())).SelectNext(1); + } - protected override void LoadComplete() + public class TestRulesetInfo : RulesetInfo + { + public override Ruleset CreateInstance() => new TestCustomisableModRuleset(); + + public class TestCustomisableModRuleset : Ruleset { - base.LoadComplete(); - - foreach (var section in ModSectionsContainer) + public override IEnumerable GetModsFor(ModType type) { - if (section.ModType == ModType.Conversion) + if (type == ModType.Conversion) { - section.Mods = new Mod[] + return new Mod[] { new TestModCustomisable1(), new TestModCustomisable2() }; } - else - section.Mods = Array.Empty(); + + return Array.Empty(); } + + public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => throw new NotImplementedException(); + + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException(); + + public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException(); + + public override string Description { get; } + public override string ShortName { get; } } } From 76aa4f9fb2ecd55d0e209a197d73ad13f5370bbe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 17:36:37 +0900 Subject: [PATCH 2759/2815] Fix code style issues --- osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index 8117a4ad78..d17408ff95 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.UserInterface { private TestModSelectOverlay modSelect; - Mod testCustomisableMod = new TestModCustomisable1(); + private readonly Mod testCustomisableMod = new TestModCustomisable1(); [Test] public void TestButtonShowsOnCustomisableMod() @@ -106,8 +106,8 @@ namespace osu.Game.Tests.Visual.UserInterface public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException(); - public override string Description { get; } - public override string ShortName { get; } + public override string Description { get; } = "test"; + public override string ShortName { get; } = "tst"; } } From 8052aeb2383da8302aa677782acb4f2457edd200 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2019 00:23:32 +0900 Subject: [PATCH 2760/2815] Fix potential nullref in disposal logic --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 247f4a2947..7f07ce620c 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -491,8 +491,8 @@ namespace osu.Game.Overlays.Mods { base.Dispose(isDisposing); - availableMods.UnbindAll(); - SelectedMods.UnbindAll(); + availableMods?.UnbindAll(); + SelectedMods?.UnbindAll(); } #endregion From bc311465609b5ee76dff71e4850feaa409863da6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2019 21:45:38 +0900 Subject: [PATCH 2761/2815] Mods -> SelectedMods --- .../TestSceneAutoJuiceStream.cs | 2 +- .../TestSceneDrawableHitObjects.cs | 2 +- .../TestSceneDrawableHitObjectsHidden.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs | 2 +- .../TestSceneHitCircleHidden.cs | 2 +- .../TestSceneOsuFlashlight.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs | 2 +- .../TestSceneSpinnerHidden.cs | 2 +- .../TestSceneTaikoSuddenDeath.cs | 2 +- .../Visual/Background/TestSceneUserDimBackgrounds.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs | 2 +- .../Visual/Gameplay/TestSceneFailAnimation.cs | 2 +- .../Visual/Gameplay/TestSceneFailJudgement.cs | 2 +- .../Visual/Gameplay/TestSceneGameplayRewinding.cs | 2 +- .../Visual/Gameplay/TestScenePlayerLoader.cs | 6 +++--- .../Visual/SongSelect/TestScenePlaySongSelect.cs | 12 ++++++------ .../UserInterface/TestSceneModSelectOverlay.cs | 2 +- .../Visual/UserInterface/TestSceneModSettings.cs | 6 +++--- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Tests/Visual/AllPlayersTestScene.cs | 2 +- osu.Game/Tests/Visual/OsuTestScene.cs | 6 +++--- osu.Game/Tests/Visual/PlayerTestScene.cs | 4 ++-- 24 files changed, 36 insertions(+), 36 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs index ab3c040b4e..74a9c05bf9 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.Tests protected override Player CreatePlayer(Ruleset ruleset) { - Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); + SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); return base.CreatePlayer(ruleset); } } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs index 02a017ce45..1eb913e900 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs @@ -151,7 +151,7 @@ namespace osu.Game.Rulesets.Catch.Tests private void addToPlayfield(DrawableCatchHitObject drawable) { - foreach (var mod in Mods.Value.OfType()) + foreach (var mod in SelectedMods.Value.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); drawableRuleset.Playfield.Add(drawable); diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs index 08107e01eb..8c3dfef39c 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Tests [SetUp] public void SetUp() => Schedule(() => { - Mods.Value = new[] { new CatchModHidden() }; + SelectedMods.Value = new[] { new CatchModHidden() }; }); } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index 64f353c4d9..098e277fff 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Tests var drawable = CreateDrawableHitCircle(circle, auto); - foreach (var mod in Mods.Value.OfType()) + foreach (var mod in SelectedMods.Value.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); return drawable; diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs index eaba82a469..21ebce8c23 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests [SetUp] public void SetUp() => Schedule(() => { - Mods.Value = new[] { new OsuModHidden() }; + SelectedMods.Value = new[] { new OsuModHidden() }; }); } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs index 64e7632b1b..412effe176 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Tests { protected override Player CreatePlayer(Ruleset ruleset) { - Mods.Value = new Mod[] { new OsuModAutoplay(), new OsuModFlashlight(), }; + SelectedMods.Value = new Mod[] { new OsuModAutoplay(), new OsuModFlashlight(), }; return base.CreatePlayer(ruleset); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index a9d5c03517..e8386363be 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -362,7 +362,7 @@ namespace osu.Game.Rulesets.Osu.Tests var drawable = CreateDrawableSlider(slider); - foreach (var mod in Mods.Value.OfType()) + foreach (var mod in SelectedMods.Value.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); drawable.OnNewResult += onNewResult; diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs index a1795fb877..d0ee1bddb5 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests [SetUp] public void SetUp() => Schedule(() => { - Mods.Value = new[] { new OsuModHidden() }; + SelectedMods.Value = new[] { new OsuModHidden() }; }); } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs index 3ed3f3e981..f53b64c729 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Tests Depth = depthIndex++ }; - foreach (var mod in Mods.Value.OfType()) + foreach (var mod in SelectedMods.Value.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); Add(drawable); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs index 2976d4fdce..dd863deed2 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests [SetUp] public void SetUp() => Schedule(() => { - Mods.Value = new[] { new OsuModHidden() }; + SelectedMods.Value = new[] { new OsuModHidden() }; }); } } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs index d0db193738..140433a523 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Taiko.Tests protected override Player CreatePlayer(Ruleset ruleset) { - Mods.Value = Mods.Value.Concat(new[] { new TaikoModSuddenDeath() }).ToArray(); + SelectedMods.Value = SelectedMods.Value.Concat(new[] { new TaikoModSuddenDeath() }).ToArray(); return new ScoreAccessiblePlayer(); } diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 2d2726bbd3..589ec7e8aa 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -280,7 +280,7 @@ namespace osu.Game.Tests.Visual.Background AddUntilStep("Song select has selection", () => songSelect.Carousel.SelectedBeatmap != null); AddStep("Set default user settings", () => { - Mods.Value = Mods.Value.Concat(new[] { new OsuModNoFail() }).ToArray(); + SelectedMods.Value = SelectedMods.Value.Concat(new[] { new OsuModNoFail() }).ToArray(); songSelect.DimLevel.Value = 0.7f; songSelect.BlurLevel.Value = 0.4f; }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index 5ee109e3dd..069b965d9b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override Player CreatePlayer(Ruleset ruleset) { - Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); + SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); return new ScoreAccessiblePlayer(); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs index f4e8a68819..992c47f856 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Gameplay { protected override Player CreatePlayer(Ruleset ruleset) { - Mods.Value = Array.Empty(); + SelectedMods.Value = Array.Empty(); return new FailPlayer(); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs index cca6301b02..1580aac8c5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Gameplay { protected override Player CreatePlayer(Ruleset ruleset) { - Mods.Value = Array.Empty(); + SelectedMods.Value = Array.Empty(); return new FailPlayer(); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs index b2b58a63fb..5336c720a1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override Player CreatePlayer(Ruleset ruleset) { - Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); + SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); return new RulesetExposingPlayer(); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index f02361e685..f68f4b8b83 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Gameplay beforeLoadAction?.Invoke(); Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); - foreach (var mod in Mods.Value.OfType()) + foreach (var mod in SelectedMods.Value.OfType()) mod.ApplyToTrack(Beatmap.Value.Track); InputManager.Child = container = new TestPlayerLoaderContainer( @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestEarlyExit() { - AddStep("load dummy beatmap", () => ResetPlayer(false, () => Mods.Value = new[] { new OsuModNightcore() })); + AddStep("load dummy beatmap", () => ResetPlayer(false, () => SelectedMods.Value = new[] { new OsuModNightcore() })); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); AddAssert("mod rate applied", () => Beatmap.Value.Track.Rate != 1); AddStep("exit loader", () => loader.Exit()); @@ -123,7 +123,7 @@ namespace osu.Game.Tests.Visual.Gameplay TestMod playerMod1 = null; TestMod playerMod2 = null; - AddStep("load player", () => { ResetPlayer(true, () => Mods.Value = new[] { gameMod = new TestMod() }); }); + AddStep("load player", () => { ResetPlayer(true, () => SelectedMods.Value = new[] { gameMod = new TestMod() }); }); AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen()); AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 5dd02c1ddd..00fa95bedc 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -256,17 +256,17 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("change ruleset", () => { - Mods.ValueChanged += onModChange; + SelectedMods.ValueChanged += onModChange; songSelect.Ruleset.ValueChanged += onRulesetChange; Ruleset.Value = new TaikoRuleset().RulesetInfo; - Mods.ValueChanged -= onModChange; + SelectedMods.ValueChanged -= onModChange; songSelect.Ruleset.ValueChanged -= onRulesetChange; }); AddAssert("mods changed before ruleset", () => modChangeIndex < rulesetChangeIndex); - AddAssert("empty mods", () => !Mods.Value.Any()); + AddAssert("empty mods", () => !SelectedMods.Value.Any()); void onModChange(ValueChangedEvent> e) => modChangeIndex = actionIndex++; void onRulesetChange(ValueChangedEvent e) => rulesetChangeIndex = actionIndex++; @@ -275,7 +275,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestModsRetainedBetweenSongSelect() { - AddAssert("empty mods", () => !Mods.Value.Any()); + AddAssert("empty mods", () => !SelectedMods.Value.Any()); createSongSelect(); @@ -285,7 +285,7 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); - AddAssert("mods retained", () => Mods.Value.Any()); + AddAssert("mods retained", () => SelectedMods.Value.Any()); } [Test] @@ -332,7 +332,7 @@ namespace osu.Game.Tests.Visual.SongSelect private void checkMusicPlaying(bool playing) => AddUntilStep($"music {(playing ? "" : "not ")}playing", () => music.IsPlaying == playing); - private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.Acronym))}", () => Mods.Value = mods); + private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.Acronym))}", () => SelectedMods.Value = mods); private void changeRuleset(int id) => AddStep($"change ruleset to {id}", () => Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == id)); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 2738b5b8fc..12ee4ceb2e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -127,7 +127,7 @@ namespace osu.Game.Tests.Visual.UserInterface var noFailMod = new OsuRuleset().GetModsFor(ModType.DifficultyReduction).FirstOrDefault(m => m is OsuModNoFail); - AddStep("set mods externally", () => { Mods.Value = new[] { noFailMod }; }); + AddStep("set mods externally", () => { SelectedMods.Value = new[] { noFailMod }; }); changeRuleset(0); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index d17408ff95..edc749cbaa 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -43,11 +43,11 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestButtonShowsOnModAlreadyAdded() { - AddStep("set active mods", () => Mods.Value = new List { testCustomisableMod }); + AddStep("set active mods", () => SelectedMods.Value = new List { testCustomisableMod }); createModSelect(); - AddAssert("mods still active", () => Mods.Value.Count == 1); + AddAssert("mods still active", () => SelectedMods.Value.Count == 1); AddStep("open", () => modSelect.Show()); AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value); @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Visual.UserInterface return Array.Empty(); } - public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => throw new NotImplementedException(); + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new NotImplementedException(); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException(); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 78a035c5d6..834a64e597 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -81,7 +81,7 @@ namespace osu.Game // todo: move this to SongSelect once Screen has the ability to unsuspend. [Cached] [Cached(typeof(IBindable>))] - protected readonly Bindable> Mods = new Bindable>(Array.Empty()); + protected readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); /// /// Mods available for the current . diff --git a/osu.Game/Tests/Visual/AllPlayersTestScene.cs b/osu.Game/Tests/Visual/AllPlayersTestScene.cs index b7d1979b0d..dd65c8c382 100644 --- a/osu.Game/Tests/Visual/AllPlayersTestScene.cs +++ b/osu.Game/Tests/Visual/AllPlayersTestScene.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual var working = CreateWorkingBeatmap(rulesetInfo); Beatmap.Value = working; - Mods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; + SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; Player?.Exit(); Player = null; diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 4a561f17a3..18dbd212cc 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual protected Bindable Ruleset; - protected Bindable> Mods; + protected Bindable> SelectedMods; protected new OsuScreenDependencies Dependencies { get; private set; } @@ -72,8 +72,8 @@ namespace osu.Game.Tests.Visual Ruleset = Dependencies.Ruleset; Ruleset.SetDefault(); - Mods = Dependencies.Mods; - Mods.SetDefault(); + SelectedMods = Dependencies.Mods; + SelectedMods.SetDefault(); if (!UseOnlineAPI) { diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 2c5a51ca02..3ed65bee61 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -53,14 +53,14 @@ namespace osu.Game.Tests.Visual { var noFailMod = ruleset.GetAllMods().FirstOrDefault(m => m is ModNoFail); if (noFailMod != null) - Mods.Value = new[] { noFailMod }; + SelectedMods.Value = new[] { noFailMod }; } if (Autoplay) { var mod = ruleset.GetAutoplayMod(); if (mod != null) - Mods.Value = Mods.Value.Concat(mod.Yield()).ToArray(); + SelectedMods.Value = SelectedMods.Value.Concat(mod.Yield()).ToArray(); } Player = CreatePlayer(ruleset); From af1566285698e8341a998d8d2f8bf7dea65697dd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 17:45:11 +0900 Subject: [PATCH 2762/2815] Fix WindUp applying too much change --- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 4 +++- osu.Game/Rulesets/Mods/ModWindDown.cs | 8 ++++---- osu.Game/Rulesets/Mods/ModWindUp.cs | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 5276c196f7..4ce364ef1a 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -45,6 +45,8 @@ namespace osu.Game.Rulesets.Mods { this.track = track; track.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); + + FinalRate.TriggerChange(); } public virtual void ApplyToBeatmap(IBeatmap beatmap) @@ -67,6 +69,6 @@ namespace osu.Game.Rulesets.Mods /// /// The amount of adjustment to apply (from 0..1). private void applyAdjustment(double amount) => - SpeedChange.Value = 1 + (Math.Sign(FinalRate.Value) * Math.Clamp(amount, 0, 1) * Math.Abs(FinalRate.Value)); + SpeedChange.Value = 1 + (FinalRate.Value - 1) * Math.Clamp(amount, 0, 1); } } diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs index 680c1a10fb..5416f1ac22 100644 --- a/osu.Game/Rulesets/Mods/ModWindDown.cs +++ b/osu.Game/Rulesets/Mods/ModWindDown.cs @@ -20,10 +20,10 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Final rate", "The speed increase to ramp towards")] public override BindableNumber FinalRate { get; } = new BindableDouble { - MinValue = -0.5, - MaxValue = -0.01, - Default = -0.25, - Value = -0.25, + MinValue = 0.5, + MaxValue = 0.99, + Default = 0.75, + Value = 0.75, Precision = 0.01, }; diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs index ca9ce0ea3e..3cf584f3dd 100644 --- a/osu.Game/Rulesets/Mods/ModWindUp.cs +++ b/osu.Game/Rulesets/Mods/ModWindUp.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Final rate", "The speed increase to ramp towards")] public override BindableNumber FinalRate { get; } = new BindableDouble { - MinValue = 0.01, + MinValue = 1.01, MaxValue = 2, Default = 1.5, Value = 1.5, From 72ea871bffd35b6cab8ac8b83a6015e628e22d80 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 12 Dec 2019 23:33:18 +0900 Subject: [PATCH 2763/2815] Make constructor protected --- 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 4ce364ef1a..133f9ceb39 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mods private Track track; - public ModTimeRamp() + protected ModTimeRamp() { // for preview purpose at song select. eventually we'll want to be able to update every frame. FinalRate.BindValueChanged(val => applyAdjustment(1), true); From ef94df917ceb55dd7cdca2ce116495998d36ea7a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 13 Dec 2019 21:56:03 +0900 Subject: [PATCH 2764/2815] Add whitespace --- osu.Game/OsuGameBase.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 834a64e597..d9b569bf0e 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -246,7 +246,8 @@ namespace osu.Game { var dict = new Dictionary>(); - foreach (ModType type in Enum.GetValues(typeof(ModType))) dict[type] = r.NewValue?.CreateInstance().GetModsFor(type).ToList(); + foreach (ModType type in Enum.GetValues(typeof(ModType))) + dict[type] = r.NewValue?.CreateInstance().GetModsFor(type).ToList(); SelectedMods.Value = Array.Empty(); AvailableMods.Value = dict; From b94d5bf82eba5c96f0303e408caf170885a86c3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 14 Dec 2019 00:42:31 +0900 Subject: [PATCH 2765/2815] Fix error when entering multiplayer game in different ruleset --- osu.Game/OsuGameBase.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index d9b569bf0e..21cc4eaccc 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -249,7 +249,8 @@ namespace osu.Game foreach (ModType type in Enum.GetValues(typeof(ModType))) dict[type] = r.NewValue?.CreateInstance().GetModsFor(type).ToList(); - SelectedMods.Value = Array.Empty(); + if (!SelectedMods.Disabled) + SelectedMods.Value = Array.Empty(); AvailableMods.Value = dict; } From ddb2cfc46de5ee7881aaa66c9e752109e935e021 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 14 Dec 2019 18:02:56 +0800 Subject: [PATCH 2766/2815] Use GetEndTime in Taiko and Mania --- osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs | 4 ++-- osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs | 4 ++-- osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs | 3 +-- osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs | 5 +---- osu.Game/Rulesets/Objects/HitObject.cs | 2 +- 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs index 059cd39641..4f7ab87fad 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs @@ -5,7 +5,7 @@ using System.Linq; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mania.Difficulty.Preprocessing; -using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mania.Difficulty.Skills { @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills protected override double StrainValueOf(DifficultyHitObject current) { var maniaCurrent = (ManiaDifficultyHitObject)current; - var endTime = (maniaCurrent.BaseObject as HoldNote)?.EndTime ?? maniaCurrent.BaseObject.StartTime; + var endTime = maniaCurrent.BaseObject.GetEndTime(); try { diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs index ed25173d38..bbbb93fd8b 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs @@ -4,7 +4,7 @@ using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mania.Difficulty.Preprocessing; -using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mania.Difficulty.Skills { @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills protected override double StrainValueOf(DifficultyHitObject current) { var maniaCurrent = (ManiaDifficultyHitObject)current; - var endTime = (maniaCurrent.BaseObject as HoldNote)?.EndTime ?? maniaCurrent.BaseObject.StartTime; + var endTime = maniaCurrent.BaseObject.GetEndTime(); double holdFactor = 1.0; // Factor in case something else is held double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs index 6f4fbd0651..c41727557b 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs @@ -3,7 +3,6 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Scoring; @@ -38,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Objects base.CreateNestedHitObjects(); if (IsStrong) - AddNested(new StrongHitObject { StartTime = (this as IHasEndTime)?.EndTime ?? StartTime }); + AddNested(new StrongHitObject { StartTime = this.GetEndTime() }); } public override Judgement CreateJudgement() => new TaikoJudgement(); diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index e61953aeb8..4b234b56d4 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Replays; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Taiko.Beatmaps; @@ -39,9 +38,7 @@ namespace osu.Game.Rulesets.Taiko.Replays for (int i = 0; i < Beatmap.HitObjects.Count; i++) { TaikoHitObject h = Beatmap.HitObjects[i]; - - IHasEndTime endTimeData = h as IHasEndTime; - double endTime = endTimeData?.EndTime ?? h.StartTime; + double endTime = h.GetEndTime(); switch (h) { diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 1179efaa6e..bd96441ebb 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Objects ApplyDefaultsToSelf(controlPointInfo, difficulty); // This is done here since ApplyDefaultsToSelf may be used to determine the end time - SampleControlPoint = controlPointInfo.SamplePointAt(((this as IHasEndTime)?.EndTime ?? StartTime) + control_point_leniency); + SampleControlPoint = controlPointInfo.SamplePointAt(this.GetEndTime() + control_point_leniency); nestedHitObjects.Clear(); From 489d9dc7b5d1f56ca7429774fe44970106a0aed8 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 14 Dec 2019 18:33:56 +0800 Subject: [PATCH 2767/2815] Switch Expandtarget and "cursormiddle" --- osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs index 02152fa51e..e96bd29ad5 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs @@ -28,18 +28,18 @@ namespace osu.Game.Rulesets.Osu.Skinning InternalChildren = new[] { + ExpandTarget = new NonPlayfieldSprite + { + Texture = skin.GetTexture("cursor"), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, new NonPlayfieldSprite { Texture = skin.GetTexture("cursormiddle"), Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - ExpandTarget = new NonPlayfieldSprite - { - Texture = skin.GetTexture("cursor"), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - } }; } From 41d4609c9238e26b8f4fb73a68bb381eff1cf04e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 15 Dec 2019 02:36:49 +0900 Subject: [PATCH 2768/2815] Fix crash on trying to retrieve mods from unavailable ruleset --- osu.Game/OsuGameBase.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 21cc4eaccc..22b8d9d012 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -246,8 +246,11 @@ namespace osu.Game { var dict = new Dictionary>(); - foreach (ModType type in Enum.GetValues(typeof(ModType))) - dict[type] = r.NewValue?.CreateInstance().GetModsFor(type).ToList(); + if (r.NewValue?.Available == true) + { + foreach (ModType type in Enum.GetValues(typeof(ModType))) + dict[type] = r.NewValue.CreateInstance().GetModsFor(type).ToList(); + } if (!SelectedMods.Disabled) SelectedMods.Value = Array.Empty(); From 244eb56455d5bbb7deb1afecdbbd6013f913294e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 15 Dec 2019 03:01:37 +0900 Subject: [PATCH 2769/2815] Fix test ruleset availability --- osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index edc749cbaa..8dcb7dcbf8 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -84,6 +84,11 @@ namespace osu.Game.Tests.Visual.UserInterface { public override Ruleset CreateInstance() => new TestCustomisableModRuleset(); + public TestRulesetInfo() + { + Available = true; + } + public class TestCustomisableModRuleset : Ruleset { public override IEnumerable GetModsFor(ModType type) From 91bb851a7dbc1db8f470be81a7bd4506d9f45a17 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 15 Dec 2019 03:32:50 +0900 Subject: [PATCH 2770/2815] 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 8b266b08ba..239e1c2d31 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -54,6 +54,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 9ec833c9ac..56d65830f9 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 1829cbe32a..a90e90db19 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -74,7 +74,7 @@ - + @@ -82,7 +82,7 @@ - + From a04f4b76bb93f9a94d06724cf803ac6a0b835a43 Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 13 Dec 2019 22:27:14 -0800 Subject: [PATCH 2771/2815] Allow changing volume using alt when hovering scroll containers --- osu.Game/Graphics/Containers/OsuScrollContainer.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Graphics/Containers/OsuScrollContainer.cs b/osu.Game/Graphics/Containers/OsuScrollContainer.cs index 2721ce55dc..df4c3a3324 100644 --- a/osu.Game/Graphics/Containers/OsuScrollContainer.cs +++ b/osu.Game/Graphics/Containers/OsuScrollContainer.cs @@ -83,6 +83,13 @@ namespace osu.Game.Graphics.Containers return base.OnDragEnd(e); } + protected override bool OnScroll(ScrollEvent e) + { + if (e.AltPressed) return false; + + return base.OnScroll(e); + } + protected override ScrollbarContainer CreateScrollbar(Direction direction) => new OsuScrollbar(direction); protected class OsuScrollbar : ScrollbarContainer From 5af363c92043d81c86d5254e07ac986c06a4d5ad Mon Sep 17 00:00:00 2001 From: Joehu Date: Sat, 14 Dec 2019 12:58:13 -0800 Subject: [PATCH 2772/2815] Use default placeholder text on chat channel search box --- osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs index 505d2d6f89..25a9a51638 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs @@ -115,11 +115,7 @@ namespace osu.Game.Overlays.Chat.Selection Font = OsuFont.GetFont(size: 20), Shadow = false, }, - search = new HeaderSearchTextBox - { - RelativeSizeAxes = Axes.X, - PlaceholderText = @"Search", - }, + search = new HeaderSearchTextBox { RelativeSizeAxes = Axes.X }, }, }, }, From 19a3c959923328c8b91e4653ee003b101f60c770 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 14 Dec 2019 21:27:26 +0800 Subject: [PATCH 2773/2815] Update InspectCode to 2019.3 --- build/InspectCode.cake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/InspectCode.cake b/build/InspectCode.cake index bd3fdf5f93..06c56dce87 100644 --- a/build/InspectCode.cake +++ b/build/InspectCode.cake @@ -1,5 +1,5 @@ #addin "nuget:?package=CodeFileSanity&version=0.0.33" -#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.2.1" +#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.3.0" #tool "nuget:?package=NVika.MSBuild&version=1.0.1" var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First(); From ab70abe8bd2b577c049cad84283f0b74e1dcbc60 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 14 Dec 2019 21:28:13 +0800 Subject: [PATCH 2774/2815] Turn off unexpected new warnings. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 ++++ osu.sln.DotSettings | 1 + 2 files changed, 5 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index ec524043ee..4acc619753 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -587,13 +587,16 @@ namespace osu.Game.Screens.Select switch (d) { case DrawableCarouselBeatmapSet set: + { lastSet = set; set.MoveToX(set.Item.State.Value == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo); set.MoveToY(currentY, 750, Easing.OutExpo); break; + } case DrawableCarouselBeatmap beatmap: + { if (beatmap.Item.State.Value == CarouselItemState.Selected) scrollTarget = currentY + beatmap.DrawHeight / 2 - DrawHeight / 2; @@ -619,6 +622,7 @@ namespace osu.Game.Screens.Select } break; + } } } diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 9b400de390..105d22fe3e 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -215,6 +215,7 @@ HINT HINT HINT + HINT DO_NOT_SHOW WARNING WARNING From 8b570233495c2c654b9493bb7d6415580c4c8174 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 14 Dec 2019 20:22:39 +0800 Subject: [PATCH 2775/2815] Require 3.1.100 SDK in global.json --- global.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/global.json b/global.json index 43bb34912a..6858d4044d 100644 --- a/global.json +++ b/global.json @@ -1,4 +1,9 @@ { + "sdk": { + "allowPrerelease": false, + "rollForward": "minor", + "version": "3.1.100" + }, "msbuild-sdks": { "Microsoft.Build.Traversal": "2.0.24" } From cea3a66d4a0f9e235aa7d5e3966281507229cfc1 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 14 Dec 2019 20:26:28 +0800 Subject: [PATCH 2776/2815] Use static local method fixed for roslyn 3.4 --- .../Visual/Online/TestSceneBeatmapSetOverlayDetails.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs index 2a45e68c0a..96c0c59695 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("set second set", () => details.BeatmapSet = secondSet); AddAssert("ratings set", () => details.Ratings.Metrics == secondSet.Metrics); - BeatmapSetInfo createSet() => new BeatmapSetInfo + static BeatmapSetInfo createSet() => new BeatmapSetInfo { Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).Select(_ => RNG.Next(10)).ToArray() }, Beatmaps = new List From c457571da6d69f41ac7722bda6532fa99722c611 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 14 Dec 2019 20:54:22 +0800 Subject: [PATCH 2777/2815] Use index and range expressions --- .editorconfig | 4 ++-- osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs | 6 +++--- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 8 ++++---- .../Visual/UserInterface/TestSceneBeatSyncedContainer.cs | 4 ++-- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 4 ++-- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 2 +- osu.Game/Graphics/Containers/LinkFlowContainer.cs | 2 +- .../Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 4 ++-- osu.Game/Rulesets/Objects/SliderPath.cs | 6 +++--- osu.sln.DotSettings | 2 +- 12 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.editorconfig b/.editorconfig index b5333ad8e7..8cdb92d11c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -176,8 +176,8 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent #Style - C# 8 features csharp_prefer_static_local_function = true:warning csharp_prefer_simple_using_statement = true:silent -csharp_style_prefer_index_operator = false:none -csharp_style_prefer_range_operator = false:none +csharp_style_prefer_index_operator = true:warning +csharp_style_prefer_range_operator = true:warning csharp_style_prefer_switch_expression = false:none #Supressing roslyn built-in analyzers diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 9069c09ae4..6e4491de94 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps prevNoteTimes.RemoveAt(0); prevNoteTimes.Add(newNoteTime); - density = (prevNoteTimes[prevNoteTimes.Count - 1] - prevNoteTimes[0]) / prevNoteTimes.Count; + density = (prevNoteTimes[^1] - prevNoteTimes[0]) / prevNoteTimes.Count; } private double lastTime; diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index 5f75cbabec..b6fc9821a4 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -286,11 +286,11 @@ namespace osu.Game.Rulesets.Osu.Tests private bool assertGreatJudge() => judgementResults.Last().Type == HitResult.Great; - private bool assertHeadMissTailTracked() => judgementResults[judgementResults.Count - 2].Type == HitResult.Great && judgementResults.First().Type == HitResult.Miss; + private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.Great && judgementResults.First().Type == HitResult.Miss; - private bool assertMidSliderJudgements() => judgementResults[judgementResults.Count - 2].Type == HitResult.Great; + private bool assertMidSliderJudgements() => judgementResults[^2].Type == HitResult.Great; - private bool assertMidSliderJudgementFail() => judgementResults[judgementResults.Count - 2].Type == HitResult.Miss; + private bool assertMidSliderJudgementFail() => judgementResults[^2].Type == HitResult.Miss; private ScoreAccessibleReplayPlayer currentPlayer; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 9b820261ab..2497e428fc 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected override bool OnDoubleClick(DoubleClickEvent e) { // Todo: This should all not occur on double click, but rather if the previous control point is hovered. - segmentStart = HitObject.Path.ControlPoints[HitObject.Path.ControlPoints.Count - 1]; + segmentStart = HitObject.Path.ControlPoints[^1]; segmentStart.Type.Value = PathType.Linear; currentSegmentLength = 1; diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index bd59e8a03f..2686ba4fd2 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -156,9 +156,9 @@ namespace osu.Game.Rulesets.Osu.Replays // TODO: Shouldn't the spinner always spin in the same direction? if (h is Spinner) { - calcSpinnerStartPosAndDirection(((OsuReplayFrame)Frames[Frames.Count - 1]).Position, out startPosition, out spinnerDirection); + calcSpinnerStartPosAndDirection(((OsuReplayFrame)Frames[^1]).Position, out startPosition, out spinnerDirection); - Vector2 spinCentreOffset = SPINNER_CENTRE - ((OsuReplayFrame)Frames[Frames.Count - 1]).Position; + Vector2 spinCentreOffset = SPINNER_CENTRE - ((OsuReplayFrame)Frames[^1]).Position; if (spinCentreOffset.Length > SPIN_RADIUS) { @@ -230,7 +230,7 @@ namespace osu.Game.Rulesets.Osu.Replays private void moveToHitObject(OsuHitObject h, Vector2 targetPos, Easing easing) { - OsuReplayFrame lastFrame = (OsuReplayFrame)Frames[Frames.Count - 1]; + OsuReplayFrame lastFrame = (OsuReplayFrame)Frames[^1]; // Wait until Auto could "see and react" to the next note. double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - reactionTime); @@ -363,7 +363,7 @@ namespace osu.Game.Rulesets.Osu.Replays } // We only want to let go of our button if we are at the end of the current replay. Otherwise something is still going on after us so we need to keep the button pressed! - if (Frames[Frames.Count - 1].Time <= endFrame.Time) + if (Frames[^1].Time <= endFrame.Time) AddFrameToReplay(endFrame); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index ed44d82bce..b0b673d6a4 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -157,7 +157,7 @@ namespace osu.Game.Tests.Visual.UserInterface private TimingControlPoint getNextTimingPoint(TimingControlPoint current) { - if (timingPoints[timingPoints.Count - 1] == current) + if (timingPoints[^1] == current) return current; int index = timingPoints.IndexOf(current); // -1 means that this is a "default beat" @@ -169,7 +169,7 @@ namespace osu.Game.Tests.Visual.UserInterface { if (timingPoints.Count == 0) return 0; - if (timingPoints[timingPoints.Count - 1] == current) + if (timingPoints[^1] == current) return (int)Math.Ceiling((Beatmap.Value.Track.Length - current.Time) / current.BeatLength); return (int)Math.Ceiling((getNextTimingPoint(current).Time - current.Time) / current.BeatLength); diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index ce2783004c..03496952e7 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -195,8 +195,8 @@ namespace osu.Game.Beatmaps.ControlPoints if (time < list[0].Time) return null; - if (time >= list[list.Count - 1].Time) - return list[list.Count - 1]; + if (time >= list[^1].Time) + return list[^1]; int l = 0; int r = list.Count - 2; diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 2b914669cb..e401e3fb97 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -34,7 +34,7 @@ namespace osu.Game.Beatmaps.Formats if (line.StartsWith(@"[", StringComparison.Ordinal) && line.EndsWith(@"]", StringComparison.Ordinal)) { - if (!Enum.TryParse(line.Substring(1, line.Length - 2), out section)) + if (!Enum.TryParse(line[1..^1], out section)) { Logger.Log($"Unknown section \"{line}\" in \"{output}\""); section = Section.None; diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 2bbac92f7f..9735f6373d 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -37,7 +37,7 @@ namespace osu.Game.Graphics.Containers foreach (var link in links) { - AddText(text.Substring(previousLinkEnd, link.Index - previousLinkEnd)); + AddText(text[previousLinkEnd..link.Index]); AddLink(text.Substring(link.Index, link.Length), link.Action, link.Argument ?? link.Url); previousLinkEnd = link.Index + link.Length; } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index b5b1e26486..7fddb442d1 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -184,7 +184,7 @@ namespace osu.Game.Rulesets.Objects.Legacy result = CreateSlider(pos, combo, comboOffset, convertControlPoints(points, pathType), length, repeatCount, nodeSamples); // The samples are played when the slider ends, which is the last node - result.Samples = nodeSamples[nodeSamples.Count - 1]; + result.Samples = nodeSamples[^1]; } else if (type.HasFlag(ConvertHitObjectType.Spinner)) { @@ -279,7 +279,7 @@ namespace osu.Game.Rulesets.Objects.Legacy { if (vertices[i] == vertices[i - 1]) { - points[points.Count - 1].Type.Value = type; + points[^1].Type.Value = type; continue; } diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 86deba3b93..293138097f 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Objects get { ensureValid(); - return cumulativeLength.Count == 0 ? 0 : cumulativeLength[cumulativeLength.Count - 1]; + return cumulativeLength.Count == 0 ? 0 : cumulativeLength[^1]; } } @@ -251,7 +251,7 @@ namespace osu.Game.Rulesets.Objects if (calculatedLength > expectedDistance) { // The path will be shortened further, in which case we should trim any more unnecessary lengths and their associated path segments - while (cumulativeLength.Count > 0 && cumulativeLength[cumulativeLength.Count - 1] >= expectedDistance) + while (cumulativeLength.Count > 0 && cumulativeLength[^1] >= expectedDistance) { cumulativeLength.RemoveAt(cumulativeLength.Count - 1); calculatedPath.RemoveAt(pathEndIndex--); @@ -269,7 +269,7 @@ namespace osu.Game.Rulesets.Objects // The direction of the segment to shorten or lengthen Vector2 dir = (calculatedPath[pathEndIndex] - calculatedPath[pathEndIndex - 1]).Normalized(); - calculatedPath[pathEndIndex] = calculatedPath[pathEndIndex - 1] + dir * (float)(expectedDistance - cumulativeLength[cumulativeLength.Count - 1]); + calculatedPath[pathEndIndex] = calculatedPath[pathEndIndex - 1] + dir * (float)(expectedDistance - cumulativeLength[^1]); cumulativeLength.Add(expectedDistance); } } diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 105d22fe3e..12571be31d 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -220,7 +220,7 @@ WARNING WARNING WARNING - DO_NOT_SHOW + WARNING WARNING True From 9062fe1935b114fd214b5d84f9319fbdb7cd2053 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Dec 2019 13:32:53 +0900 Subject: [PATCH 2778/2815] Fix crashes on custom skins due to extension-less file lookups --- osu.Game/Skinning/LegacySkinResourceStore.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Skinning/LegacySkinResourceStore.cs b/osu.Game/Skinning/LegacySkinResourceStore.cs index 72f3b9ed78..7c799d9c89 100644 --- a/osu.Game/Skinning/LegacySkinResourceStore.cs +++ b/osu.Game/Skinning/LegacySkinResourceStore.cs @@ -22,10 +22,8 @@ namespace osu.Game.Skinning if (source.Files == null) return null; - bool hasExtension = filename.Contains('.'); - var file = source.Files.Find(f => - string.Equals(hasExtension ? f.Filename : Path.ChangeExtension(f.Filename, null), filename, StringComparison.InvariantCultureIgnoreCase)); + string.Equals(f.Filename, filename, StringComparison.InvariantCultureIgnoreCase)); return file?.FileInfo.StoragePath; } From befb78f83b890f847b16cbcd2c52310bc7d15975 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Dec 2019 14:01:08 +0900 Subject: [PATCH 2779/2815] Simplify LegacySkinResourceStore by deriving from ResourceStore --- osu.Game/Skinning/LegacySkinResourceStore.cs | 60 ++++---------------- 1 file changed, 12 insertions(+), 48 deletions(-) diff --git a/osu.Game/Skinning/LegacySkinResourceStore.cs b/osu.Game/Skinning/LegacySkinResourceStore.cs index 7c799d9c89..79a4e2e932 100644 --- a/osu.Game/Skinning/LegacySkinResourceStore.cs +++ b/osu.Game/Skinning/LegacySkinResourceStore.cs @@ -3,75 +3,39 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Threading.Tasks; using osu.Framework.IO.Stores; using osu.Game.Database; namespace osu.Game.Skinning { - public class LegacySkinResourceStore : IResourceStore + public class LegacySkinResourceStore : ResourceStore where T : INamedFileInfo { private readonly IHasFiles source; - private readonly IResourceStore underlyingStore; - - private string getPathForFile(string filename) - { - if (source.Files == null) - return null; - - var file = source.Files.Find(f => - string.Equals(f.Filename, filename, StringComparison.InvariantCultureIgnoreCase)); - return file?.FileInfo.StoragePath; - } public LegacySkinResourceStore(IHasFiles source, IResourceStore underlyingStore) + : base(underlyingStore) { this.source = source; - this.underlyingStore = underlyingStore; } - public Stream GetStream(string name) + protected override IEnumerable GetFilenames(string name) { - string path = getPathForFile(name); - return path == null ? null : underlyingStore.GetStream(path); - } + if (source.Files == null) + yield break; - public IEnumerable GetAvailableResources() => source.Files.Select(f => f.Filename); - - byte[] IResourceStore.Get(string name) => GetAsync(name).Result; - - public Task GetAsync(string name) - { - string path = getPathForFile(name); - return path == null ? Task.FromResult(null) : underlyingStore.GetAsync(path); - } - - #region IDisposable Support - - private bool isDisposed; - - protected virtual void Dispose(bool disposing) - { - if (!isDisposed) + foreach (var filename in base.GetFilenames(name)) { - isDisposed = true; + var path = getPathForFile(filename); + if (path != null) + yield return path; } } - ~LegacySkinResourceStore() - { - Dispose(false); - } + private string getPathForFile(string filename) => + source.Files.Find(f => string.Equals(f.Filename, filename, StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - #endregion + public override IEnumerable GetAvailableResources() => source.Files.Select(f => f.Filename); } } From db3dc4f3755e7254c4c039ea48f23c7a2edb1bb7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 16 Dec 2019 15:15:26 +0900 Subject: [PATCH 2780/2815] Optimise cursortrail with custom vertex logic --- .../UI/Cursor/CursorTrail.cs | 70 +++++++++++-------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 80291c002e..4d6db83d7a 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private void addPart(Vector2 screenSpacePosition) { parts[currentIndex].Position = screenSpacePosition; - parts[currentIndex].Time = time; + parts[currentIndex].Time = time + 1; ++parts[currentIndex].InvalidationID; currentIndex = (currentIndex + 1) % max_sprites; @@ -201,7 +201,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private readonly TrailPart[] parts = new TrailPart[max_sprites]; private Vector2 size; - private readonly TrailBatch vertexBatch = new TrailBatch(max_sprites, 1); + private readonly QuadBatch vertexBatch = new QuadBatch(max_sprites, 1); public TrailDrawNode(CursorTrail source) : base(source) @@ -227,23 +227,50 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor shader.Bind(); shader.GetUniform("g_FadeClock").UpdateValue(ref time); - for (int i = 0; i < parts.Length; ++i) + RectangleF textureRect = texture.GetTextureRect(); + + foreach (var part in parts) { - if (parts[i].InvalidationID == -1) + if (part.InvalidationID == -1) continue; - vertexBatch.DrawTime = parts[i].Time; + if (time - part.Time >= 1) + continue; - Vector2 pos = parts[i].Position; + vertexBatch.Add(new TexturedTrailVertex + { + Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y + size.Y / 2), + TexturePosition = textureRect.BottomLeft, + Colour = DrawColourInfo.Colour.BottomLeft.Linear, + Time = part.Time + }); - DrawQuad( - texture, - new Quad(pos.X - size.X / 2, pos.Y - size.Y / 2, size.X, size.Y), - DrawColourInfo.Colour, - null, - vertexBatch.AddAction); + vertexBatch.Add(new TexturedTrailVertex + { + Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y + size.Y / 2), + TexturePosition = textureRect.BottomRight, + Colour = DrawColourInfo.Colour.BottomRight.Linear, + Time = part.Time + }); + + vertexBatch.Add(new TexturedTrailVertex + { + Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y - size.Y / 2), + TexturePosition = textureRect.TopRight, + Colour = DrawColourInfo.Colour.TopRight.Linear, + Time = part.Time + }); + + vertexBatch.Add(new TexturedTrailVertex + { + Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y - size.Y / 2), + TexturePosition = textureRect.TopLeft, + Colour = DrawColourInfo.Colour.TopLeft.Linear, + Time = part.Time + }); } + vertexBatch.Draw(); shader.Unbind(); } @@ -253,25 +280,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor vertexBatch.Dispose(); } - - // Todo: This shouldn't exist, but is currently used to reduce allocations by caching variable-capturing closures. - private class TrailBatch : QuadBatch - { - public new readonly Action AddAction; - public float DrawTime; - - public TrailBatch(int size, int maxBuffers) - : base(size, maxBuffers) - { - AddAction = v => Add(new TexturedTrailVertex - { - Position = v.Position, - TexturePosition = v.TexturePosition, - Time = DrawTime + 1, - Colour = v.Colour, - }); - } - } } [StructLayout(LayoutKind.Sequential)] From a554ca728bf0b8e4f7db9034e08a908c7c549dc5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 16 Dec 2019 15:27:54 +0900 Subject: [PATCH 2781/2815] Don't reuse the same control point references --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index d5d99640af..a4ed966abb 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -128,7 +128,7 @@ namespace osu.Game.Rulesets.Catch.Objects if (value != null) { - path.ControlPoints.AddRange(value.ControlPoints); + path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position.Value, c.Type.Value))); path.ExpectedDistance.Value = value.ExpectedDistance.Value; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 34e5a7f3cd..fe65ab78d1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Objects if (value != null) { - path.ControlPoints.AddRange(value.ControlPoints); + path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position.Value, c.Type.Value))); path.ExpectedDistance.Value = value.ExpectedDistance.Value; } } From fef1877095ff240dbb65bb6d020cc376d965e482 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2019 07:36:30 +0000 Subject: [PATCH 2782/2815] Bump ppy.osu.Game.Resources from 2019.1010.0 to 2019.1215.0 Bumps [ppy.osu.Game.Resources](https://github.com/ppy/osu-resources) from 2019.1010.0 to 2019.1215.0. - [Release notes](https://github.com/ppy/osu-resources/releases) - [Commits](https://github.com/ppy/osu-resources/compare/2019.1010.0...2019.1215.0) Signed-off-by: dependabot-preview[bot] --- 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 239e1c2d31..abb3cc8244 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -53,7 +53,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 56d65830f9..e5f34b1c7e 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 a90e90db19..c84e617285 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -73,7 +73,7 @@ - + From add04e98e18d734e84fd88a1aad35fe71a5c90ba Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 16 Dec 2019 18:10:44 +0900 Subject: [PATCH 2783/2815] Fix cursortrail texture not being bound --- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 4d6db83d7a..4e86662ec6 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -227,6 +227,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor shader.Bind(); shader.GetUniform("g_FadeClock").UpdateValue(ref time); + texture.TextureGL.Bind(); + RectangleF textureRect = texture.GetTextureRect(); foreach (var part in parts) From 83f77d9c3510cd4c68f1574c2a90993d04932779 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 16 Dec 2019 18:41:02 +0900 Subject: [PATCH 2784/2815] Make the layout faster --- osu.Game/Screens/Play/HUDOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index dc32fc7cd5..840c1d37e3 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Play { public class HUDOverlay : Container { - private const int fade_duration = 400; + private const float fade_duration = 400; private const Easing fade_easing = Easing.Out; public readonly KeyCounterDisplay KeyCounter; @@ -103,8 +103,8 @@ namespace osu.Game.Screens.Play Origin = Anchor.BottomRight, Position = -new Vector2(5, TwoLayerButton.SIZE_RETRACTED.Y), AutoSizeAxes = Axes.Both, - AutoSizeDuration = fade_duration, - AutoSizeEasing = fade_easing, + LayoutDuration = fade_duration / 2, + LayoutEasing = fade_easing, Direction = FillDirection.Vertical, Children = new Drawable[] { From a276643a4bcc8a2a6d376ec43fee0ce816cddea2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 16 Dec 2019 18:41:14 +0900 Subject: [PATCH 2785/2815] Reorder health display and score elements --- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 840c1d37e3..e2f362780d 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -78,6 +78,7 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both, Children = new Drawable[] { + HealthDisplay = CreateHealthDisplay(), topScoreContainer = new Container { Anchor = Anchor.TopCentre, @@ -90,7 +91,6 @@ namespace osu.Game.Screens.Play ComboCounter = CreateComboCounter(), }, }, - HealthDisplay = CreateHealthDisplay(), Progress = CreateProgress(), ModDisplay = CreateModsContainer(), HitErrorDisplay = CreateHitErrorDisplayOverlay(), From a85653ebec2607ca2675a1b106ebcd814c75fa66 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Dec 2019 12:24:59 +0900 Subject: [PATCH 2786/2815] Add comment --- osu.Game/Graphics/Containers/OsuScrollContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Graphics/Containers/OsuScrollContainer.cs b/osu.Game/Graphics/Containers/OsuScrollContainer.cs index df4c3a3324..ab72276ad0 100644 --- a/osu.Game/Graphics/Containers/OsuScrollContainer.cs +++ b/osu.Game/Graphics/Containers/OsuScrollContainer.cs @@ -85,6 +85,8 @@ namespace osu.Game.Graphics.Containers protected override bool OnScroll(ScrollEvent e) { + // allow for controlling volume when alt is held. + // mostly for compatibility with osu-stable. if (e.AltPressed) return false; return base.OnScroll(e); From f8ffa676931ebdc01946520c5cf5072ac7caf2f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Dec 2019 13:21:23 +0900 Subject: [PATCH 2787/2815] Add test and isolate ignore bindable from EnableUserDim --- .../Visual/Background/TestSceneUserDimContainer.cs | 14 ++++++++++++++ osu.Game/Graphics/Containers/UserDimContainer.cs | 12 ++---------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index 472c43096f..d5d4c7e5ec 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -88,6 +88,20 @@ namespace osu.Game.Tests.Visual.Background AddUntilStep("not lightened", () => userDimContainer.DimEqual(test_user_dim)); } + [Test] + public void TestIgnoreUserSettings() + { + AddStep("set dim level 0.6", () => userDimContainer.UserDimLevel.Value = test_user_dim); + AddUntilStep("dim reached", () => userDimContainer.DimEqual(test_user_dim)); + + AddStep($"ignore settings", () => userDimContainer.IgnoreUserSettings.Value = true); + AddUntilStep("no dim", () => userDimContainer.DimEqual(0)); + AddStep("set break", () => isBreakTime.Value = true); + AddAssert("no dim", () => userDimContainer.DimEqual(0)); + AddStep("clear break", () => isBreakTime.Value = false); + AddAssert("no dim", () => userDimContainer.DimEqual(0)); + } + private class TestUserDimContainer : UserDimContainer { public bool DimEqual(float expectedDimLevel) => Content.Colour == OsuColour.Gray(1f - expectedDimLevel); diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index e44e7a0d57..65c104b92f 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -59,7 +59,7 @@ namespace osu.Game.Graphics.Containers private float breakLightening => LightenDuringBreaks.Value && IsBreakTime.Value ? BREAK_LIGHTEN_AMOUNT : 0; - protected float DimLevel => Math.Max(EnableUserDim.Value ? (float)UserDimLevel.Value - breakLightening : 0, 0); + protected float DimLevel => Math.Max(EnableUserDim.Value && !IgnoreUserSettings.Value ? (float)UserDimLevel.Value - breakLightening : 0, 0); protected override Container Content => dimContent; @@ -88,7 +88,7 @@ namespace osu.Game.Graphics.Containers ShowStoryboard.ValueChanged += _ => UpdateVisuals(); ShowVideo.ValueChanged += _ => UpdateVisuals(); StoryboardReplacesBackground.ValueChanged += _ => UpdateVisuals(); - IgnoreUserSettings.ValueChanged += _ => updateSettings(); + IgnoreUserSettings.ValueChanged += _ => UpdateVisuals(); } protected override void LoadComplete() @@ -112,13 +112,5 @@ namespace osu.Game.Graphics.Containers dimContent.FadeTo(ContentDisplayed ? 1 : 0, BACKGROUND_FADE_DURATION, Easing.OutQuint); dimContent.FadeColour(OsuColour.Gray(1f - DimLevel), BACKGROUND_FADE_DURATION, Easing.OutQuint); } - - /// - /// Invoked when the IgnoreUserSettings bindable is changed - /// - private void updateSettings() - { - EnableUserDim.Value = !IgnoreUserSettings.Value; - } } } From 46dc2251e88703029e0234c18794850a5c1475e9 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2019 21:39:33 +0800 Subject: [PATCH 2788/2815] Add fxcop with every violated rule off. --- CodeAnalysis/osu.ruleset | 63 ++++++++++++++++++++++++++++++++++++++++ Directory.Build.props | 4 +++ osu.sln | 1 + 3 files changed, 68 insertions(+) create mode 100644 CodeAnalysis/osu.ruleset diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset new file mode 100644 index 0000000000..9bcca40983 --- /dev/null +++ b/CodeAnalysis/osu.ruleset @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index c0d740bac1..27a0bd0d48 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -18,7 +18,11 @@ + + + $(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset + true $(NoWarn);CS1591 diff --git a/osu.sln b/osu.sln index 1f4faae6b9..79823848f0 100644 --- a/osu.sln +++ b/osu.sln @@ -60,6 +60,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution global.json = global.json osu.Android.props = osu.Android.props osu.iOS.props = osu.iOS.props + CodeAnalysis\osu.ruleset = CodeAnalysis\osu.ruleset osu.sln.DotSettings = osu.sln.DotSettings osu.TestProject.props = osu.TestProject.props EndProjectSection From 9875fcea9971470479006fe81bf15103142fdb79 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Dec 2019 13:54:25 +0900 Subject: [PATCH 2789/2815] Add numbers to old skin for better identification --- .../Resources/old-skin/default-0.png | Bin 0 -> 2003 bytes .../Resources/old-skin/default-1.png | Bin 0 -> 1191 bytes .../Resources/old-skin/default-2.png | Bin 0 -> 1756 bytes .../Resources/old-skin/default-3.png | Bin 0 -> 1822 bytes .../Resources/old-skin/default-4.png | Bin 0 -> 1814 bytes .../Resources/old-skin/default-5.png | Bin 0 -> 1848 bytes .../Resources/old-skin/default-6.png | Bin 0 -> 2014 bytes .../Resources/old-skin/default-7.png | Bin 0 -> 1452 bytes .../Resources/old-skin/default-8.png | Bin 0 -> 1953 bytes .../Resources/old-skin/default-9.png | Bin 0 -> 1814 bytes 10 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-0.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-1.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-2.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-3.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-4.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-5.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-6.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-7.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-8.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-9.png diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-0.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-0.png new file mode 100644 index 0000000000000000000000000000000000000000..2af0569bcb30f4e87135669268698a79ac486f1f GIT binary patch literal 2003 zcmV;^2Q2uBP)0&s!{Eg(K)vw7OGA_klVuXw5~tP0kR@Dz8%i8MTl@(y z-HHy1)1^gl{JB-y+HK0-x1Q6y9t-`>_Z71(JIO;iA0MaZIq(1befqh%Io-c~gz5eR zLm$Gm41gB_aex&76Cer@2?%HRpx7S*9suqGCK+)4NX?g`CPir6_!2+@zzkReSO<_@ z^LM}y;5NVxn2{xn2}vVXR=YR*Dp~j2@G20fY&*JmX?;P$jC^e%tp`?UA%a4 zu&1YI6!hJjnwo0bzI}UET3VV}Mj~dj*+MQgfi!+r62RUDG$O!&o84~r!#T;(-`_t1 zr}!~nhvLwoLto3R)z;S5PyYV{z;cY#9Qpu~Nd?qsv4QUH?i(a@TN=^W5s+ zINU!W^Bs7wM5WSLKIbQq$h?xz7Su@L?Afz4dAr6`C(!syOAst9EQ}-NzzRxYHNR=s zu3gdz9R@X$wA};TU93o+l9ECXIGU4)pvhf!cD7k|;j34#(ya4GsxqM->FMe5LV4gL zVPg8_OE1)@(z$czD2ZQ!RAic(6H!r7MxjXaEf~YS645tVd*DWlMgMq^+n4fRFqaBN zw+4T#B&sA$0s|IFmu&!-QWAoGYhn^jPDVP-76R5i1;za`zg0#@8#V6#F((n_Uwzp! z=|!zp>m;o(75s~)Lz|tQ^^&QVlUPa%5!ppYMn;@M4{82_Uat>l%{8u0(%&~UG(-cW zi<2;Dk!NRT=LCrlP6euz!W#{;MOJ{}4oNST81*z4??Raas~UBiH*Z!|=0c@7N~ZTU zlfV`WRaXT@?cX?%NY*HM2PN^RS`t%JQ`81LLF~c6YZV+DHf%7fkUDkec52vedXomh zJW`-adE6e;ka%ICUKk*YCz)zgskynCR*g47l^%+c7!wjX z#l<_Bu4KVXl)`fOc$3fT>+5sW)GNo19pjlYI-)yb?&RjhJ6b*#`!ZjEM#_GDjmH4N zBdzDEg!iiHq8~kaw2OEp?4V4EB+$Y~_=QuaPDzp2wrv}i1{PPZa%sRPM&k^;h2Lwj zSfo2B!^6W4^4tR5xD{0CQJ?~4lo^Ze#EBCQ{70oA`X}+PQNlZN#-x8x~2pS*Ol7_U_%=En^qhTp*W9bx$4#6z~h!0;;X8EmEUKq=VAO z4EmXP6fPuFsMD)#Hk&IUA>j|u;WbLWtfZu5?bz7ZIC!_G5{GOg3pBG6=N{Y_U&tI!1Q-ydi0I`H>!)LlD z45>cBG@7uSp104&w z{3XU<8rTAj@Oe6P7}95XE9d0NleZwRn%uN$)5`{fAtKZS&YU@Osj{-Nqqnzrh9r|; z00x5*STNrSBoQ>!hK7b5aI3jmt1@(-*mT1twD2$ir-<9c^HomT($(d0h7A8xHu!Y zZ17;uHPe5A_(nb#ulv=dk=v5~f3R1R!9V>)r0sg5V) zH9!!_AOU<)Wi?B)Q+RVX$fobIVg`XPp}3U3NfT-k;)r0a7;M5VXA%n|UdF>(IS6kk lHvJ5V|K9=phm5}k7yxVV^TAT2(qBnTK@ z0-l32%{aNe_uRQn>?9|hf!n#ip2wW8x3=Ts3m|d+4Qb+fVseKf#rdn zA?|+&whi_j>@!#lY)`y^MH#JOM?c(q&ai4^Z*+HeKd=%Lm&=tO3A<6cJILgb*v)S|t*jXcwTurt&j1E-0hBxf&;7YnT zaKvIUo~)Z-yL#n|N9;wGU>Q!QGizaCA(Ck0yu7^Z+}zwOiin+^oxP!S<4u>OyDx{~UhYP_b_|;Gd6SKuUGbbgpp9`t4 zuP>&Gn4O&!z42De57RsXBCzh=d|g#lMHVtPHYN&LW+|WyMwhvehK7cGGhylN?d3wy z(_gF-T0Ak^+uOZn!Xh-gFxXFJArKb)^hJCPozzWzFLZ6OLOwxQ4y||u5%CTW3-#G%EpOKiF?SBr{}0zPSNZ7njxA)!!clPzCqVIo-wgyl56+~n(;ni_Jo1OkD1Vfhil za$v)$cVI>ZjFcm#>+F2@4IJ*tqtzP@=%Sg1mf)-rL@(a}LpQi(m5Sff)D!f5RVU)R;u zk%dU?u>@h+wc?TUV7K|g=kt+;=&^^25GQMNC+uM+ECPG1COS4pH=SMgkj!@Zy;t^zzGL^7M?C~|t z6(P*kfX1LgbBAOApi1 z{yuZhQ9(k1J@9cko>SVso=31tK4U#XWniAXQxWlxN0?XA z85)+b3Nrd#2H(8Hm=weF!_2XsP{{urDf3fW$_V)*zyQwC!s?l~#8?0T002ovPDHLk FV1oRZC2Ifx literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-2.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-2.png new file mode 100644 index 0000000000000000000000000000000000000000..dbf7bc73bc620b8782be5518443a36c91d1216cb GIT binary patch literal 1756 zcmV<21|#{2P)p$w|{(9A*RO z%p)u&yhey6EFmmnL=hHhoQDXF6GjOWgfYS};Su3)o_`i<;vgGCSVdS(SWj3U!T>%b z+#z@f_XrR8=7J&~#YA@yR?dK)=2trhXbg7}s#TVi5kV`Gf|W4K6PU!MmH&*(K^ z1P2Zr5N41hELn{>GGIKuq^71W2{vM6m$taLxGxF|3vJM?k?Rx}7t2q5wLguAeAL(1 zd#qNg4NpPuxe7O14mqn4ALo>7ayp%lgY{;GQ%tnBwmOYm3$o>}uS3pi#Q)`b!GuH* zj6OR$I(}8oojrT@wvlT|vPE&XXc}?OPItxe$&)9q8eN!WWo6gZBohMVS+q2cOy!fw z@#DvP+S=NB^j^8Ds_L?ox~~v!hiJJVuurv!|Aove4l*(_?0U#oR8-h;!+eMW>3Xn6 z(A?ZC90_TERP|ykmtMlRGWo-Y51;p^d3g4&UAr13(ik?vbCLCOa&i)k$WE$T{y?p6 z(fTowA0yl8;zM7tRasf-2Jp|bv$I#m#Kicz9gWfyi~K8Y5T<+>=jkQxa=GMn;U#X* zE&fiB$fHBaVk<)R=9P*cwed5djUQ_i*BQP+Ph_KB;!RCWZt3uM-|7r>5|ekK_9v_( zY=AqdoD1*oBftXtQN_`D5gct4Z6>5{y#*U&LWQ0n{+Oi_m^hP%Muz|*gJ@nNyo#*O zt9(Q>yrS6WVhug&PH`Qw$M^L{n|$9P>HC^+JB!{65wWKn-3I}F8i%zg4o;mqWi=A* zW5(j~V$B3@mV+SuA<7HU_T9R5>oZh} zdamPeINa&!>F1@S^Eq)H)NCX|O`7VJJiVlmEB5T!W0$7BferpI=ZA=}nHMYMkuH-q zqU-(p_n(&}f^y*oudUD#XZKi!+(JkWmNd1BHYrCSdtV{aqtmLaAW27XD*uGAG{mHl zXue~cRoxRbO8ONvuY@8&8ty^q|5LrB(b6eMBiWsSi%e>`hsB^Y;&?A{tGvra2`0;k zdtf0-nuc?HjZb;!Dk$Pa8jbW?&W6k6VDJ3N( z88eMCBHY>8={|Dg$W@K~Z!`#M^G`Ta-r+?$?w2@|ewXlp{3FQX=+UFrnQ9ptzHs4! zi*D4Zh_I0X8FeekE2XkZCas4s$Wr^}K7IQ1?Eu}8-tjRfqkd(5ZQ`ge;2%{2y}iBY zV!}4)iTxzF5K;tB0XcKx)!#O;-Sr|~m*^RFb#*T3aKvBA2T?OiWPLbEqpX$W=2Dil zQ^YyJtO=$Q59P4gY|3_RTr1+(P)rdr3CF7w#r~qaS$fxBb%?jbzE>2vxw&E0Hrz?_ z{4U-OoKBo&vQA87W@aXalXzoeqbw7*Rz^j{k#yb`>3p`{Woiv+^Jik3cPofTibjA+ z%&?M1ZLZk-d8k>dFzI}g^bYYQ8DEl2Zu*Nj%pEWL|9Oc>+x^L8s>q@RYT=G%13S#Z yFpz!1`;+&0({xl z!WaUPdgUhj#U&~~aA31EC}4od2qXp);Rk>C$q(V@0L@|`3WhWb5t+!%0Xk{M#jwO7 zFy87s^+8jVj(AW5lU`k2HI|i?>6ues16J%NAXsfYe_EJVR8;t6wX3VE zSrhpVa0^y@&r#+K0;sL=Co@y+3lnK+X)(|g6VEjuz3VLHV>CI12US&7Q6V8Ahka}! zeHcmD@7Rmu)|4I@85uk~J8N*4gi@&t=6&$O!on_*i7c2!nNn^mbApYdV-}Nxx;?_f z!vm(Krrzfv-rn9mnxCI9tTsjy9dP{X5sr3<1W z930H%4Sf)^H{*i zBS{23h>YEV&d$zkj2iBePfAL3r{lM!Xu;d&A9CaVthdcD?N@}gDm z0j^g(N?h~+p(Fv+QpuyO2L%OPpy_qMm(EHzw+0F`z+JRQz&J@&lSuWsJkUqbN^g;mSxM zT!Pmws0%MwZN(QZF1vWPUU$a#S@Iu^!cEWv=#M*<86LjhJejQzO*3&iVy0Oqs=}c^78V|VEb9L^3T!n)9mi?uJ-iwG}{)zAn>o) zRH7&YS{gtoba-xnJ#*CK%&C_sDjlchk}Nkjms_j%_xH~;(|h9TJb<>r(jQz|St)T> zBrz%a0kswq$#J<~3W$==I$n?MulP}{wpWMGgDweGY!)P?#mqK!LbJHXy&Ex1u8VV5|6Z3J^r>Hsg2ytxRT5j0W(_@fAd~9rN zQD_QHOiX0+kjBNaC8iu}0 z)H~d>*;k=$m=R7u93Pg@Ru1KL!s6m$9lpP0NF$dHN2INV1oi*UQajCJo0XN7Y2JE0 zh%4OK*!UCkszew+5Z262DjaJCM6bTjQ<}EeDTJ}Nq|}UJvgiXq_NEQNbqnx4;10eD zbg9~6h$~2Fz03AZPy#@@j*Ezh2z8d6Fe)r8ELfZioYMfu#?jHyKN*J;k2$K8egz&I zTxBA=BUD77D9Ql5?4{6dqA>RhzySB(bgRC#^C|^bHkMMh$^%4ilPQtOJaYUw8a|12={s5$!(MW|36v#JpL1403@th-uUEs@Bjb+ M07*qoM6N<$f~Rj>w*UYD literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-4.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-4.png new file mode 100644 index 0000000000000000000000000000000000000000..4564f6d8bff88ce7a1e983aa9192b8c882789f5d GIT binary patch literal 1814 zcmV+x2kH2UP)MX4xD zf25+&_`^?;7>(E#Nd$>eNnaomF(fo5Ch!6eeIN!x0uMwXfe?AmHFJrMt*yQ6a5&_FpC`1^Z%w=C zJCY8WYw)Tc313)1#2v`)6Jb+6$LNigvs5SK0`Y68Roagv#t zd04gI?(XgpsX0WL_w_Uuj!^g^YjkvUm{A7?@uH%lNN(QG(FqU9pAJXFeQ9ZFQL245 zH#f^fMrnrsQX#Tpr%s)UGP@en)6?beh7t+$b>j)X=yP{WO#V^nzYkT ziD-p&VPZL<2?uf!UK@vnZc~*cFyuGx+{z;nBC(An#oOW<1R!anXgv}n{$vuE zg|7&3&&S1m85tRoCf&**?`=v%m;{7fnh2L5k)NNR||qVjAY%u8;Hj*e1)OT4L0@dLYwS=z2sXh`JJ z%nFIRy1F#Irw9_*^);6di-j#4_6{SG#Dy$+QPaLj_>#sv?CXNWD}<+o7IH^|T&d3m z30*Ui&cZ4yD?M$zNSj}U+pZQLhy%XR^MwTqMMWqq>!TaKBZ*Om^nL@*6nY(h( zesPn{W{cm-Bw%fAZ3A+Gg>sOHTKmM+q@<)HmMw0Y1ZzYt2dx$vjA$Tln2QAKu5!?V zeB)LoVz=96w;C2o*jGwvSFF0a`rEBcq`bUb-nYh9_dt$XSRk>S;h zK?z%})-&Avj&PR7@05u^BtAxXhwD!3W*r?JNqV`9E2Ti0XQyJN6UP!^m&@uJd}_D{>E`RYWN-cbhDXLyEsd@;Ht{L zsYE0)D6<~oHKX!BSCcIMuCIl}x)SlW{ZC}8+1~;T0CqcF2-dk}y8r+H07*qoM6N<$ Ef`bE6AOHXW literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-5.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-5.png new file mode 100644 index 0000000000000000000000000000000000000000..dcef35eb594a4c047cf37dcfb7acb660cb155de6 GIT binary patch literal 1848 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-7.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-7.png new file mode 100644 index 0000000000000000000000000000000000000000..b9079ad5d5f863ccbbbeac6ab2677ac0d2645698 GIT binary patch literal 1452 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 n?OHhGXep*ZTPPG6 z96*o~Ka@eKiOiH35FtuXA%TyY06%;%fA9$jL;O%6p$U)@HP(nCkWvBxO@t7JLo&jBQyWN|r{ch6hwv-du0@2lV4-PIkJVTV8TdcDlE zHUN9TS%53R8Q=i0C7*c=FajO`wgC6o^^r7LL4u9|Pr%CzJ_65g1Lgn=fF-~R;343N zmWTD|1{y4@XBK=F@FpXB5Cch%17-mmY$vRfwfX_Q9=G--K&;Y=US3}IadB~OF)=Y- zo}Ql09v&V}%I8;BR<@RxmkkRG3tNMOgX^80oogyzgKlyIa0~d{mm&*9&%otvKoq@S zP*C8Po}L~Q931Q?YD%NgxHCLFJlECLHCIzpGpG3TAYcG}dsl^+MY4?OWk4vumz|yM zU0q$B;Ns%qbPztUxw*O3*w{E&R#rAiZr=iQ0IzjTWR*m5yIWgZqaq_CgAOAlO;1lR z#>U3BFD)(Yuv@Z(Ep`kRmx*cW?MJs1zWwjHZ{u$5*{2tO& zM2Vh)OW1yhqE9w7Gz@rqdw&7z)rN(IT^$=6o7?L-bS-%9GJYq!^$y@giRG3}K^4QRZOk=;(+P-TJ(|ymkdq z^A=`iX7+1Y12YT;GMoAXUQlxbFF*+X5*ixnl$e+pEJAc(U|_Pjxp|RI(GvB5&j6-K z$Kb(0(y~@@ad9}A27w{%&17L>%T6cLsH&>EBpM(^MMZ;T`W@g;7R}TA=Ire3nwGT! z0s_3jSbSG;x6e__q6m3|TU9hgp;bplMz*-y0d51)$3zd9hQVOi(YhuaI=*D;LCIcV z0!5dSlHx6D)!N$HaWWkRqDC^8(x4Dzl*a{1)*MLj@$r75`H=+GG*eS_oRlGpu0WOSCPWCiWQbOqp6&0EOr8s14>8`oDx;l%j zO;Q-T!D&wRgzPcd*Vjk4-JoJse;&B|5J1T9q|E^z@&731&jX&54P3P{MeR%WRnrj{ z@Rbc*mGQ(zxqVts89rcpsgIu}zhlq_b3r5{)YsS7cgTsjZKDhfEJH*V3=9lBPo}Ri zjiF;DkjVvdb91GvN>bt~>I_cStja+W3RxCUI}9E+eI0NS2%lqxrD>$5rV5L8%->|X z0V(kjC2O*7eSQ5LN`XVQqqerTF;VMQ zR8&yw`CCv>nnzDh4;AdIX0l9l{LcSUQc^Om*oHkvX=!PKg0Qqz`3@69uId<39jg}X zS?yJW0Eu>OS7J;}O)WsF{sUxy&o?$U3^Hqsj*iZ99)M(QNtrluvWzeW#;5#)V5u9s zM=)SVz9D1-IX`6$hHWC;wkxd3X4=ZSy1HZs2M7EAqf-bmvj)XxE2+W00*()!scu;p zXlv>&W5XJV=xFw>(WLZkX&P@t!xQP~-xb+I6BY-;^L{YrMsnkt+wp>M_8{EN~O1jQh5XIM=AM?&4=F#5X-efHT2G4jbi!w4Y zhLp`4z_8B3J1$wvIC4EiEm{d$oQzNi2!TmYZrnEU5qs)^eyUS^oOd6!H1_ z`BByqz7>)(Fa2M9!(H+oGBg=jEL*B$aYLa_Z3P|`o z)32V)HPqho$eCgySO5S307*qoM6N<$ Ef@aS_IsgCw literal 0 HcmV?d00001 From ecfc6dfa3d41f27190381abe28f9dd28e86e46bd Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2019 21:41:29 +0800 Subject: [PATCH 2790/2815] CA1825: use Array.Empty. --- CodeAnalysis/osu.ruleset | 3 --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 3 ++- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 4 ++-- osu.Game.Rulesets.Osu/OsuRuleset.cs | 3 ++- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 3 ++- osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs | 3 ++- .../Visual/Multiplayer/TestSceneMatchParticipants.cs | 2 +- osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs | 2 +- .../Visual/Online/TestSceneUserProfilePreviousUsernames.cs | 2 +- osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs | 2 +- .../Screens/Ladder/Components/DrawableMatchTeam.cs | 2 +- osu.Game/Beatmaps/BeatmapInfo.cs | 4 ++-- osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 2 +- osu.Game/Overlays/Mods/ModSection.cs | 2 +- osu.Game/Overlays/Music/PlaylistList.cs | 2 +- osu.Game/Overlays/UserProfileOverlay.cs | 6 ++---- osu.Game/Rulesets/Mods/Mod.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 4 ++-- osu.Game/Screens/Play/SquareGraph.cs | 2 +- osu.Game/Screens/Select/Details/FailRetryGraph.cs | 4 ++-- 20 files changed, 28 insertions(+), 29 deletions(-) diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset index 9bcca40983..61e6520beb 100644 --- a/CodeAnalysis/osu.ruleset +++ b/CodeAnalysis/osu.ruleset @@ -57,7 +57,4 @@ - - - \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 506fa23fa9..bf5b00528b 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -18,6 +18,7 @@ using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Difficulty; using osu.Game.Rulesets.Difficulty; using osu.Game.Scoring; +using System; namespace osu.Game.Rulesets.Catch { @@ -112,7 +113,7 @@ namespace osu.Game.Rulesets.Catch }; default: - return new Mod[] { }; + return Array.Empty(); } } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index a96c79b40b..3e3dc5cf66 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -158,7 +158,7 @@ namespace osu.Game.Rulesets.Mania }; default: - return new Mod[] { }; + return Array.Empty(); } } @@ -268,7 +268,7 @@ namespace osu.Game.Rulesets.Mania return stage1Bindings.Concat(stage2Bindings); } - return new KeyBinding[0]; + return Array.Empty(); } public override string GetVariantName(int variant) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 2f43909332..b182e5a658 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -26,6 +26,7 @@ using osu.Game.Rulesets.Osu.Difficulty; using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Scoring; using osu.Game.Skinning; +using System; namespace osu.Game.Rulesets.Osu { @@ -149,7 +150,7 @@ namespace osu.Game.Rulesets.Osu }; default: - return new Mod[] { }; + return Array.Empty(); } } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index ab9c95159c..0b4cb9801e 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -18,6 +18,7 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Difficulty; using osu.Game.Scoring; +using System; namespace osu.Game.Rulesets.Taiko { @@ -111,7 +112,7 @@ namespace osu.Game.Rulesets.Taiko }; default: - return new Mod[] { }; + return Array.Empty(); } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index 875e7b9758..4c5c18f38a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.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 NUnit.Framework; using osu.Framework.Graphics; @@ -31,7 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay requestCount = 0; increment = skip_time; - Child = gameplayClockContainer = new GameplayClockContainer(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), new Mod[] { }, 0) + Child = gameplayClockContainer = new GameplayClockContainer(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), Array.Empty(), 0) { RelativeSizeAxes = Axes.Both, Children = new Drawable[] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs index 50df4022dc..1ac914e27d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }); AddStep(@"set max", () => Room.MaxParticipants.Value = 10); - AddStep(@"clear users", () => Room.Participants.Value = new User[] { }); + AddStep(@"clear users", () => Room.Participants.Value = System.Array.Empty()); AddStep(@"set max to null", () => Room.MaxParticipants.Value = null); } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index 98da63508b..15f9c9a013 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Online }, Title = "osu!volunteer", Colour = "ff0000", - Achievements = new User.UserAchievement[0], + Achievements = Array.Empty(), }; public TestSceneUserProfileOverlay() diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs index d09a50b12c..048a1950fd 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Online new User { PreviousUsernames = new[] { "longusername", "longerusername" } }, new User { PreviousUsernames = new[] { "test", "angelsim", "verylongusername" } }, new User { PreviousUsernames = new[] { "ihavenoidea", "howcani", "makethistext", "anylonger" } }, - new User { PreviousUsernames = new string[0] }, + new User { PreviousUsernames = Array.Empty() }, null }; diff --git a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs index ba63013886..f3eecf8afe 100644 --- a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tournament.Screens.Editors get { if (editorInfo == null) - return new MenuItem[0]; + return Array.Empty(); return new MenuItem[] { diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs index ded21730f3..031d6bf3d2 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs @@ -192,7 +192,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components get { if (editorInfo == null) - return new MenuItem[0]; + return Array.Empty(); return new MenuItem[] { diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 6e82c465dc..bcc9ab885e 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -98,7 +98,7 @@ namespace osu.Game.Beatmaps { if (string.IsNullOrEmpty(value)) { - Bookmarks = new int[0]; + Bookmarks = Array.Empty(); return; } @@ -111,7 +111,7 @@ namespace osu.Game.Beatmaps } [NotMapped] - public int[] Bookmarks { get; set; } = new int[0]; + public int[] Bookmarks { get; set; } = Array.Empty(); public double DistanceSpacing { get; set; } public int BeatDivisor { get; set; } diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 59a27e3fde..9ea254b23f 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -55,7 +55,7 @@ namespace osu.Game.Beatmaps private class DummyRuleset : Ruleset { - public override IEnumerable GetModsFor(ModType type) => new Mod[] { }; + public override IEnumerable GetModsFor(ModType type) => Array.Empty(); public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) { diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index c55d1d8f70..7235a18a23 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -81,7 +81,7 @@ namespace osu.Game.Overlays.Mods } } - private ModButton[] buttons = { }; + private ModButton[] buttons = Array.Empty(); protected override bool OnKeyDown(KeyDownEvent e) { diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index 83528298b1..3cd04ac809 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -239,7 +239,7 @@ namespace osu.Game.Overlays.Music private class ItemSearchContainer : FillFlowContainer, IHasFilterableChildren { - public IEnumerable FilterTerms => new string[] { }; + public IEnumerable FilterTerms => Array.Empty(); public bool MatchingFilter { diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 468eb22b01..b5e7b8bedb 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.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.Graphics; @@ -55,10 +56,7 @@ namespace osu.Game.Overlays new BeatmapsSection(), new KudosuSection() } - : new ProfileSection[] - { - //new AboutSection(), - }; + : Array.Empty(); tabs = new ProfileTabControl { diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 1c280c820d..b780ec9e76 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Mods /// The mods this mod cannot be enabled with. /// [JsonIgnore] - public virtual Type[] IncompatibleMods => new Type[] { }; + public virtual Type[] IncompatibleMods => Array.Empty(); ///

/// Creates a copy of this initialised to a default state. diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 45aa904b98..2550f69286 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets /// /// The legacy enum which will be converted /// An enumerable of constructed s - public virtual IEnumerable ConvertLegacyMods(LegacyMods mods) => new Mod[] { }; + public virtual IEnumerable ConvertLegacyMods(LegacyMods mods) => Array.Empty(); public ModAutoplay GetAutoplayMod() => GetAllMods().OfType().First(); @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets /// /// A variant. /// A list of valid s. - public virtual IEnumerable GetDefaultKeyBindings(int variant = 0) => new KeyBinding[] { }; + public virtual IEnumerable GetDefaultKeyBindings(int variant = 0) => Array.Empty(); /// /// Gets the name for a key binding variant. This is used for display in the settings overlay. diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index 715ba3c065..a667466965 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Play } } - private float[] calculatedValues = { }; // values but adjusted to fit the amount of columns + private float[] calculatedValues = Array.Empty(); // values but adjusted to fit the amount of columns private int[] values; diff --git a/osu.Game/Screens/Select/Details/FailRetryGraph.cs b/osu.Game/Screens/Select/Details/FailRetryGraph.cs index 121f8efe5a..134fd0598a 100644 --- a/osu.Game/Screens/Select/Details/FailRetryGraph.cs +++ b/osu.Game/Screens/Select/Details/FailRetryGraph.cs @@ -27,8 +27,8 @@ namespace osu.Game.Screens.Select.Details metrics = value; - var retries = Metrics?.Retries ?? new int[0]; - var fails = Metrics?.Fails ?? new int[0]; + var retries = Metrics?.Retries ?? Array.Empty(); + var fails = Metrics?.Fails ?? Array.Empty(); float maxValue = fails.Any() ? fails.Zip(retries, (fail, retry) => fail + retry).Max() : 0; failGraph.MaxValue = maxValue; From d7b3578cc61f6cd99d940443ef0d14e8faeccbb2 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2019 21:52:05 +0800 Subject: [PATCH 2791/2815] CA2201: throw correct exception type. --- CodeAnalysis/osu.ruleset | 3 +++ osu.Game.Tests/Visual/TestSceneOsuGame.cs | 4 ++-- osu.Game/Online/API/APIRequest.cs | 10 +++++++++- .../Rulesets/Difficulty/Utils/LimitedCapacityStack.cs | 2 +- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset index 61e6520beb..b82799fea4 100644 --- a/CodeAnalysis/osu.ruleset +++ b/CodeAnalysis/osu.ruleset @@ -57,4 +57,7 @@ + + + \ No newline at end of file diff --git a/osu.Game.Tests/Visual/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/TestSceneOsuGame.cs index e495b2a95a..492494ada3 100644 --- a/osu.Game.Tests/Visual/TestSceneOsuGame.cs +++ b/osu.Game.Tests/Visual/TestSceneOsuGame.cs @@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual foreach (var type in requiredGameDependencies) { if (game.Dependencies.Get(type) == null) - throw new Exception($"{type} has not been cached"); + throw new InvalidOperationException($"{type} has not been cached"); } return true; @@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual foreach (var type in requiredGameBaseDependencies) { if (gameBase.Dependencies.Get(type) == null) - throw new Exception($"{type} has not been cached"); + throw new InvalidOperationException($"{type} has not been cached"); } return true; diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index b424e0f086..fcbd4d314a 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -122,7 +122,7 @@ namespace osu.Game.Online.API // attempt to decode a displayable error string. var error = JsonConvert.DeserializeObject(responseString); if (error != null) - e = new Exception(error.ErrorMessage, e); + e = new APIException(error.ErrorMessage, e); } catch { @@ -154,6 +154,14 @@ namespace osu.Game.Online.API } } + public class APIException : InvalidOperationException + { + public APIException(string messsage, Exception innerException) + : base(messsage, innerException) + { + } + } + public delegate void APIFailureHandler(Exception e); public delegate void APISuccessHandler(); diff --git a/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs b/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs index d47caf409b..3cab04d904 100644 --- a/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs +++ b/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Difficulty.Utils get { if (i < 0 || i > Count - 1) - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(i)); i += marker; if (i > capacity - 1) From 3c39fde7ff938f0397639cec8f7e871fdf8c4b9f Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2019 21:59:49 +0800 Subject: [PATCH 2792/2815] CA1065: throw NotSupportedException in properties. --- CodeAnalysis/osu.ruleset | 1 - osu.Game/Rulesets/UI/DrawableRuleset.cs | 28 ++++++++++++------------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset index b82799fea4..2f072ffa45 100644 --- a/CodeAnalysis/osu.ruleset +++ b/CodeAnalysis/osu.ruleset @@ -39,7 +39,6 @@ - diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 5033fd0686..0bb99517ef 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -515,34 +515,34 @@ namespace osu.Game.Rulesets.UI public Stream GetStream(string name) => primary.GetStream(name) ?? secondary.GetStream(name); - public IEnumerable GetAvailableResources() => throw new NotImplementedException(); + public IEnumerable GetAvailableResources() => throw new NotSupportedException(); - public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotImplementedException(); + public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotSupportedException(); - public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotImplementedException(); + public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotSupportedException(); - public BindableNumber Volume => throw new NotImplementedException(); + public BindableNumber Volume => throw new NotSupportedException(); - public BindableNumber Balance => throw new NotImplementedException(); + public BindableNumber Balance => throw new NotSupportedException(); - public BindableNumber Frequency => throw new NotImplementedException(); + public BindableNumber Frequency => throw new NotSupportedException(); - public BindableNumber Tempo => throw new NotImplementedException(); + public BindableNumber Tempo => throw new NotSupportedException(); - public IBindable GetAggregate(AdjustableProperty type) => throw new NotImplementedException(); + public IBindable GetAggregate(AdjustableProperty type) => throw new NotSupportedException(); - public IBindable AggregateVolume => throw new NotImplementedException(); + public IBindable AggregateVolume => throw new NotSupportedException(); - public IBindable AggregateBalance => throw new NotImplementedException(); + public IBindable AggregateBalance => throw new NotSupportedException(); - public IBindable AggregateFrequency => throw new NotImplementedException(); + public IBindable AggregateFrequency => throw new NotSupportedException(); - public IBindable AggregateTempo => throw new NotImplementedException(); + public IBindable AggregateTempo => throw new NotSupportedException(); public int PlaybackConcurrency { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); } public void Dispose() From 09257b0c6dd17130b5d7c6e9f31726328e9bb368 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2019 22:07:46 +0800 Subject: [PATCH 2793/2815] CA1820: use IsNullOrEmpty. --- CodeAnalysis/osu.ruleset | 1 - osu.Game/Overlays/DirectOverlay.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset index 2f072ffa45..5329e84024 100644 --- a/CodeAnalysis/osu.ruleset +++ b/CodeAnalysis/osu.ruleset @@ -48,7 +48,6 @@ - diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index aedbd1b08b..9daf55c796 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -116,7 +116,7 @@ namespace osu.Game.Overlays Filter.Search.Current.ValueChanged += text => { - if (text.NewValue != string.Empty) + if (!string.IsNullOrEmpty(text.NewValue)) { Header.Tabs.Current.Value = DirectTab.Search; From d5994ed4845b196b249f0c51db70d999ccbe359d Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2019 22:21:21 +0800 Subject: [PATCH 2794/2815] CA2208: create exceptions correctly. --- CodeAnalysis/osu.ruleset | 1 - osu.Game/Graphics/ScreenshotManager.cs | 2 +- osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs | 2 +- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 ++-- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 2 +- osu.Game/Rulesets/Scoring/HitWindows.cs | 2 +- osu.Game/Screens/Play/ReplayPlayerLoader.cs | 2 +- osu.Game/Users/UserPanel.cs | 2 +- 8 files changed, 8 insertions(+), 9 deletions(-) diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset index 5329e84024..2a0c6fb928 100644 --- a/CodeAnalysis/osu.ruleset +++ b/CodeAnalysis/osu.ruleset @@ -51,7 +51,6 @@ - diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index 02d928ec66..b9151b7393 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -119,7 +119,7 @@ namespace osu.Game.Graphics break; default: - throw new ArgumentOutOfRangeException(nameof(screenshotFormat)); + throw new InvalidOperationException($"Unknown enum member {nameof(ScreenshotFormat)} {screenshotFormat.Value}."); } notificationOverlay.Post(new SimpleNotification diff --git a/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs b/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs index 3cab04d904..1fc5abce90 100644 --- a/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs +++ b/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Difficulty.Utils public LimitedCapacityStack(int capacity) { if (capacity < 0) - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(capacity)); this.capacity = capacity; array = new T[capacity]; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index ed48ddbc2f..386805d7e5 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -150,8 +150,8 @@ namespace osu.Game.Rulesets.Objects.Drawables if (HitObject.SampleControlPoint == null) { - throw new ArgumentNullException(nameof(HitObject.SampleControlPoint), $"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}." - + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}."); + throw new InvalidOperationException($"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}." + + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}."); } samples = samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).ToArray(); diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 7fddb442d1..bdd019719b 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Objects.Legacy int repeatCount = Parsing.ParseInt(split[6]); if (repeatCount > 9000) - throw new ArgumentOutOfRangeException(nameof(repeatCount), @"Repeat count is way too high"); + throw new FormatException(@"Repeat count is way too high"); // osu-stable treated the first span of the slider as a repeat, but no repeats are happening repeatCount = Math.Max(0, repeatCount - 1); diff --git a/osu.Game/Rulesets/Scoring/HitWindows.cs b/osu.Game/Rulesets/Scoring/HitWindows.cs index 39d67f1071..018b50bd3d 100644 --- a/osu.Game/Rulesets/Scoring/HitWindows.cs +++ b/osu.Game/Rulesets/Scoring/HitWindows.cs @@ -165,7 +165,7 @@ namespace osu.Game.Rulesets.Scoring return miss; default: - throw new ArgumentException(nameof(result)); + throw new ArgumentException("Unknown enum member", nameof(result)); } } diff --git a/osu.Game/Screens/Play/ReplayPlayerLoader.cs b/osu.Game/Screens/Play/ReplayPlayerLoader.cs index 86179ef067..c8ca604902 100644 --- a/osu.Game/Screens/Play/ReplayPlayerLoader.cs +++ b/osu.Game/Screens/Play/ReplayPlayerLoader.cs @@ -15,7 +15,7 @@ namespace osu.Game.Screens.Play : base(() => new ReplayPlayer(score)) { if (score.Replay == null) - throw new ArgumentNullException(nameof(score.Replay), $"{nameof(score)} must have a non-null {nameof(score.Replay)}."); + throw new ArgumentException($"{nameof(score)} must have a non-null {nameof(score.Replay)}.", nameof(score)); scoreInfo = score.ScoreInfo; } diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index c63c12773e..6ddbc13a06 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -63,7 +63,7 @@ namespace osu.Game.Users private void load(UserProfileOverlay profile) { if (colours == null) - throw new ArgumentNullException(nameof(colours)); + throw new InvalidOperationException($"{nameof(colours)} not initialized!"); FillFlowContainer infoContainer; From e46f6627e4f0a932a5d52c255810d6277a7263c7 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2019 22:26:10 +0800 Subject: [PATCH 2795/2815] CA1052: make type static. --- CodeAnalysis/osu.ruleset | 1 - osu.Game/IO/Legacy/SerializationReader.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset index 2a0c6fb928..0756adb05c 100644 --- a/CodeAnalysis/osu.ruleset +++ b/CodeAnalysis/osu.ruleset @@ -9,7 +9,6 @@ - diff --git a/osu.Game/IO/Legacy/SerializationReader.cs b/osu.Game/IO/Legacy/SerializationReader.cs index 82b2c4be32..aeb3ce754d 100644 --- a/osu.Game/IO/Legacy/SerializationReader.cs +++ b/osu.Game/IO/Legacy/SerializationReader.cs @@ -192,7 +192,7 @@ namespace osu.Game.IO.Legacy } } - public class DynamicDeserializer + public static class DynamicDeserializer { private static VersionConfigToNamespaceAssemblyObjectBinder versionBinder; private static BinaryFormatter formatter; From caf3f774baac67213e470d66f5e21e8e8f7616c5 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2019 22:39:09 +0800 Subject: [PATCH 2796/2815] CA1309: compare strings correctly. --- CodeAnalysis/osu.ruleset | 1 + osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs | 2 +- osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs | 2 +- osu.Game/Scoring/ScoreManager.cs | 2 +- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 6 +++--- osu.Game/Screens/Select/FilterCriteria.cs | 2 +- osu.Game/Skinning/LegacySkinResourceStore.cs | 2 +- .../Storyboards/Drawables/DrawableStoryboardAnimation.cs | 2 +- osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs | 2 +- 9 files changed, 11 insertions(+), 10 deletions(-) diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset index 0756adb05c..1aa8f66c93 100644 --- a/CodeAnalysis/osu.ruleset +++ b/CodeAnalysis/osu.ruleset @@ -54,6 +54,7 @@ + \ No newline at end of file diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 4924842e81..f9d71a2a6e 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -42,7 +42,7 @@ namespace osu.Game.Beatmaps } } - private string getPathForFile(string filename) => BeatmapSetInfo.Files.FirstOrDefault(f => string.Equals(f.Filename, filename, StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; + private string getPathForFile(string filename) => BeatmapSetInfo.Files.FirstOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; private TextureStore textureStore; diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index 42865c686c..393bcfdb3c 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -70,6 +70,6 @@ namespace osu.Game.Beatmaps.ControlPoints public override bool EquivalentTo(ControlPoint other) => other is SampleControlPoint otherTyped && - string.Equals(SampleBank, otherTyped.SampleBank) && SampleVolume == otherTyped.SampleVolume; + SampleBank == otherTyped.SampleBank && SampleVolume == otherTyped.SampleVolume; } } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 3279af05b6..332b3e3f05 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -57,7 +57,7 @@ namespace osu.Game.Scoring } protected override IEnumerable GetStableImportPaths(Storage stableStorage) - => stableStorage.GetFiles(ImportFromStablePath).Where(p => HandledExtensions.Any(ext => Path.GetExtension(p)?.Equals(ext, StringComparison.InvariantCultureIgnoreCase) ?? false)); + => stableStorage.GetFiles(ImportFromStablePath).Where(p => HandledExtensions.Any(ext => Path.GetExtension(p)?.Equals(ext, StringComparison.OrdinalIgnoreCase) ?? false)); public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps(), Files.Store); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 35816fe620..301d0d4dae 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -37,13 +37,13 @@ namespace osu.Game.Screens.Select.Carousel { default: case SortMode.Artist: - return string.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist, StringComparison.InvariantCultureIgnoreCase); + return string.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist, StringComparison.OrdinalIgnoreCase); case SortMode.Title: - return string.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title, StringComparison.InvariantCultureIgnoreCase); + return string.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title, StringComparison.OrdinalIgnoreCase); case SortMode.Author: - return string.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username, StringComparison.InvariantCultureIgnoreCase); + return string.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username, StringComparison.OrdinalIgnoreCase); case SortMode.DateAdded: return otherSet.BeatmapSet.DateAdded.CompareTo(BeatmapSet.DateAdded); diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index e3ad76ac35..c4d9996377 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -105,7 +105,7 @@ namespace osu.Game.Screens.Select public string SearchTerm; - public bool Equals(OptionalTextFilter other) => SearchTerm?.Equals(other.SearchTerm) ?? true; + public bool Equals(OptionalTextFilter other) => SearchTerm == other.SearchTerm; } } } diff --git a/osu.Game/Skinning/LegacySkinResourceStore.cs b/osu.Game/Skinning/LegacySkinResourceStore.cs index 79a4e2e932..249d48b34b 100644 --- a/osu.Game/Skinning/LegacySkinResourceStore.cs +++ b/osu.Game/Skinning/LegacySkinResourceStore.cs @@ -34,7 +34,7 @@ namespace osu.Game.Skinning } private string getPathForFile(string filename) => - source.Files.Find(f => string.Equals(f.Filename, filename, StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; + source.Files.Find(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; public override IEnumerable GetAvailableResources() => source.Files.Select(f => f.Filename); } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index de3077c025..4f8e39fa1b 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -71,7 +71,7 @@ namespace osu.Game.Storyboards.Drawables { var framePath = Animation.Path.Replace(".", frame + "."); - var path = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.Equals(framePath, StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; + var path = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.Equals(framePath, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; if (path == null) continue; diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index 3a117d1713..ff48dab7e5 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -66,7 +66,7 @@ namespace osu.Game.Storyboards.Drawables [BackgroundDependencyLoader] private void load(IBindable beatmap, TextureStore textureStore) { - var path = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; + var path = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; if (path == null) return; From 61a6106e5270f692b19fe7c265b990e64254ff73 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 3 Dec 2019 19:20:49 +0800 Subject: [PATCH 2797/2815] CA2200: don't explictly throw caught exception. --- CodeAnalysis/osu.ruleset | 1 - osu.Game/Online/API/APIAccess.cs | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset index 1aa8f66c93..0ec2f24797 100644 --- a/CodeAnalysis/osu.ruleset +++ b/CodeAnalysis/osu.ruleset @@ -38,7 +38,6 @@ - diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 1c45d26afd..8bfc28e774 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json.Linq; using osu.Framework.Bindables; +using osu.Framework.Extensions.ExceptionExtensions; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Configuration; @@ -249,7 +250,7 @@ namespace osu.Game.Online.API catch { // if we couldn't deserialize the error message let's throw the original exception outwards. - throw e; + e.Rethrow(); } } From 40b43b85f17499d0e772b03ebcaf9b02434f343c Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 10 Dec 2019 21:04:26 +0800 Subject: [PATCH 2798/2815] CA1715: use prefix for generic parameters. --- CodeAnalysis/osu.ruleset | 1 - .../Objects/Drawables/DrawableTaikoHitObject.cs | 8 ++++---- ...MutableDatabaseBackedStoreWithFileIncludes.cs | 6 +++--- .../UserInterfaceV2/LabelledComponent.cs | 6 +++--- osu.Game/IO/Legacy/SerializationReader.cs | 6 +++--- osu.Game/IO/Legacy/SerializationWriter.cs | 4 ++-- osu.Game/Online/Leaderboards/Leaderboard.cs | 10 +++++----- osu.Game/Overlays/OverlayHeaderTabControl.cs | 2 +- osu.Game/Overlays/OverlayTabControl.cs | 8 ++++---- osu.Game/Overlays/Settings/SettingsSlider.cs | 16 ++++++++-------- osu.Game/Overlays/UserProfileOverlay.cs | 2 +- 11 files changed, 34 insertions(+), 35 deletions(-) diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset index 0ec2f24797..d497365f87 100644 --- a/CodeAnalysis/osu.ruleset +++ b/CodeAnalysis/osu.ruleset @@ -17,7 +17,6 @@ - diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 0db6498c12..2da5a9c403 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -105,19 +105,19 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } } - public abstract class DrawableTaikoHitObject : DrawableTaikoHitObject - where TaikoHitType : TaikoHitObject + public abstract class DrawableTaikoHitObject : DrawableTaikoHitObject + where TTaikoHit : TaikoHitObject { public override Vector2 OriginPosition => new Vector2(DrawHeight / 2); - public new TaikoHitType HitObject; + public new TTaikoHit HitObject; protected readonly Vector2 BaseSize; protected readonly TaikoPiece MainPiece; private readonly Container strongHitContainer; - protected DrawableTaikoHitObject(TaikoHitType hitObject) + protected DrawableTaikoHitObject(TTaikoHit hitObject) : base(hitObject) { HitObject = hitObject; diff --git a/osu.Game/Database/MutableDatabaseBackedStoreWithFileIncludes.cs b/osu.Game/Database/MutableDatabaseBackedStoreWithFileIncludes.cs index 5d6ff6b09b..102081cd65 100644 --- a/osu.Game/Database/MutableDatabaseBackedStoreWithFileIncludes.cs +++ b/osu.Game/Database/MutableDatabaseBackedStoreWithFileIncludes.cs @@ -7,9 +7,9 @@ using osu.Framework.Platform; namespace osu.Game.Database { - public abstract class MutableDatabaseBackedStoreWithFileIncludes : MutableDatabaseBackedStore - where T : class, IHasPrimaryKey, ISoftDelete, IHasFiles - where U : INamedFileInfo + public abstract class MutableDatabaseBackedStoreWithFileIncludes : MutableDatabaseBackedStore + where T : class, IHasPrimaryKey, ISoftDelete, IHasFiles + where TFileInfo : INamedFileInfo { protected MutableDatabaseBackedStoreWithFileIncludes(IDatabaseContextFactory contextFactory, Storage storage = null) : base(contextFactory, storage) diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs index 1819b36667..dd6a902989 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs @@ -7,15 +7,15 @@ using osu.Framework.Graphics.UserInterface; namespace osu.Game.Graphics.UserInterfaceV2 { - public abstract class LabelledComponent : LabelledDrawable, IHasCurrentValue - where T : Drawable, IHasCurrentValue + public abstract class LabelledComponent : LabelledDrawable, IHasCurrentValue + where TDrawable : Drawable, IHasCurrentValue { protected LabelledComponent(bool padded) : base(padded) { } - public Bindable Current + public Bindable Current { get => Component.Current; set => Component.Current = value; diff --git a/osu.Game/IO/Legacy/SerializationReader.cs b/osu.Game/IO/Legacy/SerializationReader.cs index aeb3ce754d..17cbd19838 100644 --- a/osu.Game/IO/Legacy/SerializationReader.cs +++ b/osu.Game/IO/Legacy/SerializationReader.cs @@ -116,13 +116,13 @@ namespace osu.Game.IO.Legacy } /// Reads a generic Dictionary from the buffer. - public IDictionary ReadDictionary() + public IDictionary ReadDictionary() { int count = ReadInt32(); if (count < 0) return null; - IDictionary d = new Dictionary(); - for (int i = 0; i < count; i++) d[(T)ReadObject()] = (U)ReadObject(); + IDictionary d = new Dictionary(); + for (int i = 0; i < count; i++) d[(TKey)ReadObject()] = (TValue)ReadObject(); return d; } diff --git a/osu.Game/IO/Legacy/SerializationWriter.cs b/osu.Game/IO/Legacy/SerializationWriter.cs index f30e4492af..c75de93bc8 100644 --- a/osu.Game/IO/Legacy/SerializationWriter.cs +++ b/osu.Game/IO/Legacy/SerializationWriter.cs @@ -102,7 +102,7 @@ namespace osu.Game.IO.Legacy } /// Writes a generic IDictionary to the buffer. - public void Write(IDictionary d) + public void Write(IDictionary d) { if (d == null) { @@ -112,7 +112,7 @@ namespace osu.Game.IO.Legacy { Write(d.Count); - foreach (KeyValuePair kvp in d) + foreach (KeyValuePair kvp in d) { WriteObject(kvp.Key); WriteObject(kvp.Value); diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 94c50185da..9c48ebd09b 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -19,7 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Online.Leaderboards { - public abstract class Leaderboard : Container, IOnlineComponent + public abstract class Leaderboard : Container, IOnlineComponent { private const double fade_duration = 300; @@ -39,9 +39,9 @@ namespace osu.Game.Online.Leaderboards protected override Container Content => content; - private IEnumerable scores; + private IEnumerable scores; - public IEnumerable Scores + public IEnumerable Scores { get => scores; set @@ -288,7 +288,7 @@ namespace osu.Game.Online.Leaderboards /// /// A callback which should be called when fetching is completed. Scheduling is not required. /// An responsible for the fetch operation. This will be queued and performed automatically. - protected abstract APIRequest FetchScores(Action> scoresCallback); + protected abstract APIRequest FetchScores(Action> scoresCallback); private Placeholder currentPlaceholder; @@ -359,6 +359,6 @@ namespace osu.Game.Online.Leaderboards } } - protected abstract LeaderboardScore CreateDrawableScore(ScoreInfo model, int index); + protected abstract LeaderboardScore CreateDrawableScore(TScoreInfo model, int index); } } diff --git a/osu.Game/Overlays/OverlayHeaderTabControl.cs b/osu.Game/Overlays/OverlayHeaderTabControl.cs index 5b56771dc1..7d0cdad6d8 100644 --- a/osu.Game/Overlays/OverlayHeaderTabControl.cs +++ b/osu.Game/Overlays/OverlayHeaderTabControl.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays AccentColour = AccentColour, }; - private class OverlayHeaderTabItem : OverlayTabItem + private class OverlayHeaderTabItem : OverlayTabItem { public OverlayHeaderTabItem(string value) : base(value) diff --git a/osu.Game/Overlays/OverlayTabControl.cs b/osu.Game/Overlays/OverlayTabControl.cs index 20649c8a74..4c396eabc1 100644 --- a/osu.Game/Overlays/OverlayTabControl.cs +++ b/osu.Game/Overlays/OverlayTabControl.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays foreach (TabItem tabItem in TabContainer) { - ((OverlayTabItem)tabItem).AccentColour = value; + ((OverlayTabItem)tabItem).AccentColour = value; } } } @@ -59,9 +59,9 @@ namespace osu.Game.Overlays protected override Dropdown CreateDropdown() => null; - protected override TabItem CreateTabItem(T value) => new OverlayTabItem(value); + protected override TabItem CreateTabItem(T value) => new OverlayTabItem(value); - protected class OverlayTabItem : TabItem + protected class OverlayTabItem : TabItem { private readonly ExpandingBar bar; @@ -84,7 +84,7 @@ namespace osu.Game.Overlays } } - public OverlayTabItem(U value) + public OverlayTabItem(T value) : base(value) { AutoSizeAxes = Axes.X; diff --git a/osu.Game/Overlays/Settings/SettingsSlider.cs b/osu.Game/Overlays/Settings/SettingsSlider.cs index 20e08c0cd8..96c0279a7b 100644 --- a/osu.Game/Overlays/Settings/SettingsSlider.cs +++ b/osu.Game/Overlays/Settings/SettingsSlider.cs @@ -12,11 +12,11 @@ namespace osu.Game.Overlays.Settings { } - public class SettingsSlider : SettingsItem - where T : struct, IEquatable, IComparable, IConvertible - where U : OsuSliderBar, new() + public class SettingsSlider : SettingsItem + where TValue : struct, IEquatable, IComparable, IConvertible + where TSlider : OsuSliderBar, new() { - protected override Drawable CreateControl() => new U + protected override Drawable CreateControl() => new TSlider { Margin = new MarginPadding { Top = 5, Bottom = 5 }, RelativeSizeAxes = Axes.X @@ -24,14 +24,14 @@ namespace osu.Game.Overlays.Settings public bool TransferValueOnCommit { - get => ((U)Control).TransferValueOnCommit; - set => ((U)Control).TransferValueOnCommit = value; + get => ((TSlider)Control).TransferValueOnCommit; + set => ((TSlider)Control).TransferValueOnCommit = value; } public float KeyboardStep { - get => ((U)Control).KeyboardStep; - set => ((U)Control).KeyboardStep = value; + get => ((TSlider)Control).KeyboardStep; + set => ((TSlider)Control).KeyboardStep = value; } } } diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index b5e7b8bedb..a34fc619a8 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -165,7 +165,7 @@ namespace osu.Game.Overlays AccentColour = colours.Seafoam; } - private class ProfileTabItem : OverlayTabItem + private class ProfileTabItem : OverlayTabItem { public ProfileTabItem(ProfileSection value) : base(value) From 5079feaad16ccd4df3b0e993d4bc7c6705deb686 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Dec 2019 14:04:03 +0900 Subject: [PATCH 2799/2815] Remove unnecessary string interpolation --- osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index d5d4c7e5ec..fede99f450 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.Background AddStep("set dim level 0.6", () => userDimContainer.UserDimLevel.Value = test_user_dim); AddUntilStep("dim reached", () => userDimContainer.DimEqual(test_user_dim)); - AddStep($"ignore settings", () => userDimContainer.IgnoreUserSettings.Value = true); + AddStep("ignore settings", () => userDimContainer.IgnoreUserSettings.Value = true); AddUntilStep("no dim", () => userDimContainer.DimEqual(0)); AddStep("set break", () => isBreakTime.Value = true); AddAssert("no dim", () => userDimContainer.DimEqual(0)); From 946a202ee555cc26f12aabca4d9964dea2d99cdf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Dec 2019 15:34:16 +0900 Subject: [PATCH 2800/2815] Fix online replays not being available locally --- osu.Game/Beatmaps/BeatmapManager.cs | 4 +++- osu.Game/Database/DownloadableArchiveModelManager.cs | 6 ++++-- osu.Game/Scoring/ScoreManager.cs | 4 +++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index a2e750cac5..a10ad73817 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -158,7 +158,9 @@ namespace osu.Game.Beatmaps void resetIds() => beatmapSet.Beatmaps.ForEach(b => b.OnlineBeatmapID = null); } - protected override bool CheckLocalAvailability(BeatmapSetInfo model, IQueryable items) => items.Any(b => b.OnlineBeatmapSetID == model.OnlineBeatmapSetID); + protected override bool CheckLocalAvailability(BeatmapSetInfo model, IQueryable items) + => base.CheckLocalAvailability(model, items) + || (model.OnlineBeatmapSetID != null && items.Any(b => b.OnlineBeatmapSetID == model.OnlineBeatmapSetID)); /// /// Delete a beatmap difficulty. diff --git a/osu.Game/Database/DownloadableArchiveModelManager.cs b/osu.Game/Database/DownloadableArchiveModelManager.cs index 243060388f..5f688c149d 100644 --- a/osu.Game/Database/DownloadableArchiveModelManager.cs +++ b/osu.Game/Database/DownloadableArchiveModelManager.cs @@ -33,7 +33,8 @@ namespace osu.Game.Database private readonly MutableDatabaseBackedStoreWithFileIncludes modelStore; - protected DownloadableArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, IAPIProvider api, MutableDatabaseBackedStoreWithFileIncludes modelStore, IIpcHost importHost = null) + protected DownloadableArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, IAPIProvider api, MutableDatabaseBackedStoreWithFileIncludes modelStore, + IIpcHost importHost = null) : base(storage, contextFactory, modelStore, importHost) { this.api = api; @@ -124,7 +125,8 @@ namespace osu.Game.Database /// The whose existence needs to be checked. /// The usable items present in the store. /// Whether the exists. - protected abstract bool CheckLocalAvailability(TModel model, IQueryable items); + protected virtual bool CheckLocalAvailability(TModel model, IQueryable items) + => model.ID > 0 && items.Any(i => i.ID == model.ID && i.Files.Any()); public ArchiveDownloadRequest GetExistingDownload(TModel model) => currentDownloads.Find(r => r.Model.Equals(model)); diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 3279af05b6..5c846e8062 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -69,6 +69,8 @@ namespace osu.Game.Scoring protected override ArchiveDownloadRequest CreateDownloadRequest(ScoreInfo score, bool minimiseDownload) => new DownloadReplayRequest(score); - protected override bool CheckLocalAvailability(ScoreInfo model, IQueryable items) => items.Any(s => s.Equals(model) && s.Files.Any()); + protected override bool CheckLocalAvailability(ScoreInfo model, IQueryable items) + => base.CheckLocalAvailability(model, items) + || (model.OnlineScoreID != null && items.Any(i => i.OnlineScoreID == model.OnlineScoreID)); } } From 59c3b39ed8a7ab63fe653ec60a0d9cdf3e4551af Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Dec 2019 15:46:50 +0900 Subject: [PATCH 2801/2815] Add test --- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 47 ++++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index 89b5db9e1b..a95e699470 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -10,6 +11,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; using osu.Game.Beatmaps; +using osu.Game.IO.Archives; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; @@ -154,7 +156,30 @@ namespace osu.Game.Tests.Scores.IO } } - private async Task loadIntoOsu(OsuGameBase osu, ScoreInfo score) + [Test] + public async Task TestOnlineScoreIsAvailableLocally() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithDeletedBeatmapSet")) + { + try + { + var osu = await loadOsu(host); + + await loadIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2 }, new TestArchiveReader()); + + var scoreManager = osu.Dependencies.Get(); + + // Note: A new score reference is used here since the import process mutates the original object to set an ID + Assert.That(scoreManager.IsAvailableLocally(new ScoreInfo { OnlineScoreID = 2 })); + } + finally + { + host.Exit(); + } + } + } + + private async Task loadIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null) { var beatmapManager = osu.Dependencies.Get(); @@ -165,7 +190,7 @@ namespace osu.Game.Tests.Scores.IO score.Ruleset = new OsuRuleset().RulesetInfo; var scoreManager = osu.Dependencies.Get(); - await scoreManager.Import(score); + await scoreManager.Import(score, archive); return scoreManager.GetAllUsableScores().FirstOrDefault(); } @@ -196,5 +221,23 @@ namespace osu.Game.Tests.Scores.IO Assert.IsTrue(task.Wait(timeout), failureMessage); } + + private class TestArchiveReader : ArchiveReader + { + public TestArchiveReader() + : base("test_archive") + { + } + + public override Stream GetStream(string name) => new MemoryStream(); + + public override void Dispose() + { + } + + public override IEnumerable Filenames => new[] { "test_file.osr" }; + + public override Stream GetUnderlyingStream() => new MemoryStream(); + } } } From 927ba4c1133088a67233a32fd56ccbe670c97500 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 17 Dec 2019 15:20:05 +0800 Subject: [PATCH 2802/2815] Update expected exception in test. --- osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs b/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs index 1c78b63499..d5ac38008e 100644 --- a/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs +++ b/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.NonVisual { Assert.AreEqual(0, stack.Count); - Assert.Throws(() => + Assert.Throws(() => { int unused = stack[0]; }); @@ -55,7 +55,7 @@ namespace osu.Game.Tests.NonVisual // e.g. indices 3, 4, 5, 6 (out of range) for (int i = stack.Count; i < stack.Count + capacity; i++) { - Assert.Throws(() => + Assert.Throws(() => { int unused = stack[i]; }); @@ -80,7 +80,7 @@ namespace osu.Game.Tests.NonVisual // e.g. indices 3, 4, 5, 6 (out of range) for (int i = stack.Count; i < stack.Count + capacity; i++) { - Assert.Throws(() => + Assert.Throws(() => { int unused = stack[i]; }); From 6da581f3fe86047762679905ca3ce14bbccb836b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Dec 2019 16:35:40 +0900 Subject: [PATCH 2803/2815] Snap based on end position/time of the previous object --- .../Edit/OsuDistanceSnapGrid.cs | 3 ++- .../Edit/OsuHitObjectComposer.cs | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs index bde86a2890..ff3be97427 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using JetBrains.Annotations; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; @@ -10,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Edit public class OsuDistanceSnapGrid : CircularDistanceSnapGrid { public OsuDistanceSnapGrid(OsuHitObject hitObject, [CanBeNull] OsuHitObject nextHitObject = null) - : base(hitObject.StackedPosition, hitObject.StartTime, nextHitObject?.StartTime) + : base(hitObject.StackedEndPosition, hitObject.GetEndTime(), nextHitObject?.StartTime) { Masking = true; } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 675b09fc6d..a2c1a5f5f4 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -92,7 +92,24 @@ namespace osu.Game.Rulesets.Osu.Edit return null; OsuHitObject sourceObject = EditorBeatmap.HitObjects[sourceIndex]; - OsuHitObject targetObject = sourceIndex + targetOffset < EditorBeatmap.HitObjects.Count ? EditorBeatmap.HitObjects[sourceIndex + targetOffset] : null; + + int targetIndex = sourceIndex + targetOffset; + OsuHitObject targetObject = null; + + // Keep advancing the target object while its start time falls before the end time of the source object + while (true) + { + if (targetIndex >= EditorBeatmap.HitObjects.Count) + break; + + if (EditorBeatmap.HitObjects[targetIndex].StartTime >= sourceObject.GetEndTime()) + { + targetObject = EditorBeatmap.HitObjects[targetIndex]; + break; + } + + targetIndex++; + } return new OsuDistanceSnapGrid(sourceObject, targetObject); } From 1e798a8dbe1c31244e52e7ab7d01ec96a5ce76ff Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Dec 2019 17:53:48 +0900 Subject: [PATCH 2804/2815] Add abstract implementation of slider path --- .../Objects/Drawables/DrawableSlider.cs | 2 +- .../Drawables/Pieces/DrawableSliderPath.cs | 70 ++++++++++++++ .../Objects/Drawables/Pieces/SliderBody.cs | 92 ++++--------------- 3 files changed, 88 insertions(+), 76 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DrawableSliderPath.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 1e0402d492..e6ab2e580c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -201,7 +201,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.ApplySkin(skin, allowFallback); - Body.BorderSize = skin.GetConfig(OsuSkinConfiguration.SliderBorderSize)?.Value ?? SliderBody.DEFAULT_BORDER_SIZE; + Body.BorderSize = skin.GetConfig(OsuSkinConfiguration.SliderBorderSize)?.Value ?? 1; sliderPathRadius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS; updatePathRadius(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DrawableSliderPath.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DrawableSliderPath.cs new file mode 100644 index 0000000000..c31d6beb01 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DrawableSliderPath.cs @@ -0,0 +1,70 @@ +// 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.Lines; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + public abstract class DrawableSliderPath : SmoothPath + { + protected const float BORDER_PORTION = 0.128f; + protected const float GRADIENT_PORTION = 1 - BORDER_PORTION; + + private const float border_max_size = 8f; + private const float border_min_size = 0f; + + private Color4 borderColour = Color4.White; + + public Color4 BorderColour + { + get => borderColour; + set + { + if (borderColour == value) + return; + + borderColour = value; + + InvalidateTexture(); + } + } + + private Color4 accentColour = Color4.White; + + public Color4 AccentColour + { + get => accentColour; + set + { + if (accentColour == value) + return; + + accentColour = value; + + InvalidateTexture(); + } + } + + private float borderSize = 1; + + public float BorderSize + { + get => borderSize; + set + { + if (borderSize == value) + return; + + if (value < border_min_size || value > border_max_size) + return; + + borderSize = value; + + InvalidateTexture(); + } + } + + protected float CalculatedBorderPortion => BorderSize * BORDER_PORTION; + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs index 24a437c20e..dcaa9748e9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; using osuTK; @@ -12,9 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public abstract class SliderBody : CompositeDrawable { - public const float DEFAULT_BORDER_SIZE = 1; - - private SliderPath path; + private DrawableSliderPath path; protected Path Path => path; @@ -80,19 +79,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } /// - /// Initialises a new , releasing all resources retained by the old one. + /// Initialises a new , releasing all resources retained by the old one. /// public virtual void RecyclePath() { - InternalChild = path = new SliderPath + InternalChild = path = CreateSliderPath().With(p => { - Position = path?.Position ?? Vector2.Zero, - PathRadius = path?.PathRadius ?? 10, - AccentColour = path?.AccentColour ?? Color4.White, - BorderColour = path?.BorderColour ?? Color4.White, - BorderSize = path?.BorderSize ?? DEFAULT_BORDER_SIZE, - Vertices = path?.Vertices ?? Array.Empty() - }; + p.Position = path?.Position ?? Vector2.Zero; + p.PathRadius = path?.PathRadius ?? 10; + p.AccentColour = path?.AccentColour ?? Color4.White; + p.BorderColour = path?.BorderColour ?? Color4.White; + p.BorderSize = path?.BorderSize ?? 1; + p.Vertices = path?.Vertices ?? Array.Empty(); + }); } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => path.ReceivePositionalInputAt(screenSpacePos); @@ -103,77 +102,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces /// The vertices protected void SetVertices(IReadOnlyList vertices) => path.Vertices = vertices; - private class SliderPath : SmoothPath + protected virtual DrawableSliderPath CreateSliderPath() => new DefaultDrawableSliderPath(); + + protected class DefaultDrawableSliderPath : DrawableSliderPath { - private const float border_max_size = 8f; - private const float border_min_size = 0f; - - private const float border_portion = 0.128f; - private const float gradient_portion = 1 - border_portion; - private const float opacity_at_centre = 0.3f; private const float opacity_at_edge = 0.8f; - private Color4 borderColour = Color4.White; - - public Color4 BorderColour - { - get => borderColour; - set - { - if (borderColour == value) - return; - - borderColour = value; - - InvalidateTexture(); - } - } - - private Color4 accentColour = Color4.White; - - public Color4 AccentColour - { - get => accentColour; - set - { - if (accentColour == value) - return; - - accentColour = value; - - InvalidateTexture(); - } - } - - private float borderSize = DEFAULT_BORDER_SIZE; - - public float BorderSize - { - get => borderSize; - set - { - if (borderSize == value) - return; - - if (value < border_min_size || value > border_max_size) - return; - - borderSize = value; - - InvalidateTexture(); - } - } - - private float calculatedBorderPortion => BorderSize * border_portion; - protected override Color4 ColourAt(float position) { - if (calculatedBorderPortion != 0f && position <= calculatedBorderPortion) + if (CalculatedBorderPortion != 0f && position <= CalculatedBorderPortion) return BorderColour; - position -= calculatedBorderPortion; - return new Color4(AccentColour.R, AccentColour.G, AccentColour.B, (opacity_at_edge - (opacity_at_edge - opacity_at_centre) * position / gradient_portion) * AccentColour.A); + position -= CalculatedBorderPortion; + return new Color4(AccentColour.R, AccentColour.G, AccentColour.B, (opacity_at_edge - (opacity_at_edge - opacity_at_centre) * position / GRADIENT_PORTION) * AccentColour.A); } } } From 8cd96acffc2e3f3fe61062983804da1a4db33ff9 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 17 Dec 2019 12:05:35 +0300 Subject: [PATCH 2805/2815] CounterPill implementation --- .../Online/TestSceneProfileCounterPill.cs | 42 +++++++++++++ .../Overlays/Profile/Sections/CounterPill.cs | 61 +++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneProfileCounterPill.cs create mode 100644 osu.Game/Overlays/Profile/Sections/CounterPill.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneProfileCounterPill.cs b/osu.Game.Tests/Visual/Online/TestSceneProfileCounterPill.cs new file mode 100644 index 0000000000..468239cf08 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneProfileCounterPill.cs @@ -0,0 +1,42 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Game.Overlays.Profile.Sections; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneProfileCounterPill : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(CounterPill) + }; + + private readonly CounterPill pill; + private readonly BindableInt value = new BindableInt(); + + public TestSceneProfileCounterPill() + { + Child = pill = new CounterPill + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Current = { BindTarget = value } + }; + } + + [Test] + public void TestVisibility() + { + AddStep("Set value to 0", () => value.Value = 0); + AddAssert("Check hidden", () => !pill.IsPresent); + AddStep("Set value to 10", () => value.Value = 10); + AddAssert("Check visible", () => pill.IsPresent); + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/CounterPill.cs b/osu.Game/Overlays/Profile/Sections/CounterPill.cs new file mode 100644 index 0000000000..bd760c4139 --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/CounterPill.cs @@ -0,0 +1,61 @@ +// 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.Containers; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Framework.Bindables; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Overlays.Profile.Sections +{ + public class CounterPill : CircularContainer + { + private const int duration = 200; + + public readonly BindableInt Current = new BindableInt(); + + private readonly OsuSpriteText counter; + + public CounterPill() + { + AutoSizeAxes = Axes.Both; + Alpha = 0; + Masking = true; + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.05f) + }, + counter = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Horizontal = 10, Vertical = 5 }, + Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold) + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Current.BindValueChanged(onCurrentChanged, true); + } + + private void onCurrentChanged(ValueChangedEvent value) + { + if (value.NewValue == 0) + { + this.FadeOut(duration, Easing.OutQuint); + return; + } + + counter.Text = value.NewValue.ToString(); + this.FadeIn(duration, Easing.OutQuint); + } + } +} From 9caed9e98a25993d6984eb74d541c9e3ad1c6012 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Dec 2019 18:16:25 +0900 Subject: [PATCH 2806/2815] Add legacy slider body support --- .../Objects/Drawables/DrawableSlider.cs | 6 ++- .../Drawables/Pieces/SnakingSliderBody.cs | 12 ++--- osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 3 +- .../Skinning/LegacySliderBody.cs | 47 +++++++++++++++++++ .../Skinning/OsuLegacySkinTransformer.cs | 6 +++ 5 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index e6ab2e580c..46a219a69c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -24,9 +24,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public DrawableSliderHead HeadCircle => headContainer.Child; public DrawableSliderTail TailCircle => tailContainer.Child; - public readonly SnakingSliderBody Body; public readonly SliderBall Ball; + public SnakingSliderBody Body => (SnakingSliderBody)skinnedBody.Drawable; + + private readonly SkinnableDrawable skinnedBody; private readonly Container headContainer; private readonly Container tailContainer; private readonly Container tickContainer; @@ -51,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables InternalChildren = new Drawable[] { - Body = new SnakingSliderBody(s), + skinnedBody = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBody), _ => new SnakingSliderBody(), confineMode: ConfineMode.NoScaling), tickContainer = new Container { RelativeSizeAxes = Axes.Both }, repeatContainer = new Container { RelativeSizeAxes = Axes.Both }, Ball = new SliderBall(s, this) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs index f2150280b3..8a8668d6af 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osuTK; @@ -50,16 +51,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces /// private Vector2 snakedPathOffset; - private readonly Slider slider; - - public SnakingSliderBody(Slider slider) - { - this.slider = slider; - } + private Slider slider; [BackgroundDependencyLoader] - private void load() + private void load(DrawableHitObject drawableObject) { + slider = (Slider)drawableObject.HitObject; + Refresh(); } diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs index 8dd48eace0..4ea4220faf 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs @@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Osu ReverseArrow, HitCircleText, SliderFollowCircle, - SliderBall + SliderBall, + SliderBody, } } diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs new file mode 100644 index 0000000000..6a26529f4c --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs @@ -0,0 +1,47 @@ +// 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.Extensions.Color4Extensions; +using osu.Framework.MathUtils; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Skinning +{ + public class LegacySliderBody : SnakingSliderBody + { + protected override DrawableSliderPath CreateSliderPath() => new LegacyDrawableSliderPath(); + + private class LegacyDrawableSliderPath : DrawableSliderPath + { + public new Color4 AccentColour => new Color4(base.AccentColour.R, base.AccentColour.G, base.AccentColour.B, 0.70f); + + protected override Color4 ColourAt(float position) + { + if (CalculatedBorderPortion != 0f && position <= CalculatedBorderPortion) + return BorderColour; + + position -= BORDER_PORTION; + + Color4 outerColour = AccentColour.Darken(0.1f); + Color4 innerColour = lighten(AccentColour, 0.5f); + + return Interpolation.ValueAt(position / GRADIENT_PORTION, outerColour, innerColour, 0, 1); + } + + /// + /// Lightens a colour in a way more friendly to dark or strong colours. + /// + private static Color4 lighten(Color4 color, float amount) + { + amount *= 0.5f; + return new Color4( + Math.Min(1, color.R * (1 + 0.5f * amount) + 1 * amount), + Math.Min(1, color.G * (1 + 0.5f * amount) + 1 * amount), + Math.Min(1, color.B * (1 + 0.5f * amount) + 1 * amount), + color.A); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index f5b7d9166f..71770dedce 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -73,6 +73,12 @@ namespace osu.Game.Rulesets.Osu.Skinning return null; + case OsuSkinComponents.SliderBody: + if (hasHitCircle.Value) + return new LegacySliderBody(); + + return null; + case OsuSkinComponents.HitCircle: if (hasHitCircle.Value) return new LegacyMainCirclePiece(); From 023892738af69c35902c39c43945906fb0b284ea Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 17 Dec 2019 12:36:44 +0300 Subject: [PATCH 2807/2815] Integration into overlay --- .../Beatmaps/PaginatedBeatmapContainer.cs | 22 +++++++++++++++ .../Profile/Sections/PaginatedContainer.cs | 28 +++++++++++++++++-- .../Sections/Ranks/PaginatedScoreContainer.cs | 12 ++++++++ osu.Game/Users/User.cs | 18 ++++++++++++ 4 files changed, 77 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index 919f8a2fa0..6684420cf4 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -38,5 +38,27 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }; + + protected override int GetCount(User user) + { + switch (type) + { + default: + case BeatmapSetType.Favourite: + return user.FavouriteBeatmapsetCount; + + case BeatmapSetType.Graveyard: + return user.GraveyardBeatmapsetCount; + + case BeatmapSetType.Loved: + return user.LovedBeatmapsetCount; + + case BeatmapSetType.RankedAndApproved: + return user.RankedAndApprovedBeatmapsetCount; + + case BeatmapSetType.Unranked: + return user.UnrankedBeatmapsetCount; + } + } } } diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index dc1a847b14..94d5f99f86 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -23,6 +23,7 @@ namespace osu.Game.Overlays.Profile.Sections private readonly OsuSpriteText missingText; private APIRequest> retrievalRequest; private CancellationTokenSource loadCancellation; + private readonly BindableInt count = new BindableInt(); [Resolved] private IAPIProvider api { get; set; } @@ -44,11 +45,28 @@ namespace osu.Game.Overlays.Profile.Sections Children = new Drawable[] { - new OsuSpriteText + new FillFlowContainer { - Text = header, - Font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), Margin = new MarginPadding { Top = 10, Bottom = 10 }, + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = header, + Font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold), + }, + new CounterPill + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Current = { BindTarget = count } + } + } }, ItemsContainer = new FillFlowContainer { @@ -90,6 +108,8 @@ namespace osu.Game.Overlays.Profile.Sections VisiblePages = 0; ItemsContainer.Clear(); + count.Value = GetCount(e.NewValue); + if (e.NewValue != null) showMore(); } @@ -124,6 +144,8 @@ namespace osu.Game.Overlays.Profile.Sections }, loadCancellation.Token); }); + protected virtual int GetCount(User user) => 0; + protected abstract APIRequest> CreateRequest(); protected abstract Drawable CreateDrawableItem(TModel model); diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 5b58fc0930..fa74d76dfc 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -32,6 +32,18 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks protected override APIRequest> CreateRequest() => new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); + protected override int GetCount(User user) + { + switch (type) + { + default: + return 0; + + case ScoreType.Firsts: + return user.ScoresFirstCount; + } + } + protected override Drawable CreateDrawableItem(APILegacyScoreInfo model) { switch (type) diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index b15789f324..ebd9dbecd1 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -126,6 +126,24 @@ namespace osu.Game.Users [JsonProperty(@"follower_count")] public int FollowerCount; + [JsonProperty(@"favourite_beatmapset_count")] + public int FavouriteBeatmapsetCount; + + [JsonProperty(@"graveyard_beatmapset_count")] + public int GraveyardBeatmapsetCount; + + [JsonProperty(@"loved_beatmapset_count")] + public int LovedBeatmapsetCount; + + [JsonProperty(@"ranked_and_approved_beatmapset_count")] + public int RankedAndApprovedBeatmapsetCount; + + [JsonProperty(@"unranked_beatmapset_count")] + public int UnrankedBeatmapsetCount; + + [JsonProperty(@"scores_first_count")] + public int ScoresFirstCount; + [JsonProperty] private string[] playstyle { From bc9177983adc209fa185cbf549de01bcbab77222 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 17 Dec 2019 12:50:50 +0300 Subject: [PATCH 2808/2815] Fix possible null --- osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index 94d5f99f86..a30ff786fb 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -108,10 +108,11 @@ namespace osu.Game.Overlays.Profile.Sections VisiblePages = 0; ItemsContainer.Clear(); - count.Value = GetCount(e.NewValue); - if (e.NewValue != null) + { showMore(); + count.Value = GetCount(e.NewValue); + } } private void showMore() From 7c2884700eaffe391e37fa79a80deb3a30a431aa Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Dec 2019 19:29:27 +0900 Subject: [PATCH 2809/2815] Fix various display issues by abstracting further --- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 5 +- .../Objects/Drawables/DrawableRepeatPoint.cs | 3 +- .../Objects/Drawables/DrawableSlider.cs | 52 +++++------------ .../Drawables/Pieces/PlaySliderBody.cs | 57 +++++++++++++++++++ .../Objects/Drawables/Pieces/SliderBody.cs | 2 +- .../Skinning/LegacySliderBody.cs | 2 +- 6 files changed, 78 insertions(+), 43 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/PlaySliderBody.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index 7e20feba02..0cca3ae40c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -11,6 +11,7 @@ using osu.Game.Configuration; 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 { @@ -57,8 +58,8 @@ namespace osu.Game.Rulesets.Osu.Mods slider.AccentColour.BindValueChanged(_ => { //will trigger on skin change. - slider.Body.AccentColour = slider.AccentColour.Value.Opacity(0); - slider.Body.BorderColour = slider.AccentColour.Value; + ((PlaySliderBody)slider.Body.Drawable).AccentColour = slider.AccentColour.Value.Opacity(0); + ((PlaySliderBody)slider.Body.Drawable).BorderColour = slider.AccentColour.Value; }, true); break; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index 71cb9a9691..b81d94a673 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.MathUtils; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osu.Game.Rulesets.Scoring; using osuTK; using osu.Game.Skinning; @@ -98,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public void UpdateSnakingPosition(Vector2 start, Vector2 end) { bool isRepeatAtEnd = repeatPoint.RepeatIndex % 2 == 0; - List curve = drawableSlider.Body.CurrentCurve; + List curve = ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve; Position = isRepeatAtEnd ? end : start; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 46a219a69c..03183beff1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -11,7 +11,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Scoring; using osuTK.Graphics; @@ -25,10 +24,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public DrawableSliderTail TailCircle => tailContainer.Child; public readonly SliderBall Ball; + public readonly SkinnableDrawable Body; - public SnakingSliderBody Body => (SnakingSliderBody)skinnedBody.Drawable; + private PlaySliderBody sliderBody => (PlaySliderBody)Body.Drawable; - private readonly SkinnableDrawable skinnedBody; private readonly Container headContainer; private readonly Container tailContainer; private readonly Container tickContainer; @@ -39,10 +38,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly IBindable positionBindable = new Bindable(); private readonly IBindable stackHeightBindable = new Bindable(); private readonly IBindable scaleBindable = new Bindable(); - private readonly IBindable pathVersion = new Bindable(); - - [Resolved(CanBeNull = true)] - private OsuRulesetConfigManager config { get; set; } public DrawableSlider(Slider s) : base(s) @@ -53,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables InternalChildren = new Drawable[] { - skinnedBody = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBody), _ => new SnakingSliderBody(), confineMode: ConfineMode.NoScaling), + Body = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBody), _ => new DefaultSliderBody(), confineMode: ConfineMode.NoScaling), tickContainer = new Container { RelativeSizeAxes = Axes.Both }, repeatContainer = new Container { RelativeSizeAxes = Axes.Both }, Ball = new SliderBall(s, this) @@ -72,28 +67,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables [BackgroundDependencyLoader] private void load() { - config?.BindWith(OsuRulesetSetting.SnakingInSliders, Body.SnakingIn); - config?.BindWith(OsuRulesetSetting.SnakingOutSliders, Body.SnakingOut); - positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); stackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); - scaleBindable.BindValueChanged(scale => - { - updatePathRadius(); - Ball.Scale = new Vector2(scale.NewValue); - }); + scaleBindable.BindValueChanged(scale => Ball.Scale = new Vector2(scale.NewValue)); positionBindable.BindTo(HitObject.PositionBindable); stackHeightBindable.BindTo(HitObject.StackHeightBindable); scaleBindable.BindTo(HitObject.ScaleBindable); - pathVersion.BindTo(slider.Path.Version); - - pathVersion.BindValueChanged(_ => Body.Refresh()); AccentColour.BindValueChanged(colour => { - Body.AccentColour = colour.NewValue; - foreach (var drawableHitObject in NestedHitObjects) drawableHitObject.AccentColour.Value = colour.NewValue; }, true); @@ -171,16 +154,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); Ball.UpdateProgress(completionProgress); - Body.UpdateProgress(completionProgress); + sliderBody.UpdateProgress(completionProgress); foreach (DrawableHitObject hitObject in NestedHitObjects) { - if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(slider.Path.PositionAt(Body.SnakedStart ?? 0), slider.Path.PositionAt(Body.SnakedEnd ?? 0)); + if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(slider.Path.PositionAt(sliderBody.SnakedStart ?? 0), slider.Path.PositionAt(sliderBody.SnakedEnd ?? 0)); if (hitObject is IRequireTracking t) t.Tracking = Ball.Tracking; } - Size = Body.Size; - OriginPosition = Body.PathOffset; + Size = sliderBody.Size; + OriginPosition = sliderBody.PathOffset; if (DrawSize != Vector2.Zero) { @@ -194,28 +177,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public override void OnKilled() { base.OnKilled(); - Body.RecyclePath(); + sliderBody.RecyclePath(); } - private float sliderPathRadius; - protected override void ApplySkin(ISkinSource skin, bool allowFallback) { base.ApplySkin(skin, allowFallback); - Body.BorderSize = skin.GetConfig(OsuSkinConfiguration.SliderBorderSize)?.Value ?? 1; - sliderPathRadius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS; - updatePathRadius(); - - Body.AccentColour = skin.GetConfig(OsuSkinColour.SliderTrackOverride)?.Value ?? AccentColour.Value; - Body.BorderColour = skin.GetConfig(OsuSkinColour.SliderBorder)?.Value ?? Color4.White; - bool allowBallTint = skin.GetConfig(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false; Ball.Colour = allowBallTint ? AccentColour.Value : Color4.White; } - private void updatePathRadius() => Body.PathRadius = slider.Scale * sliderPathRadius; - protected override void CheckForResult(bool userTriggered, double timeOffset) { if (userTriggered || Time.Current < slider.EndTime) @@ -266,6 +238,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public Drawable ProxiedLayer => HeadCircle.ProxiedLayer; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Body.ReceivePositionalInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => sliderBody.ReceivePositionalInputAt(screenSpacePos); + + private class DefaultSliderBody : PlaySliderBody + { + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/PlaySliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/PlaySliderBody.cs new file mode 100644 index 0000000000..aa9caf193e --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/PlaySliderBody.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.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Configuration; +using osu.Game.Rulesets.Osu.Skinning; +using osu.Game.Skinning; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + public abstract class PlaySliderBody : SnakingSliderBody + { + private IBindable scaleBindable; + private IBindable pathVersion; + private IBindable accentColour; + + [Resolved] + private DrawableHitObject drawableObject { get; set; } + + [Resolved(CanBeNull = true)] + private OsuRulesetConfigManager config { get; set; } + + private Slider slider; + private float defaultPathRadius; + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + slider = (Slider)drawableObject.HitObject; + defaultPathRadius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS; + + scaleBindable = slider.ScaleBindable.GetBoundCopy(); + scaleBindable.BindValueChanged(_ => updatePathRadius(), true); + + pathVersion = slider.Path.Version.GetBoundCopy(); + pathVersion.BindValueChanged(_ => Refresh()); + + accentColour = drawableObject.AccentColour.GetBoundCopy(); + accentColour.BindValueChanged(accent => updateAccentColour(skin, accent.NewValue), true); + + config?.BindWith(OsuRulesetSetting.SnakingInSliders, SnakingIn); + config?.BindWith(OsuRulesetSetting.SnakingOutSliders, SnakingOut); + + BorderSize = skin.GetConfig(OsuSkinConfiguration.SliderBorderSize)?.Value ?? 1; + BorderColour = skin.GetConfig(OsuSkinColour.SliderBorder)?.Value ?? Color4.White; + } + + private void updatePathRadius() + => PathRadius = defaultPathRadius * scaleBindable.Value; + + private void updateAccentColour(ISkinSource skin, Color4 defaultAccentColour) + => AccentColour = skin.GetConfig(OsuSkinColour.SliderTrackOverride)?.Value ?? defaultAccentColour; + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs index dcaa9748e9..8758a4a066 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs @@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces protected virtual DrawableSliderPath CreateSliderPath() => new DefaultDrawableSliderPath(); - protected class DefaultDrawableSliderPath : DrawableSliderPath + private class DefaultDrawableSliderPath : DrawableSliderPath { private const float opacity_at_centre = 0.3f; private const float opacity_at_edge = 0.8f; diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs index 6a26529f4c..18a5d7a320 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs @@ -9,7 +9,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning { - public class LegacySliderBody : SnakingSliderBody + public class LegacySliderBody : PlaySliderBody { protected override DrawableSliderPath CreateSliderPath() => new LegacyDrawableSliderPath(); From f6cde911e2b4a13e57acb84863d9b524668c2985 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 17 Dec 2019 13:41:28 +0300 Subject: [PATCH 2810/2815] Use switch expressions --- .../Beatmaps/PaginatedBeatmapContainer.cs | 28 ++++++------------- .../Sections/Ranks/PaginatedScoreContainer.cs | 14 +++------- 2 files changed, 12 insertions(+), 30 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index 6684420cf4..fcd12e2b54 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -39,26 +39,14 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps Origin = Anchor.TopCentre, }; - protected override int GetCount(User user) + protected override int GetCount(User user) => type switch { - switch (type) - { - default: - case BeatmapSetType.Favourite: - return user.FavouriteBeatmapsetCount; - - case BeatmapSetType.Graveyard: - return user.GraveyardBeatmapsetCount; - - case BeatmapSetType.Loved: - return user.LovedBeatmapsetCount; - - case BeatmapSetType.RankedAndApproved: - return user.RankedAndApprovedBeatmapsetCount; - - case BeatmapSetType.Unranked: - return user.UnrankedBeatmapsetCount; - } - } + BeatmapSetType.Favourite => user.FavouriteBeatmapsetCount, + BeatmapSetType.Graveyard => user.GraveyardBeatmapsetCount, + BeatmapSetType.Loved => user.LovedBeatmapsetCount, + BeatmapSetType.RankedAndApproved => user.RankedAndApprovedBeatmapsetCount, + BeatmapSetType.Unranked => user.UnrankedBeatmapsetCount, + _ => 0 + }; } } diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index fa74d76dfc..e0f1c935da 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -32,17 +32,11 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks protected override APIRequest> CreateRequest() => new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); - protected override int GetCount(User user) + protected override int GetCount(User user) => type switch { - switch (type) - { - default: - return 0; - - case ScoreType.Firsts: - return user.ScoresFirstCount; - } - } + ScoreType.Firsts => user.ScoresFirstCount, + _ => 0 + }; protected override Drawable CreateDrawableItem(APILegacyScoreInfo model) { From 527ab1a72f08ce6c3c5eef7226ec166c9fb726b1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Dec 2019 19:49:13 +0900 Subject: [PATCH 2811/2815] Fix traceable mod not working on skin change --- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 15 ++++++++------- osu.Game/Skinning/SkinReloadableDrawable.cs | 15 ++++++++++++++- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index 0cca3ae40c..cf1ce517e8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -55,13 +55,8 @@ namespace osu.Game.Rulesets.Osu.Mods break; case DrawableSlider slider: - slider.AccentColour.BindValueChanged(_ => - { - //will trigger on skin change. - ((PlaySliderBody)slider.Body.Drawable).AccentColour = slider.AccentColour.Value.Opacity(0); - ((PlaySliderBody)slider.Body.Drawable).BorderColour = slider.AccentColour.Value; - }, true); - + slider.Body.OnSkinChanged += () => applySliderState(slider); + applySliderState(slider); break; case DrawableSpinner spinner: @@ -70,5 +65,11 @@ namespace osu.Game.Rulesets.Osu.Mods break; } } + + private void applySliderState(DrawableSlider slider) + { + ((PlaySliderBody)slider.Body.Drawable).AccentColour = slider.AccentColour.Value.Opacity(0); + ((PlaySliderBody)slider.Body.Drawable).BorderColour = slider.AccentColour.Value; + } } } diff --git a/osu.Game/Skinning/SkinReloadableDrawable.cs b/osu.Game/Skinning/SkinReloadableDrawable.cs index 6d0b22dd51..4a1aaa62bf 100644 --- a/osu.Game/Skinning/SkinReloadableDrawable.cs +++ b/osu.Game/Skinning/SkinReloadableDrawable.cs @@ -12,6 +12,11 @@ namespace osu.Game.Skinning /// public abstract class SkinReloadableDrawable : CompositeDrawable { + /// + /// Invoked when has changed. + /// + public event Action OnSkinChanged; + /// /// The current skin source. /// @@ -43,12 +48,18 @@ namespace osu.Game.Skinning private void onChange() => // schedule required to avoid calls after disposed. // note that this has the side-effect of components only performing a skin change when they are alive. - Scheduler.AddOnce(() => SkinChanged(CurrentSkin, allowDefaultFallback)); + Scheduler.AddOnce(skinChanged); protected override void LoadAsyncComplete() { base.LoadAsyncComplete(); + skinChanged(); + } + + private void skinChanged() + { SkinChanged(CurrentSkin, allowDefaultFallback); + OnSkinChanged?.Invoke(); } /// @@ -66,6 +77,8 @@ namespace osu.Game.Skinning if (CurrentSkin != null) CurrentSkin.SourceChanged -= onChange; + + OnSkinChanged = null; } } } From 2d85145eeccde1868c1fb843c852450e37c2abf7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Dec 2019 19:52:33 +0900 Subject: [PATCH 2812/2815] Make legacy accent colour multiplicative --- osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs index 18a5d7a320..dea08f843e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Skinning private class LegacyDrawableSliderPath : DrawableSliderPath { - public new Color4 AccentColour => new Color4(base.AccentColour.R, base.AccentColour.G, base.AccentColour.B, 0.70f); + public new Color4 AccentColour => new Color4(base.AccentColour.R, base.AccentColour.G, base.AccentColour.B, base.AccentColour.A * 0.70f); protected override Color4 ColourAt(float position) { From 49bf8d27d10ad2ceaf9feefe737b2f60ce2dffdd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Dec 2019 20:08:13 +0900 Subject: [PATCH 2813/2815] Move CreateScoreProcessor() to Ruleset --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 5 +++++ osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs | 4 ---- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 6 ++++++ osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 4 ---- osu.Game.Rulesets.Osu/OsuRuleset.cs | 6 ++++++ osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 4 ---- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 5 +++++ osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs | 4 ---- osu.Game/Rulesets/Ruleset.cs | 7 +++++++ osu.Game/Rulesets/UI/DrawableRuleset.cs | 9 --------- osu.Game/Screens/Play/Player.cs | 2 +- 11 files changed, 30 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 9fe287d1cc..ba5c1dee92 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -16,7 +16,9 @@ using osu.Game.Rulesets.Replays.Types; using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Difficulty; +using osu.Game.Rulesets.Catch.Scoring; using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; namespace osu.Game.Rulesets.Catch @@ -24,6 +26,9 @@ namespace osu.Game.Rulesets.Catch public class CatchRuleset : Ruleset { public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableCatchRuleset(this, beatmap, mods); + + public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new CatchScoreProcessor(beatmap); + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap); diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index 278ff97195..fdd820b891 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -10,10 +10,8 @@ using osu.Game.Replays; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Catch.Replays; -using osu.Game.Rulesets.Catch.Scoring; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; @@ -32,8 +30,6 @@ namespace osu.Game.Rulesets.Catch.UI TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450); } - public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(Beatmap); - protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, CreateDrawableRepresentation); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index bcafadb4c5..dc98a063b4 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -25,6 +25,8 @@ using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Difficulty; using osu.Game.Rulesets.Mania.Edit; +using osu.Game.Rulesets.Mania.Scoring; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; namespace osu.Game.Rulesets.Mania @@ -32,7 +34,11 @@ namespace osu.Game.Rulesets.Mania public class ManiaRuleset : Ruleset { public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableManiaRuleset(this, beatmap, mods); + + public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new ManiaScoreProcessor(beatmap); + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap); + public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score); public const string SHORT_NAME = "mania"; diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index cf1970c28b..2c497541a8 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -14,11 +14,9 @@ using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Replays; -using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osuTK; @@ -67,8 +65,6 @@ namespace osu.Game.Rulesets.Mania.UI protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages); - public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(Beatmap); - public override int Variant => (int)(Beatmap.Stages.Count == 1 ? PlayfieldType.Single : PlayfieldType.Dual) + Beatmap.TotalColumns; protected override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant); diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index beaa788229..ff08d97385 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -23,7 +23,9 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Difficulty; +using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.Skinning; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Skinning; @@ -32,7 +34,11 @@ namespace osu.Game.Rulesets.Osu public class OsuRuleset : Ruleset { public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableOsuRuleset(this, beatmap, mods); + + public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new OsuScoreProcessor(beatmap); + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap); + public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap); public const string SHORT_NAME = "osu"; diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index 039d38e4fd..a37ef8d9a0 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -14,8 +14,6 @@ using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Replays; -using osu.Game.Rulesets.Osu.Scoring; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osuTK; @@ -33,8 +31,6 @@ namespace osu.Game.Rulesets.Osu.UI public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // always show the gameplay cursor - public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(Beatmap); - protected override Playfield CreatePlayfield() => new OsuPlayfield(); protected override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo); diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 68e6a3b07e..59ce715a1f 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -15,8 +15,10 @@ using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Taiko.Replays; using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Difficulty; +using osu.Game.Rulesets.Taiko.Scoring; using osu.Game.Scoring; namespace osu.Game.Rulesets.Taiko @@ -24,6 +26,9 @@ namespace osu.Game.Rulesets.Taiko public class TaikoRuleset : Ruleset { public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableTaikoRuleset(this, beatmap, mods); + + public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new TaikoScoreProcessor(beatmap); + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap); public const string SHORT_NAME = "taiko"; diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 2233658428..0c7495aa52 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -5,10 +5,8 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects.Drawables; -using osu.Game.Rulesets.Taiko.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Taiko.Replays; using osu.Framework.Input; @@ -40,8 +38,6 @@ namespace osu.Game.Rulesets.Taiko.UI new BarLineGenerator(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar.Major ? new DrawableBarLineMajor(bar) : new DrawableBarLine(bar))); } - public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(Beatmap); - public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer(); protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 45aa904b98..cfe04fc6d1 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -18,6 +18,7 @@ using osu.Game.Beatmaps.Legacy; using osu.Game.Configuration; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Skinning; @@ -62,6 +63,12 @@ namespace osu.Game.Rulesets /// public abstract DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null); + /// + /// Creates a for a beatmap converted to this ruleset. + /// + /// The score processor. + public virtual ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new ScoreProcessor(beatmap); + /// /// Creates a to convert a to one that is applicable for this . /// diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 5033fd0686..4ff8ce9e0a 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -305,8 +305,6 @@ namespace osu.Game.Rulesets.UI /// The Playfield. protected abstract Playfield CreatePlayfield(); - public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(Beatmap); - /// /// Applies the active mods to this DrawableRuleset. /// @@ -475,13 +473,6 @@ namespace osu.Game.Rulesets.UI /// Invoked when the user requests to pause while the resume overlay is active. /// public abstract void CancelResume(); - - /// - /// Create a for the associated ruleset and link with this - /// . - /// - /// A score processor. - public abstract ScoreProcessor CreateScoreProcessor(); } public class BeatmapInvalidForRulesetException : ArgumentException diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index fa320e9a4f..5dfdeb5ebc 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -128,7 +128,7 @@ namespace osu.Game.Screens.Play DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); - ScoreProcessor = DrawableRuleset.CreateScoreProcessor(); + ScoreProcessor = ruleset.CreateScoreProcessor(playableBeatmap); ScoreProcessor.Mods.BindTo(Mods); if (!ScoreProcessor.Mode.Disabled) From 35276c37399e9fbfcc0b16c88eb4448760e16470 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Dec 2019 21:26:23 +0900 Subject: [PATCH 2814/2815] Prevent test scene failures through casting softly --- .../Objects/Drawables/DrawableSlider.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 03183beff1..cd3c572ba0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public readonly SliderBall Ball; public readonly SkinnableDrawable Body; - private PlaySliderBody sliderBody => (PlaySliderBody)Body.Drawable; + private PlaySliderBody sliderBody => Body.Drawable as PlaySliderBody; private readonly Container headContainer; private readonly Container tailContainer; @@ -154,16 +154,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); Ball.UpdateProgress(completionProgress); - sliderBody.UpdateProgress(completionProgress); + sliderBody?.UpdateProgress(completionProgress); foreach (DrawableHitObject hitObject in NestedHitObjects) { - if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(slider.Path.PositionAt(sliderBody.SnakedStart ?? 0), slider.Path.PositionAt(sliderBody.SnakedEnd ?? 0)); + if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(slider.Path.PositionAt(sliderBody?.SnakedStart ?? 0), slider.Path.PositionAt(sliderBody?.SnakedEnd ?? 0)); if (hitObject is IRequireTracking t) t.Tracking = Ball.Tracking; } - Size = sliderBody.Size; - OriginPosition = sliderBody.PathOffset; + Size = sliderBody?.Size ?? Vector2.Zero; + OriginPosition = sliderBody?.PathOffset ?? Vector2.Zero; if (DrawSize != Vector2.Zero) { @@ -177,7 +177,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public override void OnKilled() { base.OnKilled(); - sliderBody.RecyclePath(); + sliderBody?.RecyclePath(); } protected override void ApplySkin(ISkinSource skin, bool allowFallback) @@ -238,7 +238,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public Drawable ProxiedLayer => HeadCircle.ProxiedLayer; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => sliderBody.ReceivePositionalInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => sliderBody?.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos); private class DefaultSliderBody : PlaySliderBody { From 69da6ed9a16438aa6b9fe034ee127879b4d66c00 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Dec 2019 23:53:18 +0900 Subject: [PATCH 2815/2815] Fix test re-using the same storage --- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index a95e699470..a139c3a8c2 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -159,7 +159,7 @@ namespace osu.Game.Tests.Scores.IO [Test] public async Task TestOnlineScoreIsAvailableLocally() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithDeletedBeatmapSet")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestOnlineScoreIsAvailableLocally")) { try {
/// Creates the conversion value for a . A conversion value stores information about the converted . @@ -176,6 +184,32 @@ namespace osu.Game.Tests.Beatmaps [JsonProperty] public List Mappings = new List(); } + + private class ConversionWorkingBeatmap : WorkingBeatmap + { + public Action, IBeatmapConverter> ConversionGenerated; + + private readonly IBeatmap beatmap; + + public ConversionWorkingBeatmap(IBeatmap beatmap) + : base(beatmap.BeatmapInfo, null) + { + this.beatmap = beatmap; + } + + protected override IBeatmap GetBeatmap() => beatmap; + + protected override Texture GetBackground() => throw new NotImplementedException(); + + protected override Track GetTrack() => throw new NotImplementedException(); + + protected override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) + { + var converter = base.CreateBeatmapConverter(beatmap, ruleset); + converter.ObjectConverted += (orig, converted) => ConversionGenerated?.Invoke(orig, converted, converter); + return converter; + } + } } public abstract class BeatmapConversionTest : BeatmapConversionTest, TConvertValue> From ed4dda19363a1a112b4d55b6719c4d456073a459 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 31 Jul 2019 19:49:25 +0900 Subject: [PATCH 0464/2815] Support beatmap conversion tests with mods --- .../CatchBeatmapConversionTest.cs | 2 +- .../ManiaBeatmapConversionTest.cs | 2 +- osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs | 2 +- .../TaikoBeatmapConversionTest.cs | 2 +- osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs | 8 ++++---- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index e45ed8c6f4..3a695ca179 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Tests [TestCase("spinner")] [TestCase("spinner-and-circles")] [TestCase("slider")] - public new void Test(string name) + public void Test(string name) { base.Test(name); } diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs index 51c7ba029a..3ca9dcc42c 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mania.Tests protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; [TestCase("basic")] - public new void Test(string name) + public void Test(string name) { base.Test(name); } diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs index f98d63e6c7..6a6d0abe24 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Tests [TestCase("basic")] [TestCase("colinear-perfect-curve")] [TestCase("slider-ticks")] - public new void Test(string name) + public void Test(string name) { base.Test(name); } diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs index 68ae7544c2..bafa814582 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Tests [NonParallelizable] [TestCase("basic")] [TestCase("slider-generating-drumroll")] - public new void Test(string name) + public void Test(string name) { base.Test(name); } diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 1f2d457624..a555a52e42 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -28,9 +28,9 @@ namespace osu.Game.Tests.Beatmaps protected abstract string ResourceAssembly { get; } - protected void Test(string name) + protected void Test(string name, params Type[] mods) { - var ourResult = convert(name); + var ourResult = convert(name, mods.Select(m => (Mod)Activator.CreateInstance(m)).ToArray()); var expectedResult = read(name); Assert.Multiple(() => @@ -92,7 +92,7 @@ namespace osu.Game.Tests.Beatmaps }); } - private ConvertResult convert(string name) + private ConvertResult convert(string name, Mod[] mods) { var beatmap = getBeatmap(name); @@ -110,7 +110,7 @@ namespace osu.Game.Tests.Beatmaps } }; - working.GetPlayableBeatmap(rulesetInstance.RulesetInfo, Array.Empty()); + working.GetPlayableBeatmap(rulesetInstance.RulesetInfo, mods); return new ConvertResult { From fdc6a3958d3655e799420469c1fe17f4cecd11a9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 31 Jul 2019 18:44:01 +0900 Subject: [PATCH 0465/2815] Make catch HR properly utilise the RNG --- .../Beatmaps/CatchBeatmapProcessor.cs | 119 +++++++++++++++++- .../MathUtils/FastRandom.cs | 8 ++ .../Mods/CatchModHardRock.cs | 112 +---------------- 3 files changed, 127 insertions(+), 112 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index 645cb5701a..343f48f15c 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -10,6 +10,8 @@ using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects.Types; using osuTK; using osu.Game.Rulesets.Catch.MathUtils; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Catch.Beatmaps { @@ -26,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps { base.PostProcess(); - applyPositionOffsets(); + ApplyPositionOffsets(Beatmap); initialiseHyperDash((List)Beatmap.HitObjects); @@ -40,15 +42,23 @@ namespace osu.Game.Rulesets.Catch.Beatmaps } } - private void applyPositionOffsets() + public static void ApplyPositionOffsets(IBeatmap beatmap, params Mod[] mods) { var rng = new FastRandom(RNG_SEED); - // todo: HardRock displacement should be applied here - foreach (var obj in Beatmap.HitObjects) + bool shouldApplyHardRockOffset = mods.Any(m => m is ModHardRock); + float? lastPosition = null; + double lastStartTime = 0; + + foreach (var obj in beatmap.HitObjects) { switch (obj) { + case Fruit fruit: + if (shouldApplyHardRockOffset) + applyHardRockOffset(fruit, ref lastPosition, ref lastStartTime, rng); + break; + case BananaShower bananaShower: foreach (var banana in bananaShower.NestedHitObjects.OfType()) { @@ -76,6 +86,107 @@ namespace osu.Game.Rulesets.Catch.Beatmaps } } + private static void applyHardRockOffset(HitObject hitObject, ref float? lastPosition, ref double lastStartTime, FastRandom rng) + { + if (hitObject is JuiceStream stream) + { + lastPosition = stream.EndX; + lastStartTime = stream.EndTime; + return; + } + + if (!(hitObject is Fruit)) + return; + + var catchObject = (CatchHitObject)hitObject; + + float position = catchObject.X; + double startTime = hitObject.StartTime; + + if (lastPosition == null) + { + lastPosition = position; + lastStartTime = startTime; + + return; + } + + float positionDiff = position - lastPosition.Value; + double timeDiff = startTime - lastStartTime; + + if (timeDiff > 1000) + { + lastPosition = position; + lastStartTime = startTime; + return; + } + + if (positionDiff == 0) + { + applyRandomOffset(ref position, timeDiff / 4d, rng); + catchObject.X = position; + return; + } + + if (Math.Abs(positionDiff * CatchPlayfield.BASE_WIDTH) < timeDiff / 3d) + applyOffset(ref position, positionDiff); + + catchObject.X = position; + + lastPosition = position; + lastStartTime = startTime; + } + + /// + /// Applies a random offset in a random direction to a position, ensuring that the final position remains within the boundary of the playfield. + /// + /// The position which the offset should be applied to. + /// The maximum offset, cannot exceed 20px. + /// The random number generator. + private static void applyRandomOffset(ref float position, double maxOffset, FastRandom rng) + { + bool right = rng.NextBool(); + float rand = Math.Min(20, (float)rng.Next(0, Math.Max(0, maxOffset))) / CatchPlayfield.BASE_WIDTH; + + if (right) + { + // Clamp to the right bound + if (position + rand <= 1) + position += rand; + else + position -= rand; + } + else + { + // Clamp to the left bound + if (position - rand >= 0) + position -= rand; + else + position += rand; + } + } + + /// + /// Applies an offset to a position, ensuring that the final position remains within the boundary of the playfield. + /// + /// The position which the offset should be applied to. + /// The amount to offset by. + private static void applyOffset(ref float position, float amount) + { + if (amount > 0) + { + // Clamp to the right bound + if (position + amount < 1) + position += amount; + } + else + { + // Clamp to the left bound + if (position + amount > 0) + position += amount; + } + } + private void initialiseHyperDash(List objects) { List objectWithDroplets = new List(); diff --git a/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs b/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs index b3605b013b..c721ff862a 100644 --- a/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs +++ b/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs @@ -61,6 +61,14 @@ namespace osu.Game.Rulesets.Catch.MathUtils /// The random value. public int Next(int lowerBound, int upperBound) => (int)(lowerBound + NextDouble() * (upperBound - lowerBound)); + /// + /// Generates a random integer value within the range [, ). + /// + /// The lower bound of the range. + /// The upper bound of the range. + /// The random value. + public int Next(double lowerBound, double upperBound) => (int)(lowerBound + NextDouble() * (upperBound - lowerBound)); + /// /// Generates a random double value within the range [0, 1). /// diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs index 060e51e31d..ced1900ba9 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs @@ -1,121 +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.MathUtils; -using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Mods; -using System; -using osu.Game.Rulesets.Objects; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Beatmaps; namespace osu.Game.Rulesets.Catch.Mods { - public class CatchModHardRock : ModHardRock, IApplicableToHitObject + public class CatchModHardRock : ModHardRock, IApplicableToBeatmap { public override double ScoreMultiplier => 1.12; public override bool Ranked => true; - private float? lastPosition; - private double lastStartTime; - - public void ApplyToHitObject(HitObject hitObject) - { - if (hitObject is JuiceStream stream) - { - lastPosition = stream.EndX; - lastStartTime = stream.EndTime; - return; - } - - if (!(hitObject is Fruit)) - return; - - var catchObject = (CatchHitObject)hitObject; - - float position = catchObject.X; - double startTime = hitObject.StartTime; - - if (lastPosition == null) - { - lastPosition = position; - lastStartTime = startTime; - - return; - } - - float positionDiff = position - lastPosition.Value; - double timeDiff = startTime - lastStartTime; - - if (timeDiff > 1000) - { - lastPosition = position; - lastStartTime = startTime; - return; - } - - if (positionDiff == 0) - { - applyRandomOffset(ref position, timeDiff / 4d); - catchObject.X = position; - return; - } - - if (Math.Abs(positionDiff * CatchPlayfield.BASE_WIDTH) < timeDiff / 3d) - applyOffset(ref position, positionDiff); - - catchObject.X = position; - - lastPosition = position; - lastStartTime = startTime; - } - - /// - /// Applies a random offset in a random direction to a position, ensuring that the final position remains within the boundary of the playfield. - /// - /// The position which the offset should be applied to. - /// The maximum offset, cannot exceed 20px. - private void applyRandomOffset(ref float position, double maxOffset) - { - bool right = RNG.NextBool(); - float rand = Math.Min(20, (float)RNG.NextDouble(0, Math.Max(0, maxOffset))) / CatchPlayfield.BASE_WIDTH; - - if (right) - { - // Clamp to the right bound - if (position + rand <= 1) - position += rand; - else - position -= rand; - } - else - { - // Clamp to the left bound - if (position - rand >= 0) - position -= rand; - else - position += rand; - } - } - - /// - /// Applies an offset to a position, ensuring that the final position remains within the boundary of the playfield. - /// - /// The position which the offset should be applied to. - /// The amount to offset by. - private void applyOffset(ref float position, float amount) - { - if (amount > 0) - { - // Clamp to the right bound - if (position + amount < 1) - position += amount; - } - else - { - // Clamp to the left bound - if (position + amount > 0) - position += amount; - } - } + public void ApplyToBeatmap(IBeatmap beatmap) => CatchBeatmapProcessor.ApplyPositionOffsets(beatmap, this); } } From 38a2b9d92b5af230ee315f6e223f4a6da3f46373 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Aug 2019 13:33:00 +0900 Subject: [PATCH 0466/2815] Fix multiple invocations by using a separate variable --- .../Beatmaps/CatchBeatmapProcessor.cs | 32 +++++++++---------- .../Objects/CatchHitObject.cs | 14 +++++++- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index 343f48f15c..ffff9b854d 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -11,7 +11,6 @@ using osu.Game.Rulesets.Objects.Types; using osuTK; using osu.Game.Rulesets.Catch.MathUtils; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Catch.Beatmaps { @@ -50,8 +49,10 @@ namespace osu.Game.Rulesets.Catch.Beatmaps float? lastPosition = null; double lastStartTime = 0; - foreach (var obj in beatmap.HitObjects) + foreach (var obj in beatmap.HitObjects.OfType()) { + obj.XOffset = 0; + switch (obj) { case Fruit fruit: @@ -62,7 +63,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps case BananaShower bananaShower: foreach (var banana in bananaShower.NestedHitObjects.OfType()) { - banana.X = (float)rng.NextDouble(); + banana.XOffset = (float)rng.NextDouble(); rng.Next(); // osu!stable retrieved a random banana type rng.Next(); // osu!stable retrieved a random banana rotation rng.Next(); // osu!stable retrieved a random banana colour @@ -75,10 +76,9 @@ namespace osu.Game.Rulesets.Catch.Beatmaps { var hitObject = (CatchHitObject)nested; if (hitObject is TinyDroplet) - hitObject.X += rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH; + hitObject.XOffset = MathHelper.Clamp(rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH, -hitObject.X, 1 - hitObject.X); else if (hitObject is Droplet) rng.Next(); // osu!stable retrieved a random droplet rotation - hitObject.X = MathHelper.Clamp(hitObject.X, 0, 1); } break; @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps } } - private static void applyHardRockOffset(HitObject hitObject, ref float? lastPosition, ref double lastStartTime, FastRandom rng) + private static void applyHardRockOffset(CatchHitObject hitObject, ref float? lastPosition, ref double lastStartTime, FastRandom rng) { if (hitObject is JuiceStream stream) { @@ -98,42 +98,40 @@ namespace osu.Game.Rulesets.Catch.Beatmaps if (!(hitObject is Fruit)) return; - var catchObject = (CatchHitObject)hitObject; - - float position = catchObject.X; + float offsetPosition = hitObject.X; double startTime = hitObject.StartTime; if (lastPosition == null) { - lastPosition = position; + lastPosition = offsetPosition; lastStartTime = startTime; return; } - float positionDiff = position - lastPosition.Value; + float positionDiff = offsetPosition - lastPosition.Value; double timeDiff = startTime - lastStartTime; if (timeDiff > 1000) { - lastPosition = position; + lastPosition = offsetPosition; lastStartTime = startTime; return; } if (positionDiff == 0) { - applyRandomOffset(ref position, timeDiff / 4d, rng); - catchObject.X = position; + applyRandomOffset(ref offsetPosition, timeDiff / 4d, rng); + hitObject.XOffset = hitObject.X - offsetPosition; return; } if (Math.Abs(positionDiff * CatchPlayfield.BASE_WIDTH) < timeDiff / 3d) - applyOffset(ref position, positionDiff); + applyOffset(ref offsetPosition, positionDiff); - catchObject.X = position; + hitObject.XOffset = hitObject.X - offsetPosition; - lastPosition = position; + lastPosition = offsetPosition; lastStartTime = startTime; } diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index be76edc01b..19a1b59752 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -3,6 +3,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -12,7 +13,18 @@ namespace osu.Game.Rulesets.Catch.Objects { public const double OBJECT_RADIUS = 44; - public float X { get; set; } + private float x; + + public float X + { + get => x + XOffset; + set => x = value; + } + + /// + /// A random offset applied to , set by the . + /// + internal float XOffset { get; set; } public double TimePreempt = 1000; From e2420af10c844d9459fd906b3035086c949e3266 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Aug 2019 13:37:40 +0900 Subject: [PATCH 0467/2815] Fix applying mods to the wrong beatmap --- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index baf921ddfc..c6105f6566 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -142,7 +142,7 @@ namespace osu.Game.Beatmaps processor?.PostProcess(); foreach (var mod in mods.OfType()) - mod.ApplyToBeatmap(Beatmap); + mod.ApplyToBeatmap(converted); return converted; } From d05b9b1734bf9734ce9434721ef5ee990ef6dd38 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 1 Aug 2019 14:19:45 +0900 Subject: [PATCH 0468/2815] Use dummyAPI and move load check into MusicController --- .../Visual/Menus/TestSceneScreenNavigation.cs | 26 +++++++++++++------ osu.Game/OsuGame.cs | 2 +- osu.Game/Overlays/MusicController.cs | 4 +++ osu.Game/Screens/Multi/Multiplayer.cs | 2 +- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs index f71e8dc2ce..88dac8d0ff 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs @@ -8,11 +8,11 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Screens; @@ -111,6 +111,7 @@ namespace osu.Game.Tests.Visual.Menus [Test] public void TestOpenOptionsAndExitWithEscape() { + AddUntilStep("Wait for options to load", () => osuGame.Settings.IsLoaded); AddStep("Enter menu", () => pressAndRelease(Key.Enter)); AddStep("Move mouse to options overlay", () => InputManager.MoveMouseTo(optionsButtonPosition)); AddStep("Click options overlay", () => InputManager.Click(MouseButton.Left)); @@ -122,13 +123,7 @@ namespace osu.Game.Tests.Visual.Menus private void pushAndConfirm(Func newScreen, string screenName) { Screen screen = null; - AddStep($"Push new {screenName}", () => - { - if (screenName == "song select") - Logger.Log("fuck"); - - osuGame.ScreenStack.Push(screen = newScreen()); - }); + AddStep($"Push new {screenName}", () => osuGame.ScreenStack.Push(screen = newScreen())); AddUntilStep($"Wait for new {screenName}", () => osuGame.ScreenStack.CurrentScreen == screen && screen.IsLoaded); } @@ -160,6 +155,21 @@ namespace osu.Game.Tests.Visual.Menus public new SettingsPanel Settings => base.Settings; protected override Loader CreateLoader() => new TestLoader(); + + private DependencyContainer dependencies; + + private DummyAPIAccess dummyAPI; + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => + dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + protected override void LoadComplete() + { + base.LoadComplete(); + dependencies.CacheAs(dummyAPI = new DummyAPIAccess()); + + dummyAPI.Login("Rhythm Champion", "osu!"); + } } private class TestSongSelect : PlaySongSelect diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e7c0d01f31..63ec349d3d 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -318,7 +318,7 @@ namespace osu.Game private void currentTrackCompleted() { - if (!Beatmap.Value.Track.Looping && !Beatmap.Disabled && musicController.IsLoaded) + if (!Beatmap.Value.Track.Looping && !Beatmap.Disabled) musicController.NextTrack(); } diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index abbcec5094..91ae92320a 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -316,6 +316,10 @@ namespace osu.Game.Overlays private void next(bool instant = false) { + // beatmapSets doesn't get populated until loading has completed. + if (!IsLoaded) + return; + if (!instant) queuedDirection = TransformDirection.Next; diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 3d9997e236..9550d883fd 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -168,7 +168,7 @@ namespace osu.Game.Screens.Multi public void APIStateChanged(IAPIProvider api, APIState state) { - if (RequireOnline && state != APIState.Online) + if (state != APIState.Online) forcefullyExit(); } From 423857f403581907eef4c44cc97199e5d84546f3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Aug 2019 14:57:07 +0900 Subject: [PATCH 0469/2815] Fix inverted offset --- osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index ffff9b854d..3ee2928573 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -122,14 +122,14 @@ namespace osu.Game.Rulesets.Catch.Beatmaps if (positionDiff == 0) { applyRandomOffset(ref offsetPosition, timeDiff / 4d, rng); - hitObject.XOffset = hitObject.X - offsetPosition; + hitObject.XOffset = offsetPosition - hitObject.X; return; } if (Math.Abs(positionDiff * CatchPlayfield.BASE_WIDTH) < timeDiff / 3d) applyOffset(ref offsetPosition, positionDiff); - hitObject.XOffset = hitObject.X - offsetPosition; + hitObject.XOffset = offsetPosition - hitObject.X; lastPosition = offsetPosition; lastStartTime = startTime; From 8a1d690011665ab19aceb2bd6ada65185517e7d2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Aug 2019 14:57:17 +0900 Subject: [PATCH 0470/2815] Reset tick offsets --- .../Beatmaps/CatchBeatmapProcessor.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index 3ee2928573..5ab47c1611 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -74,10 +74,12 @@ namespace osu.Game.Rulesets.Catch.Beatmaps case JuiceStream juiceStream: foreach (var nested in juiceStream.NestedHitObjects) { - var hitObject = (CatchHitObject)nested; - if (hitObject is TinyDroplet) - hitObject.XOffset = MathHelper.Clamp(rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH, -hitObject.X, 1 - hitObject.X); - else if (hitObject is Droplet) + var catchObject = (CatchHitObject)nested; + catchObject.XOffset = 0; + + if (catchObject is TinyDroplet) + catchObject.XOffset = MathHelper.Clamp(rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH, -catchObject.X, 1 - catchObject.X); + else if (catchObject is Droplet) rng.Next(); // osu!stable retrieved a random droplet rotation } From b978727422b8a163aaa7402ba91951a6dc303e80 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Aug 2019 14:58:17 +0900 Subject: [PATCH 0471/2815] Add stream + repeat slider tests --- .../CatchBeatmapConversionTest.cs | 7 +- ...ock-repeat-slider-expected-conversion.json | 150 +++++++++++ .../Beatmaps/hardrock-repeat-slider.osu | 18 ++ .../hardrock-stream-expected-conversion.json | 234 ++++++++++++++++++ .../Testing/Beatmaps/hardrock-stream.osu | 107 ++++++++ 5 files changed, 514 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider.osu create mode 100644 osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream.osu diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index 3a695ca179..80ac64c5f9 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using Newtonsoft.Json; using NUnit.Framework; using osu.Framework.MathUtils; +using osu.Game.Rulesets.Catch.Mods; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects; @@ -22,9 +23,11 @@ namespace osu.Game.Rulesets.Catch.Tests [TestCase("spinner")] [TestCase("spinner-and-circles")] [TestCase("slider")] - public void Test(string name) + [TestCase("hardrock-stream", new[] { typeof(CatchModHardRock) })] + [TestCase("hardrock-repeat-slider", new[] { typeof(CatchModHardRock) })] + public new void Test(string name, params Type[] mods) { - base.Test(name); + base.Test(name, mods); } protected override IEnumerable CreateConvertValue(HitObject hitObject) diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider-expected-conversion.json new file mode 100644 index 0000000000..83f9e30800 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider-expected-conversion.json @@ -0,0 +1,150 @@ +{ + "Mappings": [{ + "StartTime": 369, + "Objects": [{ + "StartTime": 369, + "Position": 177 + }, + { + "StartTime": 450, + "Position": 216.539276 + }, + { + "StartTime": 532, + "Position": 256.5667 + }, + { + "StartTime": 614, + "Position": 296.594116 + }, + { + "StartTime": 696, + "Position": 336.621521 + }, + { + "StartTime": 778, + "Position": 376.99762 + }, + { + "StartTime": 860, + "Position": 337.318878 + }, + { + "StartTime": 942, + "Position": 297.291443 + }, + { + "StartTime": 1024, + "Position": 257.264038 + }, + { + "StartTime": 1106, + "Position": 217.2366 + }, + { + "StartTime": 1188, + "Position": 177 + }, + { + "StartTime": 1270, + "Position": 216.818192 + }, + { + "StartTime": 1352, + "Position": 256.8456 + }, + { + "StartTime": 1434, + "Position": 296.873047 + }, + { + "StartTime": 1516, + "Position": 336.900452 + }, + { + "StartTime": 1598, + "Position": 376.99762 + }, + { + "StartTime": 1680, + "Position": 337.039948 + }, + { + "StartTime": 1762, + "Position": 297.0125 + }, + { + "StartTime": 1844, + "Position": 256.9851 + }, + { + "StartTime": 1926, + "Position": 216.957672 + }, + { + "StartTime": 2008, + "Position": 177 + }, + { + "StartTime": 2090, + "Position": 217.097137 + }, + { + "StartTime": 2172, + "Position": 257.124573 + }, + { + "StartTime": 2254, + "Position": 297.152 + }, + { + "StartTime": 2336, + "Position": 337.179443 + }, + { + "StartTime": 2418, + "Position": 376.99762 + }, + { + "StartTime": 2500, + "Position": 336.760956 + }, + { + "StartTime": 2582, + "Position": 296.733643 + }, + { + "StartTime": 2664, + "Position": 256.7062 + }, + { + "StartTime": 2746, + "Position": 216.678772 + }, + { + "StartTime": 2828, + "Position": 177 + }, + { + "StartTime": 2909, + "Position": 216.887909 + }, + { + "StartTime": 2991, + "Position": 256.915344 + }, + { + "StartTime": 3073, + "Position": 296.942749 + }, + { + "StartTime": 3155, + "Position": 336.970184 + }, + { + "StartTime": 3237, + "Position": 376.99762 + } + ] + }] +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider.osu new file mode 100644 index 0000000000..c1971edf2c --- /dev/null +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider.osu @@ -0,0 +1,18 @@ +osu file format v14 + +[General] +StackLeniency: 0.4 +Mode: 0 + +[Difficulty] +CircleSize:4 +OverallDifficulty:7 +ApproachRate:8 +SliderMultiplier:1.6 +SliderTickRate:4 + +[TimingPoints] +369,327.868852459016,4,2,2,32,1,0 + +[HitObjects] +177,191,369,6,0,L|382:192,7,200 diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream-expected-conversion.json new file mode 100644 index 0000000000..bbc16ab912 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream-expected-conversion.json @@ -0,0 +1,234 @@ +{ + "Mappings": [{ + "StartTime": 369, + "Objects": [{ + "StartTime": 369, + "Position": 258 + }] + }, + { + "StartTime": 450, + "Objects": [{ + "StartTime": 450, + "Position": 254 + }] + }, + { + "StartTime": 532, + "Objects": [{ + "StartTime": 532, + "Position": 241 + }] + }, + { + "StartTime": 614, + "Objects": [{ + "StartTime": 614, + "Position": 238 + }] + }, + { + "StartTime": 696, + "Objects": [{ + "StartTime": 696, + "Position": 238 + }] + }, + { + "StartTime": 778, + "Objects": [{ + "StartTime": 778, + "Position": 278 + }] + }, + { + "StartTime": 860, + "Objects": [{ + "StartTime": 860, + "Position": 238 + }] + }, + { + "StartTime": 942, + "Objects": [{ + "StartTime": 942, + "Position": 278 + }] + }, + { + "StartTime": 1024, + "Objects": [{ + "StartTime": 1024, + "Position": 238 + }] + }, + { + "StartTime": 1106, + "Objects": [{ + "StartTime": 1106, + "Position": 278 + }] + }, + { + "StartTime": 1188, + "Objects": [{ + "StartTime": 1188, + "Position": 278 + }] + }, + { + "StartTime": 1270, + "Objects": [{ + "StartTime": 1270, + "Position": 278 + }] + }, + { + "StartTime": 1352, + "Objects": [{ + "StartTime": 1352, + "Position": 238 + }] + }, + { + "StartTime": 1434, + "Objects": [{ + "StartTime": 1434, + "Position": 258 + }] + }, + { + "StartTime": 1516, + "Objects": [{ + "StartTime": 1516, + "Position": 253 + }] + }, + { + "StartTime": 1598, + "Objects": [{ + "StartTime": 1598, + "Position": 238 + }] + }, + { + "StartTime": 1680, + "Objects": [{ + "StartTime": 1680, + "Position": 260 + }] + }, + { + "StartTime": 1762, + "Objects": [{ + "StartTime": 1762, + "Position": 238 + }] + }, + { + "StartTime": 1844, + "Objects": [{ + "StartTime": 1844, + "Position": 278 + }] + }, + { + "StartTime": 1926, + "Objects": [{ + "StartTime": 1926, + "Position": 278 + }] + }, + { + "StartTime": 2008, + "Objects": [{ + "StartTime": 2008, + "Position": 238 + }] + }, + { + "StartTime": 2090, + "Objects": [{ + "StartTime": 2090, + "Position": 238 + }] + }, + { + "StartTime": 2172, + "Objects": [{ + "StartTime": 2172, + "Position": 243 + }] + }, + { + "StartTime": 2254, + "Objects": [{ + "StartTime": 2254, + "Position": 278 + }] + }, + { + "StartTime": 2336, + "Objects": [{ + "StartTime": 2336, + "Position": 278 + }] + }, + { + "StartTime": 2418, + "Objects": [{ + "StartTime": 2418, + "Position": 238 + }] + }, + { + "StartTime": 2500, + "Objects": [{ + "StartTime": 2500, + "Position": 258 + }] + }, + { + "StartTime": 2582, + "Objects": [{ + "StartTime": 2582, + "Position": 256 + }] + }, + { + "StartTime": 2664, + "Objects": [{ + "StartTime": 2664, + "Position": 242 + }] + }, + { + "StartTime": 2746, + "Objects": [{ + "StartTime": 2746, + "Position": 238 + }] + }, + { + "StartTime": 2828, + "Objects": [{ + "StartTime": 2828, + "Position": 238 + }] + }, + { + "StartTime": 2909, + "Objects": [{ + "StartTime": 2909, + "Position": 271 + }] + }, + { + "StartTime": 2991, + "Objects": [{ + "StartTime": 2991, + "Position": 254 + }] + } + ] +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream.osu new file mode 100644 index 0000000000..62835135df --- /dev/null +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream.osu @@ -0,0 +1,107 @@ +osu file format v14 + +[General] +AudioFilename: audio.mp3 +AudioLeadIn: 0 +PreviewTime: 53483 +Countdown: 0 +SampleSet: Soft +StackLeniency: 0.4 +Mode: 0 +LetterboxInBreaks: 0 +WidescreenStoryboard: 0 + +[Editor] +Bookmarks: 369,1680,12172,22664,33155,43647,54139,64631,75123,85614,96106,103975,114467,124959,135287 +DistanceSpacing: 1 +BeatDivisor: 4 +GridSize: 8 +TimelineZoom: 2.899999 + +[Metadata] +Title:Silhouette +TitleUnicode:シルエット +Artist:KANA-BOON +ArtistUnicode:KANA-BOON +Creator:Nevo +Version:lit120's Hard +Source:NARUTO-ナルト-疾風伝 +Tags:shippuuden shipuuden shippuden nine tails opening 16 kage cut ver version naotoshi nao tomori shunao shogunmoon sotarks pika [[pika]] frostwich lit120 shiratoi armin a_r_m_i_n +BeatmapID:1653115 +BeatmapSetID:780952 + +[Difficulty] +HPDrainRate:6 +CircleSize:4 +OverallDifficulty:7 +ApproachRate:8 +SliderMultiplier:1.6 +SliderTickRate:1 + +[Events] +//Background and Video events +0,0,"OSpGdEP.png",0,0 +//Break Periods +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Sound Samples + +[TimingPoints] +369,327.868852459016,4,2,2,32,1,0 +1680,-100,4,2,2,42,0,0 +6926,-100,4,2,2,52,0,0 +11844,-100,4,2,2,5,0,0 +12172,-100,4,2,2,62,0,0 +22664,-100,4,2,2,52,0,0 +43647,-100,4,2,2,52,0,0 +53483,-100,4,2,2,32,0,0 +54139,-100,4,2,2,72,0,1 +96106,-100,4,2,2,52,0,0 +103975,-100,4,2,2,82,0,1 +124959,-100,4,2,2,62,0,0 +130205,-100,4,2,2,72,0,0 +132991,-100,4,2,2,82,0,0 + + +[Colours] +Combo1 : 154,53,255 +Combo2 : 253,211,47 +Combo3 : 86,168,250 +Combo4 : 192,192,192 + +[HitObjects] +258,189,369,1,0,0:0:0:0: +258,189,450,1,0,0:0:0:0: +258,189,532,1,0,0:0:0:0: +258,189,614,1,0,0:0:0:0: +258,189,696,1,0,0:0:0:0: +258,189,778,1,0,0:0:0:0: +258,189,860,1,0,0:0:0:0: +258,189,942,1,0,0:0:0:0: +258,189,1024,1,0,0:0:0:0: +258,189,1106,1,0,0:0:0:0: +258,189,1188,1,0,0:0:0:0: +258,189,1270,1,0,0:0:0:0: +258,189,1352,1,0,0:0:0:0: +258,189,1434,1,0,0:0:0:0: +258,189,1516,1,0,0:0:0:0: +258,189,1598,1,0,0:0:0:0: +258,189,1680,1,0,0:0:0:0: +258,189,1762,1,0,0:0:0:0: +258,189,1844,1,0,0:0:0:0: +258,189,1926,1,0,0:0:0:0: +258,189,2008,1,0,0:0:0:0: +258,189,2090,1,0,0:0:0:0: +258,189,2172,1,0,0:0:0:0: +258,189,2254,1,0,0:0:0:0: +258,189,2336,1,0,0:0:0:0: +258,189,2418,1,0,0:0:0:0: +258,189,2500,1,0,0:0:0:0: +258,189,2582,1,0,0:0:0:0: +258,189,2664,1,0,0:0:0:0: +258,189,2746,1,0,0:0:0:0: +258,189,2828,1,0,0:0:0:0: +258,189,2909,1,0,0:0:0:0: +258,189,2991,1,0,0:0:0:0: From d772bbaf8c77bae4a280a8facca67832b208d007 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 1 Aug 2019 10:04:04 +0300 Subject: [PATCH 0472/2815] Simplify disposing --- osu.Game/OsuGame.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 431e6c91f9..b9e2b79b05 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -307,11 +307,10 @@ namespace osu.Game if (nextBeatmap?.Track != null) nextBeatmap.Track.Completed += currentTrackCompleted; - var oldBeatmap = beatmap.OldValue; - if (oldBeatmap?.Track != null) - oldBeatmap.Track.Completed -= currentTrackCompleted; + using (var oldBeatmap = beatmap.OldValue) + if (oldBeatmap?.Track != null) + oldBeatmap.Track.Completed -= currentTrackCompleted; - oldBeatmap?.Dispose(); nextBeatmap?.LoadBeatmapAsync(); } From 0f36088ef894f0f94869d0c5df3d7bddfbb00151 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 1 Aug 2019 11:06:29 +0300 Subject: [PATCH 0473/2815] Add loved section --- osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs | 1 + osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs b/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs index f3384163b8..fb1385793f 100644 --- a/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs @@ -27,6 +27,7 @@ namespace osu.Game.Online.API.Requests { Favourite, RankedAndApproved, + Loved, Unranked, Graveyard } diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs index ae91837d29..37f017277f 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs @@ -18,6 +18,7 @@ namespace osu.Game.Overlays.Profile.Sections { new PaginatedBeatmapContainer(BeatmapSetType.Favourite, User, "Favourite Beatmaps"), new PaginatedBeatmapContainer(BeatmapSetType.RankedAndApproved, User, "Ranked & Approved Beatmaps"), + new PaginatedBeatmapContainer(BeatmapSetType.Loved, User, "Loved Beatmaps"), new PaginatedBeatmapContainer(BeatmapSetType.Unranked, User, "Pending Beatmaps"), new PaginatedBeatmapContainer(BeatmapSetType.Graveyard, User, "Graveyarded Beatmaps"), }; From a2468e599b292d1c4d4da4cc5d49b87d38c01d98 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Aug 2019 17:31:26 +0900 Subject: [PATCH 0474/2815] Fix ticks in repeat spans being returned in reverse order --- .../Rulesets/Objects/SliderEventGenerator.cs | 60 ++++++++++++++----- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs index 7cda7485f9..9047d338aa 100644 --- a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs +++ b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs @@ -3,13 +3,15 @@ using System; using System.Collections.Generic; +using System.Linq; using osuTK; namespace osu.Game.Rulesets.Objects { public static class SliderEventGenerator { - public static IEnumerable Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount, double? legacyLastTickOffset) + public static IEnumerable Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount, + double? legacyLastTickOffset) { // A very lenient maximum length of a slider for ticks to be generated. // This exists for edge cases such as /b/1573664 where the beatmap has been edited by the user, and should never be reached in normal usage. @@ -36,24 +38,17 @@ namespace osu.Game.Rulesets.Objects var spanStartTime = startTime + span * spanDuration; var reversed = span % 2 == 1; - for (var d = tickDistance; d <= length; d += tickDistance) + var ticks = generateTicks(span, spanStartTime, spanDuration, reversed, length, tickDistance, minDistanceFromEnd); + + if (reversed) { - if (d >= length - minDistanceFromEnd) - break; - - var pathProgress = d / length; - var timeProgress = reversed ? 1 - pathProgress : pathProgress; - - yield return new SliderEventDescriptor - { - Type = SliderEventType.Tick, - SpanIndex = span, - SpanStartTime = spanStartTime, - Time = spanStartTime + timeProgress * spanDuration, - PathProgress = pathProgress, - }; + // For repeat spans, ticks are returned in reverse-StartTime order, which is undesirable for some rulesets + ticks = ticks.Reverse(); } + foreach (var e in ticks) + yield return e; + if (span < spanCount - 1) { yield return new SliderEventDescriptor @@ -103,6 +98,39 @@ namespace osu.Game.Rulesets.Objects PathProgress = spanCount % 2, }; } + + /// + /// Generates the ticks for a span of the slider. + /// + /// The span index. + /// The start time of the span. + /// The duration of the span. + /// Whether the span is reversed. + /// The length of the path. + /// The distance between each tick. + /// The distance from the end of the path at which ticks are not allowed to be added. + /// A for each tick. If is true, the ticks will be returned in reverse-StartTime order. + private static IEnumerable generateTicks(int spanIndex, double spanStartTime, double spanDuration, bool reversed, double length, double tickDistance, + double minDistanceFromEnd) + { + for (var d = tickDistance; d <= length; d += tickDistance) + { + if (d >= length - minDistanceFromEnd) + break; + + var pathProgress = d / length; + var timeProgress = reversed ? 1 - pathProgress : pathProgress; + + yield return new SliderEventDescriptor + { + Type = SliderEventType.Tick, + SpanIndex = spanIndex, + SpanStartTime = spanStartTime, + Time = spanStartTime + timeProgress * spanDuration, + PathProgress = pathProgress, + }; + } + } } /// From f2b940f930a89ebd14370eff3d231f402579197e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Aug 2019 17:31:08 +0900 Subject: [PATCH 0475/2815] Add tests --- .../OsuBeatmapConversionTest.cs | 2 + .../repeat-slider-expected-conversion.json | 222 +++++++++++ .../Testing/Beatmaps/repeat-slider.osu | 18 + ...ven-repeat-slider-expected-conversion.json | 348 ++++++++++++++++++ .../Testing/Beatmaps/uneven-repeat-slider.osu | 19 + .../Beatmaps/SliderEventGenerationTest.cs | 116 ++++++ 6 files changed, 725 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/repeat-slider-expected-conversion.json create mode 100644 osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/repeat-slider.osu create mode 100644 osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/uneven-repeat-slider-expected-conversion.json create mode 100644 osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/uneven-repeat-slider.osu create mode 100644 osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs index f98d63e6c7..451ecf97ea 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs @@ -21,6 +21,8 @@ namespace osu.Game.Rulesets.Osu.Tests [TestCase("basic")] [TestCase("colinear-perfect-curve")] [TestCase("slider-ticks")] + [TestCase("repeat-slider")] + [TestCase("uneven-repeat-slider")] public new void Test(string name) { base.Test(name); diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/repeat-slider-expected-conversion.json b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/repeat-slider-expected-conversion.json new file mode 100644 index 0000000000..fb4050963f --- /dev/null +++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/repeat-slider-expected-conversion.json @@ -0,0 +1,222 @@ +{ + "Mappings": [{ + "StartTime": 369, + "Objects": [{ + "StartTime": 369, + "EndTime": 369, + "X": 177, + "Y": 191 + }, + { + "StartTime": 450, + "EndTime": 450, + "X": 216.539276, + "Y": 191.192871 + }, + { + "StartTime": 532, + "EndTime": 532, + "X": 256.5667, + "Y": 191.388138 + }, + { + "StartTime": 614, + "EndTime": 614, + "X": 296.594116, + "Y": 191.583389 + }, + { + "StartTime": 696, + "EndTime": 696, + "X": 336.621521, + "Y": 191.778641 + }, + { + "StartTime": 778, + "EndTime": 778, + "X": 376.648926, + "Y": 191.9739 + }, + { + "StartTime": 860, + "EndTime": 860, + "X": 337.318878, + "Y": 191.782043 + }, + { + "StartTime": 942, + "EndTime": 942, + "X": 297.291443, + "Y": 191.586792 + }, + { + "StartTime": 1024, + "EndTime": 1024, + "X": 257.264038, + "Y": 191.391541 + }, + { + "StartTime": 1106, + "EndTime": 1106, + "X": 217.2366, + "Y": 191.196274 + }, + { + "StartTime": 1188, + "EndTime": 1188, + "X": 177.209213, + "Y": 191.001022 + }, + { + "StartTime": 1270, + "EndTime": 1270, + "X": 216.818192, + "Y": 191.194229 + }, + { + "StartTime": 1352, + "EndTime": 1352, + "X": 256.8456, + "Y": 191.3895 + }, + { + "StartTime": 1434, + "EndTime": 1434, + "X": 296.873047, + "Y": 191.584747 + }, + { + "StartTime": 1516, + "EndTime": 1516, + "X": 336.900452, + "Y": 191.78 + }, + { + "StartTime": 1598, + "EndTime": 1598, + "X": 376.927917, + "Y": 191.975266 + }, + { + "StartTime": 1680, + "EndTime": 1680, + "X": 337.039948, + "Y": 191.780685 + }, + { + "StartTime": 1762, + "EndTime": 1762, + "X": 297.0125, + "Y": 191.585434 + }, + { + "StartTime": 1844, + "EndTime": 1844, + "X": 256.9851, + "Y": 191.390167 + }, + { + "StartTime": 1926, + "EndTime": 1926, + "X": 216.957672, + "Y": 191.194916 + }, + { + "StartTime": 2008, + "EndTime": 2008, + "X": 177.069717, + "Y": 191.000336 + }, + { + "StartTime": 2090, + "EndTime": 2090, + "X": 217.097137, + "Y": 191.1956 + }, + { + "StartTime": 2172, + "EndTime": 2172, + "X": 257.124573, + "Y": 191.390854 + }, + { + "StartTime": 2254, + "EndTime": 2254, + "X": 297.152, + "Y": 191.5861 + }, + { + "StartTime": 2336, + "EndTime": 2336, + "X": 337.179443, + "Y": 191.781372 + }, + { + "StartTime": 2418, + "EndTime": 2418, + "X": 376.7884, + "Y": 191.974579 + }, + { + "StartTime": 2500, + "EndTime": 2500, + "X": 336.760956, + "Y": 191.779327 + }, + { + "StartTime": 2582, + "EndTime": 2582, + "X": 296.733643, + "Y": 191.584076 + }, + { + "StartTime": 2664, + "EndTime": 2664, + "X": 256.7062, + "Y": 191.388809 + }, + { + "StartTime": 2746, + "EndTime": 2746, + "X": 216.678772, + "Y": 191.193558 + }, + { + "StartTime": 2828, + "EndTime": 2828, + "X": 177.348663, + "Y": 191.0017 + }, + { + "StartTime": 2909, + "EndTime": 2909, + "X": 216.887909, + "Y": 191.19458 + }, + { + "StartTime": 2991, + "EndTime": 2991, + "X": 256.915344, + "Y": 191.389832 + }, + { + "StartTime": 3073, + "EndTime": 3073, + "X": 296.942749, + "Y": 191.585083 + }, + { + "StartTime": 3155, + "EndTime": 3155, + "X": 336.970184, + "Y": 191.78035 + }, + { + "StartTime": 3201, + "EndTime": 3201, + "X": 376.99762, + "Y": 191.9756 + } + ] + }] +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/repeat-slider.osu b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/repeat-slider.osu new file mode 100644 index 0000000000..624d905384 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/repeat-slider.osu @@ -0,0 +1,18 @@ +osu file format v14 + +[General] +StackLeniency: 0.4 +Mode: 0 + +[Difficulty] +CircleSize:4 +OverallDifficulty:7 +ApproachRate:8 +SliderMultiplier:1.6 +SliderTickRate:4 + +[TimingPoints] +369,327.868852459016,4,2,2,32,1,0 + +[HitObjects] +177,191,369,6,0,L|382:192,7,200 \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/uneven-repeat-slider-expected-conversion.json b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/uneven-repeat-slider-expected-conversion.json new file mode 100644 index 0000000000..12d1645c04 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/uneven-repeat-slider-expected-conversion.json @@ -0,0 +1,348 @@ +{ + "Mappings": [{ + "StartTime": 369, + "Objects": [{ + "StartTime": 369, + "EndTime": 369, + "X": 127, + "Y": 194 + }, + { + "StartTime": 450, + "EndTime": 450, + "X": 166.53389, + "Y": 193.8691 + }, + { + "StartTime": 532, + "EndTime": 532, + "X": 206.555847, + "Y": 193.736572 + }, + { + "StartTime": 614, + "EndTime": 614, + "X": 246.57782, + "Y": 193.60405 + }, + { + "StartTime": 696, + "EndTime": 696, + "X": 286.5998, + "Y": 193.471527 + }, + { + "StartTime": 778, + "EndTime": 778, + "X": 326.621765, + "Y": 193.339 + }, + { + "StartTime": 860, + "EndTime": 860, + "X": 366.6437, + "Y": 193.206482 + }, + { + "StartTime": 942, + "EndTime": 942, + "X": 406.66568, + "Y": 193.073959 + }, + { + "StartTime": 970, + "EndTime": 970, + "X": 420.331726, + "Y": 193.0287 + }, + { + "StartTime": 997, + "EndTime": 997, + "X": 407.153748, + "Y": 193.072342 + }, + { + "StartTime": 1079, + "EndTime": 1079, + "X": 367.131775, + "Y": 193.204865 + }, + { + "StartTime": 1161, + "EndTime": 1161, + "X": 327.1098, + "Y": 193.337387 + }, + { + "StartTime": 1243, + "EndTime": 1243, + "X": 287.08783, + "Y": 193.46991 + }, + { + "StartTime": 1325, + "EndTime": 1325, + "X": 247.0659, + "Y": 193.602432 + }, + { + "StartTime": 1407, + "EndTime": 1407, + "X": 207.043915, + "Y": 193.734955 + }, + { + "StartTime": 1489, + "EndTime": 1489, + "X": 167.021988, + "Y": 193.867477 + }, + { + "StartTime": 1571, + "EndTime": 1571, + "X": 127, + "Y": 194 + }, + { + "StartTime": 1653, + "EndTime": 1653, + "X": 167.021988, + "Y": 193.867477 + }, + { + "StartTime": 1735, + "EndTime": 1735, + "X": 207.043976, + "Y": 193.734955 + }, + { + "StartTime": 1817, + "EndTime": 1817, + "X": 247.065887, + "Y": 193.602432 + }, + { + "StartTime": 1899, + "EndTime": 1899, + "X": 287.08783, + "Y": 193.46991 + }, + { + "StartTime": 1981, + "EndTime": 1981, + "X": 327.1098, + "Y": 193.337387 + }, + { + "StartTime": 2062, + "EndTime": 2062, + "X": 366.643738, + "Y": 193.206482 + }, + { + "StartTime": 2144, + "EndTime": 2144, + "X": 406.665649, + "Y": 193.073959 + }, + { + "StartTime": 2172, + "EndTime": 2172, + "X": 420.331726, + "Y": 193.0287 + }, + { + "StartTime": 2199, + "EndTime": 2199, + "X": 407.153748, + "Y": 193.072342 + }, + { + "StartTime": 2281, + "EndTime": 2281, + "X": 367.1318, + "Y": 193.204865 + }, + { + "StartTime": 2363, + "EndTime": 2363, + "X": 327.1098, + "Y": 193.337387 + }, + { + "StartTime": 2445, + "EndTime": 2445, + "X": 287.08783, + "Y": 193.46991 + }, + { + "StartTime": 2527, + "EndTime": 2527, + "X": 247.065887, + "Y": 193.602432 + }, + { + "StartTime": 2609, + "EndTime": 2609, + "X": 207.043976, + "Y": 193.734955 + }, + { + "StartTime": 2691, + "EndTime": 2691, + "X": 167.021988, + "Y": 193.867477 + }, + { + "StartTime": 2773, + "EndTime": 2773, + "X": 127, + "Y": 194 + }, + { + "StartTime": 2855, + "EndTime": 2855, + "X": 167.021988, + "Y": 193.867477 + }, + { + "StartTime": 2937, + "EndTime": 2937, + "X": 207.043976, + "Y": 193.734955 + }, + { + "StartTime": 3019, + "EndTime": 3019, + "X": 247.065948, + "Y": 193.602432 + }, + { + "StartTime": 3101, + "EndTime": 3101, + "X": 287.087952, + "Y": 193.46991 + }, + { + "StartTime": 3183, + "EndTime": 3183, + "X": 327.109772, + "Y": 193.337387 + }, + { + "StartTime": 3265, + "EndTime": 3265, + "X": 367.131775, + "Y": 193.204865 + }, + { + "StartTime": 3347, + "EndTime": 3347, + "X": 407.153748, + "Y": 193.072342 + }, + { + "StartTime": 3374, + "EndTime": 3374, + "X": 420.331726, + "Y": 193.0287 + }, + { + "StartTime": 3401, + "EndTime": 3401, + "X": 407.153748, + "Y": 193.072342 + }, + { + "StartTime": 3483, + "EndTime": 3483, + "X": 367.131775, + "Y": 193.204865 + }, + { + "StartTime": 3565, + "EndTime": 3565, + "X": 327.109772, + "Y": 193.337387 + }, + { + "StartTime": 3647, + "EndTime": 3647, + "X": 287.087952, + "Y": 193.46991 + }, + { + "StartTime": 3729, + "EndTime": 3729, + "X": 247.065948, + "Y": 193.602432 + }, + { + "StartTime": 3811, + "EndTime": 3811, + "X": 207.043976, + "Y": 193.734955 + }, + { + "StartTime": 3893, + "EndTime": 3893, + "X": 167.021988, + "Y": 193.867477 + }, + { + "StartTime": 3975, + "EndTime": 3975, + "X": 127, + "Y": 194 + }, + { + "StartTime": 4057, + "EndTime": 4057, + "X": 167.021988, + "Y": 193.867477 + }, + { + "StartTime": 4139, + "EndTime": 4139, + "X": 207.043976, + "Y": 193.734955 + }, + { + "StartTime": 4221, + "EndTime": 4221, + "X": 247.065948, + "Y": 193.602432 + }, + { + "StartTime": 4303, + "EndTime": 4303, + "X": 287.087952, + "Y": 193.46991 + }, + { + "StartTime": 4385, + "EndTime": 4385, + "X": 327.109772, + "Y": 193.337387 + }, + { + "StartTime": 4467, + "EndTime": 4467, + "X": 367.131775, + "Y": 193.204865 + }, + { + "StartTime": 4540, + "EndTime": 4540, + "X": 420.331726, + "Y": 193.0287 + }, + { + "StartTime": 4549, + "EndTime": 4549, + "X": 407.153748, + "Y": 193.072342 + } + ] + }] +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/uneven-repeat-slider.osu b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/uneven-repeat-slider.osu new file mode 100644 index 0000000000..64aeeb0c07 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/uneven-repeat-slider.osu @@ -0,0 +1,19 @@ +osu file format v14 + +[General] +StackLeniency: 0.4 +Mode: 0 + +[Difficulty] +CircleSize:4 +OverallDifficulty:7 +ApproachRate:8 +SliderMultiplier:1.6 +SliderTickRate:4 + +[TimingPoints] +369,327.868852459016,4,2,2,32,1,0 + +[HitObjects] +// A slider with an un-even amount of ticks +127,194,369,6,0,L|429:193,7,293.333333333333 diff --git a/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs b/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs new file mode 100644 index 0000000000..9fba0f1668 --- /dev/null +++ b/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs @@ -0,0 +1,116 @@ +// 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 NUnit.Framework; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Tests.Beatmaps +{ + [TestFixture] + public class SliderEventGenerationTest + { + private const double start_time = 0; + private const double span_duration = 1000; + + [Test] + public void TestSingleSpan() + { + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null).ToArray(); + + Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head)); + Assert.That(events[0].Time, Is.EqualTo(start_time)); + + Assert.That(events[1].Type, Is.EqualTo(SliderEventType.Tick)); + Assert.That(events[1].Time, Is.EqualTo(span_duration / 2)); + + Assert.That(events[3].Type, Is.EqualTo(SliderEventType.Tail)); + Assert.That(events[3].Time, Is.EqualTo(span_duration)); + } + + [Test] + public void TestRepeat() + { + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null).ToArray(); + + Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head)); + Assert.That(events[0].Time, Is.EqualTo(start_time)); + + Assert.That(events[1].Type, Is.EqualTo(SliderEventType.Tick)); + Assert.That(events[1].Time, Is.EqualTo(span_duration / 2)); + + Assert.That(events[2].Type, Is.EqualTo(SliderEventType.Repeat)); + Assert.That(events[2].Time, Is.EqualTo(span_duration)); + + Assert.That(events[3].Type, Is.EqualTo(SliderEventType.Tick)); + Assert.That(events[3].Time, Is.EqualTo(span_duration + span_duration / 2)); + + Assert.That(events[5].Type, Is.EqualTo(SliderEventType.Tail)); + Assert.That(events[5].Time, Is.EqualTo(2 * span_duration)); + } + + [Test] + public void TestNonEvenTicks() + { + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null).ToArray(); + + Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head)); + Assert.That(events[0].Time, Is.EqualTo(start_time)); + + Assert.That(events[1].Type, Is.EqualTo(SliderEventType.Tick)); + Assert.That(events[1].Time, Is.EqualTo(300)); + + Assert.That(events[2].Type, Is.EqualTo(SliderEventType.Tick)); + Assert.That(events[2].Time, Is.EqualTo(600)); + + Assert.That(events[3].Type, Is.EqualTo(SliderEventType.Tick)); + Assert.That(events[3].Time, Is.EqualTo(900)); + + Assert.That(events[4].Type, Is.EqualTo(SliderEventType.Repeat)); + Assert.That(events[4].Time, Is.EqualTo(span_duration)); + + Assert.That(events[5].Type, Is.EqualTo(SliderEventType.Tick)); + Assert.That(events[5].Time, Is.EqualTo(1100)); + + Assert.That(events[6].Type, Is.EqualTo(SliderEventType.Tick)); + Assert.That(events[6].Time, Is.EqualTo(1400)); + + Assert.That(events[7].Type, Is.EqualTo(SliderEventType.Tick)); + Assert.That(events[7].Time, Is.EqualTo(1700)); + + Assert.That(events[9].Type, Is.EqualTo(SliderEventType.Tail)); + Assert.That(events[9].Time, Is.EqualTo(2 * span_duration)); + } + + [Test] + public void TestLegacyLastTickOffset() + { + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100).ToArray(); + + Assert.That(events[2].Type, Is.EqualTo(SliderEventType.LegacyLastTick)); + Assert.That(events[2].Time, Is.EqualTo(900)); + } + + [Test] + public void TestMinimumTickDistance() + { + const double velocity = 5; + const double min_distance = velocity * 10; + + var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0).ToArray(); + + Assert.Multiple(() => + { + int tickIndex = -1; + + while (++tickIndex < events.Length) + { + if (events[tickIndex].Type != SliderEventType.Tick) + continue; + + Assert.That(events[tickIndex].Time, Is.LessThan(span_duration - min_distance).Or.GreaterThan(span_duration + min_distance)); + } + }); + } + } +} From 0ff1c6184b31768e3aff483b2c603213ae91e6c7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Aug 2019 17:36:20 +0900 Subject: [PATCH 0476/2815] Add generation comment --- osu.Game/Rulesets/Objects/SliderEventGenerator.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs index 9047d338aa..0d8796b4cb 100644 --- a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs +++ b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs @@ -118,6 +118,7 @@ namespace osu.Game.Rulesets.Objects if (d >= length - minDistanceFromEnd) break; + // Always generate ticks from the start of the path rather than the span to ensure that ticks in repeat spans are positioned identically to those in non-repeat spans var pathProgress = d / length; var timeProgress = reversed ? 1 - pathProgress : pathProgress; From 2ae8cba24d8c951bb7d6331b6c7151d1c02e40b4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Aug 2019 17:58:20 +0900 Subject: [PATCH 0477/2815] Add hardrock spinner test --- .../CatchBeatmapConversionTest.cs | 1 + .../hardrock-spinner-expected-conversion.json | 74 +++++++++++++++++++ .../Testing/Beatmaps/hardrock-spinner.osu | 18 +++++ 3 files changed, 93 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner.osu diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index 80ac64c5f9..dd272599ab 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Catch.Tests [TestCase("slider")] [TestCase("hardrock-stream", new[] { typeof(CatchModHardRock) })] [TestCase("hardrock-repeat-slider", new[] { typeof(CatchModHardRock) })] + [TestCase("hardrock-spinner", new[] { typeof(CatchModHardRock) })] public new void Test(string name, params Type[] mods) { base.Test(name, mods); diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner-expected-conversion.json new file mode 100644 index 0000000000..7333b600fb --- /dev/null +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner-expected-conversion.json @@ -0,0 +1,74 @@ +{ + "Mappings": [{ + "StartTime": 369, + "Objects": [{ + "StartTime": 369, + "Position": 65 + }, + { + "StartTime": 450, + "Position": 482 + }, + { + "StartTime": 532, + "Position": 164 + }, + { + "StartTime": 614, + "Position": 315 + }, + { + "StartTime": 696, + "Position": 145 + }, + { + "StartTime": 778, + "Position": 159 + }, + { + "StartTime": 860, + "Position": 310 + }, + { + "StartTime": 942, + "Position": 441 + }, + { + "StartTime": 1024, + "Position": 428 + }, + { + "StartTime": 1106, + "Position": 243 + }, + { + "StartTime": 1188, + "Position": 422 + }, + { + "StartTime": 1270, + "Position": 481 + }, + { + "StartTime": 1352, + "Position": 104 + }, + { + "StartTime": 1434, + "Position": 473 + }, + { + "StartTime": 1516, + "Position": 135 + }, + { + "StartTime": 1598, + "Position": 360 + }, + { + "StartTime": 1680, + "Position": 123 + } + ] + }] +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner.osu new file mode 100644 index 0000000000..208d21a344 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner.osu @@ -0,0 +1,18 @@ +osu file format v14 + +[General] +StackLeniency: 0.4 +Mode: 0 + +[Difficulty] +CircleSize:4 +OverallDifficulty:7 +ApproachRate:8 +SliderMultiplier:1.6 +SliderTickRate:4 + +[TimingPoints] +369,327.868852459016,4,2,2,32,1,0 + +[HitObjects] +256,192,369,12,0,1680,0:0:0:0: From e9eea7157d9cf0fa333513a98171ff6b3203e23c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Aug 2019 18:04:57 +0900 Subject: [PATCH 0478/2815] Trim unnecessary file contents --- .../Testing/Beatmaps/hardrock-stream.osu | 59 +------------------ 1 file changed, 1 insertion(+), 58 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream.osu index 62835135df..321af4604b 100644 --- a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream.osu +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream.osu @@ -1,75 +1,18 @@ osu file format v14 [General] -AudioFilename: audio.mp3 -AudioLeadIn: 0 -PreviewTime: 53483 -Countdown: 0 -SampleSet: Soft StackLeniency: 0.4 Mode: 0 -LetterboxInBreaks: 0 -WidescreenStoryboard: 0 - -[Editor] -Bookmarks: 369,1680,12172,22664,33155,43647,54139,64631,75123,85614,96106,103975,114467,124959,135287 -DistanceSpacing: 1 -BeatDivisor: 4 -GridSize: 8 -TimelineZoom: 2.899999 - -[Metadata] -Title:Silhouette -TitleUnicode:シルエット -Artist:KANA-BOON -ArtistUnicode:KANA-BOON -Creator:Nevo -Version:lit120's Hard -Source:NARUTO-ナルト-疾風伝 -Tags:shippuuden shipuuden shippuden nine tails opening 16 kage cut ver version naotoshi nao tomori shunao shogunmoon sotarks pika [[pika]] frostwich lit120 shiratoi armin a_r_m_i_n -BeatmapID:1653115 -BeatmapSetID:780952 [Difficulty] -HPDrainRate:6 CircleSize:4 OverallDifficulty:7 ApproachRate:8 SliderMultiplier:1.6 SliderTickRate:1 -[Events] -//Background and Video events -0,0,"OSpGdEP.png",0,0 -//Break Periods -//Storyboard Layer 0 (Background) -//Storyboard Layer 1 (Fail) -//Storyboard Layer 2 (Pass) -//Storyboard Layer 3 (Foreground) -//Storyboard Sound Samples - [TimingPoints] 369,327.868852459016,4,2,2,32,1,0 -1680,-100,4,2,2,42,0,0 -6926,-100,4,2,2,52,0,0 -11844,-100,4,2,2,5,0,0 -12172,-100,4,2,2,62,0,0 -22664,-100,4,2,2,52,0,0 -43647,-100,4,2,2,52,0,0 -53483,-100,4,2,2,32,0,0 -54139,-100,4,2,2,72,0,1 -96106,-100,4,2,2,52,0,0 -103975,-100,4,2,2,82,0,1 -124959,-100,4,2,2,62,0,0 -130205,-100,4,2,2,72,0,0 -132991,-100,4,2,2,82,0,0 - - -[Colours] -Combo1 : 154,53,255 -Combo2 : 253,211,47 -Combo3 : 86,168,250 -Combo4 : 192,192,192 [HitObjects] 258,189,369,1,0,0:0:0:0: @@ -104,4 +47,4 @@ Combo4 : 192,192,192 258,189,2746,1,0,0:0:0:0: 258,189,2828,1,0,0:0:0:0: 258,189,2909,1,0,0:0:0:0: -258,189,2991,1,0,0:0:0:0: +258,189,2991,1,0,0:0:0:0: \ No newline at end of file From f0941a11b513b2f9a506d140124037f05d3235af Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 1 Aug 2019 21:22:08 +0900 Subject: [PATCH 0479/2815] Remove no longer needed property --- osu.Game/Screens/Multi/Multiplayer.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 9550d883fd..90806bab6e 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -34,8 +34,6 @@ namespace osu.Game.Screens.Multi public override bool DisallowExternalBeatmapRulesetChanges => true; - protected virtual bool RequireOnline => true; - private readonly MultiplayerWaveContainer waves; private readonly OsuButton createButton; From 6ab9f645a2f436944d5a583b1faeefdf1e0d121a Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 1 Aug 2019 21:26:52 +0900 Subject: [PATCH 0480/2815] Remove usage of no longer needed property --- osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs index 88dac8d0ff..08afe90de8 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs @@ -97,14 +97,14 @@ namespace osu.Game.Tests.Visual.Menus [Test] public void TestExitMultiWithEscape() { - pushAndConfirm(() => new TestMultiplayer(), "multiplayer"); + pushAndConfirm(() => new Screens.Multi.Multiplayer(), "multiplayer"); exitViaEscapeAndConfirm(); } [Test] public void TestExitMultiWithBackButton() { - pushAndConfirm(() => new TestMultiplayer(), "multiplayer"); + pushAndConfirm(() => new Screens.Multi.Multiplayer(), "multiplayer"); exitViaBackButtonAndConfirm(); } @@ -177,11 +177,6 @@ namespace osu.Game.Tests.Visual.Menus public ModSelectOverlay ModSelectOverlay => ModSelect; } - private class TestMultiplayer : Screens.Multi.Multiplayer - { - protected override bool RequireOnline => false; - } - private class TestLoader : Loader { protected override ShaderPrecompiler CreateShaderPrecompiler() => new TestShaderPrecompiler(); From d55875800067ec90f60f55e492a26c2a30dc0ca3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 1 Aug 2019 19:14:07 +0300 Subject: [PATCH 0481/2815] Add PreviousUsernames field to the User --- osu.Game/Users/User.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 34bd4bbaae..22c0a4fcd0 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -20,6 +20,9 @@ namespace osu.Game.Users [JsonProperty(@"username")] public string Username; + [JsonProperty(@"previous_usernames")] + public string[] PreviousUsernames; + [JsonProperty(@"country")] public Country Country; From 90c59ab39d3058189e530ed25e3597284ea27b5b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 1 Aug 2019 21:26:59 +0300 Subject: [PATCH 0482/2815] implement PreviousUsernamesContainer --- .../TestScenePreviousUsernamesContainer.cs | 52 +++++ .../Header/PreviousUsernamesContainer.cs | 179 ++++++++++++++++++ 2 files changed, 231 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs create mode 100644 osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs diff --git a/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs b/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs new file mode 100644 index 0000000000..0469c230d0 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.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 NUnit.Framework; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Overlays.Profile.Header; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Online +{ + [TestFixture] + public class TestScenePreviousUsernamesContainer : OsuTestScene + { + public TestScenePreviousUsernamesContainer() + { + Bindable user = new Bindable(); + + Child = new PreviousUsernamesContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + User = { BindTarget = user }, + }; + + AddStep("single username", () => user.Value = new User + { + PreviousUsernames = new[] { "username1" }, + }); + + AddStep("two usernames", () => user.Value = new User + { + PreviousUsernames = new[] { "longusername", "longerusername" }, + }); + + AddStep("three usernames", () => user.Value = new User + { + PreviousUsernames = new[] { "test", "angelsim", "verylongusername" }, + }); + + AddStep("four usernames", () => user.Value = new User + { + PreviousUsernames = new[] { "ihavenoidea", "howcani", "makethistext" , "anylonger" }, + }); + + AddStep("no username", () => user.Value = new User + { + PreviousUsernames = new string[0], + }); + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs b/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs new file mode 100644 index 0000000000..39bd2388a4 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs @@ -0,0 +1,179 @@ +// 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.Events; +using osu.Game.Graphics; +using osu.Game.Users; +using osuTK; +using System; +using System.Linq; + +namespace osu.Game.Overlays.Profile.Header +{ + public class PreviousUsernamesContainer : Container + { + private const int duration = 200; + private const int margin = 10; + private const int width = 350; + private const int move_offset = 30; + + public readonly Bindable User = new Bindable(); + + private readonly HoverIconContainer hoverIcon; + private readonly ContentContainer contentContainer; + + public PreviousUsernamesContainer() + { + AutoSizeAxes = Axes.Y; + Width = width; + Children = new Drawable[] + { + contentContainer = new ContentContainer(), + hoverIcon = new HoverIconContainer(), + }; + + hoverIcon.ActivateHover += () => + { + contentContainer.Show(); + this.MoveToY(-move_offset, duration, Easing.OutQuint); + }; + + User.BindValueChanged(onUserChanged); + } + + private void onUserChanged(ValueChangedEvent user) + { + contentContainer.Clear(); + + var usernames = user.NewValue.PreviousUsernames; + + if (usernames.Any()) + { + var amount = usernames.Count(); + + for (int i = 0; i < amount; i++) + { + string text = (i + 1 == amount) ? usernames[i] : $@"{usernames[i]},"; + + contentContainer.Usernames.AddText(new SpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true), + Text = text, + }); + } + + Show(); + return; + } + + Hide(); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + contentContainer.Hide(); + this.MoveToY(0, duration, Easing.OutQuint); + } + + private class ContentContainer : VisibilityContainer + { + private const int header_height = 25; + private const int content_padding = 60; + + public readonly TextFlowContainer Usernames; + + private readonly Box background; + + public ContentContainer() + { + AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; + Masking = true; + AlwaysPresent = true; + CornerRadius = 5; + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + Height = header_height, + Children = new Drawable[] + { + new SpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Padding = new MarginPadding { Left = content_padding }, + Text = @"formerly known as", + Font = OsuFont.GetFont(size: 14, italics: true) + } + } + }, + Usernames = new TextFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + Spacing = new Vector2(5, 5), + Padding = new MarginPadding { Left = content_padding, Bottom = margin }, + }, + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.GreySeafoamDarker; + } + + public override void Clear(bool disposeChildren) => Usernames.Clear(disposeChildren); + + protected override void PopIn() => this.FadeIn(duration, Easing.OutQuint); + + protected override void PopOut() => this.FadeOut(duration, Easing.OutQuint); + } + + private class HoverIconContainer : Container + { + public Action ActivateHover; + + public HoverIconContainer() + { + AutoSizeAxes = Axes.Both; + Child = new SpriteIcon + { + Margin = new MarginPadding(margin) { Top = 7 }, + Size = new Vector2(20), + Icon = FontAwesome.Solid.IdCard, + }; + } + + protected override bool OnHover(HoverEvent e) + { + ActivateHover?.Invoke(); + return base.OnHover(e); + } + } + } +} From 13fe1732d8cf8b76aa689a585ad6c3fe63128119 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 1 Aug 2019 21:32:04 +0300 Subject: [PATCH 0483/2815] adjust height values to avoid random jumps --- .../Overlays/Profile/Header/PreviousUsernamesContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs b/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs index 39bd2388a4..67b9bcb651 100644 --- a/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs +++ b/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Overlays.Profile.Header private const int duration = 200; private const int margin = 10; private const int width = 350; - private const int move_offset = 30; + private const int move_offset = 20; public readonly Bindable User = new Bindable(); @@ -163,7 +163,7 @@ namespace osu.Game.Overlays.Profile.Header AutoSizeAxes = Axes.Both; Child = new SpriteIcon { - Margin = new MarginPadding(margin) { Top = 7 }, + Margin = new MarginPadding(margin) { Top = 6 }, Size = new Vector2(20), Icon = FontAwesome.Solid.IdCard, }; From e66a3494b9fbd43c9f49d718af1683106d86dc93 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 1 Aug 2019 22:50:01 +0300 Subject: [PATCH 0484/2815] make the container hidden by default --- osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs b/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs index 67b9bcb651..5165f0d0df 100644 --- a/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs +++ b/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs @@ -45,6 +45,7 @@ namespace osu.Game.Overlays.Profile.Header }; User.BindValueChanged(onUserChanged); + Hide(); } private void onUserChanged(ValueChangedEvent user) From 55475927685aee49e5686899d6898ff8093fc7d4 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 1 Aug 2019 23:04:18 +0300 Subject: [PATCH 0485/2815] CI fixes --- .../Visual/Online/TestScenePreviousUsernamesContainer.cs | 2 +- .../Overlays/Profile/Header/PreviousUsernamesContainer.cs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs b/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs index 0469c230d0..a33de887d7 100644 --- a/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("four usernames", () => user.Value = new User { - PreviousUsernames = new[] { "ihavenoidea", "howcani", "makethistext" , "anylonger" }, + PreviousUsernames = new[] { "ihavenoidea", "howcani", "makethistext", "anylonger" }, }); AddStep("no username", () => user.Value = new User diff --git a/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs b/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs index 5165f0d0df..1c98cff616 100644 --- a/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs +++ b/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs @@ -25,11 +25,12 @@ namespace osu.Game.Overlays.Profile.Header public readonly Bindable User = new Bindable(); - private readonly HoverIconContainer hoverIcon; private readonly ContentContainer contentContainer; public PreviousUsernamesContainer() { + HoverIconContainer hoverIcon; + AutoSizeAxes = Axes.Y; Width = width; Children = new Drawable[] @@ -56,7 +57,7 @@ namespace osu.Game.Overlays.Profile.Header if (usernames.Any()) { - var amount = usernames.Count(); + var amount = usernames.Length; for (int i = 0; i < amount; i++) { From de96e5dfc605f559269a2e045652ab2453267d2d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 2 Aug 2019 07:41:11 +0300 Subject: [PATCH 0486/2815] Apply suggested changes --- .../TestScenePreviousUsernamesContainer.cs | 5 ++ .../Header/PreviousUsernamesContainer.cs | 58 +++++++++---------- 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs b/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs index a33de887d7..4f3a2e47c6 100644 --- a/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs @@ -43,6 +43,11 @@ namespace osu.Game.Tests.Visual.Online PreviousUsernames = new[] { "ihavenoidea", "howcani", "makethistext", "anylonger" }, }); + AddStep("many usernames", () => user.Value = new User + { + PreviousUsernames = new[] { "ihavenoidea", "howcani", "makethistext", "anylonger", "but", "ican", "try", "tomake", "this" }, + }); + AddStep("no username", () => user.Value = new User { PreviousUsernames = new string[0], diff --git a/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs b/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs index 1c98cff616..e8443dff1b 100644 --- a/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs +++ b/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs @@ -16,12 +16,12 @@ using System.Linq; namespace osu.Game.Overlays.Profile.Header { - public class PreviousUsernamesContainer : Container + public class PreviousUsernamesContainer : CompositeDrawable { private const int duration = 200; private const int margin = 10; - private const int width = 350; - private const int move_offset = 20; + private const int width = 310; + private const int move_offset = 15; public readonly Bindable User = new Bindable(); @@ -33,43 +33,35 @@ namespace osu.Game.Overlays.Profile.Header AutoSizeAxes = Axes.Y; Width = width; - Children = new Drawable[] + + AddRangeInternal(new Drawable[] { contentContainer = new ContentContainer(), hoverIcon = new HoverIconContainer(), - }; + }); hoverIcon.ActivateHover += () => { contentContainer.Show(); this.MoveToY(-move_offset, duration, Easing.OutQuint); }; + } - User.BindValueChanged(onUserChanged); - Hide(); + protected override void LoadComplete() + { + base.LoadComplete(); + User.BindValueChanged(onUserChanged, true); } private void onUserChanged(ValueChangedEvent user) { - contentContainer.Clear(); + contentContainer.Text = string.Empty; - var usernames = user.NewValue.PreviousUsernames; + var usernames = user.NewValue?.PreviousUsernames; - if (usernames.Any()) + if (usernames?.Any() ?? false) { - var amount = usernames.Length; - - for (int i = 0; i < amount; i++) - { - string text = (i + 1 == amount) ? usernames[i] : $@"{usernames[i]},"; - - contentContainer.Usernames.AddText(new SpriteText - { - Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true), - Text = text, - }); - } - + contentContainer.Text = string.Join(", ", usernames); Show(); return; } @@ -86,11 +78,15 @@ namespace osu.Game.Overlays.Profile.Header private class ContentContainer : VisibilityContainer { - private const int header_height = 25; - private const int content_padding = 60; + private const int header_height = 20; + private const int content_padding = 40; - public readonly TextFlowContainer Usernames; + public string Text + { + set => usernames.Text = value; + } + private readonly TextFlowContainer usernames; private readonly Box background; public ContentContainer() @@ -126,16 +122,16 @@ namespace osu.Game.Overlays.Profile.Header Origin = Anchor.BottomLeft, Padding = new MarginPadding { Left = content_padding }, Text = @"formerly known as", - Font = OsuFont.GetFont(size: 14, italics: true) + Font = OsuFont.GetFont(size: 10, italics: true) } } }, - Usernames = new TextFlowContainer + usernames = new TextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold, italics: true)) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Full, - Spacing = new Vector2(5, 5), + Spacing = new Vector2(0, 5), Padding = new MarginPadding { Left = content_padding, Bottom = margin }, }, } @@ -149,8 +145,6 @@ namespace osu.Game.Overlays.Profile.Header background.Colour = colours.GreySeafoamDarker; } - public override void Clear(bool disposeChildren) => Usernames.Clear(disposeChildren); - protected override void PopIn() => this.FadeIn(duration, Easing.OutQuint); protected override void PopOut() => this.FadeOut(duration, Easing.OutQuint); @@ -166,7 +160,7 @@ namespace osu.Game.Overlays.Profile.Header Child = new SpriteIcon { Margin = new MarginPadding(margin) { Top = 6 }, - Size = new Vector2(20), + Size = new Vector2(15), Icon = FontAwesome.Solid.IdCard, }; } From 4c0a9aeab701381ad602cde155b636327ba979f7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 2 Aug 2019 07:44:09 +0300 Subject: [PATCH 0487/2815] Add null user step --- .../Visual/Online/TestScenePreviousUsernamesContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs b/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs index 4f3a2e47c6..4a3b3403b2 100644 --- a/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs @@ -52,6 +52,8 @@ namespace osu.Game.Tests.Visual.Online { PreviousUsernames = new string[0], }); + + AddStep("null user", () => user.Value = null); } } } From ba3f8a643a899f268d75882af1c44ba045f13180 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 2 Aug 2019 14:16:57 +0900 Subject: [PATCH 0488/2815] Update cake --- build/cakebuild.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/cakebuild.csproj b/build/cakebuild.csproj index 8ccce35e26..d5a6d44740 100644 --- a/build/cakebuild.csproj +++ b/build/cakebuild.csproj @@ -5,7 +5,7 @@ netcoreapp2.0 - - + + \ No newline at end of file From 1b5a9aecffdef0b307c3712d0d85992928b0d894 Mon Sep 17 00:00:00 2001 From: DTSDAO Date: Fri, 2 Aug 2019 23:34:25 +0800 Subject: [PATCH 0489/2815] Add iOS URL Scheme support --- .../Graphics/Containers/LinkFlowContainer.cs | 47 +--------------- osu.Game/Online/Chat/MessageFormatter.cs | 54 ++++++++++++++++++- osu.Game/OsuGame.cs | 22 ++++++++ osu.iOS/AppDelegate.cs | 7 ++- osu.iOS/Info.plist | 14 +++++ 5 files changed, 93 insertions(+), 51 deletions(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 15068d81c0..51a842fc84 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -86,52 +86,7 @@ namespace osu.Game.Graphics.Containers { RelativeSizeAxes = Axes.Both, TooltipText = tooltipText ?? (url != text ? url : string.Empty), - Action = action ?? (() => - { - switch (linkType) - { - case LinkAction.OpenBeatmap: - // TODO: proper query params handling - if (linkArgument != null && int.TryParse(linkArgument.Contains('?') ? linkArgument.Split('?')[0] : linkArgument, out int beatmapId)) - game?.ShowBeatmap(beatmapId); - break; - - case LinkAction.OpenBeatmapSet: - if (int.TryParse(linkArgument, out int setId)) - game?.ShowBeatmapSet(setId); - break; - - case LinkAction.OpenChannel: - try - { - channelManager?.OpenChannel(linkArgument); - } - catch (ChannelNotFoundException) - { - Logger.Log($"The requested channel \"{linkArgument}\" does not exist"); - } - - break; - - case LinkAction.OpenEditorTimestamp: - case LinkAction.JoinMultiplayerMatch: - case LinkAction.Spectate: - showNotImplementedError?.Invoke(); - break; - - case LinkAction.External: - game?.OpenUrlExternally(url); - break; - - case LinkAction.OpenUserProfile: - if (long.TryParse(linkArgument, out long userId)) - game?.ShowUser(userId); - break; - - default: - throw new NotImplementedException($"This {nameof(LinkAction)} ({linkType.ToString()}) is missing an associated action."); - } - }), + Action = action ?? (() => LinkUtils.HandleLink(url, linkType, linkArgument, game, channelManager, showNotImplementedError)), }); return drawables; diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 4aaffdd161..ee8f45803b 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text.RegularExpressions; +using osu.Framework.Logging; namespace osu.Game.Online.Chat { @@ -98,7 +100,7 @@ namespace osu.Game.Online.Chat } } - private static LinkDetails getLinkDetails(string url) + public static LinkDetails getLinkDetails(string url) { var args = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); args[0] = args[0].TrimEnd(':'); @@ -288,4 +290,54 @@ namespace osu.Game.Online.Chat public int CompareTo(Link otherLink) => Index > otherLink.Index ? 1 : -1; } + + public static class LinkUtils + { + public static void HandleLink(string url, LinkAction linkType, string linkArgument, OsuGame game, ChannelManager channelManager = null, Action showNotImplementedError = null) + { + switch (linkType) + { + case LinkAction.OpenBeatmap: + // TODO: proper query params handling + if (linkArgument != null && int.TryParse(linkArgument.Contains('?') ? linkArgument.Split('?')[0] : linkArgument, out int beatmapId)) + game?.ShowBeatmap(beatmapId); + break; + + case LinkAction.OpenBeatmapSet: + if (int.TryParse(linkArgument, out int setId)) + game?.ShowBeatmapSet(setId); + break; + + case LinkAction.OpenChannel: + try + { + channelManager?.OpenChannel(linkArgument); + } + catch (ChannelNotFoundException) + { + Logger.Log($"The requested channel \"{linkArgument}\" does not exist"); + } + + break; + + case LinkAction.OpenEditorTimestamp: + case LinkAction.JoinMultiplayerMatch: + case LinkAction.Spectate: + showNotImplementedError?.Invoke(); + break; + + case LinkAction.External: + game?.OpenUrlExternally(url); + break; + + case LinkAction.OpenUserProfile: + if (long.TryParse(linkArgument, out long userId)) + game?.ShowUser(userId); + break; + + default: + throw new NotImplementedException($"This {nameof(LinkAction)} ({linkType.ToString()}) is missing an associated action."); + } + } + } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e71dd67bf2..5dea67253d 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -43,6 +43,7 @@ using osu.Game.Scoring; using osu.Game.Screens.Select; using osu.Game.Utils; using LogLevel = osu.Framework.Logging.LogLevel; +using static osu.Game.Online.Chat.MessageFormatter; namespace osu.Game { @@ -90,6 +91,8 @@ namespace osu.Game private IntroScreen introScreen; + private bool loaded = false; + private Bindable configRuleset; private Bindable configSkin; @@ -190,10 +193,29 @@ namespace osu.Game Audio.AddAdjustment(AdjustableProperty.Volume, inactiveVolumeFade); Beatmap.BindValueChanged(beatmapChanged, true); + + loaded = true; } private ExternalLinkOpener externalLinkOpener; + public void HandleUrl(string url) + { + Logger.Log($"Request to handle url: {url}"); + if (loaded) + { + Action showNotImplementedError = () => notifications?.Post(new SimpleNotification + { + Text = @"This link type is not yet supported!", + Icon = FontAwesome.Solid.LifeRing, + }); + LinkDetails linkDetails = getLinkDetails(url); + Schedule(() => LinkUtils.HandleLink(url, linkDetails.Action, linkDetails.Argument, this, channelManager, showNotImplementedError)); + } + else + Scheduler.AddDelayed(() => HandleUrl(url), 1000); + } + public void OpenUrlExternally(string url) { if (url.StartsWith("/")) diff --git a/osu.iOS/AppDelegate.cs b/osu.iOS/AppDelegate.cs index 9ef21e014c..e727305997 100644 --- a/osu.iOS/AppDelegate.cs +++ b/osu.iOS/AppDelegate.cs @@ -1,10 +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.Threading.Tasks; using Foundation; using osu.Framework.iOS; -using osu.Game; +using osu.Framework.Threading; using UIKit; namespace osu.iOS @@ -16,9 +15,9 @@ namespace osu.iOS protected override Framework.Game CreateGame() => game = new OsuGameIOS(); - public override bool OpenUrl(UIApplication application, NSUrl url, string sourceApplication, NSObject annotation) + public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options) { - Task.Run(() => game.Import(url.Path)); + game.HandleUrl(url.AbsoluteString); return true; } } diff --git a/osu.iOS/Info.plist b/osu.iOS/Info.plist index 4fbc67e27b..5fe8cd6d5b 100644 --- a/osu.iOS/Info.plist +++ b/osu.iOS/Info.plist @@ -107,5 +107,19 @@ + UIFileSharingEnabled + + CFBundleURLTypes + + + CFBundleURLSchemes + + osu + osump + + CFBundleTypeRole + Editor + + From 471103fd101180e78f2d54fc34d96cb5198dcada Mon Sep 17 00:00:00 2001 From: DTSDAO Date: Sat, 3 Aug 2019 00:00:22 +0800 Subject: [PATCH 0490/2815] Fix code format --- osu.Game/Graphics/Containers/LinkFlowContainer.cs | 1 - osu.Game/Online/Chat/MessageFormatter.cs | 2 +- osu.Game/OsuGame.cs | 8 +++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 51a842fc84..ab4629cfff 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -8,7 +8,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using System.Collections.Generic; using osu.Framework.Graphics; -using osu.Framework.Logging; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Users; diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index ee8f45803b..347139975e 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -100,7 +100,7 @@ namespace osu.Game.Online.Chat } } - public static LinkDetails getLinkDetails(string url) + public static LinkDetails GetLinkDetails(string url) { var args = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); args[0] = args[0].TrimEnd(':'); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 5dea67253d..fa68142fea 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -91,7 +91,7 @@ namespace osu.Game private IntroScreen introScreen; - private bool loaded = false; + private bool loaded; private Bindable configRuleset; @@ -193,8 +193,6 @@ namespace osu.Game Audio.AddAdjustment(AdjustableProperty.Volume, inactiveVolumeFade); Beatmap.BindValueChanged(beatmapChanged, true); - - loaded = true; } private ExternalLinkOpener externalLinkOpener; @@ -202,6 +200,7 @@ namespace osu.Game public void HandleUrl(string url) { Logger.Log($"Request to handle url: {url}"); + if (loaded) { Action showNotImplementedError = () => notifications?.Post(new SimpleNotification @@ -402,6 +401,8 @@ namespace osu.Game protected override void LoadComplete() { + loaded = false; + base.LoadComplete(); // The next time this is updated is in UpdateAfterChildren, which occurs too late and results @@ -597,6 +598,7 @@ namespace osu.Game settings.State.ValueChanged += _ => updateScreenOffset(); notifications.State.ValueChanged += _ => updateScreenOffset(); + loaded = true; } public class GameIdleTracker : IdleTracker From e84c79d14051ecb220429c38cdb321e53f9a0b24 Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 2 Aug 2019 13:16:33 -0700 Subject: [PATCH 0491/2815] Update osu!direct categories sorting with web changes --- .../API/Requests/SearchBeatmapSetsRequest.cs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index d9d05ff285..c8c36789c4 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -27,24 +27,25 @@ namespace osu.Game.Online.API.Requests } // ReSharper disable once ImpureMethodCallOnReadonlyValueField - protected override string Target => $@"beatmapsets/search?q={query}&m={ruleset.ID ?? 0}&s={(int)searchCategory}&sort={sortCriteria.ToString().ToLowerInvariant()}_{directionString}"; + protected override string Target => $@"beatmapsets/search?q={query}&m={ruleset.ID ?? 0}&s={searchCategory.ToString().ToLowerInvariant()}&sort={sortCriteria.ToString().ToLowerInvariant()}_{directionString}"; } public enum BeatmapSearchCategory { - Any = 7, + Any, - [Description("Ranked & Approved")] - RankedApproved = 0, - Qualified = 3, - Loved = 8, - Favourites = 2, + [Description("Has Leaderboard")] + Leaderboard, + Ranked, + Qualified, + Loved, + Favourites, [Description("Pending & WIP")] - PendingWIP = 4, - Graveyard = 5, + Pending, + Graveyard, [Description("My Maps")] - MyMaps = 6, + Mine, } } From 0082695cd827a574e91a0c8be4d0bd8da1288e8e Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 2 Aug 2019 13:22:58 -0700 Subject: [PATCH 0492/2815] Choose default category sorting instead of being always first --- osu.Game/Overlays/Direct/FilterControl.cs | 1 + .../Overlays/SearchableList/SearchableListFilterControl.cs | 4 ++++ osu.Game/Overlays/Social/FilterControl.cs | 1 + osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs | 1 + 4 files changed, 7 insertions(+) diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs index 4b43542b43..8b04bf0387 100644 --- a/osu.Game/Overlays/Direct/FilterControl.cs +++ b/osu.Game/Overlays/Direct/FilterControl.cs @@ -18,6 +18,7 @@ namespace osu.Game.Overlays.Direct protected override Color4 BackgroundColour => OsuColour.FromHex(@"384552"); protected override DirectSortCriteria DefaultTab => DirectSortCriteria.Ranked; + protected override BeatmapSearchCategory DefaultCategory => BeatmapSearchCategory.Leaderboard; protected override Drawable CreateSupplementaryControls() => rulesetSelector = new DirectRulesetSelector(); diff --git a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs index b0a8a0e77d..a0c4e9a080 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs @@ -26,6 +26,7 @@ namespace osu.Game.Overlays.SearchableList protected abstract Color4 BackgroundColour { get; } protected abstract T DefaultTab { get; } + protected abstract U DefaultCategory { get; } protected virtual Drawable CreateSupplementaryControls() => null; /// @@ -109,6 +110,9 @@ namespace osu.Game.Overlays.SearchableList Tabs.Current.Value = DefaultTab; Tabs.Current.TriggerChange(); + + DisplayStyleControl.Dropdown.Current.Value = DefaultCategory; + DisplayStyleControl.Dropdown.Current.TriggerChange(); } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/Social/FilterControl.cs b/osu.Game/Overlays/Social/FilterControl.cs index 6abf6ec98d..1c2cb95dfe 100644 --- a/osu.Game/Overlays/Social/FilterControl.cs +++ b/osu.Game/Overlays/Social/FilterControl.cs @@ -12,6 +12,7 @@ namespace osu.Game.Overlays.Social { protected override Color4 BackgroundColour => OsuColour.FromHex(@"47253a"); protected override SocialSortCriteria DefaultTab => SocialSortCriteria.Rank; + protected override SortDirection DefaultCategory => SortDirection.Ascending; public FilterControl() { diff --git a/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs b/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs index 8e14f76e4b..d0d983bbff 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs @@ -14,6 +14,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components { protected override Color4 BackgroundColour => OsuColour.FromHex(@"362e42"); protected override PrimaryFilter DefaultTab => PrimaryFilter.Open; + protected override SecondaryFilter DefaultCategory => SecondaryFilter.Public; protected override float ContentHorizontalPadding => base.ContentHorizontalPadding + OsuScreen.HORIZONTAL_OVERFLOW_PADDING; From f81238b8b18b38546615c2992df68ca35a19baa8 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 3 Aug 2019 04:45:41 +0300 Subject: [PATCH 0493/2815] Add online test --- .../TestScenePreviousUsernamesContainer.cs | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs b/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs index 4a3b3403b2..dafc1870e4 100644 --- a/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs @@ -2,8 +2,11 @@ // 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.Graphics; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Overlays.Profile.Header; using osu.Game.Users; @@ -12,10 +15,13 @@ namespace osu.Game.Tests.Visual.Online [TestFixture] public class TestScenePreviousUsernamesContainer : OsuTestScene { + [Resolved] + private IAPIProvider api { get; set; } + + private readonly Bindable user = new Bindable(); + public TestScenePreviousUsernamesContainer() { - Bindable user = new Bindable(); - Child = new PreviousUsernamesContainer { Anchor = Anchor.Centre, @@ -55,5 +61,17 @@ namespace osu.Game.Tests.Visual.Online AddStep("null user", () => user.Value = null); } + + protected override void LoadComplete() + { + base.LoadComplete(); + + AddStep("online user (Angelsim)", () => + { + var request = new GetUserRequest(1777162); + request.Success += user => this.user.Value = user; + api.Queue(request); + }); + } } } From 37be4fbf1622d0f80271b5400a497b0bfdfb9984 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 3 Aug 2019 05:34:14 +0300 Subject: [PATCH 0494/2815] Use GridContainer for layout --- .../TestScenePreviousUsernamesContainer.cs | 43 ++--- .../Header/PreviousUsernamesContainer.cs | 159 +++++++++--------- 2 files changed, 90 insertions(+), 112 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs b/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs index dafc1870e4..5636f8756d 100644 --- a/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs @@ -29,37 +29,22 @@ namespace osu.Game.Tests.Visual.Online User = { BindTarget = user }, }; - AddStep("single username", () => user.Value = new User + User[] users = new[] { - PreviousUsernames = new[] { "username1" }, - }); + new User { PreviousUsernames = new[] { "username1" } }, + new User { PreviousUsernames = new[] { "longusername", "longerusername" } }, + new User { PreviousUsernames = new[] { "test", "angelsim", "verylongusername" } }, + new User { PreviousUsernames = new[] { "ihavenoidea", "howcani", "makethistext", "anylonger" } }, + new User { PreviousUsernames = new string[0] }, + null + }; - AddStep("two usernames", () => user.Value = new User - { - PreviousUsernames = new[] { "longusername", "longerusername" }, - }); - - AddStep("three usernames", () => user.Value = new User - { - PreviousUsernames = new[] { "test", "angelsim", "verylongusername" }, - }); - - AddStep("four usernames", () => user.Value = new User - { - PreviousUsernames = new[] { "ihavenoidea", "howcani", "makethistext", "anylonger" }, - }); - - AddStep("many usernames", () => user.Value = new User - { - PreviousUsernames = new[] { "ihavenoidea", "howcani", "makethistext", "anylonger", "but", "ican", "try", "tomake", "this" }, - }); - - AddStep("no username", () => user.Value = new User - { - PreviousUsernames = new string[0], - }); - - AddStep("null user", () => user.Value = null); + AddStep("single username", () => user.Value = users[0]); + AddStep("two usernames", () => user.Value = users[1]); + AddStep("three usernames", () => user.Value = users[2]); + AddStep("four usernames", () => user.Value = users[3]); + AddStep("no username", () => user.Value = users[4]); + AddStep("null user", () => user.Value = users[5]); } protected override void LoadComplete() diff --git a/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs b/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs index e8443dff1b..36034fe8db 100644 --- a/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs +++ b/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs @@ -25,7 +25,9 @@ namespace osu.Game.Overlays.Profile.Header public readonly Bindable User = new Bindable(); - private readonly ContentContainer contentContainer; + private readonly TextFlowContainer text; + private readonly Box background; + private readonly SpriteText header; public PreviousUsernamesContainer() { @@ -33,18 +35,68 @@ namespace osu.Game.Overlays.Profile.Header AutoSizeAxes = Axes.Y; Width = width; + Masking = true; + CornerRadius = 5; AddRangeInternal(new Drawable[] { - contentContainer = new ContentContainer(), - hoverIcon = new HoverIconContainer(), + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new GridContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize) + }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Distributed) + }, + Content = new[] + { + new Drawable[] + { + hoverIcon = new HoverIconContainer(), + header = new SpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Text = @"formerly known as", + Font = OsuFont.GetFont(size: 10, italics: true) + } + }, + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + }, + text = new TextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold, italics: true)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + Margin = new MarginPadding { Bottom = margin, Top = margin / 2 } + } + } + } + } }); - hoverIcon.ActivateHover += () => - { - contentContainer.Show(); - this.MoveToY(-move_offset, duration, Easing.OutQuint); - }; + hoverIcon.ActivateHover += showContent; + hideContent(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.GreySeafoamDarker; } protected override void LoadComplete() @@ -55,13 +107,13 @@ namespace osu.Game.Overlays.Profile.Header private void onUserChanged(ValueChangedEvent user) { - contentContainer.Text = string.Empty; + text.Text = string.Empty; var usernames = user.NewValue?.PreviousUsernames; if (usernames?.Any() ?? false) { - contentContainer.Text = string.Join(", ", usernames); + text.Text = string.Join(", ", usernames); Show(); return; } @@ -72,82 +124,23 @@ namespace osu.Game.Overlays.Profile.Header protected override void OnHoverLost(HoverLostEvent e) { base.OnHoverLost(e); - contentContainer.Hide(); - this.MoveToY(0, duration, Easing.OutQuint); + hideContent(); } - private class ContentContainer : VisibilityContainer + private void showContent() { - private const int header_height = 20; - private const int content_padding = 40; + text.FadeIn(duration, Easing.OutQuint); + header.FadeIn(duration, Easing.OutQuint); + background.FadeIn(duration, Easing.OutQuint); + this.MoveToY(-move_offset, duration, Easing.OutQuint); + } - public string Text - { - set => usernames.Text = value; - } - - private readonly TextFlowContainer usernames; - private readonly Box background; - - public ContentContainer() - { - AutoSizeAxes = Axes.Y; - RelativeSizeAxes = Axes.X; - Masking = true; - AlwaysPresent = true; - CornerRadius = 5; - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.X, - Height = header_height, - Children = new Drawable[] - { - new SpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Padding = new MarginPadding { Left = content_padding }, - Text = @"formerly known as", - Font = OsuFont.GetFont(size: 10, italics: true) - } - } - }, - usernames = new TextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold, italics: true)) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Full, - Spacing = new Vector2(0, 5), - Padding = new MarginPadding { Left = content_padding, Bottom = margin }, - }, - } - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - background.Colour = colours.GreySeafoamDarker; - } - - protected override void PopIn() => this.FadeIn(duration, Easing.OutQuint); - - protected override void PopOut() => this.FadeOut(duration, Easing.OutQuint); + private void hideContent() + { + text.FadeOut(duration, Easing.OutQuint); + header.FadeOut(duration, Easing.OutQuint); + background.FadeOut(duration, Easing.OutQuint); + this.MoveToY(0, duration, Easing.OutQuint); } private class HoverIconContainer : Container @@ -159,7 +152,7 @@ namespace osu.Game.Overlays.Profile.Header AutoSizeAxes = Axes.Both; Child = new SpriteIcon { - Margin = new MarginPadding(margin) { Top = 6 }, + Margin = new MarginPadding { Top = 6, Left = margin, Right = margin * 2 }, Size = new Vector2(15), Icon = FontAwesome.Solid.IdCard, }; From 416f9d89dba59a8cc85e020e643506f4f02b1b44 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 3 Aug 2019 05:49:01 +0300 Subject: [PATCH 0495/2815] CI fixes --- .../Visual/Online/TestScenePreviousUsernamesContainer.cs | 2 +- osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs b/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs index 5636f8756d..b891fda0f4 100644 --- a/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.Online User = { BindTarget = user }, }; - User[] users = new[] + User[] users = { new User { PreviousUsernames = new[] { "username1" } }, new User { PreviousUsernames = new[] { "longusername", "longerusername" } }, diff --git a/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs b/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs index 36034fe8db..b53ac8eb80 100644 --- a/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs +++ b/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs @@ -82,7 +82,7 @@ namespace osu.Game.Overlays.Profile.Header RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Full, - Margin = new MarginPadding { Bottom = margin, Top = margin / 2 } + Margin = new MarginPadding { Bottom = margin, Top = margin / 2f } } } } From b244b2fe3d378f7050ba3f8dc9f88a098a98b776 Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 2 Aug 2019 21:13:29 -0700 Subject: [PATCH 0496/2815] Add hover click sounds to leaderboard mod filter --- osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs index d158186899..544acc7eb2 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs @@ -81,7 +81,8 @@ namespace osu.Game.Graphics.UserInterface Colour = Color4.White, Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, - } + }, + new HoverClickSounds() }; Current.ValueChanged += selected => From c9e14c8f063afae562789111864fc094601a9202 Mon Sep 17 00:00:00 2001 From: DTSDAO Date: Sat, 3 Aug 2019 19:46:57 +0800 Subject: [PATCH 0497/2815] Fix typo --- osu.Game/Online/Chat/MessageFormatter.cs | 4 ++-- osu.Game/OsuGame.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 347139975e..73396ce4d2 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -78,7 +78,7 @@ namespace osu.Game.Online.Chat //since we just changed the line display text, offset any already processed links. result.Links.ForEach(l => l.Index -= l.Index > index ? m.Length - displayText.Length : 0); - var details = getLinkDetails(linkText); + var details = GetLinkDetails(linkText); result.Links.Add(new Link(linkText, index, displayText.Length, linkActionOverride ?? details.Action, details.Argument)); //adjust the offset for processing the current matches group. @@ -95,7 +95,7 @@ namespace osu.Game.Online.Chat var link = m.Groups["link"].Value; var indexLength = link.Length; - var details = getLinkDetails(link); + var details = GetLinkDetails(link); result.Links.Add(new Link(link, index, indexLength, details.Action, details.Argument)); } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index fa68142fea..c478ad0a57 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -208,7 +208,7 @@ namespace osu.Game Text = @"This link type is not yet supported!", Icon = FontAwesome.Solid.LifeRing, }); - LinkDetails linkDetails = getLinkDetails(url); + LinkDetails linkDetails = GetLinkDetails(url); Schedule(() => LinkUtils.HandleLink(url, linkDetails.Action, linkDetails.Argument, this, channelManager, showNotImplementedError)); } else From c8dd29067b2f44354a624bf67567b6f382bd1bd5 Mon Sep 17 00:00:00 2001 From: Roman Kapustin Date: Sun, 4 Aug 2019 00:34:51 +0300 Subject: [PATCH 0498/2815] Update Entity Framework Core --- osu.Desktop/osu.Desktop.csproj | 4 ++-- osu.Game/osu.Game.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 8c9e1f279f..538aaf2d7a 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -28,8 +28,8 @@ - - + + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 1f91ce1cd8..cf325b77be 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -11,8 +11,8 @@ - - + + From 115cf47ed5e8a697c25b24395094f076b0a47955 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sat, 3 Aug 2019 15:20:44 -0700 Subject: [PATCH 0499/2815] Fix settings sidebar showing scrollbar at max ui scale --- osu.Game/Overlays/Settings/Sidebar.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Settings/Sidebar.cs b/osu.Game/Overlays/Settings/Sidebar.cs index a80b7c1108..358f94b659 100644 --- a/osu.Game/Overlays/Settings/Sidebar.cs +++ b/osu.Game/Overlays/Settings/Sidebar.cs @@ -84,6 +84,7 @@ namespace osu.Game.Overlays.Settings Content.Anchor = Anchor.CentreLeft; Content.Origin = Anchor.CentreLeft; RelativeSizeAxes = Axes.Both; + ScrollbarVisible = false; } } From f6f96658c7b7a51f327702d9a612806af0484da2 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sat, 3 Aug 2019 15:22:06 -0700 Subject: [PATCH 0500/2815] Fix settings sidebar being behind toolbar --- osu.Game/Overlays/SettingsPanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index 474f529bb1..9dd0def453 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -186,7 +186,7 @@ namespace osu.Game.Overlays base.UpdateAfterChildren(); ContentContainer.Margin = new MarginPadding { Left = Sidebar?.DrawWidth ?? 0 }; - ContentContainer.Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; + Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; } protected class SettingsSectionsContainer : SectionsContainer From c8acbdb1d9600db601c9967f0d2e18b15584be80 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 4 Aug 2019 06:15:15 +0300 Subject: [PATCH 0501/2815] Update the colour --- osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs index c5e61f68f4..fa60a37ddb 100644 --- a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs +++ b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs @@ -80,7 +80,6 @@ namespace osu.Game.Overlays.Profile.Header.Components private void load(OsuColour colours) { background.Colour = colours.Pink; - iconContainer.Colour = colours.GreySeafoam; } } } From a30d7912b1b34cb0d06b013683143994549b7f57 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 4 Aug 2019 11:09:12 +0300 Subject: [PATCH 0502/2815] Move DimmedLoadingAnimation to it's own file --- .../UserInterface/DimmedLoadingAnimation.cs | 44 +++++++++++++++++++ osu.Game/Screens/Select/BeatmapDetails.cs | 35 +-------------- 2 files changed, 45 insertions(+), 34 deletions(-) create mode 100644 osu.Game/Graphics/UserInterface/DimmedLoadingAnimation.cs diff --git a/osu.Game/Graphics/UserInterface/DimmedLoadingAnimation.cs b/osu.Game/Graphics/UserInterface/DimmedLoadingAnimation.cs new file mode 100644 index 0000000000..30c8c5f143 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/DimmedLoadingAnimation.cs @@ -0,0 +1,44 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osuTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Extensions.Color4Extensions; + +namespace osu.Game.Graphics.UserInterface +{ + public class DimmedLoadingAnimation : VisibilityContainer + { + private const float transition_duration = 250; + + private readonly LoadingAnimation loading; + + public DimmedLoadingAnimation() + { + RelativeSizeAxes = Axes.Both; + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.5f), + }, + loading = new LoadingAnimation(), + }; + } + + protected override void PopIn() + { + this.FadeIn(transition_duration, Easing.OutQuint); + loading.Show(); + } + + protected override void PopOut() + { + this.FadeOut(transition_duration, Easing.OutQuint); + loading.Hide(); + } + } +} diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 23dd87d8ea..9d2e6f1719 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -156,10 +156,7 @@ namespace osu.Game.Screens.Select }, }, }, - loading = new DimmedLoadingAnimation - { - RelativeSizeAxes = Axes.Both, - }, + loading = new DimmedLoadingAnimation(), }; } @@ -365,35 +362,5 @@ namespace osu.Game.Screens.Select }); } } - - private class DimmedLoadingAnimation : VisibilityContainer - { - private readonly LoadingAnimation loading; - - public DimmedLoadingAnimation() - { - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.5f), - }, - loading = new LoadingAnimation(), - }; - } - - protected override void PopIn() - { - this.FadeIn(transition_duration, Easing.OutQuint); - loading.Show(); - } - - protected override void PopOut() - { - this.FadeOut(transition_duration, Easing.OutQuint); - loading.Hide(); - } - } } } From fd44ca3233a90207122b0ae16d9606bf09fa9755 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 4 Aug 2019 13:54:23 +0300 Subject: [PATCH 0503/2815] Rename Animation to Layer --- .../{DimmedLoadingAnimation.cs => DimmedLoadingLayer.cs} | 4 ++-- osu.Game/Screens/Select/BeatmapDetails.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Graphics/UserInterface/{DimmedLoadingAnimation.cs => DimmedLoadingLayer.cs} (91%) diff --git a/osu.Game/Graphics/UserInterface/DimmedLoadingAnimation.cs b/osu.Game/Graphics/UserInterface/DimmedLoadingLayer.cs similarity index 91% rename from osu.Game/Graphics/UserInterface/DimmedLoadingAnimation.cs rename to osu.Game/Graphics/UserInterface/DimmedLoadingLayer.cs index 30c8c5f143..b7d2222f33 100644 --- a/osu.Game/Graphics/UserInterface/DimmedLoadingAnimation.cs +++ b/osu.Game/Graphics/UserInterface/DimmedLoadingLayer.cs @@ -9,13 +9,13 @@ using osu.Framework.Extensions.Color4Extensions; namespace osu.Game.Graphics.UserInterface { - public class DimmedLoadingAnimation : VisibilityContainer + public class DimmedLoadingLayer : VisibilityContainer { private const float transition_duration = 250; private readonly LoadingAnimation loading; - public DimmedLoadingAnimation() + public DimmedLoadingLayer() { RelativeSizeAxes = Axes.Both; Children = new Drawable[] diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 9d2e6f1719..577d999388 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -35,7 +35,7 @@ namespace osu.Game.Screens.Select private readonly MetadataSection description, source, tags; private readonly Container failRetryContainer; private readonly FailRetryGraph failRetryGraph; - private readonly DimmedLoadingAnimation loading; + private readonly DimmedLoadingLayer loading; private IAPIProvider api; @@ -156,7 +156,7 @@ namespace osu.Game.Screens.Select }, }, }, - loading = new DimmedLoadingAnimation(), + loading = new DimmedLoadingLayer(), }; } From 3ae5428dad6e81dc0f7d5e3043247063774ee2d2 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 4 Aug 2019 14:15:16 +0300 Subject: [PATCH 0504/2815] ProfileRulesetSelector improvements --- .../Online/TestSceneProfileRulesetSelector.cs | 8 +++++- .../Components/ProfileRulesetSelector.cs | 28 ++++++++++--------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneProfileRulesetSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneProfileRulesetSelector.cs index c344cb9598..d439e6a7c3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneProfileRulesetSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneProfileRulesetSelector.cs @@ -9,6 +9,7 @@ using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Taiko; +using osu.Game.Users; namespace osu.Game.Tests.Visual.Online { @@ -34,7 +35,12 @@ namespace osu.Game.Tests.Visual.Online AddStep("set mania as default", () => selector.SetDefaultRuleset(new ManiaRuleset().RulesetInfo)); AddStep("set taiko as default", () => selector.SetDefaultRuleset(new TaikoRuleset().RulesetInfo)); AddStep("set catch as default", () => selector.SetDefaultRuleset(new CatchRuleset().RulesetInfo)); - AddStep("select default ruleset", selector.SelectDefaultRuleset); + + AddStep("User with osu as default", () => selector.User.Value = new User { PlayMode = "osu" }); + AddStep("User with mania as default", () => selector.User.Value = new User { PlayMode = "mania" }); + AddStep("User with taiko as default", () => selector.User.Value = new User { PlayMode = "taiko" }); + AddStep("User with catch as default", () => selector.User.Value = new User { PlayMode = "fruits" }); + AddStep("null user", () => selector.User.Value = null); } } } diff --git a/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetSelector.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetSelector.cs index b6112a6501..09bc2f2cdc 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetSelector.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetSelector.cs @@ -2,11 +2,13 @@ // 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.UserInterface; using osu.Game.Graphics; using osu.Game.Rulesets; +using osu.Game.Users; using osuTK; using osuTK.Graphics; @@ -16,6 +18,8 @@ namespace osu.Game.Overlays.Profile.Header.Components { private Color4 accentColour = Color4.White; + public readonly Bindable User = new Bindable(); + public ProfileRulesetSelector() { TabContainer.Masking = false; @@ -32,24 +36,22 @@ namespace osu.Game.Overlays.Profile.Header.Components ((ProfileRulesetTabItem)tabItem).AccentColour = accentColour; } - public void SetDefaultRuleset(RulesetInfo ruleset) + protected override void LoadComplete() { - // Todo: This method shouldn't exist, but bindables don't provide the concept of observing a change to the default value - foreach (TabItem tabItem in TabContainer) - ((ProfileRulesetTabItem)tabItem).IsDefault = ((ProfileRulesetTabItem)tabItem).Value.ID == ruleset.ID; + base.LoadComplete(); + + User.BindValueChanged(onUserChanged, true); } - public void SelectDefaultRuleset() + private void onUserChanged(ValueChangedEvent user) + { + SetDefaultRuleset(Rulesets.GetRuleset(user.NewValue?.PlayMode ?? "osu")); + } + + public void SetDefaultRuleset(RulesetInfo ruleset) { - // Todo: This method shouldn't exist, but bindables don't provide the concept of observing a change to the default value foreach (TabItem tabItem in TabContainer) - { - if (((ProfileRulesetTabItem)tabItem).IsDefault) - { - Current.Value = ((ProfileRulesetTabItem)tabItem).Value; - return; - } - } + ((ProfileRulesetTabItem)tabItem).IsDefault = ((ProfileRulesetTabItem)tabItem).Value.ID == ruleset.ID; } protected override TabItem CreateTabItem(RulesetInfo value) => new ProfileRulesetTabItem(value) From d693a54c84eb088a31fc9ebefcc9d7d811db60d1 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 4 Aug 2019 14:35:26 +0300 Subject: [PATCH 0505/2815] Move RankHistoryData to User Statistics --- .../Visual/Online/TestSceneRankGraph.cs | 47 +++++++------------ .../Online/TestSceneUserProfileOverlay.cs | 12 ++--- .../Profile/Header/Components/RankGraph.cs | 19 +++++--- .../Profile/Header/DetailHeaderContainer.cs | 2 +- osu.Game/Users/User.cs | 5 +- osu.Game/Users/UserStatistics.cs | 3 ++ 6 files changed, 42 insertions(+), 46 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs b/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs index 709e75ab13..c70cc4ae4e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs @@ -69,28 +69,22 @@ namespace osu.Game.Tests.Visual.Online } }); - AddStep("null user", () => graph.User.Value = null); + AddStep("null user", () => graph.Statistics.Value = null); AddStep("rank only", () => { - graph.User.Value = new User + graph.Statistics.Value = new UserStatistics { - Statistics = new UserStatistics - { - Ranks = new UserStatistics.UserRanks { Global = 123456 }, - PP = 12345, - } + Ranks = new UserStatistics.UserRanks { Global = 123456 }, + PP = 12345, }; }); AddStep("with rank history", () => { - graph.User.Value = new User + graph.Statistics.Value = new UserStatistics { - Statistics = new UserStatistics - { - Ranks = new UserStatistics.UserRanks { Global = 89000 }, - PP = 12345, - }, + Ranks = new UserStatistics.UserRanks { Global = 89000 }, + PP = 12345, RankHistory = new User.RankHistoryData { Data = data, @@ -100,13 +94,10 @@ namespace osu.Game.Tests.Visual.Online AddStep("with zero values", () => { - graph.User.Value = new User + graph.Statistics.Value = new UserStatistics { - Statistics = new UserStatistics - { - Ranks = new UserStatistics.UserRanks { Global = 89000 }, - PP = 12345, - }, + Ranks = new UserStatistics.UserRanks { Global = 89000 }, + PP = 12345, RankHistory = new User.RankHistoryData { Data = dataWithZeros, @@ -116,13 +107,10 @@ namespace osu.Game.Tests.Visual.Online AddStep("small amount of data", () => { - graph.User.Value = new User + graph.Statistics.Value = new UserStatistics { - Statistics = new UserStatistics - { - Ranks = new UserStatistics.UserRanks { Global = 12000 }, - PP = 12345, - }, + Ranks = new UserStatistics.UserRanks { Global = 12000 }, + PP = 12345, RankHistory = new User.RankHistoryData { Data = smallData, @@ -132,13 +120,10 @@ namespace osu.Game.Tests.Visual.Online AddStep("graph with edges", () => { - graph.User.Value = new User + graph.Statistics.Value = new UserStatistics { - Statistics = new UserStatistics - { - Ranks = new UserStatistics.UserRanks { Global = 12000 }, - PP = 12345, - }, + Ranks = new UserStatistics.UserRanks { Global = 12000 }, + PP = 12345, RankHistory = new User.RankHistoryData { Data = edgyData, diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index c2376aa153..84c99d8c3a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -50,12 +50,12 @@ namespace osu.Game.Tests.Visual.Online { Current = 727, Progress = 69, - } - }, - RankHistory = new User.RankHistoryData - { - Mode = @"osu", - Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray() + }, + RankHistory = new User.RankHistoryData + { + Mode = @"osu", + Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray() + }, }, Badges = new[] { diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index 5f79386b76..4818cd8df6 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private KeyValuePair[] ranks; private int dayIndex; - public Bindable User = new Bindable(); + public readonly Bindable Statistics = new Bindable(); public RankGraph() { @@ -56,8 +56,6 @@ namespace osu.Game.Overlays.Profile.Header.Components }; graph.OnBallMove += i => dayIndex = i; - - User.ValueChanged += userChanged; } [BackgroundDependencyLoader] @@ -66,18 +64,25 @@ namespace osu.Game.Overlays.Profile.Header.Components graph.LineColour = colours.Yellow; } - private void userChanged(ValueChangedEvent e) + protected override void LoadComplete() + { + base.LoadComplete(); + + Statistics.BindValueChanged(statistics => updateStatistics(statistics.NewValue)); + } + + private void updateStatistics(UserStatistics statistics) { placeholder.FadeIn(fade_duration, Easing.Out); - if (e.NewValue?.Statistics?.Ranks.Global == null) + if (statistics?.Ranks.Global == null) { graph.FadeOut(fade_duration, Easing.Out); ranks = null; return; } - int[] userRanks = e.NewValue.RankHistory?.Data ?? new[] { e.NewValue.Statistics.Ranks.Global.Value }; + int[] userRanks = statistics.RankHistory?.Data ?? new[] { statistics.Ranks.Global.Value }; ranks = userRanks.Select((x, index) => new KeyValuePair(index, x)).Where(x => x.Value != 0).ToArray(); if (ranks.Length > 1) @@ -191,7 +196,7 @@ namespace osu.Game.Overlays.Profile.Header.Components } } - public string TooltipText => User.Value?.Statistics?.Ranks.Global == null ? "" : $"#{ranks[dayIndex].Value:#,##0}|{ranked_days - ranks[dayIndex].Key + 1}"; + public string TooltipText => Statistics.Value?.Ranks.Global == null ? "" : $"#{ranks[dayIndex].Value:#,##0}|{ranked_days - ranks[dayIndex].Key + 1}"; public ITooltip GetCustomTooltip() => new RankGraphTooltip(); diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs index 0db1cb32d7..6ee0d9ee8f 100644 --- a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs @@ -179,7 +179,7 @@ namespace osu.Game.Overlays.Profile.Header detailGlobalRank.Content = user?.Statistics?.Ranks.Global?.ToString("\\##,##0") ?? "-"; detailCountryRank.Content = user?.Statistics?.Ranks.Country?.ToString("\\##,##0") ?? "-"; - rankGraph.User.Value = user; + rankGraph.Statistics.Value = user?.Statistics; } private class ScoreRankInfo : CompositeDrawable diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 34bd4bbaae..b738eff4a6 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -159,7 +159,10 @@ namespace osu.Game.Users } [JsonProperty(@"rankHistory")] - public RankHistoryData RankHistory; + private RankHistoryData rankHistory + { + set => Statistics.RankHistory = value; + } [JsonProperty("badges")] public Badge[] Badges; diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index 7afbef01c5..032ec2e05f 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -4,6 +4,7 @@ using System; using Newtonsoft.Json; using osu.Game.Scoring; +using static osu.Game.Users.User; namespace osu.Game.Users { @@ -113,5 +114,7 @@ namespace osu.Game.Users [JsonProperty(@"country")] public int? Country; } + + public RankHistoryData RankHistory; } } From cd7b6d2d27de45747cf439a4676e0b3c0ba5cc43 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 4 Aug 2019 15:00:02 +0300 Subject: [PATCH 0506/2815] TestCase improvement --- .../Online/TestSceneProfileRulesetSelector.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneProfileRulesetSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneProfileRulesetSelector.cs index d439e6a7c3..1f5ba67e03 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneProfileRulesetSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneProfileRulesetSelector.cs @@ -10,6 +10,7 @@ using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Taiko; using osu.Game.Users; +using osu.Framework.Bindables; namespace osu.Game.Tests.Visual.Online { @@ -24,11 +25,13 @@ namespace osu.Game.Tests.Visual.Online public TestSceneProfileRulesetSelector() { ProfileRulesetSelector selector; + Bindable user = new Bindable(); Child = selector = new ProfileRulesetSelector { Anchor = Anchor.Centre, Origin = Anchor.Centre, + User = { BindTarget = user } }; AddStep("set osu! as default", () => selector.SetDefaultRuleset(new OsuRuleset().RulesetInfo)); @@ -36,11 +39,11 @@ namespace osu.Game.Tests.Visual.Online AddStep("set taiko as default", () => selector.SetDefaultRuleset(new TaikoRuleset().RulesetInfo)); AddStep("set catch as default", () => selector.SetDefaultRuleset(new CatchRuleset().RulesetInfo)); - AddStep("User with osu as default", () => selector.User.Value = new User { PlayMode = "osu" }); - AddStep("User with mania as default", () => selector.User.Value = new User { PlayMode = "mania" }); - AddStep("User with taiko as default", () => selector.User.Value = new User { PlayMode = "taiko" }); - AddStep("User with catch as default", () => selector.User.Value = new User { PlayMode = "fruits" }); - AddStep("null user", () => selector.User.Value = null); + AddStep("User with osu as default", () => user.Value = new User { PlayMode = "osu" }); + AddStep("User with mania as default", () => user.Value = new User { PlayMode = "mania" }); + AddStep("User with taiko as default", () => user.Value = new User { PlayMode = "taiko" }); + AddStep("User with catch as default", () => user.Value = new User { PlayMode = "fruits" }); + AddStep("null user", () => user.Value = null); } } } From 2f5d23b35419f78763924593e5dc03876b0baeb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Mon, 5 Aug 2019 01:02:42 +0200 Subject: [PATCH 0507/2815] add join command --- osu.Game/Online/Chat/ChannelManager.cs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 3af11ff20f..30135d5b8b 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -213,8 +213,27 @@ namespace osu.Game.Online.Chat PostMessage(content, true); break; + case "j": + case "join": + if (string.IsNullOrWhiteSpace(content)) + { + target.AddNewMessages(new ErrorMessage("Usage: /join [channel]")); + break; + } + + var channel = availableChannels.Where(c => c.Name == content || c.Name == $"#{content}").FirstOrDefault(); + if (channel == null) + { + target.AddNewMessages(new ErrorMessage($"Channel '{content}' not found.")); + break; + } + + JoinChannel(channel); + CurrentChannel.Value = channel; + break; + case "help": - target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action]")); + target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action], /join [channel]")); break; default: From d83971713118afe7dff0ea2f31d47e540b9d3f27 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Aug 2019 13:18:29 +0900 Subject: [PATCH 0508/2815] Remove unnecessary intermediate method --- .../Profile/Header/Components/ProfileRulesetSelector.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetSelector.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetSelector.cs index 09bc2f2cdc..2c9a3dd5f9 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetSelector.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetSelector.cs @@ -40,12 +40,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { base.LoadComplete(); - User.BindValueChanged(onUserChanged, true); - } - - private void onUserChanged(ValueChangedEvent user) - { - SetDefaultRuleset(Rulesets.GetRuleset(user.NewValue?.PlayMode ?? "osu")); + User.BindValueChanged(u => SetDefaultRuleset(Rulesets.GetRuleset(u.NewValue?.PlayMode ?? "osu")), true); } public void SetDefaultRuleset(RulesetInfo ruleset) From 379c9e8b7caf3f8382418d818741632ce5dd0303 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Aug 2019 10:02:28 +0200 Subject: [PATCH 0509/2815] Use expression body --- osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs | 5 +---- osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs | 5 +---- osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs | 5 +---- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index 3a695ca179..b9699a88e4 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -22,10 +22,7 @@ namespace osu.Game.Rulesets.Catch.Tests [TestCase("spinner")] [TestCase("spinner-and-circles")] [TestCase("slider")] - public void Test(string name) - { - base.Test(name); - } + public void Test(string name) => base.Test(name); protected override IEnumerable CreateConvertValue(HitObject hitObject) { diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs index 3ca9dcc42c..6f10540973 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs @@ -20,10 +20,7 @@ namespace osu.Game.Rulesets.Mania.Tests protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; [TestCase("basic")] - public void Test(string name) - { - base.Test(name); - } + public void Test(string name) => base.Test(name); protected override IEnumerable CreateConvertValue(HitObject hitObject) { diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs index bafa814582..6c1882b4e2 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs @@ -20,10 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Tests [NonParallelizable] [TestCase("basic")] [TestCase("slider-generating-drumroll")] - public void Test(string name) - { - base.Test(name); - } + public void Test(string name) => base.Test(name); protected override IEnumerable CreateConvertValue(HitObject hitObject) { From ee9e8f6261d92ca24f364b7147c54dca4e542d39 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Aug 2019 17:58:16 +0900 Subject: [PATCH 0510/2815] Fix memory leaks from download buttons --- osu.Game/Online/DownloadTrackingComposite.cs | 30 +++++++++++--------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs index 62d6efcb6f..7bfdc7ff69 100644 --- a/osu.Game/Online/DownloadTrackingComposite.cs +++ b/osu.Game/Online/DownloadTrackingComposite.cs @@ -48,22 +48,24 @@ namespace osu.Game.Online attachDownload(manager.GetExistingDownload(modelInfo.NewValue)); }, true); - manager.DownloadBegan += download => - { - if (download.Model.Equals(Model.Value)) - attachDownload(download); - }; - - manager.DownloadFailed += download => - { - if (download.Model.Equals(Model.Value)) - attachDownload(null); - }; - + manager.DownloadBegan += downloadBegan; + manager.DownloadFailed += downloadFailed; manager.ItemAdded += itemAdded; manager.ItemRemoved += itemRemoved; } + private void downloadBegan(ArchiveDownloadRequest request) + { + if (request.Model.Equals(Model.Value)) + attachDownload(request); + } + + private void downloadFailed(ArchiveDownloadRequest request) + { + if (request.Model.Equals(Model.Value)) + attachDownload(null); + } + private ArchiveDownloadRequest attachedRequest; private void attachDownload(ArchiveDownloadRequest request) @@ -126,8 +128,10 @@ namespace osu.Game.Online if (manager != null) { - manager.DownloadBegan -= attachDownload; + manager.DownloadBegan -= downloadBegan; + manager.DownloadFailed -= downloadFailed; manager.ItemAdded -= itemAdded; + manager.ItemRemoved -= itemRemoved; } State.UnbindAll(); From 749a00cc2f324490096fadd5bc3eeee12f13ce81 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 5 Aug 2019 12:49:54 +0300 Subject: [PATCH 0511/2815] Force bindable change --- osu.Game/Overlays/Profile/Header/Components/RankGraph.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index 4818cd8df6..9cb9d48de7 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -68,7 +68,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { base.LoadComplete(); - Statistics.BindValueChanged(statistics => updateStatistics(statistics.NewValue)); + Statistics.BindValueChanged(statistics => updateStatistics(statistics.NewValue), true); } private void updateStatistics(UserStatistics statistics) From 11916782ba9dd82b51ede9cf57e4e36252f05e34 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Aug 2019 19:23:13 +0900 Subject: [PATCH 0512/2815] Fix drawable channels remaining in memory after being closed --- osu.Game/Overlays/ChatOverlay.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index e223856b27..53a05656b1 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -256,6 +256,9 @@ namespace osu.Game.Overlays loadedChannels.Add(loaded); LoadComponentAsync(loaded, l => { + if (currentChannel.Value != e.NewValue) + return; + loading.Hide(); currentChannelContainer.Clear(false); @@ -381,7 +384,18 @@ namespace osu.Game.Overlays foreach (Channel channel in channels) { ChannelTabControl.RemoveChannel(channel); - loadedChannels.Remove(loadedChannels.Find(c => c.Channel == channel)); + + var loaded = loadedChannels.Find(c => c.Channel == channel); + + if (loaded != null) + { + loadedChannels.Remove(loaded); + + // Because the container is only cleared in the async load callback of a new channel, it is forcefully cleared + // to ensure that the previous channel doesn't get updated after it's disposed + currentChannelContainer.Remove(loaded); + loaded.Dispose(); + } } } From 0fe6585975a7efdf6b00a73f7be41593c7ea6e20 Mon Sep 17 00:00:00 2001 From: DTSDAO Date: Mon, 5 Aug 2019 22:30:35 +0800 Subject: [PATCH 0513/2815] Fix iOS importing --- osu.Game/Database/ArchiveModelManager.cs | 2 +- osu.Game/OsuGame.cs | 24 ++++++++---------------- osu.iOS/AppDelegate.cs | 6 +++++- osu.iOS/Info.plist | 2 ++ osu.iOS/OsuGameIOS.cs | 2 ++ 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index efb76deff8..37ea7b2ca9 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -54,7 +54,7 @@ namespace osu.Game.Database public virtual string[] HandledExtensions => new[] { ".zip" }; - public virtual bool SupportsImportFromStable => RuntimeInfo.IsDesktop; + public virtual bool SupportsImportFromStable => (RuntimeInfo.IsDesktop || RuntimeInfo.OS == RuntimeInfo.Platform.iOS); protected readonly FileStore Files; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b64f04de2a..846677a0a9 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -91,8 +91,6 @@ namespace osu.Game private IntroScreen introScreen; - private bool loaded; - private Bindable configRuleset; private Bindable configSkin; @@ -201,18 +199,15 @@ namespace osu.Game { Logger.Log($"Request to handle url: {url}"); - if (loaded) + Action showNotImplementedError = () => notifications?.Post(new SimpleNotification { - Action showNotImplementedError = () => notifications?.Post(new SimpleNotification - { - Text = @"This link type is not yet supported!", - Icon = FontAwesome.Solid.LifeRing, - }); - LinkDetails linkDetails = GetLinkDetails(url); - Schedule(() => LinkUtils.HandleLink(url, linkDetails.Action, linkDetails.Argument, this, channelManager, showNotImplementedError)); - } - else - Scheduler.AddDelayed(() => HandleUrl(url), 1000); + Text = @"This link type is not yet supported!", + Icon = FontAwesome.Solid.LifeRing, + }); + + LinkDetails linkDetails = GetLinkDetails(url); + + Schedule(() => LinkUtils.HandleLink(url, linkDetails.Action, linkDetails.Argument, this, channelManager, showNotImplementedError)); } public void OpenUrlExternally(string url) @@ -403,8 +398,6 @@ namespace osu.Game protected override void LoadComplete() { - loaded = false; - base.LoadComplete(); // The next time this is updated is in UpdateAfterChildren, which occurs too late and results @@ -600,7 +593,6 @@ namespace osu.Game settings.State.ValueChanged += _ => updateScreenOffset(); notifications.State.ValueChanged += _ => updateScreenOffset(); - loaded = true; } public class GameIdleTracker : IdleTracker diff --git a/osu.iOS/AppDelegate.cs b/osu.iOS/AppDelegate.cs index e727305997..07e0245195 100644 --- a/osu.iOS/AppDelegate.cs +++ b/osu.iOS/AppDelegate.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.Threading.Tasks; using Foundation; using osu.Framework.iOS; using osu.Framework.Threading; @@ -17,7 +18,10 @@ namespace osu.iOS public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options) { - game.HandleUrl(url.AbsoluteString); + if (url.IsFileUrl) + Task.Run(() => game.Import(url.Path)); + else + Task.Run(() => game.HandleUrl(url.AbsoluteString)); return true; } } diff --git a/osu.iOS/Info.plist b/osu.iOS/Info.plist index f3067bdcec..5fe8cd6d5b 100644 --- a/osu.iOS/Info.plist +++ b/osu.iOS/Info.plist @@ -14,6 +14,8 @@ 0.1.0 LSRequiresIPhoneOS + LSSupportsOpeningDocumentsInPlace + MinimumOSVersion 10.0 UIDeviceFamily diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index 6cf18df9a6..ac66357fc9 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -3,12 +3,14 @@ using System; using Foundation; +using osu.Framework.Platform; using osu.Game; namespace osu.iOS { public class OsuGameIOS : OsuGame { + public override Storage GetStorageForStableInstall() => Storage; public override Version AssemblyVersion => new Version(NSBundle.MainBundle.InfoDictionary["CFBundleVersion"].ToString()); } } From a7ac411c2562e1f774b77111c10abc8ea6667fe8 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 5 Aug 2019 17:57:04 -0700 Subject: [PATCH 0514/2815] Fix footer button hover sounds playing in unclickable area --- osu.Game/Screens/Select/Footer.cs | 2 +- osu.Game/Screens/Select/FooterButton.cs | 12 ++++++------ osu.Game/Screens/Select/FooterButtonMods.cs | 1 + 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs index 0680711f1c..1dc7081c1c 100644 --- a/osu.Game/Screens/Select/Footer.cs +++ b/osu.Game/Screens/Select/Footer.cs @@ -94,7 +94,7 @@ namespace osu.Game.Screens.Select buttons = new FillFlowContainer { Direction = FillDirection.Horizontal, - Spacing = new Vector2(0.2f, 0), + Spacing = new Vector2(-FooterButton.SHEAR_WIDTH, 0), AutoSizeAxes = Axes.Both, } } diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index e18a086a10..90bc902ad8 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -17,7 +17,9 @@ namespace osu.Game.Screens.Select { public class FooterButton : OsuClickableContainer { - private static readonly Vector2 shearing = new Vector2(0.15f, 0); + public static readonly float SHEAR_WIDTH = 7.5f; + + protected static readonly Vector2 SHEARING = new Vector2(SHEAR_WIDTH / Footer.HEIGHT, 0); public string Text { @@ -59,16 +61,16 @@ namespace osu.Game.Screens.Select private readonly Box box; private readonly Box light; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos); - public FooterButton() { AutoSizeAxes = Axes.Both; + Shear = SHEARING; Children = new Drawable[] { TextContainer = new Container { - Size = new Vector2(100, 50), + Size = new Vector2(100 - SHEAR_WIDTH, 50), + Shear = -SHEARING, Child = SpriteText = new OsuSpriteText { Anchor = Anchor.Centre, @@ -78,14 +80,12 @@ namespace osu.Game.Screens.Select box = new Box { RelativeSizeAxes = Axes.Both, - Shear = shearing, EdgeSmoothness = new Vector2(2, 0), Colour = Color4.White, Alpha = 0, }, light = new Box { - Shear = shearing, Height = 4, EdgeSmoothness = new Vector2(2, 0), RelativeSizeAxes = Axes.X, diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index fce4d1b2e2..58d92dbca6 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -32,6 +32,7 @@ namespace osu.Game.Screens.Select { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, + Shear = -SHEARING, Child = modDisplay = new FooterModDisplay { DisplayUnrankedText = false, From cd6fe918821aec9d150311bcc6ac0ec653b672dc Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 5 Aug 2019 19:56:35 +0900 Subject: [PATCH 0515/2815] Log error for invalid events --- .../Formats/LegacyBeatmapDecoderTest.cs | 17 + osu.Game.Tests/Resources/invalid-events.osu | 1004 +++++++++++++++++ .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 6 +- .../Formats/LegacyStoryboardDecoder.cs | 3 +- 4 files changed, 1028 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tests/Resources/invalid-events.osu diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index d087251e7e..98464b8d91 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -482,5 +482,22 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(hitObjects[0].Samples[0].Bank, hitObjects[0].Samples[1].Bank); } } + + [Test] + public void TestDecodeInvalidEvents() + { + using (var normalResStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) + using (var normalStream = new StreamReader(normalResStream)) + using (var resStream = TestResources.OpenResource("invalid-events.osu")) + using (var stream = new StreamReader(resStream)) + { + var decoder = Decoder.GetDecoder(stream); + var goodBeatmap = decoder.Decode(normalStream); + Beatmap badBeatmap = null; + + Assert.DoesNotThrow(() => badBeatmap = decoder.Decode(stream)); + Assert.AreEqual(goodBeatmap.HitObjects.Count, badBeatmap.HitObjects.Count); + } + } } } diff --git a/osu.Game.Tests/Resources/invalid-events.osu b/osu.Game.Tests/Resources/invalid-events.osu new file mode 100644 index 0000000000..a8e767585c --- /dev/null +++ b/osu.Game.Tests/Resources/invalid-events.osu @@ -0,0 +1,1004 @@ +osu file format v14 + +[General] +AudioFilename: 03. Renatus - Soleily 192kbps.mp3 +AudioLeadIn: 0 +PreviewTime: 164471 +Countdown: 0 +SampleSet: Soft +StackLeniency: 0.7 +Mode: 0 +LetterboxInBreaks: 0 +WidescreenStoryboard: 0 + +[Editor] +Bookmarks: 11505,22054,32604,43153,53703,64252,74802,85351,95901,106450,116999,119637,130186,140735,151285,161834,164471,175020,185570,196119,206669,209306 +DistanceSpacing: 1.8 +BeatDivisor: 4 +GridSize: 4 +TimelineZoom: 2 + +[Metadata] +Title:Renatus +TitleUnicode:Renatus +Artist:Soleily +ArtistUnicode:Soleily +Creator:Gamu +Version:Insane +Source: +Tags:MBC7 Unisphere 地球ヤバイEP Chikyu Yabai +BeatmapID:557821 +BeatmapSetID:241526 + +[Difficulty] +HPDrainRate:6.5 +CircleSize:4 +OverallDifficulty:8 +ApproachRate:9 +SliderMultiplier:1.8 +SliderTickRate:2 + +[Events] +bad,event,this,should,fail +//Background and Video events +0,0,"machinetop_background.jpg",0,0 +//Break Periods +2,122474,140135 +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Sound Samples + +[TimingPoints] +956,329.67032967033,4,2,0,60,1,0 +20736,-100,4,2,0,65,0,0 +22054,-100,4,2,0,70,0,0 +43153,-100,4,2,0,60,0,0 +48428,-100,4,2,0,50,0,0 +52879,-100,4,2,0,50,0,0 +53373,-100,4,2,0,60,0,0 +53703,-100,4,2,0,70,0,1 +74719,-100,4,2,0,70,0,0 +74802,-100,4,2,0,70,0,1 +95901,-100,4,2,0,70,0,0 +116999,-133.333333333333,4,2,0,50,0,0 +117164,-133.333333333333,4,2,0,30,0,0 +117329,-79.9999999999999,4,2,0,50,0,0 +117659,-100,4,2,0,50,0,0 +118977,-100,4,2,0,60,0,0 +119307,-100,4,2,0,70,0,0 +119637,659.340659340659,4,2,0,80,1,0 +119966,-100,4,2,0,70,0,0 +120296,-100,4,2,0,60,0,0 +120626,-100,4,2,0,50,0,0 +120955,-100,4,2,0,40,0,0 +121285,-100,4,2,0,30,0,0 +121615,-100,4,2,0,20,0,0 +121944,-100,4,2,0,10,0,0 +122274,-100,4,2,0,5,0,0 +140735,-100,4,2,0,50,0,0 +151285,-80,4,2,0,60,0,0 +161834,329.67032967033,4,2,0,65,1,0 +164141,-100,4,2,0,70,0,0 +164471,-100,4,2,0,70,0,1 +185487,-100,4,2,0,70,0,0 +185570,-100,4,2,0,70,0,1 +206669,659.340659340659,4,2,0,80,1,0 +206998,-100,4,2,0,70,0,0 +207328,-100,4,2,0,60,0,0 +207658,-100,4,2,0,50,0,0 +207987,-100,4,2,0,40,0,0 +208317,-100,4,2,0,30,0,0 +208647,-100,4,2,0,20,0,0 +208976,-100,4,2,0,10,0,0 +209306,-100,4,2,0,5,0,0 + + +[Colours] +Combo1 : 142,199,255 +Combo2 : 255,128,128 +Combo3 : 128,255,255 +Combo4 : 128,255,128 +Combo5 : 255,187,255 +Combo6 : 255,177,140 +Combo7 : 100,100,100,100 + +[HitObjects] +192,168,956,6,0,P|184:128|200:80,1,90,4|0,1:2|0:0,0:0:0:0: +304,56,1285,1,8,0:0:0:0: +244,236,1450,2,0,P|204:252|156:244,1,90,2|0,0:0|0:0,0:0:0:0: +276,156,1780,2,0,P|310:181|329:226,1,90,2|8,1:2|0:0,0:0:0:0: +300,328,2109,1,2,0:0:0:0: +192,332,2274,6,0,L|144:340,2,45,0|0|0,1:0|0:0|0:0,0:0:0:0: +388,300,2604,1,8,0:0:0:0: +244,236,2769,1,0,1:0:0:0: +232,208,2851,1,0,0:0:0:0: +224,176,2934,1,0,0:0:0:0: +228,144,3016,1,0,0:0:0:0: +244,116,3098,1,0,1:0:0:0: +332,52,3263,2,0,P|376:48|424:56,1,90,8|0,0:0|0:0,0:0:0:0: +488,228,3593,5,0,1:0:0:0: +460,240,3675,1,0,0:0:0:0: +428,236,3758,1,0,0:0:0:0: +292,160,3923,2,0,P|288:204|300:252,1,90,8|0,0:0|0:0,0:0:0:0: +316,276,4170,1,0,0:0:0:0: +344,292,4252,2,0,L|388:300,1,45,0|0,0:0|0:0,0:0:0:0: +288,356,4417,2,0,L|244:364,1,45,0|0,1:0|0:0,0:0:0:0: +168,328,4582,2,0,P|124:324|72:332,1,90,8|0,0:0|0:0,0:0:0:0: +24,188,4912,5,0,1:0:0:0: +56,192,4994,1,0,0:0:0:0: +88,196,5076,1,0,0:0:0:0: +148,108,5241,1,8,0:0:0:0: +188,240,5406,1,0,1:0:0:0: +188,240,5488,1,0,0:0:0:0: +188,240,5571,2,0,L|168:328,1,90,0|0,0:0|1:0,0:0:0:0: +260,216,5901,2,0,P|236:180|188:164,1,90,8|0,0:0|0:0,0:0:0:0: +248,296,6230,6,0,L|348:292,1,90,0|0,1:0|0:0,0:0:0:0: +504,232,6560,1,8,0:0:0:0: +400,204,6725,1,0,0:0:0:0: +392,176,6807,1,0,0:0:0:0: +384,144,6890,1,0,0:0:0:0: +376,116,6972,1,0,0:0:0:0: +368,88,7054,1,0,1:0:0:0: +188,48,7219,2,0,L|208:140,1,90,8|0,0:0|0:0,0:0:0:0: +248,296,7549,5,0,1:0:0:0: +207,135,7714,1,0,0:0:0:0: +156,232,7879,1,8,0:0:0:0: +316,191,8043,1,0,1:0:0:0: +316,191,8126,1,0,0:0:0:0: +316,191,8208,2,0,L|372:200,1,45,0|0,0:0|0:0,0:0:0:0: +492,200,8373,2,0,L|447:207,1,45,0|0,1:0|0:0,0:0:0:0: +408,136,8538,2,0,P|396:92|400:48,1,90,8|0,0:0|0:0,0:0:0:0: +260,32,8868,5,0,1:0:0:0: +252,64,8950,1,0,0:0:0:0: +236,92,9032,2,0,P|204:116|148:128,1,90,0|8,0:0|0:0,0:0:0:0: +28,188,9362,1,0,0:0:0:0: +60,196,9445,1,0,0:0:0:0: +88,212,9527,2,0,P|112:244|124:300,1,90,0|0,0:0|1:0,0:0:0:0: +112,128,9857,2,0,P|152:156|184:196,1,90,8|0,0:0|0:0,0:0:0:0: +216,288,10186,5,0,1:0:0:0: +216,288,10269,1,0,0:0:0:0: +216,288,10351,1,0,0:0:0:0: +268,192,10516,1,8,0:0:0:0: +356,128,10681,1,0,1:0:0:0: +388,120,10763,1,0,0:0:0:0: +420,128,10846,2,0,P|440:168|436:220,1,90,0|0,0:0|1:0,0:0:0:0: +332,328,11175,2,0,L|280:332,1,45,8|8,0:0|0:0,0:0:0:0: +216,288,11340,2,0,L|164:292,1,45,0|0,1:0|0:0,1:0:0:0: +100,248,11505,5,4,1:2:0:0: +148,116,11670,1,2,0:0:0:0: +268,192,11835,1,10,0:0:0:0: +136,328,11999,2,0,L|44:336,1,90,2|0,0:0|0:0,0:0:0:0: +216,288,12329,1,2,1:2:0:0: +148,116,12494,1,10,0:0:0:0: +100,248,12659,1,2,0:0:0:0: +268,192,12824,5,0,1:0:0:0: +268,192,12906,1,0,0:0:0:0: +268,192,12988,1,0,0:0:0:0: +340,272,13153,2,0,P|384:276|432:264,1,90,8|0,0:0|1:0,0:0:0:0: +452,244,13401,1,0,0:0:0:0: +468,216,13483,2,0,L|476:124,1,90,0|0,0:0|1:0,0:0:0:0: +368,32,13813,2,0,L|360:121,1,90,8|0,0:0|0:0,0:0:0:0: +340,272,14142,6,0,L|316:316,2,45,0|0|0,1:0|0:0|0:0,0:0:0:0: +452,244,14472,1,8,0:0:0:0: +268,192,14637,1,0,0:0:0:0: +236,188,14719,1,0,0:0:0:0: +204,192,14802,2,0,P|172:228|160:272,1,90,0|0,0:0|1:0,0:0:0:0: +128,140,15131,2,0,P|160:104|172:60,1,90,8|0,0:0|0:0,0:0:0:0: +64,52,15461,6,0,L|20:68,2,45,0|0|0,1:0|0:0|0:0,0:0:0:0: +171,64,15791,1,8,0:0:0:0: +264,8,15956,2,0,L|356:12,1,90,0|0,1:0|0:0,0:0:0:0: +452,56,16285,1,0,1:0:0:0: +296,140,16450,2,0,L|206:136,1,90,8|0,0:0|0:0,0:0:0:0: +108,184,16780,6,0,P|92:224|96:272,1,90,0|0,1:0|0:0,0:0:0:0: +200,244,17109,1,8,0:0:0:0: +108,108,17274,2,0,L|12:116,1,90,0|0,0:0|0:0,0:0:0:0: +200,244,17604,1,0,1:0:0:0: +296,140,17769,2,0,L|385:132,1,90,8|0,0:0|0:0,0:0:0:0: +480,184,18098,5,0,1:0:0:0: +488,216,18181,1,0,0:0:0:0: +496,248,18263,2,0,L|492:340,1,90,0|8,0:0|0:0,0:0:0:0: +404,224,18593,2,0,L|396:176,2,45,0|0|0,1:0|0:0|0:0,0:0:0:0: +304,264,18923,1,0,1:0:0:0: +200,244,19087,2,0,P|156:240|108:248,1,90,8|0,0:0|0:0,0:0:0:0: +296,140,19417,6,0,P|340:144|388:136,1,90,0|0,1:0|0:0,0:0:0:0: +440,44,19747,1,8,0:0:0:0: +404,224,19912,1,0,0:0:0:0: +404,224,19994,1,0,0:0:0:0: +404,224,20076,2,0,L|412:320,1,90,0|0,0:0|1:0,0:0:0:0: +200,244,20406,2,0,L|192:154,1,90,8|0,0:0|0:0,0:0:0:0: +184,44,20736,5,4,1:2:0:0: +152,40,20818,1,0,0:0:0:0: +120,48,20901,1,0,0:0:0:0: +96,68,20983,1,0,0:0:0:0: +76,92,21065,1,2,0:3:0:0: +64,120,21148,1,0,0:0:0:0: +60,152,21230,1,0,1:0:0:0: +64,184,21313,1,0,0:0:0:0: +76,212,21395,2,0,L|96:252,3,45,0|0|0|0,0:0|0:0|1:0|0:0,0:0:0:0: +144,316,21725,2,0,L|188:324,3,45,0|0|2|0,0:0|0:0|0:3|0:0,0:0:0:0: +268,340,22054,6,0,L|364:336,1,90,4|0,1:2|0:0,0:0:0:0: +452,280,22384,1,8,0:0:0:0: +512,188,22549,2,0,P|516:144|504:96,1,90,2|0,0:0|0:0,0:0:0:0: +340,24,22879,2,0,P|336:68|348:116,1,90,2|8,1:2|0:0,0:0:0:0: +420,192,23208,1,2,0:0:0:0: +328,252,23373,6,0,L|232:240,1,90,0|0,1:0|0:0,0:0:0:0: +64,256,23703,1,8,0:0:0:0: +144,184,23868,2,0,P|148:140|136:88,1,90,0|0,1:0|0:0,0:0:0:0: +40,52,24197,1,2,1:2:0:0: +139,95,24362,1,8,0:0:0:0: +216,20,24527,1,0,0:0:0:0: +315,63,24692,6,0,P|360:72|408:68,1,90,2|0,1:2|0:0,0:0:0:0: +492,132,25021,1,8,0:0:0:0: +412,204,25186,2,0,P|403:249|407:297,1,90,2|0,0:0|0:0,0:0:0:0: +268,328,25516,2,0,P|277:283|273:235,1,90,2|8,1:2|0:0,0:0:0:0: +232,140,25846,2,0,P|187:131|139:135,1,90,2|0,0:0|1:0,0:0:0:0: +64,208,26175,5,2,0:0:0:0: +44,316,26340,1,8,0:0:0:0: +148,280,26505,1,2,1:2:0:0: +456,208,26835,1,2,1:2:0:0: +476,316,26999,1,10,0:0:0:0: +372,280,27164,1,2,0:0:0:0: +356,172,27329,6,0,L|380:80,1,90,0|0,1:0|0:0,0:0:0:0: +456,208,27659,1,8,0:0:0:0: +300,236,27824,1,2,0:0:0:0: +300,236,27906,1,0,0:0:0:0: +300,236,27988,2,0,L|208:228,1,90,0|2,0:0|1:2,0:0:0:0: +140,312,28318,1,8,0:0:0:0: +372,280,28483,2,0,L|464:272,1,90,2|0,0:0|1:0,0:0:0:0: +500,136,28813,5,2,0:0:0:0: +432,56,28977,1,8,0:0:0:0: +328,24,29142,2,0,P|284:24|236:28,1,90,2|0,1:2|0:0,0:0:0:0: +80,144,29472,1,2,1:2:0:0: +116,44,29637,1,10,0:0:0:0: +184,128,29802,1,2,0:0:0:0: +20,88,29966,6,0,P|1:164|73:227,1,180,2|10,1:2|0:0,0:0:0:0: +184,128,30461,2,0,P|227:120|276:124,1,90,2|0,0:0|0:0,0:0:0:0: +392,188,30791,1,2,1:2:0:0: +272,260,30956,1,8,0:0:0:0: +396,328,31120,1,0,0:0:0:0: +256,348,31285,5,2,1:2:0:0: +224,344,31368,1,0,1:0:0:0: +192,340,31450,2,0,L|172:248,1,90,2|0,1:2|1:0,0:0:0:0: +8,136,31780,2,0,L|27:223,1,90,2|0,1:2|0:0,0:0:0:0: +56,328,32109,1,2,1:2:0:0: +108,192,32274,1,2,1:2:0:0: +100,160,32357,1,0,1:0:0:0: +92,132,32439,1,2,1:2:0:0: +84,104,32521,1,0,1:0:0:0: +76,72,32604,6,0,P|100:112|148:136,1,90,4|0,1:2|0:0,0:0:0:0: +240,168,32934,1,8,0:0:0:0: +336,124,33098,2,0,L|344:80,2,45,2|0|0,0:0|0:0|0:0,0:0:0:0: +264,248,33428,2,0,P|220:248|176:220,1,90,2|8,1:2|0:0,0:0:0:0: +260,84,33758,1,2,0:0:0:0: +344,212,33923,5,0,1:0:0:0: +344,212,34005,1,0,0:0:0:0: +344,212,34087,1,0,0:0:0:0: +440,160,34252,1,8,0:0:0:0: +312,320,34417,2,0,P|272:336|220:324,1,90,0|0,1:0|0:0,0:0:0:0: +156,176,34747,2,0,P|196:160|248:172,2,90,2|8|0,1:2|0:0|0:0,0:0:0:0: +132,280,35241,5,2,1:2:0:0: +132,280,35324,1,0,0:0:0:0: +132,280,35406,2,0,L|120:376,1,90,0|8,0:0|0:0,0:0:0:0: +312,320,35736,2,0,L|300:230,1,90,2|0,0:0|0:0,0:0:0:0: +316,124,36065,1,2,1:2:0:0: +400,192,36230,1,8,0:0:0:0: +300,230,36395,2,0,P|255:231|211:224,1,90,2|0,0:0|1:0,0:0:0:0: +24,132,36725,5,0,0:0:0:0: +132,152,36890,1,8,0:0:0:0: +60,232,37054,1,2,1:2:0:0: +60,232,37137,1,0,0:0:0:0: +60,232,37219,1,0,0:0:0:0: +92,56,37384,2,0,L|184:44,1,90,2|10,1:2|0:0,0:0:0:0: +316,124,37714,2,0,L|226:135,1,90,2|0,0:0|1:0,0:0:0:0: +60,232,38043,6,0,P|52:276|64:328,1,90,0|8,0:0|0:0,0:0:0:0: +220,152,38373,2,0,P|176:144|124:156,1,90,2|0,0:0|0:0,0:0:0:0: +176,252,38703,1,2,1:2:0:0: +323,213,38868,2,0,L|316:124,1,90,8|2,0:0|0:0,0:0:0:0: +332,320,39197,5,0,1:0:0:0: +424,260,39362,1,2,0:0:0:0: +260,272,39527,2,0,P|246:313|256:360,1,90,8|2,0:0|1:2,0:0:0:0: +408,336,39857,1,0,0:0:0:0: +176,252,40021,2,0,L|80:260,2,90,2|10|2,1:2|0:0|0:0,0:0:0:0: +324,212,40516,5,2,1:2:0:0: +324,212,40598,1,0,1:0:0:0: +324,212,40681,1,0,1:0:0:0: +200,336,40846,1,2,1:2:0:0: +236,188,41010,1,2,1:2:0:0: +236,188,41093,1,0,1:0:0:0: +236,188,41175,1,0,1:0:0:0: +281,357,41340,1,2,1:2:0:0: +176,252,41505,1,2,1:2:0:0: +176,252,41587,1,0,1:0:0:0: +176,252,41670,1,0,1:0:0:0: +344,297,41835,5,2,1:2:0:0: +432,232,41999,1,2,1:2:0:0: +444,204,42082,1,0,1:0:0:0: +448,172,42164,1,0,1:0:0:0: +444,140,42247,1,0,1:0:0:0: +432,112,42329,2,0,L|440:64,2,45,2|0|0,1:2|1:0|1:0,0:0:0:0: +236,188,42659,1,0,0:0:0:0: +340,172,42824,1,2,0:3:0:0: +272,88,42988,1,0,0:0:0:0: +132,160,43153,6,0,P|148:248|220:296,1,180,4|8,1:2|0:0,0:0:0:0: +324,320,43648,2,0,L|336:364,2,45,0|0|0,0:0|0:0|0:0,0:0:0:0: +292,216,43977,1,0,1:0:0:0: +396,240,44142,2,0,P|440:244|488:232,1,90,8|0,0:0|0:0,0:0:0:0: +328,124,44472,6,0,P|284:120|236:132,1,90,0|0,1:0|0:0,0:0:0:0: +168,212,44802,1,8,0:0:0:0: +192,316,44966,1,0,1:0:0:0: +140,220,45131,1,0,0:0:0:0: +83,310,45296,1,0,1:0:0:0: +114,205,45461,1,8,0:0:0:0: +10,229,45626,1,0,0:0:0:0: +106,176,45791,6,0,P|113:133|108:85,1,90,0|0,1:0|0:0,0:0:0:0: +204,136,46120,1,8,0:0:0:0: +256,40,46285,1,0,0:0:0:0: +256,40,46368,1,0,0:0:0:0: +256,40,46450,2,0,L|356:44,1,90,0|0,0:0|1:0,0:0:0:0: +501,124,46780,2,0,L|412:128,1,90,8|0,0:0|0:0,0:0:0:0: +324,192,47109,5,0,1:0:0:0: +356,296,47274,1,0,0:0:0:0: +284,216,47439,1,8,0:0:0:0: +269,323,47604,1,0,1:0:0:0: +237,220,47769,1,0,0:0:0:0: +178,311,47934,1,0,1:0:0:0: +191,203,48098,1,8,0:0:0:0: +99,261,48263,1,0,0:0:0:0: +156,168,48428,6,0,B|176:112|136:64|136:64|200:96,1,180,4|8,1:2|0:0,0:0:0:0: +300,124,48923,2,0,L|392:120,1,90,0|0,0:0|0:0,0:0:0:0: +468,48,49252,1,0,1:0:0:0: +390,120,49417,2,0,P|390:164|406:208,1,90,8|0,0:0|0:0,0:0:0:0: +352,344,49747,6,0,P|352:300|336:256,1,90,4|0,1:2|0:0,0:0:0:0: +240,208,50076,1,8,0:0:0:0: +163,320,50241,2,0,P|207:324|252:316,1,90,0|0,1:0|0:0,0:0:0:0: +240,208,50571,1,0,1:0:0:0: +76,296,50736,2,0,P|76:340|92:384,1,90,8|0,0:0|0:0,0:0:0:0: +312,164,51065,6,0,P|236:124|160:184,1,180,4|8,1:2|0:0,0:0:0:0: +247,297,51560,2,0,L|240:208,1,90,0|0,0:0|0:0,0:0:0:0: +224,48,51890,1,0,1:0:0:0: +332,56,52054,2,0,L|366:58,5,30,8|0|0|0|0|0,0:0|0:0|0:0|0:0|0:0|0:0,0:0:0:0: +408,64,52384,6,0,P|420:108|416:156,1,90,0|0,1:0|0:0,0:0:0:0: +360,260,52714,1,8,0:0:0:0: +247,297,52879,2,0,B|203:281|159:297|159:297|115:313|71:297,1,180,0|0,1:0|1:0,0:0:0:0: +116,196,53373,1,8,0:0:0:0: +120,164,53456,1,0,0:0:0:0: +124,132,53538,1,0,0:0:0:0: +128,100,53620,1,0,0:0:0:0: +132,68,53703,5,4,1:2:0:0: +40,136,53868,1,0,0:0:0:0: +204,160,54032,2,0,L|304:152,1,90,8|0,0:0|0:0,0:0:0:0: +408,64,54362,1,0,0:0:0:0: +408,64,54445,1,0,0:0:0:0: +408,64,54527,2,0,P|404:112|416:160,1,90,0|8,1:0|0:0,0:0:0:0: +484,236,54857,1,0,0:0:0:0: +428,328,55021,5,0,1:0:0:0: +328,296,55186,1,0,0:0:0:0: +328,296,55269,1,0,0:0:0:0: +328,296,55351,1,8,0:0:0:0: +416,300,55516,1,0,1:0:0:0: +472,208,55681,1,0,0:0:0:0: +316,268,55846,1,0,1:0:0:0: +460,180,56010,1,8,0:0:0:0: +304,240,56175,1,0,0:0:0:0: +404,272,56340,5,0,1:0:0:0: +448,152,56505,1,0,0:0:0:0: +448,152,56587,1,0,0:0:0:0: +448,152,56670,2,0,P|456:112|448:60,1,90,8|0,0:0|0:0,0:0:0:0: +268,28,56999,2,0,P|260:68|268:120,1,90,0|0,0:0|1:0,0:0:0:0: +404,272,57329,2,0,P|444:280|496:272,2,90,8|0|0,0:0|0:0|1:0,0:0:0:0: +304,240,57824,5,0,0:0:0:0: +252,336,57988,1,8,0:0:0:0: +196,244,58153,1,0,1:0:0:0: +24,256,58318,1,0,0:0:0:0: +116,200,58483,1,0,1:0:0:0: +136,60,58648,1,8,0:0:0:0: +192,152,58813,1,0,0:0:0:0: +304,240,58977,6,0,P|348:252|396:248,1,90,0|0,1:0|0:0,0:0:0:0: +456,116,59307,2,0,P|412:104|364:108,1,90,8|0,0:0|0:0,0:0:0:0: +273,161,59637,1,0,0:0:0:0: +136,60,59802,1,0,1:0:0:0: +192,152,59966,1,8,0:0:0:0: +23,177,60131,1,0,0:0:0:0: +129,203,60296,5,0,1:0:0:0: +88,304,60461,2,0,P|132:311|176:303,1,90,0|8,0:0|0:0,0:0:0:0: +304,240,60791,1,0,1:0:0:0: +304,240,60873,1,0,0:0:0:0: +304,240,60956,2,0,L|312:288,3,45,0|0|0|0,0:0|0:0|1:0|0:0,0:0:0:0: +384,256,61285,2,0,L|392:304,3,45,8|0|0|0,0:0|0:0|0:0|0:0,0:0:0:0: +464,272,61615,5,2,1:2:0:0: +488,168,61780,1,2,0:0:0:0: +428,80,61945,1,10,0:0:0:0: +332,32,62109,2,0,P|288:28|240:36,1,90,2|0,0:0|0:0,0:0:0:0: +28,216,62439,1,2,1:2:0:0: +88,304,62604,1,10,0:0:0:0: +184,352,62769,2,0,P|228:356|276:348,1,90,2|0,0:0|1:0,0:0:0:0: +384,256,63098,6,0,P|409:219|426:174,1,90,2|8,0:0|0:0,0:0:0:0: +428,80,63428,2,0,L|420:36,2,45,2|0|0,1:2|0:0|0:0,0:0:0:0: +456,288,63758,1,2,1:2:0:0: +324,200,63923,1,10,1:2:0:0: +292,204,64005,1,0,1:0:0:0: +260,208,64087,1,2,1:2:0:0: +228,212,64170,1,0,1:0:0:0: +196,216,64252,5,4,1:2:0:0: +104,160,64417,1,0,0:0:0:0: +228,296,64582,2,0,L|320:284,1,90,8|0,0:0|0:0,0:0:0:0: +344,112,64912,1,0,0:0:0:0: +344,112,64994,1,0,0:0:0:0: +344,112,65076,2,0,L|254:123,1,90,0|8,1:0|0:0,0:0:0:0: +144,284,65406,2,0,P|148:328|176:364,1,90,0|0,0:0|1:0,0:0:0:0: +196,216,65736,5,0,0:0:0:0: +196,216,65818,1,0,0:0:0:0: +196,216,65901,2,0,P|155:198|110:205,1,90,8|0,0:0|1:0,0:0:0:0: +36,284,66230,1,0,0:0:0:0: +4,180,66395,1,0,1:0:0:0: +132,24,66560,1,8,0:0:0:0: +100,128,66725,1,0,0:0:0:0: +24,48,66890,5,0,1:0:0:0: +212,108,67054,1,0,0:0:0:0: +212,108,67137,1,0,0:0:0:0: +212,108,67219,2,0,L|300:92,1,90,8|0,0:0|0:0,0:0:0:0: +472,144,67549,2,0,L|384:160,1,90,0|0,0:0|1:0,0:0:0:0: +196,216,67879,2,0,P|240:216|288:240,1,90,8|0,0:0|0:0,0:0:0:0: +324,336,68208,5,0,1:0:0:0: +144,288,68373,1,0,0:0:0:0: +58,170,68538,1,8,0:0:0:0: +196,215,68703,1,0,1:0:0:0: +58,260,68868,1,0,0:0:0:0: +144,142,69032,2,0,L|138:108,2,30,0|0|0,1:0|0:0|0:0,0:0:0:0: +144,142,69197,2,0,P|184:124|232:132,1,90,8|0,0:0|0:0,0:0:0:0: +312,248,69527,6,0,L|324:338,1,90,0|0,1:0|0:0,0:0:0:0: +436,248,69857,1,8,0:0:0:0: +432,216,69939,1,0,0:0:0:0: +428,184,70021,1,0,0:0:0:0: +328,120,70186,1,0,0:0:0:0: +324,152,70269,1,0,0:0:0:0: +320,184,70351,1,0,1:0:0:0: +316,216,70434,1,0,0:0:0:0: +312,248,70516,2,0,L|320:300,1,45,8|0,0:0|0:0,0:0:0:0: +244,340,70681,2,0,L|237:295,1,45,0|0,0:0|0:0,0:0:0:0: +216,224,70846,6,0,P|168:216|124:224,1,90,0|0,1:0|0:0,0:0:0:0: +40,288,71175,1,8,0:0:0:0: +2,95,71340,2,0,P|-4:139|4:184,1,90,0|0,1:0|0:0,0:0:0:0: +164,304,71670,1,0,1:0:0:0: +312,248,71835,1,8,0:0:0:0: +244,340,71999,1,0,0:0:0:0: +216,224,72164,6,0,L|228:132,1,90,0|0,1:0|0:0,0:0:0:0: +332,148,72494,2,0,L|344:56,1,90,8|0,0:0|0:0,0:0:0:0: +312,248,72824,1,0,0:0:0:0: +164,304,72988,1,0,1:0:0:0: +332,336,73153,1,8,0:0:0:0: +360,324,73236,1,0,0:0:0:0: +384,304,73318,1,0,0:0:0:0: +399,276,73401,1,0,0:0:0:0: +403,244,73483,6,0,L|396:200,3,45,4|0|2|0,1:2|0:0|0:0|1:0,0:0:0:0: +420,112,73813,2,0,L|427:68,3,45,2|0|2|0,1:2|0:0|1:2|0:0,0:0:0:0: +352,16,74142,2,0,L|345:60,3,45,0|0|2|0,0:0|1:0|1:2|0:0,0:0:0:0: +332,148,74472,1,2,1:2:0:0: +332,148,74554,1,0,1:0:0:0: +332,148,74637,1,2,1:2:0:0: +332,148,74719,1,0,1:0:0:0: +332,148,74802,6,0,P|360:216|320:312,1,180,4|2,1:2|0:3,0:0:0:0: +190,310,75296,2,0,P|151:231|180:148,1,180,4|0,1:2|0:0,0:0:0:0: +256,56,75791,1,0,0:0:0:0: +332,148,75956,1,2,0:3:0:0: +179,148,76120,5,4,1:2:0:0: +336,64,76285,1,4,1:2:0:0: +256,224,76450,1,2,0:3:0:0: +176,64,76615,1,4,1:2:0:0: +256,140,76780,2,0,L|256:324,1,180,2|0,0:0|0:0,0:0:0:0: +364,300,77274,1,2,0:3:0:0: +148,300,77439,6,0,P|104:316|76:356,1,90,4|0,1:2|0:0,0:0:0:0: +24,252,77769,2,0,L|16:208,3,45,8|0|0|0,0:0|0:0|0:0|0:0,0:0:0:0: +96,212,78098,2,0,L|104:168,3,45,0|0|0|0,0:0|0:0|1:0|0:0,0:0:0:0: +32,128,78428,2,0,L|24:84,3,45,8|0|0|0,0:0|0:0|0:0|0:0,0:0:0:0: +104,88,78758,5,2,1:2:0:0: +204,132,78923,1,0,0:0:0:0: +236,124,79005,1,0,0:0:0:0: +268,116,79087,2,0,L|280:68,3,45,8|0|0|0,0:0|0:0|1:0|0:0,0:0:0:0: +348,100,79417,2,0,L|360:52,3,45,0|0|0|0,0:0|0:0|1:0|0:0,0:0:0:0: +428,84,79747,1,8,1:2:0:0: +460,76,79829,1,0,1:0:0:0: +492,68,79912,1,0,1:0:0:0: +492,260,80076,6,0,P|400:248|328:296,1,180,4|2,1:2|0:3,0:0:0:0: +144,236,80571,2,0,P|236:248|308:200,1,180,4|0,1:2|0:0,0:0:0:0: +348,100,81065,2,0,P|348:56|336:8,1,90,0|2,0:0|0:3,0:0:0:0: +140,48,81395,5,4,1:2:0:0: +244,68,81560,1,4,1:2:0:0: +144,236,81725,1,2,0:3:0:0: +176,133,81890,1,4,1:2:0:0: +184,304,82054,2,0,P|100:300|68:220,1,180,2|0,0:0|0:0,0:0:0:0: +100,116,82549,1,2,0:3:0:0: +264,244,82714,6,0,L|272:340,1,90,4|0,1:2|0:0,0:0:0:0: +380,316,83043,1,8,0:0:0:0: +396,288,83126,1,0,0:0:0:0: +400,256,83208,1,0,0:0:0:0: +396,224,83291,1,0,0:0:0:0: +380,196,83373,2,0,L|336:176,3,45,0|0|0|0,0:0|0:0|1:0|0:0,0:0:0:0: +272,148,83703,1,8,0:0:0:0: +256,120,83785,1,0,0:0:0:0: +252,88,83868,1,0,0:0:0:0: +256,56,83950,1,0,0:0:0:0: +272,28,84032,6,0,L|316:8,3,45,2|0|0|0,1:2|0:0|0:0|0:0,0:0:0:0: +360,72,84362,2,0,L|408:72,3,45,8|0|0|0,0:0|0:0|1:0|0:0,0:0:0:0: +421,149,84692,2,0,L|464:169,3,45,2|0|0|0,0:0|0:0|1:0|0:0,0:0:0:0: +443,244,85021,2,0,L|473:281,3,45,8|0|0|0,1:2|1:0|0:0|0:0,0:0:0:0: +422,339,85351,6,0,L|240:348,1,180,4|2,1:2|0:3,0:0:0:0: +76,172,85846,2,0,L|255:163,1,180,4|0,1:2|0:0,0:0:0:0: +421,149,86340,2,0,P|435:107|428:56,1,90,0|2,0:0|0:3,0:0:0:0: +228,56,86670,5,4,1:2:0:0: +280,192,86835,1,4,1:2:0:0: +328,96,86999,1,2,0:3:0:0: +180,152,87164,1,4,1:2:0:0: +28,100,87330,2,0,P|16:56|20:8,1,90,2|0,0:0|0:0,0:0:0:0: +0,180,87659,1,0,0:0:0:0: +28,284,87824,1,2,0:3:0:0: +108,352,87988,6,0,P|152:360|196:356,1,90,4|0,1:2|0:0,0:0:0:0: +276,284,88318,1,8,0:0:0:0: +304,272,88401,1,0,0:0:0:0: +336,268,88483,1,0,0:0:0:0: +368,272,88565,1,0,0:0:0:0: +396,284,88648,2,0,L|432:312,1,45,0|0,0:0|0:0,0:0:0:0: +488,252,88813,2,0,L|452:224,1,45,0|0,1:0|0:0,0:0:0:0: +400,164,88977,2,0,L|396:116,3,45,8|0|0|0,0:0|0:0|0:0|0:0,0:0:0:0: +316,64,89307,6,0,L|320:160,1,90,2|0,1:2|0:0,0:0:0:0: +276,284,89637,1,8,0:0:0:0: +248,296,89719,1,0,0:0:0:0: +216,300,89802,1,0,1:0:0:0: +184,296,89884,1,0,0:0:0:0: +156,284,89966,2,0,L|120:256,1,45,0|0,0:0|0:0,0:0:0:0: +176,200,90131,2,0,L|140:172,1,45,0|0,1:0|0:0,0:0:0:0: +196,116,90296,2,0,L|160:88,3,45,8|0|0|0,1:2|1:0|1:0|0:0,0:0:0:0: +92,44,90626,6,0,P|48:44|24:160,1,180,4|2,1:2|0:3,0:0:0:0: +156,284,91120,2,0,B|200:300|244:284|244:284|288:268|332:284,1,180,4|0,1:2|0:0,0:0:0:0: +176,200,91615,2,0,P|176:156|196:116,1,90,0|2,0:0|0:3,0:0:0:0: +264,28,91945,6,0,L|353:39,1,90,4|0,1:2|1:0,0:0:0:0: +453,159,92274,2,0,L|364:148,1,90,2|4,0:3|1:2,0:0:0:0: +268,196,92604,2,0,P|260:268|328:348,1,180,2|0,0:0|0:0,0:0:0:0: +364,248,93098,1,2,0:3:0:0: +176,200,93263,5,4,1:2:0:0: +72,228,93428,1,0,1:0:0:0: +152,92,93593,1,0,1:0:0:0: +256,64,93758,1,0,1:0:0:0: +336,200,93923,5,0,1:0:0:0: +440,228,94087,1,0,1:0:0:0: +360,92,94252,1,0,1:0:0:0: +256,64,94417,1,0,1:0:0:0: +176,200,94582,5,2,1:2:0:0: +168,228,94664,1,0,1:0:0:0: +168,260,94747,1,0,1:0:0:0: +172,292,94829,1,0,1:0:0:0: +192,316,94912,1,0,1:0:0:0: +220,328,94994,1,0,1:0:0:0: +252,332,95076,1,0,1:0:0:0: +280,320,95159,1,0,1:0:0:0: +300,296,95241,2,0,L|308:248,3,45,2|0|0|0,1:2|1:0|1:0|1:0,0:0:0:0: +312,172,95571,2,0,L|304:127,3,45,0|0|0|0,1:0|1:0|1:0|1:0,0:0:0:0: +256,64,95901,6,0,P|208:56|164:60,1,90,4|0,1:2|0:0,0:0:0:0: +76,116,96230,1,8,0:0:0:0: +60,224,96395,1,0,0:0:0:0: +60,224,96477,1,0,0:0:0:0: +160,184,96642,1,0,0:0:0:0: +160,184,96725,1,0,1:0:0:0: +63,26,96890,2,0,L|76:116,1,90,8|0,0:0|0:0,0:0:0:0: +136,272,97219,5,0,1:0:0:0: +168,268,97302,1,0,0:0:0:0: +200,264,97384,1,0,0:0:0:0: +232,260,97466,1,0,0:0:0:0: +264,256,97549,1,8,0:0:0:0: +384,136,97714,1,0,1:0:0:0: +376,168,97796,1,0,0:0:0:0: +380,200,97879,1,0,0:0:0:0: +392,228,97961,1,0,0:0:0:0: +416,248,98043,2,0,P|464:260|512:260,2,90,0|8|0,1:0|0:0|0:0,0:0:0:0: +231,105,98538,6,0,L|188:116,2,45,0|0|0,1:0|0:0|0:0,0:0:0:0: +376,56,98868,2,0,L|420:64,1,45,8|0,0:0|0:0,0:0:0:0: +384,136,99032,1,0,0:0:0:0: +384,136,99115,2,0,P|340:128|304:92,1,90,0|0,0:0|0:0,0:0:0:0: +303,18,99362,2,0,L|207:26,2,90,0|8|0,1:0|0:0|0:0,0:0:0:0: +452,88,99857,5,0,1:0:0:0: +465,116,99939,1,0,0:0:0:0: +466,147,100021,1,0,0:0:0:0: +456,177,100104,1,0,0:0:0:0: +436,201,100186,2,0,P|416:213|389:216,3,45,8|0|0|0,0:0|0:0|1:0|0:0,0:0:0:0: +320,188,100516,2,0,P|300:176|273:173,3,45,0|0|0|0,0:0|1:0|1:0|0:0,0:0:0:0: +204,200,100846,2,0,P|192:220|189:247,3,45,8|0|0|0,0:0|0:0|1:0|0:0,0:0:0:0: +188,320,101175,6,0,P|143:322|100:310,1,90,0|0,1:0|0:0,0:0:0:0: +76,292,101423,1,0,0:0:0:0: +76,292,101505,1,8,0:0:0:0: +76,292,101587,2,0,L|72:248,1,45 +12,68,101835,2,0,L|6:24,2,45,0|0|0,0:0|0:0|1:0,0:0:0:0: +104,140,102164,2,0,L|171:132,3,45,8|0|0|0,0:0|0:0|0:0|0:0,0:0:0:0: +224,124,102494,6,0,P|236:164|232:216,1,90,0|0,1:0|0:0,0:0:0:0: +288,296,102824,1,8,0:0:0:0: +288,296,102906,1,0,0:0:0:0: +288,296,102988,2,0,P|328:284|380:288,1,90,0|0,1:0|0:0,0:0:0:0: +404,304,103236,1,0,0:0:0:0: +424,328,103318,1,0,1:0:0:0: +448,188,103483,2,0,L|440:140,3,45,8|0|0|0,0:0|0:0|0:0|0:0,0:0:0:0: +424,72,103813,5,0,1:0:0:0: +324,112,103977,1,0,0:0:0:0: +324,112,104060,1,0,0:0:0:0: +324,112,104142,2,0,P|280:116|232:104,1,90,8|0,0:0|0:0,0:0:0:0: +160,28,104472,1,0,0:0:0:0: +216,208,104637,1,0,1:0:0:0: +216,208,104719,1,0,0:0:0:0: +216,208,104802,1,8,0:0:0:0: +352,240,104966,1,0,0:0:0:0: +384,244,105049,1,0,0:0:0:0: +416,248,105131,6,0,L|460:240,4,45,0|0|0|0|8,1:0|0:0|0:0|0:0|0:0,0:0:0:0: +272,288,105626,1,0,1:0:0:0: +264,320,105708,1,0,0:0:0:0: +256,352,105791,2,0,L|204:356,5,30,0|0|0|0|0|0,0:0|0:0|0:0|1:0|0:0|0:0,0:0:0:0: +156,332,106120,2,0,L|104:336,5,30,8|0|0|0|0|0,0:0|0:0|0:0|1:0|0:0|0:0,0:0:0:0: +56,312,106450,5,4,1:2:0:0: +4,188,106615,1,0,0:0:0:0: +168,220,106780,2,0,P|127:232|79:228,1,90,8|0,0:0|0:0,0:0:0:0: +112,124,107109,1,0,0:0:0:0: +272,216,107274,2,0,L|264:316,1,90,0|8,1:0|0:0,0:0:0:0: +400,268,107604,1,0,0:0:0:0: +428,132,107769,5,0,1:0:0:0: +428,132,107851,1,0,0:0:0:0: +428,132,107934,1,0,0:0:0:0: +428,132,108016,1,0,0:0:0:0: +428,132,108098,1,8,0:0:0:0: +332,84,108263,2,0,P|288:80|232:88,1,90,0|0,1:0|0:0,0:0:0:0: +112,124,108593,1,0,1:0:0:0: +148,264,108758,1,8,0:0:0:0: +16,236,108923,1,0,0:0:0:0: +264,126,109087,6,0,L|272:216,1,90,0|0,1:0|0:0,0:0:0:0: +452,224,109417,2,0,L|460:320,1,90,8|0,0:0|0:0,0:0:0:0: +360,232,109747,1,0,0:0:0:0: +348,56,109912,1,0,1:0:0:0: +416,140,110076,1,8,0:0:0:0: +256,112,110241,2,0,P|212:120|160:112,1,90,0|0,0:0|1:0,0:0:0:0: +348,56,110571,6,0,L|331:150,1,90,0|8,0:0|0:0,0:0:0:0: +208,328,110901,2,0,L|191:239,1,90,0|0,1:0|0:0,0:0:0:0: +184,216,111148,1,0,1:0:0:0: +178,194,111230,1,0,1:0:0:0: +68,272,111395,1,8,0:0:0:0: +56,136,111560,1,0,1:0:0:0: +178,194,111725,6,0,P|219:203|267:199,1,90,4|0,1:2|0:0,0:0:0:0: +364,148,112054,1,8,0:0:0:0: +384,256,112219,2,0,P|406:291|443:322,1,90,0|0,0:0|0:0,0:0:0:0: +488,224,112549,1,0,1:0:0:0: +304,232,112714,2,0,L|208:224,2,90,8|0|0,0:0|0:0|1:0,0:0:0:0: +208,328,113208,6,0,L|112:320,1,90,0|8,0:0|0:0,0:0:0:0: +26,184,113538,2,0,L|116:192,1,90,0|0,1:0|0:0,0:0:0:0: +304,232,113868,1,0,1:0:0:0: +116,192,114032,1,8,0:0:0:0: +224,132,114197,1,0,0:0:0:0: +208,328,114362,6,0,B|272:360|320:312|320:312|340:368,1,180,4|8,1:2|0:0,0:0:0:0: +304,232,114857,2,0,P|300:184|308:140,1,90,0|0,0:0|0:0,0:0:0:0: +384,64,115186,1,0,1:0:0:0: +307,143,115351,1,8,0:0:0:0: +256,48,115516,1,0,0:0:0:0: +456,24,115681,6,0,B|482:101|420:136|420:136|440:184,1,180,4|8,1:2|0:0,0:0:0:0: +384,64,116175,2,0,P|340:56|296:64,1,90,0|0,1:0|0:0,0:0:0:0: +211,171,116505,1,0,1:0:0:0: +439,181,116670,2,0,L|448:84,1,90,8|0,0:0|0:0,0:0:0:0: +372,296,116999,6,2,L|304:292,1,67.5000025749208,2|0,0:1|0:0,0:0:0:0: +136,252,117329,6,2,P|196:260|212:172,1,168.75,0|0,0:0|0:0,0:0:0:0: +192,148,117659,1,2,0:3:0:0: +164,132,117741,1,2,0:3:0:0: +132,124,117824,1,2,1:3:0:0: +100,132,117906,1,2,0:3:0:0: +72,148,117988,2,0,L|52:56,1,90,2|8,0:3|0:0,0:0:0:0: +36,244,118318,5,0,1:0:0:0: +76,344,118483,1,0,1:0:0:0: +184,352,118648,1,0,1:0:0:0: +244,264,118813,1,0,1:0:0:0: +244,264,118895,1,0,1:0:0:0: +244,264,118977,2,0,L|288:260,3,45,2|0|2|0,1:2|0:0|0:0|0:0,0:0:0:0: +332,328,119307,2,0,L|376:324,3,45,2|0|2|0,1:2|0:0|0:0|0:0,0:0:0:0: +412,252,119637,5,4,1:2:0:0: +256,192,119719,12,0,122274,0:0:0:0: +256,192,140735,6,0,L|228:156,1,45,4|0,1:2|0:0,0:0:0:0: +152,132,141065,2,0,P|129:129|104:136,1,45 +48,192,141395,2,0,P|40:236|52:280,1,90,8|8,0:0|0:0,0:0:0:0: +196,352,142054,6,0,L|308:340,1,90,8|8,0:0|1:2,0:0:0:0: +336,280,142549,1,0,0:0:0:0: +404,324,142713,1,8,0:0:0:0: +404,324,142878,1,8,0:0:0:0: +292,120,143373,5,0,1:0:0:0: +212,104,143538,1,0,0:0:0:0: +140,140,143702,1,0,0:0:0:0: +120,220,143867,1,0,0:0:0:0: +144,296,144032,2,0,P|184:320|228:316,1,90,10|8,0:0|0:0,0:0:0:0: +372,212,144691,6,0,P|327:209|290:232,1,90,10|8,0:0|1:2,0:0:0:0: +348,288,145186,1,0,0:0:0:0: +452,220,145351,1,10,0:0:0:0: +452,220,145516,1,8,0:0:0:0: +328,36,146010,5,2,1:2:0:0: +264,88,146175,1,0,0:0:0:0: +184,108,146340,1,0,0:0:0:0: +104,88,146505,1,0,0:0:0:0: +44,36,146669,1,8,0:0:0:0: +44,36,146999,1,8,0:0:0:0: +44,36,147329,6,0,L|24:84,1,45,8|0,0:0|0:0,0:0:0:0: +52,156,147658,2,0,L|71:204,1,45,8|0,1:2|0:0,0:0:0:0: +144,236,147988,1,8,0:0:0:0: +144,236,148153,1,8,0:0:0:0: +316,64,148647,5,0,1:0:0:0: +380,116,148812,1,0,0:0:0:0: +408,192,148977,1,0,0:0:0:0: +380,268,149142,1,0,0:0:0:0: +316,320,149307,2,0,L|224:316,1,90,10|8,0:0|0:0,0:0:0:0: +64,248,149966,5,10,0:0:0:0: +144,236,150131,1,0,0:0:0:0: +188,168,150296,1,8,1:2:0:0: +192,88,150461,1,0,0:0:0:0: +140,24,150626,2,0,P|120:16|96:20,1,45,10|0,0:0|0:0,0:0:0:0: +260,132,150955,2,0,P|280:140|304:136,1,45,2|0,0:0|0:0,0:0:0:0: +476,48,151285,6,0,L|484:160,1,112.5,4|0,1:2|0:0,0:0:0:0: +464,236,151779,1,0,0:0:0:0: +436,308,151944,2,0,P|380:320|324:308,1,112.5,8|8,0:0|0:0,0:0:0:0: +76,308,152604,6,0,P|132:320|188:308,1,112.5,8|8,0:0|1:2,0:0:0:0: +256,88,153263,1,8,0:0:0:0: +256,168,153428,1,8,0:0:0:0: +256,168,153922,5,4,1:2:0:0: +256,248,154087,1,0,0:0:0:0: +324,128,154252,1,0,0:0:0:0: +188,128,154417,1,0,0:0:0:0: +332,212,154582,2,0,L|388:204,1,56.25,10|0,0:0|0:0,0:0:0:0: +492,152,154911,2,0,L|436:144,1,56.25,8|0,0:0|0:0,0:0:0:0: +324,128,155241,5,10,0:0:0:0: +180,212,155406,1,0,0:0:0:0: +332,212,155571,1,8,1:2:0:0: +188,128,155735,1,0,0:0:0:0: +256,248,155900,1,10,0:0:0:0: +256,248,156065,2,0,L|256:304,2,56.25,0|0|0,0:0|0:0|0:0,0:0:0:0: +180,212,156560,6,0,L|124:204,1,56.25,4|0,1:2|0:0,0:0:0:0: +20,152,156889,2,0,L|76:144,1,56.25,0|0,0:0|0:0,0:0:0:0: +188,128,157219,2,0,P|212:72|192:16,1,112.5,8|8,0:0|0:0,0:0:0:0: +132,72,157713,1,0,0:0:0:0: +180,212,157878,6,0,L|236:208,1,56.25,8|0,0:0|0:0,0:0:0:0: +360,252,158208,2,8,L|304:248,1,56.25,8|0,1:2|0:0,0:0:0:0: +168,292,158538,2,0,L|160:356,2,56.25,8|8|0,0:0|0:0|0:0,0:0:0:0: +180,212,159032,1,0,0:0:0:0: +144,140,159197,6,0,P|104:128|36:148,1,112.5,2|0,1:2|0:0,0:0:0:0: +12,220,159691,1,0,0:0:0:0: +36,296,159856,2,0,P|60:316|92:324,1,56.25,8|0,0:0|0:0,0:0:0:0: +215,264,160186,2,0,P|189:273|168:292,1,56.25,8|0,0:0|0:0,0:0:0:0: +228,344,160516,6,0,L|284:340,1,56.25,10|0,0:0|0:0,0:0:0:0: +328,276,160845,2,0,L|384:272,1,56.25,8|0,1:2|0:0,0:0:0:0: +428,208,161175,1,8,0:0:0:0: +440,128,161340,1,8,0:0:0:0: +400,60,161505,1,2,0:0:0:0: +328,28,161669,1,0,0:0:0:0: +212,76,161834,6,0,P|200:120|208:164,1,90,2|0,1:2|1:0,0:0:0:0: +300,308,162163,2,0,P|312:264|304:220,1,90,2|0,1:2|1:0,0:0:0:0: +140,236,162493,2,0,P|184:248|228:240,1,90,2|0,1:2|1:0,0:0:0:0: +372,148,162823,2,0,P|328:136|284:144,1,90,2|0,1:2|1:0,0:0:0:0: +104,316,163152,5,2,1:2:0:0: +78,297,163235,1,0,1:0:0:0: +60,270,163317,1,0,1:0:0:0: +54,239,163399,1,0,1:0:0:0: +58,207,163482,1,2,1:2:0:0: +74,180,163564,1,0,1:0:0:0: +98,159,163647,1,0,1:0:0:0: +127,149,163729,1,0,1:0:0:0: +158,150,163812,2,0,L|208:160,1,45,2|0,1:2|1:0,0:0:0:0: +344,184,163976,2,0,L|294:194,1,45,0|0,1:0|1:0,0:0:0:0: +140,236,164141,1,4,1:2:0:0: +140,236,164471,6,0,L|232:252,1,90,4|0,1:2|0:0,0:0:0:0: +344,184,164801,1,8,0:0:0:0: +380,284,164965,1,0,0:0:0:0: +368,104,165130,2,0,P|324:104|284:128,1,90,0|0,0:0|1:0,0:0:0:0: +356,360,165460,2,0,P|400:360|440:336,1,90,8|0,0:0|0:0,0:0:0:0: +432,208,165790,5,0,1:0:0:0: +292,260,165954,1,0,0:0:0:0: +344,184,166119,1,8,0:0:0:0: +204,236,166284,1,0,1:0:0:0: +204,236,166366,1,0,0:0:0:0: +204,236,166449,2,0,L|216:328,1,90,0|0,0:0|1:0,0:0:0:0: +120,208,166779,2,0,L|131:118,1,90,8|0,0:0|0:0,0:0:0:0: +204,236,167108,5,0,1:0:0:0: +32,216,167273,1,0,0:0:0:0: +130,118,167438,1,8,0:0:0:0: +110,298,167603,1,0,0:0:0:0: +110,298,167685,1,0,0:0:0:0: +110,298,167768,2,0,L|121:208,1,90,0|0,0:0|1:0,0:0:0:0: +304,40,168097,2,0,L|315:130,1,90,8|0,0:0|0:0,0:0:0:0: +328,236,168427,5,0,1:0:0:0: +184,148,168592,1,0,0:0:0:0: +314,129,168757,1,8,0:0:0:0: +197,254,168921,1,0,1:0:0:0: +197,254,169004,1,0,0:0:0:0: +197,254,169086,2,0,P|220:292|260:312,1,90,0|0,0:0|1:0,0:0:0:0: +409,210,169416,2,0,P|365:211|328:236,1,90,8|0,0:0|0:0,0:0:0:0: +488,232,169746,6,0,P|487:192|464:149,1,90,0|0,1:0|0:0,0:0:0:0: +314,129,170075,1,8,0:0:0:0: +409,210,170240,1,0,0:0:0:0: +332,40,170405,2,0,L|240:36,1,90,0|0,0:0|1:0,0:0:0:0: +68,144,170735,2,0,L|157:140,1,90,8|0,0:0|0:0,0:0:0:0: +314,129,171064,5,0,1:0:0:0: +332,40,171229,1,0,0:0:0:0: +324,216,171394,1,8,0:0:0:0: +306,305,171559,1,0,1:0:0:0: +257,178,171724,1,0,0:0:0:0: +168,160,171888,1,0,1:0:0:0: +384,164,172053,1,8,0:0:0:0: +473,182,172218,1,0,0:0:0:0: +306,305,172383,6,0,L|216:312,1,90,0|0,1:0|0:0,0:0:0:0: +60,172,172713,1,8,0:0:0:0: +120,260,172877,1,0,0:0:0:0: +168,160,173042,2,0,L|172:68,1,90,0|0,0:0|1:0,0:0:0:0: +309,216,173372,2,0,L|306:306,1,90,8|0,0:0|0:0,0:0:0:0: +120,260,173702,5,0,1:0:0:0: +152,256,173784,1,0,1:0:0:0: +184,252,173866,1,0,1:0:0:0: +309,216,174031,1,8,0:0:0:0: +103,168,174196,1,0,1:0:0:0: +135,164,174279,1,0,1:0:0:0: +167,160,174361,1,0,1:0:0:0: +292,124,174526,1,0,1:0:0:0: +87,76,174691,1,8,1:2:0:0: +119,72,174773,1,0,1:0:0:0: +151,68,174855,1,0,1:0:0:0: +276,32,175020,6,0,L|368:40,1,90,0|0,1:0|0:0,0:0:0:0: +448,108,175350,1,8,0:0:0:0: +292,124,175515,1,0,0:0:0:0: +292,124,175597,1,0,0:0:0:0: +292,124,175680,2,0,L|308:216,1,90,0|0,0:0|1:0,0:0:0:0: +328,320,176009,1,8,0:0:0:0: +408,248,176174,1,0,0:0:0:0: +220,300,176339,6,0,P|176:304|128:292,1,90,0|0,1:0|0:0,0:0:0:0: +16,120,176669,1,8,0:0:0:0: +120,152,176834,1,0,1:0:0:0: +120,152,176916,1,0,0:0:0:0: +120,152,176998,2,0,L|124:200,1,45 +212,176,177163,2,0,L|239:215,1,45,0|0,1:0|0:0,0:0:0:0: +292,124,177328,2,0,P|302:79|283:30,1,90,8|0,0:0|0:0,0:0:0:0: +344,192,177658,6,0,P|372:156|376:104,1,90,0|0,1:0|0:0,0:0:0:0: +212,88,177987,1,8,0:0:0:0: +272,228,178152,1,0,0:0:0:0: +272,228,178235,1,0,0:0:0:0: +272,228,178317,1,0,0:0:0:0: +292,124,178482,1,0,1:0:0:0: +180,180,178647,1,8,0:0:0:0: +200,284,178812,1,0,0:0:0:0: +292,124,178976,5,0,1:0:0:0: +288,92,179059,1,0,0:0:0:0: +280,60,179141,2,0,P|248:24|208:14,1,90,0|8,0:0|0:0,0:0:0:0: +22,65,179471,2,0,P|67:71|112:68,1,90,0|0,1:0|0:0,0:0:0:0: +212,88,179801,1,0,1:0:0:0: +22,65,179965,1,8,0:0:0:0: +180,180,180130,5,0,0:0:0:0: +180,180,180213,1,0,0:0:0:0: +180,180,180295,2,0,P|184:224|172:272,1,90,0|0,1:0|0:0,0:0:0:0: +76,216,180625,2,0,P|72:172|84:124,1,90,8|0,0:0|0:0,0:0:0:0: +380,240,180954,2,0,P|384:284|372:332,1,90,0|0,0:0|1:0,0:0:0:0: +276,276,181284,2,0,P|272:232|284:184,1,90,8|0,0:0|0:0,0:0:0:0: +374,129,181614,5,0,1:0:0:0: +300,352,181779,2,0,L|204:348,2,90,0|8|0,0:0|0:0|1:0,0:0:0:0: +448,180,182273,1,2,0:0:0:0: +448,180,182438,1,2,1:2:0:0: +276,276,182603,1,10,0:0:0:0: +276,276,182768,1,2,0:0:0:0: +96,200,182932,6,0,L|88:108,1,90,0|0,1:0|0:0,0:0:0:0: +96,200,183262,1,8,0:0:0:0: +12,68,183427,2,0,P|72:24|164:68,1,180,0|0,0:0|1:0,0:0:0:0: +140,272,183921,2,0,P|92:284|52:271,1,90,8|0,0:0|0:0,0:0:0:0: +176,156,184251,5,0,1:0:0:0: +208,152,184334,1,0,1:0:0:0: +240,148,184416,1,0,1:0:0:0: +308,64,184581,1,8,0:0:0:0: +296,240,184746,1,0,1:0:0:0: +312,268,184828,1,0,1:0:0:0: +336,284,184910,1,0,1:0:0:0: +368,292,184993,1,0,1:0:0:0: +400,288,185075,1,0,1:0:0:0: +464,184,185240,1,8,0:0:0:0: +468,152,185323,1,0,0:0:0:0: +472,120,185405,2,0,L|464:76,1,45,0|0,1:0|1:0,0:0:0:0: +388,96,185570,6,0,P|360:132|316:148,1,90,4|0,1:2|0:0,0:0:0:0: +224,46,185899,2,0,P|268:43|308:63,1,90,8|0,0:0|0:0,0:0:0:0: +296,240,186229,1,0,0:0:0:0: +308,64,186394,1,0,1:0:0:0: +296,240,186559,2,0,L|312:332,1,90,8|0,0:0|0:0,0:0:0:0: +464,184,186888,6,0,P|420:180|372:188,1,90,0|0,1:0|0:0,0:0:0:0: +296,240,187218,1,8,0:0:0:0: +136,292,187383,2,0,P|94:277|54:249,1,90,0|0,1:0|0:0,0:0:0:0: +21,159,187713,1,0,1:0:0:0: +104,8,187877,2,0,L|124:96,1,90,10|0,0:0|0:0,0:0:0:0: +124,96,188207,6,0,P|152:132|196:148,1,90,0|0,1:0|0:0,0:0:0:0: +287,46,188537,2,0,P|243:43|204:63,1,90,8|0,0:0|0:0,0:0:0:0: +216,240,188866,1,2,0:0:0:0: +204,64,189031,1,0,1:0:0:0: +216,240,189196,2,0,L|200:332,1,90,8|0,0:0|0:0,0:0:0:0: +40,240,189526,5,2,1:2:0:0: +128,192,189691,1,0,0:0:0:0: +216,240,189855,1,8,0:0:0:0: +304,192,190020,1,0,1:0:0:0: +392,240,190185,2,0,L|400:332,1,90,2|0,0:0|1:0,0:0:0:0: +464,168,190515,2,0,L|456:76,1,90,8|0,0:0|0:0,0:0:0:0: +392,240,190844,6,0,P|364:272|312:292,1,90,2|0,1:2|0:0,0:0:0:0: +220,140,191174,2,0,P|248:108|296:92,1,90,8|0,0:0|0:0,0:0:0:0: +324,96,191421,1,0,0:0:0:0: +356,104,191504,2,0,L|340:16,1,90,0|0,0:0|1:0,0:0:0:0: +256,276,191834,2,0,L|272:364,1,90,8|0,0:0|0:0,0:0:0:0: +392,240,192163,5,0,1:0:0:0: +356,104,192328,1,0,0:0:0:0: +220,140,192493,1,8,0:0:0:0: +256,276,192658,1,0,1:0:0:0: +305,191,192823,1,0,0:0:0:0: +212,56,192987,1,0,1:0:0:0: +200,220,193152,1,10,0:0:0:0: +200,220,193482,6,0,P|156:228|108:220,1,90,0|0,1:0|0:0,0:0:0:0: +88,116,193812,1,8,0:0:0:0: +16,192,193976,1,0,0:0:0:0: +16,192,194059,1,0,0:0:0:0: +16,192,194141,2,0,L|28:288,1,90,2|0,0:0|1:0,0:0:0:0: +188,309,194471,2,0,L|200:220,1,90,8|0,0:0|0:0,0:0:0:0: +216,112,194801,5,2,1:2:0:0: +216,112,194883,1,0,1:0:0:0: +216,112,194965,1,0,1:0:0:0: +361,25,195130,1,8,0:0:0:0: +294,180,195295,1,0,1:0:0:0: +294,180,195377,1,0,1:0:0:0: +294,180,195460,1,2,0:0:0:0: +256,16,195625,1,0,1:0:0:0: +384,127,195790,1,10,1:2:0:0: +416,132,195872,1,0,1:0:0:0: +448,140,195954,2,0,L|452:84,1,45,0|0,1:0|1:0,0:0:0:0: +416,216,196119,6,0,P|412:264|432:312,1,90,4|0,1:2|0:0,0:0:0:0: +304,268,196449,2,0,P|308:220|288:172,1,90,8|0,0:0|0:0,0:0:0:0: +216,112,196779,2,0,L|120:104,1,90,0|0,0:0|1:0,0:0:0:0: +52,248,197108,2,0,L|141:255,1,90,8|0,0:0|0:0,0:0:0:0: +304,268,197438,5,0,1:0:0:0: +416,216,197603,1,0,0:0:0:0: +408,340,197768,1,8,0:0:0:0: +332,180,197932,1,0,1:0:0:0: +332,180,198015,1,0,0:0:0:0: +332,180,198097,2,0,P|360:140|400:120,1,90,0|0,0:0|1:0,0:0:0:0: +484,284,198427,1,10,0:0:0:0: +304,268,198592,1,2,0:0:0:0: +416,216,198757,6,0,P|428:172|420:124,1,90,2|0,1:2|0:0,0:0:0:0: +344,52,199086,1,8,0:0:0:0: +332,180,199251,1,0,0:0:0:0: +164,236,199416,2,0,P|152:192|160:144,1,90,0|0,0:0|1:0,0:0:0:0: +236,72,199746,1,8,0:0:0:0: +248,200,199910,1,0,0:0:0:0: +156,328,200075,6,0,L|56:320,1,90,2|0,1:2|0:0,0:0:0:0: +164,236,200405,1,8,0:0:0:0: +256,292,200570,2,0,P|300:296|344:284,1,90,0|0,1:0|0:0,0:0:0:0: +432,220,200899,2,0,L|460:308,2,90,0|8|0,1:0|0:0|0:0,0:0:0:0: +392,120,201394,5,4,1:2:0:0: +396,32,201559,1,0,1:0:0:0: +316,72,201724,1,0,1:0:0:0: +256,6,201888,1,0,1:0:0:0: +228,91,202053,1,0,1:0:0:0: +139,87,202218,1,0,1:0:0:0: +179,166,202383,1,0,1:0:0:0: +113,226,202548,1,0,1:0:0:0: +197,253,202713,5,4,1:2:0:0: +193,342,202877,1,0,1:0:0:0: +272,302,203042,1,0,1:0:0:0: +332,367,203207,1,0,1:0:0:0: +359,283,203372,1,2,1:2:0:0: +448,287,203537,1,2,1:2:0:0: +407,208,203702,1,2,1:2:0:0: +472,147,203866,1,2,1:2:0:0: +387,121,204031,5,4,1:2:0:0: +360,100,204114,1,0,1:0:0:0: +344,72,204196,1,0,1:0:0:0: +336,40,204279,1,0,1:0:0:0: +340,8,204361,1,0,1:0:0:0: +316,28,204443,1,0,1:0:0:0: +284,32,204526,1,0,1:0:0:0: +252,28,204608,1,0,1:0:0:0: +228,8,204691,2,0,L|184:20,7,45,4|0|0|0|0|0|0|0,1:2|1:0|1:0|1:0|1:0|1:0|1:0|1:0,0:0:0:0: +112,56,205350,5,4,1:2:0:0: +100,84,205432,1,0,1:0:0:0: +96,116,205515,1,0,1:0:0:0: +100,148,205597,1,0,1:0:0:0: +112,176,205680,1,0,1:0:0:0: +124,204,205762,1,0,1:0:0:0: +128,236,205844,1,0,1:0:0:0: +124,268,205927,1,0,1:0:0:0: +112,296,206009,2,0,L|71:313,3,45,2|0|2|0,1:2|0:0|0:0|0:0,0:0:0:0: +192,312,206339,2,0,L|175:353,3,45,2|0|2|0,1:2|0:0|0:0|0:0,0:0:0:0: +256,264,206669,5,4,1:2:0:0: +256,192,206751,12,0,209306,0:0:0:0: diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 3cd425ea44..24843f8c32 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -290,8 +290,12 @@ namespace osu.Game.Beatmaps.Formats string[] split = line.Split(','); EventType type; + if (!Enum.TryParse(split[0], out type)) - throw new InvalidDataException($@"Unknown event type {split[0]}"); + { + Logger.Log($"A beatmap event could not be parsed and will be ignored: {split[0]}", LoggingTarget.Runtime, LogLevel.Important); + return; + } switch (type) { diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index f6e2bf6966..1dcafb4669 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -10,6 +10,7 @@ using osuTK; using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.IO.File; +using osu.Framework.Logging; using osu.Game.Storyboards; namespace osu.Game.Beatmaps.Formats @@ -83,7 +84,7 @@ namespace osu.Game.Beatmaps.Formats EventType type; if (!Enum.TryParse(split[0], out type)) - throw new InvalidDataException($@"Unknown event type {split[0]}"); + Logger.Log($"A storyboard event could not be parsed and will be ignored: {split[0]}", LoggingTarget.Runtime, LogLevel.Important); switch (type) { From a5c17ae26d81b778c2b8e87f2c1c074d7bf72267 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 6 Aug 2019 10:14:36 +0900 Subject: [PATCH 0516/2815] Don't use GetDecoder --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 98464b8d91..7e0374023e 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -486,12 +486,13 @@ namespace osu.Game.Tests.Beatmaps.Formats [Test] public void TestDecodeInvalidEvents() { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var normalResStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var normalStream = new StreamReader(normalResStream)) using (var resStream = TestResources.OpenResource("invalid-events.osu")) using (var stream = new StreamReader(resStream)) { - var decoder = Decoder.GetDecoder(stream); var goodBeatmap = decoder.Decode(normalStream); Beatmap badBeatmap = null; From 1fc7ddf621611e9d773fb5b0df218d8a18992681 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 5 Aug 2019 18:14:41 -0700 Subject: [PATCH 0517/2815] Fix text depth regression --- osu.Game/Screens/Select/FooterButton.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index 90bc902ad8..15088f0eb3 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -67,16 +67,6 @@ namespace osu.Game.Screens.Select Shear = SHEARING; Children = new Drawable[] { - TextContainer = new Container - { - Size = new Vector2(100 - SHEAR_WIDTH, 50), - Shear = -SHEARING, - Child = SpriteText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - } - }, box = new Box { RelativeSizeAxes = Axes.Both, @@ -90,6 +80,16 @@ namespace osu.Game.Screens.Select EdgeSmoothness = new Vector2(2, 0), RelativeSizeAxes = Axes.X, }, + TextContainer = new Container + { + Size = new Vector2(100 - SHEAR_WIDTH, 50), + Shear = -SHEARING, + Child = SpriteText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }, }; } From b8c38d4dfd5ba9a8cd3acc70ab0f0fcda7495cb7 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 6 Aug 2019 10:36:26 +0900 Subject: [PATCH 0518/2815] remove unnecessary assert --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 7e0374023e..bbc3b05d89 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -494,9 +494,8 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var stream = new StreamReader(resStream)) { var goodBeatmap = decoder.Decode(normalStream); - Beatmap badBeatmap = null; + var badBeatmap = decoder.Decode(stream); - Assert.DoesNotThrow(() => badBeatmap = decoder.Decode(stream)); Assert.AreEqual(goodBeatmap.HitObjects.Count, badBeatmap.HitObjects.Count); } } From 2c32d886d7cf77e7615ebe93a7afadb00b1d81ef Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 6 Aug 2019 10:39:54 +0900 Subject: [PATCH 0519/2815] Add better asserts --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index bbc3b05d89..e88c3c8ecc 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -496,6 +496,8 @@ namespace osu.Game.Tests.Beatmaps.Formats var goodBeatmap = decoder.Decode(normalStream); var badBeatmap = decoder.Decode(stream); + Assert.AreEqual(goodBeatmap.Breaks[0].Duration, badBeatmap.Breaks[0].Duration); + Assert.AreEqual(goodBeatmap.Metadata.BackgroundFile, badBeatmap.Metadata.BackgroundFile); Assert.AreEqual(goodBeatmap.HitObjects.Count, badBeatmap.HitObjects.Count); } } From 66b02c02831a2004c53b755d0159fea08c9bf2ad Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 6 Aug 2019 12:27:10 +0900 Subject: [PATCH 0520/2815] log type as well --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 24843f8c32..c92c0ba22d 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -293,7 +293,7 @@ namespace osu.Game.Beatmaps.Formats if (!Enum.TryParse(split[0], out type)) { - Logger.Log($"A beatmap event could not be parsed and will be ignored: {split[0]}", LoggingTarget.Runtime, LogLevel.Important); + Logger.Log($"A beatmap {type} event could not be parsed and will be ignored: {split[0]}", LoggingTarget.Runtime, LogLevel.Important); return; } diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 1dcafb4669..512eea0822 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -84,7 +84,7 @@ namespace osu.Game.Beatmaps.Formats EventType type; if (!Enum.TryParse(split[0], out type)) - Logger.Log($"A storyboard event could not be parsed and will be ignored: {split[0]}", LoggingTarget.Runtime, LogLevel.Important); + Logger.Log($"A storyboard {type} event could not be parsed and will be ignored: {split[0]}", LoggingTarget.Runtime, LogLevel.Important); switch (type) { From 497d2cb67784287c5217fe0c5279bfab7748bab7 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 6 Aug 2019 12:35:18 +0900 Subject: [PATCH 0521/2815] shorten tests and rename --- .../Formats/LegacyBeatmapDecoderTest.cs | 13 +- osu.Game.Tests/Resources/invalid-events.osu | 992 +----------------- osu.Game.Tests/Resources/valid-events.osu | 12 + 3 files changed, 19 insertions(+), 998 deletions(-) create mode 100644 osu.Game.Tests/Resources/valid-events.osu diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index e88c3c8ecc..49a6f646ba 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -488,17 +488,16 @@ namespace osu.Game.Tests.Beatmaps.Formats { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; - using (var normalResStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) - using (var normalStream = new StreamReader(normalResStream)) - using (var resStream = TestResources.OpenResource("invalid-events.osu")) - using (var stream = new StreamReader(resStream)) + using (var goodResStream = TestResources.OpenResource("valid-events.osu")) + using (var goodStream = new StreamReader(goodResStream)) + using (var badResStream = TestResources.OpenResource("invalid-events.osu")) + using (var badStream = new StreamReader(badResStream)) { - var goodBeatmap = decoder.Decode(normalStream); - var badBeatmap = decoder.Decode(stream); + var goodBeatmap = decoder.Decode(goodStream); + var badBeatmap = decoder.Decode(badStream); Assert.AreEqual(goodBeatmap.Breaks[0].Duration, badBeatmap.Breaks[0].Duration); Assert.AreEqual(goodBeatmap.Metadata.BackgroundFile, badBeatmap.Metadata.BackgroundFile); - Assert.AreEqual(goodBeatmap.HitObjects.Count, badBeatmap.HitObjects.Count); } } } diff --git a/osu.Game.Tests/Resources/invalid-events.osu b/osu.Game.Tests/Resources/invalid-events.osu index a8e767585c..df86b26dba 100644 --- a/osu.Game.Tests/Resources/invalid-events.osu +++ b/osu.Game.Tests/Resources/invalid-events.osu @@ -1,43 +1,5 @@ osu file format v14 -[General] -AudioFilename: 03. Renatus - Soleily 192kbps.mp3 -AudioLeadIn: 0 -PreviewTime: 164471 -Countdown: 0 -SampleSet: Soft -StackLeniency: 0.7 -Mode: 0 -LetterboxInBreaks: 0 -WidescreenStoryboard: 0 - -[Editor] -Bookmarks: 11505,22054,32604,43153,53703,64252,74802,85351,95901,106450,116999,119637,130186,140735,151285,161834,164471,175020,185570,196119,206669,209306 -DistanceSpacing: 1.8 -BeatDivisor: 4 -GridSize: 4 -TimelineZoom: 2 - -[Metadata] -Title:Renatus -TitleUnicode:Renatus -Artist:Soleily -ArtistUnicode:Soleily -Creator:Gamu -Version:Insane -Source: -Tags:MBC7 Unisphere 地球ヤバイEP Chikyu Yabai -BeatmapID:557821 -BeatmapSetID:241526 - -[Difficulty] -HPDrainRate:6.5 -CircleSize:4 -OverallDifficulty:8 -ApproachRate:9 -SliderMultiplier:1.8 -SliderTickRate:2 - [Events] bad,event,this,should,fail //Background and Video events @@ -45,960 +7,8 @@ bad,event,this,should,fail //Break Periods 2,122474,140135 //Storyboard Layer 0 (Background) +this,is,also,bad //Storyboard Layer 1 (Fail) //Storyboard Layer 2 (Pass) //Storyboard Layer 3 (Foreground) //Storyboard Sound Samples - -[TimingPoints] -956,329.67032967033,4,2,0,60,1,0 -20736,-100,4,2,0,65,0,0 -22054,-100,4,2,0,70,0,0 -43153,-100,4,2,0,60,0,0 -48428,-100,4,2,0,50,0,0 -52879,-100,4,2,0,50,0,0 -53373,-100,4,2,0,60,0,0 -53703,-100,4,2,0,70,0,1 -74719,-100,4,2,0,70,0,0 -74802,-100,4,2,0,70,0,1 -95901,-100,4,2,0,70,0,0 -116999,-133.333333333333,4,2,0,50,0,0 -117164,-133.333333333333,4,2,0,30,0,0 -117329,-79.9999999999999,4,2,0,50,0,0 -117659,-100,4,2,0,50,0,0 -118977,-100,4,2,0,60,0,0 -119307,-100,4,2,0,70,0,0 -119637,659.340659340659,4,2,0,80,1,0 -119966,-100,4,2,0,70,0,0 -120296,-100,4,2,0,60,0,0 -120626,-100,4,2,0,50,0,0 -120955,-100,4,2,0,40,0,0 -121285,-100,4,2,0,30,0,0 -121615,-100,4,2,0,20,0,0 -121944,-100,4,2,0,10,0,0 -122274,-100,4,2,0,5,0,0 -140735,-100,4,2,0,50,0,0 -151285,-80,4,2,0,60,0,0 -161834,329.67032967033,4,2,0,65,1,0 -164141,-100,4,2,0,70,0,0 -164471,-100,4,2,0,70,0,1 -185487,-100,4,2,0,70,0,0 -185570,-100,4,2,0,70,0,1 -206669,659.340659340659,4,2,0,80,1,0 -206998,-100,4,2,0,70,0,0 -207328,-100,4,2,0,60,0,0 -207658,-100,4,2,0,50,0,0 -207987,-100,4,2,0,40,0,0 -208317,-100,4,2,0,30,0,0 -208647,-100,4,2,0,20,0,0 -208976,-100,4,2,0,10,0,0 -209306,-100,4,2,0,5,0,0 - - -[Colours] -Combo1 : 142,199,255 -Combo2 : 255,128,128 -Combo3 : 128,255,255 -Combo4 : 128,255,128 -Combo5 : 255,187,255 -Combo6 : 255,177,140 -Combo7 : 100,100,100,100 - -[HitObjects] -192,168,956,6,0,P|184:128|200:80,1,90,4|0,1:2|0:0,0:0:0:0: -304,56,1285,1,8,0:0:0:0: -244,236,1450,2,0,P|204:252|156:244,1,90,2|0,0:0|0:0,0:0:0:0: -276,156,1780,2,0,P|310:181|329:226,1,90,2|8,1:2|0:0,0:0:0:0: -300,328,2109,1,2,0:0:0:0: -192,332,2274,6,0,L|144:340,2,45,0|0|0,1:0|0:0|0:0,0:0:0:0: -388,300,2604,1,8,0:0:0:0: -244,236,2769,1,0,1:0:0:0: -232,208,2851,1,0,0:0:0:0: -224,176,2934,1,0,0:0:0:0: -228,144,3016,1,0,0:0:0:0: -244,116,3098,1,0,1:0:0:0: -332,52,3263,2,0,P|376:48|424:56,1,90,8|0,0:0|0:0,0:0:0:0: -488,228,3593,5,0,1:0:0:0: -460,240,3675,1,0,0:0:0:0: -428,236,3758,1,0,0:0:0:0: -292,160,3923,2,0,P|288:204|300:252,1,90,8|0,0:0|0:0,0:0:0:0: -316,276,4170,1,0,0:0:0:0: -344,292,4252,2,0,L|388:300,1,45,0|0,0:0|0:0,0:0:0:0: -288,356,4417,2,0,L|244:364,1,45,0|0,1:0|0:0,0:0:0:0: -168,328,4582,2,0,P|124:324|72:332,1,90,8|0,0:0|0:0,0:0:0:0: -24,188,4912,5,0,1:0:0:0: -56,192,4994,1,0,0:0:0:0: -88,196,5076,1,0,0:0:0:0: -148,108,5241,1,8,0:0:0:0: -188,240,5406,1,0,1:0:0:0: -188,240,5488,1,0,0:0:0:0: -188,240,5571,2,0,L|168:328,1,90,0|0,0:0|1:0,0:0:0:0: -260,216,5901,2,0,P|236:180|188:164,1,90,8|0,0:0|0:0,0:0:0:0: -248,296,6230,6,0,L|348:292,1,90,0|0,1:0|0:0,0:0:0:0: -504,232,6560,1,8,0:0:0:0: -400,204,6725,1,0,0:0:0:0: -392,176,6807,1,0,0:0:0:0: -384,144,6890,1,0,0:0:0:0: -376,116,6972,1,0,0:0:0:0: -368,88,7054,1,0,1:0:0:0: -188,48,7219,2,0,L|208:140,1,90,8|0,0:0|0:0,0:0:0:0: -248,296,7549,5,0,1:0:0:0: -207,135,7714,1,0,0:0:0:0: -156,232,7879,1,8,0:0:0:0: -316,191,8043,1,0,1:0:0:0: -316,191,8126,1,0,0:0:0:0: -316,191,8208,2,0,L|372:200,1,45,0|0,0:0|0:0,0:0:0:0: -492,200,8373,2,0,L|447:207,1,45,0|0,1:0|0:0,0:0:0:0: -408,136,8538,2,0,P|396:92|400:48,1,90,8|0,0:0|0:0,0:0:0:0: -260,32,8868,5,0,1:0:0:0: -252,64,8950,1,0,0:0:0:0: -236,92,9032,2,0,P|204:116|148:128,1,90,0|8,0:0|0:0,0:0:0:0: -28,188,9362,1,0,0:0:0:0: -60,196,9445,1,0,0:0:0:0: -88,212,9527,2,0,P|112:244|124:300,1,90,0|0,0:0|1:0,0:0:0:0: -112,128,9857,2,0,P|152:156|184:196,1,90,8|0,0:0|0:0,0:0:0:0: -216,288,10186,5,0,1:0:0:0: -216,288,10269,1,0,0:0:0:0: -216,288,10351,1,0,0:0:0:0: -268,192,10516,1,8,0:0:0:0: -356,128,10681,1,0,1:0:0:0: -388,120,10763,1,0,0:0:0:0: -420,128,10846,2,0,P|440:168|436:220,1,90,0|0,0:0|1:0,0:0:0:0: -332,328,11175,2,0,L|280:332,1,45,8|8,0:0|0:0,0:0:0:0: -216,288,11340,2,0,L|164:292,1,45,0|0,1:0|0:0,1:0:0:0: -100,248,11505,5,4,1:2:0:0: -148,116,11670,1,2,0:0:0:0: -268,192,11835,1,10,0:0:0:0: -136,328,11999,2,0,L|44:336,1,90,2|0,0:0|0:0,0:0:0:0: -216,288,12329,1,2,1:2:0:0: -148,116,12494,1,10,0:0:0:0: -100,248,12659,1,2,0:0:0:0: -268,192,12824,5,0,1:0:0:0: -268,192,12906,1,0,0:0:0:0: -268,192,12988,1,0,0:0:0:0: -340,272,13153,2,0,P|384:276|432:264,1,90,8|0,0:0|1:0,0:0:0:0: -452,244,13401,1,0,0:0:0:0: -468,216,13483,2,0,L|476:124,1,90,0|0,0:0|1:0,0:0:0:0: -368,32,13813,2,0,L|360:121,1,90,8|0,0:0|0:0,0:0:0:0: -340,272,14142,6,0,L|316:316,2,45,0|0|0,1:0|0:0|0:0,0:0:0:0: -452,244,14472,1,8,0:0:0:0: -268,192,14637,1,0,0:0:0:0: -236,188,14719,1,0,0:0:0:0: -204,192,14802,2,0,P|172:228|160:272,1,90,0|0,0:0|1:0,0:0:0:0: -128,140,15131,2,0,P|160:104|172:60,1,90,8|0,0:0|0:0,0:0:0:0: -64,52,15461,6,0,L|20:68,2,45,0|0|0,1:0|0:0|0:0,0:0:0:0: -171,64,15791,1,8,0:0:0:0: -264,8,15956,2,0,L|356:12,1,90,0|0,1:0|0:0,0:0:0:0: -452,56,16285,1,0,1:0:0:0: -296,140,16450,2,0,L|206:136,1,90,8|0,0:0|0:0,0:0:0:0: -108,184,16780,6,0,P|92:224|96:272,1,90,0|0,1:0|0:0,0:0:0:0: -200,244,17109,1,8,0:0:0:0: -108,108,17274,2,0,L|12:116,1,90,0|0,0:0|0:0,0:0:0:0: -200,244,17604,1,0,1:0:0:0: -296,140,17769,2,0,L|385:132,1,90,8|0,0:0|0:0,0:0:0:0: -480,184,18098,5,0,1:0:0:0: -488,216,18181,1,0,0:0:0:0: -496,248,18263,2,0,L|492:340,1,90,0|8,0:0|0:0,0:0:0:0: -404,224,18593,2,0,L|396:176,2,45,0|0|0,1:0|0:0|0:0,0:0:0:0: -304,264,18923,1,0,1:0:0:0: -200,244,19087,2,0,P|156:240|108:248,1,90,8|0,0:0|0:0,0:0:0:0: -296,140,19417,6,0,P|340:144|388:136,1,90,0|0,1:0|0:0,0:0:0:0: -440,44,19747,1,8,0:0:0:0: -404,224,19912,1,0,0:0:0:0: -404,224,19994,1,0,0:0:0:0: -404,224,20076,2,0,L|412:320,1,90,0|0,0:0|1:0,0:0:0:0: -200,244,20406,2,0,L|192:154,1,90,8|0,0:0|0:0,0:0:0:0: -184,44,20736,5,4,1:2:0:0: -152,40,20818,1,0,0:0:0:0: -120,48,20901,1,0,0:0:0:0: -96,68,20983,1,0,0:0:0:0: -76,92,21065,1,2,0:3:0:0: -64,120,21148,1,0,0:0:0:0: -60,152,21230,1,0,1:0:0:0: -64,184,21313,1,0,0:0:0:0: -76,212,21395,2,0,L|96:252,3,45,0|0|0|0,0:0|0:0|1:0|0:0,0:0:0:0: -144,316,21725,2,0,L|188:324,3,45,0|0|2|0,0:0|0:0|0:3|0:0,0:0:0:0: -268,340,22054,6,0,L|364:336,1,90,4|0,1:2|0:0,0:0:0:0: -452,280,22384,1,8,0:0:0:0: -512,188,22549,2,0,P|516:144|504:96,1,90,2|0,0:0|0:0,0:0:0:0: -340,24,22879,2,0,P|336:68|348:116,1,90,2|8,1:2|0:0,0:0:0:0: -420,192,23208,1,2,0:0:0:0: -328,252,23373,6,0,L|232:240,1,90,0|0,1:0|0:0,0:0:0:0: -64,256,23703,1,8,0:0:0:0: -144,184,23868,2,0,P|148:140|136:88,1,90,0|0,1:0|0:0,0:0:0:0: -40,52,24197,1,2,1:2:0:0: -139,95,24362,1,8,0:0:0:0: -216,20,24527,1,0,0:0:0:0: -315,63,24692,6,0,P|360:72|408:68,1,90,2|0,1:2|0:0,0:0:0:0: -492,132,25021,1,8,0:0:0:0: -412,204,25186,2,0,P|403:249|407:297,1,90,2|0,0:0|0:0,0:0:0:0: -268,328,25516,2,0,P|277:283|273:235,1,90,2|8,1:2|0:0,0:0:0:0: -232,140,25846,2,0,P|187:131|139:135,1,90,2|0,0:0|1:0,0:0:0:0: -64,208,26175,5,2,0:0:0:0: -44,316,26340,1,8,0:0:0:0: -148,280,26505,1,2,1:2:0:0: -456,208,26835,1,2,1:2:0:0: -476,316,26999,1,10,0:0:0:0: -372,280,27164,1,2,0:0:0:0: -356,172,27329,6,0,L|380:80,1,90,0|0,1:0|0:0,0:0:0:0: -456,208,27659,1,8,0:0:0:0: -300,236,27824,1,2,0:0:0:0: -300,236,27906,1,0,0:0:0:0: -300,236,27988,2,0,L|208:228,1,90,0|2,0:0|1:2,0:0:0:0: -140,312,28318,1,8,0:0:0:0: -372,280,28483,2,0,L|464:272,1,90,2|0,0:0|1:0,0:0:0:0: -500,136,28813,5,2,0:0:0:0: -432,56,28977,1,8,0:0:0:0: -328,24,29142,2,0,P|284:24|236:28,1,90,2|0,1:2|0:0,0:0:0:0: -80,144,29472,1,2,1:2:0:0: -116,44,29637,1,10,0:0:0:0: -184,128,29802,1,2,0:0:0:0: -20,88,29966,6,0,P|1:164|73:227,1,180,2|10,1:2|0:0,0:0:0:0: -184,128,30461,2,0,P|227:120|276:124,1,90,2|0,0:0|0:0,0:0:0:0: -392,188,30791,1,2,1:2:0:0: -272,260,30956,1,8,0:0:0:0: -396,328,31120,1,0,0:0:0:0: -256,348,31285,5,2,1:2:0:0: -224,344,31368,1,0,1:0:0:0: -192,340,31450,2,0,L|172:248,1,90,2|0,1:2|1:0,0:0:0:0: -8,136,31780,2,0,L|27:223,1,90,2|0,1:2|0:0,0:0:0:0: -56,328,32109,1,2,1:2:0:0: -108,192,32274,1,2,1:2:0:0: -100,160,32357,1,0,1:0:0:0: -92,132,32439,1,2,1:2:0:0: -84,104,32521,1,0,1:0:0:0: -76,72,32604,6,0,P|100:112|148:136,1,90,4|0,1:2|0:0,0:0:0:0: -240,168,32934,1,8,0:0:0:0: -336,124,33098,2,0,L|344:80,2,45,2|0|0,0:0|0:0|0:0,0:0:0:0: -264,248,33428,2,0,P|220:248|176:220,1,90,2|8,1:2|0:0,0:0:0:0: -260,84,33758,1,2,0:0:0:0: -344,212,33923,5,0,1:0:0:0: -344,212,34005,1,0,0:0:0:0: -344,212,34087,1,0,0:0:0:0: -440,160,34252,1,8,0:0:0:0: -312,320,34417,2,0,P|272:336|220:324,1,90,0|0,1:0|0:0,0:0:0:0: -156,176,34747,2,0,P|196:160|248:172,2,90,2|8|0,1:2|0:0|0:0,0:0:0:0: -132,280,35241,5,2,1:2:0:0: -132,280,35324,1,0,0:0:0:0: -132,280,35406,2,0,L|120:376,1,90,0|8,0:0|0:0,0:0:0:0: -312,320,35736,2,0,L|300:230,1,90,2|0,0:0|0:0,0:0:0:0: -316,124,36065,1,2,1:2:0:0: -400,192,36230,1,8,0:0:0:0: -300,230,36395,2,0,P|255:231|211:224,1,90,2|0,0:0|1:0,0:0:0:0: -24,132,36725,5,0,0:0:0:0: -132,152,36890,1,8,0:0:0:0: -60,232,37054,1,2,1:2:0:0: -60,232,37137,1,0,0:0:0:0: -60,232,37219,1,0,0:0:0:0: -92,56,37384,2,0,L|184:44,1,90,2|10,1:2|0:0,0:0:0:0: -316,124,37714,2,0,L|226:135,1,90,2|0,0:0|1:0,0:0:0:0: -60,232,38043,6,0,P|52:276|64:328,1,90,0|8,0:0|0:0,0:0:0:0: -220,152,38373,2,0,P|176:144|124:156,1,90,2|0,0:0|0:0,0:0:0:0: -176,252,38703,1,2,1:2:0:0: -323,213,38868,2,0,L|316:124,1,90,8|2,0:0|0:0,0:0:0:0: -332,320,39197,5,0,1:0:0:0: -424,260,39362,1,2,0:0:0:0: -260,272,39527,2,0,P|246:313|256:360,1,90,8|2,0:0|1:2,0:0:0:0: -408,336,39857,1,0,0:0:0:0: -176,252,40021,2,0,L|80:260,2,90,2|10|2,1:2|0:0|0:0,0:0:0:0: -324,212,40516,5,2,1:2:0:0: -324,212,40598,1,0,1:0:0:0: -324,212,40681,1,0,1:0:0:0: -200,336,40846,1,2,1:2:0:0: -236,188,41010,1,2,1:2:0:0: -236,188,41093,1,0,1:0:0:0: -236,188,41175,1,0,1:0:0:0: -281,357,41340,1,2,1:2:0:0: -176,252,41505,1,2,1:2:0:0: -176,252,41587,1,0,1:0:0:0: -176,252,41670,1,0,1:0:0:0: -344,297,41835,5,2,1:2:0:0: -432,232,41999,1,2,1:2:0:0: -444,204,42082,1,0,1:0:0:0: -448,172,42164,1,0,1:0:0:0: -444,140,42247,1,0,1:0:0:0: -432,112,42329,2,0,L|440:64,2,45,2|0|0,1:2|1:0|1:0,0:0:0:0: -236,188,42659,1,0,0:0:0:0: -340,172,42824,1,2,0:3:0:0: -272,88,42988,1,0,0:0:0:0: -132,160,43153,6,0,P|148:248|220:296,1,180,4|8,1:2|0:0,0:0:0:0: -324,320,43648,2,0,L|336:364,2,45,0|0|0,0:0|0:0|0:0,0:0:0:0: -292,216,43977,1,0,1:0:0:0: -396,240,44142,2,0,P|440:244|488:232,1,90,8|0,0:0|0:0,0:0:0:0: -328,124,44472,6,0,P|284:120|236:132,1,90,0|0,1:0|0:0,0:0:0:0: -168,212,44802,1,8,0:0:0:0: -192,316,44966,1,0,1:0:0:0: -140,220,45131,1,0,0:0:0:0: -83,310,45296,1,0,1:0:0:0: -114,205,45461,1,8,0:0:0:0: -10,229,45626,1,0,0:0:0:0: -106,176,45791,6,0,P|113:133|108:85,1,90,0|0,1:0|0:0,0:0:0:0: -204,136,46120,1,8,0:0:0:0: -256,40,46285,1,0,0:0:0:0: -256,40,46368,1,0,0:0:0:0: -256,40,46450,2,0,L|356:44,1,90,0|0,0:0|1:0,0:0:0:0: -501,124,46780,2,0,L|412:128,1,90,8|0,0:0|0:0,0:0:0:0: -324,192,47109,5,0,1:0:0:0: -356,296,47274,1,0,0:0:0:0: -284,216,47439,1,8,0:0:0:0: -269,323,47604,1,0,1:0:0:0: -237,220,47769,1,0,0:0:0:0: -178,311,47934,1,0,1:0:0:0: -191,203,48098,1,8,0:0:0:0: -99,261,48263,1,0,0:0:0:0: -156,168,48428,6,0,B|176:112|136:64|136:64|200:96,1,180,4|8,1:2|0:0,0:0:0:0: -300,124,48923,2,0,L|392:120,1,90,0|0,0:0|0:0,0:0:0:0: -468,48,49252,1,0,1:0:0:0: -390,120,49417,2,0,P|390:164|406:208,1,90,8|0,0:0|0:0,0:0:0:0: -352,344,49747,6,0,P|352:300|336:256,1,90,4|0,1:2|0:0,0:0:0:0: -240,208,50076,1,8,0:0:0:0: -163,320,50241,2,0,P|207:324|252:316,1,90,0|0,1:0|0:0,0:0:0:0: -240,208,50571,1,0,1:0:0:0: -76,296,50736,2,0,P|76:340|92:384,1,90,8|0,0:0|0:0,0:0:0:0: -312,164,51065,6,0,P|236:124|160:184,1,180,4|8,1:2|0:0,0:0:0:0: -247,297,51560,2,0,L|240:208,1,90,0|0,0:0|0:0,0:0:0:0: -224,48,51890,1,0,1:0:0:0: -332,56,52054,2,0,L|366:58,5,30,8|0|0|0|0|0,0:0|0:0|0:0|0:0|0:0|0:0,0:0:0:0: -408,64,52384,6,0,P|420:108|416:156,1,90,0|0,1:0|0:0,0:0:0:0: -360,260,52714,1,8,0:0:0:0: -247,297,52879,2,0,B|203:281|159:297|159:297|115:313|71:297,1,180,0|0,1:0|1:0,0:0:0:0: -116,196,53373,1,8,0:0:0:0: -120,164,53456,1,0,0:0:0:0: -124,132,53538,1,0,0:0:0:0: -128,100,53620,1,0,0:0:0:0: -132,68,53703,5,4,1:2:0:0: -40,136,53868,1,0,0:0:0:0: -204,160,54032,2,0,L|304:152,1,90,8|0,0:0|0:0,0:0:0:0: -408,64,54362,1,0,0:0:0:0: -408,64,54445,1,0,0:0:0:0: -408,64,54527,2,0,P|404:112|416:160,1,90,0|8,1:0|0:0,0:0:0:0: -484,236,54857,1,0,0:0:0:0: -428,328,55021,5,0,1:0:0:0: -328,296,55186,1,0,0:0:0:0: -328,296,55269,1,0,0:0:0:0: -328,296,55351,1,8,0:0:0:0: -416,300,55516,1,0,1:0:0:0: -472,208,55681,1,0,0:0:0:0: -316,268,55846,1,0,1:0:0:0: -460,180,56010,1,8,0:0:0:0: -304,240,56175,1,0,0:0:0:0: -404,272,56340,5,0,1:0:0:0: -448,152,56505,1,0,0:0:0:0: -448,152,56587,1,0,0:0:0:0: -448,152,56670,2,0,P|456:112|448:60,1,90,8|0,0:0|0:0,0:0:0:0: -268,28,56999,2,0,P|260:68|268:120,1,90,0|0,0:0|1:0,0:0:0:0: -404,272,57329,2,0,P|444:280|496:272,2,90,8|0|0,0:0|0:0|1:0,0:0:0:0: -304,240,57824,5,0,0:0:0:0: -252,336,57988,1,8,0:0:0:0: -196,244,58153,1,0,1:0:0:0: -24,256,58318,1,0,0:0:0:0: -116,200,58483,1,0,1:0:0:0: -136,60,58648,1,8,0:0:0:0: -192,152,58813,1,0,0:0:0:0: -304,240,58977,6,0,P|348:252|396:248,1,90,0|0,1:0|0:0,0:0:0:0: -456,116,59307,2,0,P|412:104|364:108,1,90,8|0,0:0|0:0,0:0:0:0: -273,161,59637,1,0,0:0:0:0: -136,60,59802,1,0,1:0:0:0: -192,152,59966,1,8,0:0:0:0: -23,177,60131,1,0,0:0:0:0: -129,203,60296,5,0,1:0:0:0: -88,304,60461,2,0,P|132:311|176:303,1,90,0|8,0:0|0:0,0:0:0:0: -304,240,60791,1,0,1:0:0:0: -304,240,60873,1,0,0:0:0:0: -304,240,60956,2,0,L|312:288,3,45,0|0|0|0,0:0|0:0|1:0|0:0,0:0:0:0: -384,256,61285,2,0,L|392:304,3,45,8|0|0|0,0:0|0:0|0:0|0:0,0:0:0:0: -464,272,61615,5,2,1:2:0:0: -488,168,61780,1,2,0:0:0:0: -428,80,61945,1,10,0:0:0:0: -332,32,62109,2,0,P|288:28|240:36,1,90,2|0,0:0|0:0,0:0:0:0: -28,216,62439,1,2,1:2:0:0: -88,304,62604,1,10,0:0:0:0: -184,352,62769,2,0,P|228:356|276:348,1,90,2|0,0:0|1:0,0:0:0:0: -384,256,63098,6,0,P|409:219|426:174,1,90,2|8,0:0|0:0,0:0:0:0: -428,80,63428,2,0,L|420:36,2,45,2|0|0,1:2|0:0|0:0,0:0:0:0: -456,288,63758,1,2,1:2:0:0: -324,200,63923,1,10,1:2:0:0: -292,204,64005,1,0,1:0:0:0: -260,208,64087,1,2,1:2:0:0: -228,212,64170,1,0,1:0:0:0: -196,216,64252,5,4,1:2:0:0: -104,160,64417,1,0,0:0:0:0: -228,296,64582,2,0,L|320:284,1,90,8|0,0:0|0:0,0:0:0:0: -344,112,64912,1,0,0:0:0:0: -344,112,64994,1,0,0:0:0:0: -344,112,65076,2,0,L|254:123,1,90,0|8,1:0|0:0,0:0:0:0: -144,284,65406,2,0,P|148:328|176:364,1,90,0|0,0:0|1:0,0:0:0:0: -196,216,65736,5,0,0:0:0:0: -196,216,65818,1,0,0:0:0:0: -196,216,65901,2,0,P|155:198|110:205,1,90,8|0,0:0|1:0,0:0:0:0: -36,284,66230,1,0,0:0:0:0: -4,180,66395,1,0,1:0:0:0: -132,24,66560,1,8,0:0:0:0: -100,128,66725,1,0,0:0:0:0: -24,48,66890,5,0,1:0:0:0: -212,108,67054,1,0,0:0:0:0: -212,108,67137,1,0,0:0:0:0: -212,108,67219,2,0,L|300:92,1,90,8|0,0:0|0:0,0:0:0:0: -472,144,67549,2,0,L|384:160,1,90,0|0,0:0|1:0,0:0:0:0: -196,216,67879,2,0,P|240:216|288:240,1,90,8|0,0:0|0:0,0:0:0:0: -324,336,68208,5,0,1:0:0:0: -144,288,68373,1,0,0:0:0:0: -58,170,68538,1,8,0:0:0:0: -196,215,68703,1,0,1:0:0:0: -58,260,68868,1,0,0:0:0:0: -144,142,69032,2,0,L|138:108,2,30,0|0|0,1:0|0:0|0:0,0:0:0:0: -144,142,69197,2,0,P|184:124|232:132,1,90,8|0,0:0|0:0,0:0:0:0: -312,248,69527,6,0,L|324:338,1,90,0|0,1:0|0:0,0:0:0:0: -436,248,69857,1,8,0:0:0:0: -432,216,69939,1,0,0:0:0:0: -428,184,70021,1,0,0:0:0:0: -328,120,70186,1,0,0:0:0:0: -324,152,70269,1,0,0:0:0:0: -320,184,70351,1,0,1:0:0:0: -316,216,70434,1,0,0:0:0:0: -312,248,70516,2,0,L|320:300,1,45,8|0,0:0|0:0,0:0:0:0: -244,340,70681,2,0,L|237:295,1,45,0|0,0:0|0:0,0:0:0:0: -216,224,70846,6,0,P|168:216|124:224,1,90,0|0,1:0|0:0,0:0:0:0: -40,288,71175,1,8,0:0:0:0: -2,95,71340,2,0,P|-4:139|4:184,1,90,0|0,1:0|0:0,0:0:0:0: -164,304,71670,1,0,1:0:0:0: -312,248,71835,1,8,0:0:0:0: -244,340,71999,1,0,0:0:0:0: -216,224,72164,6,0,L|228:132,1,90,0|0,1:0|0:0,0:0:0:0: -332,148,72494,2,0,L|344:56,1,90,8|0,0:0|0:0,0:0:0:0: -312,248,72824,1,0,0:0:0:0: -164,304,72988,1,0,1:0:0:0: -332,336,73153,1,8,0:0:0:0: -360,324,73236,1,0,0:0:0:0: -384,304,73318,1,0,0:0:0:0: -399,276,73401,1,0,0:0:0:0: -403,244,73483,6,0,L|396:200,3,45,4|0|2|0,1:2|0:0|0:0|1:0,0:0:0:0: -420,112,73813,2,0,L|427:68,3,45,2|0|2|0,1:2|0:0|1:2|0:0,0:0:0:0: -352,16,74142,2,0,L|345:60,3,45,0|0|2|0,0:0|1:0|1:2|0:0,0:0:0:0: -332,148,74472,1,2,1:2:0:0: -332,148,74554,1,0,1:0:0:0: -332,148,74637,1,2,1:2:0:0: -332,148,74719,1,0,1:0:0:0: -332,148,74802,6,0,P|360:216|320:312,1,180,4|2,1:2|0:3,0:0:0:0: -190,310,75296,2,0,P|151:231|180:148,1,180,4|0,1:2|0:0,0:0:0:0: -256,56,75791,1,0,0:0:0:0: -332,148,75956,1,2,0:3:0:0: -179,148,76120,5,4,1:2:0:0: -336,64,76285,1,4,1:2:0:0: -256,224,76450,1,2,0:3:0:0: -176,64,76615,1,4,1:2:0:0: -256,140,76780,2,0,L|256:324,1,180,2|0,0:0|0:0,0:0:0:0: -364,300,77274,1,2,0:3:0:0: -148,300,77439,6,0,P|104:316|76:356,1,90,4|0,1:2|0:0,0:0:0:0: -24,252,77769,2,0,L|16:208,3,45,8|0|0|0,0:0|0:0|0:0|0:0,0:0:0:0: -96,212,78098,2,0,L|104:168,3,45,0|0|0|0,0:0|0:0|1:0|0:0,0:0:0:0: -32,128,78428,2,0,L|24:84,3,45,8|0|0|0,0:0|0:0|0:0|0:0,0:0:0:0: -104,88,78758,5,2,1:2:0:0: -204,132,78923,1,0,0:0:0:0: -236,124,79005,1,0,0:0:0:0: -268,116,79087,2,0,L|280:68,3,45,8|0|0|0,0:0|0:0|1:0|0:0,0:0:0:0: -348,100,79417,2,0,L|360:52,3,45,0|0|0|0,0:0|0:0|1:0|0:0,0:0:0:0: -428,84,79747,1,8,1:2:0:0: -460,76,79829,1,0,1:0:0:0: -492,68,79912,1,0,1:0:0:0: -492,260,80076,6,0,P|400:248|328:296,1,180,4|2,1:2|0:3,0:0:0:0: -144,236,80571,2,0,P|236:248|308:200,1,180,4|0,1:2|0:0,0:0:0:0: -348,100,81065,2,0,P|348:56|336:8,1,90,0|2,0:0|0:3,0:0:0:0: -140,48,81395,5,4,1:2:0:0: -244,68,81560,1,4,1:2:0:0: -144,236,81725,1,2,0:3:0:0: -176,133,81890,1,4,1:2:0:0: -184,304,82054,2,0,P|100:300|68:220,1,180,2|0,0:0|0:0,0:0:0:0: -100,116,82549,1,2,0:3:0:0: -264,244,82714,6,0,L|272:340,1,90,4|0,1:2|0:0,0:0:0:0: -380,316,83043,1,8,0:0:0:0: -396,288,83126,1,0,0:0:0:0: -400,256,83208,1,0,0:0:0:0: -396,224,83291,1,0,0:0:0:0: -380,196,83373,2,0,L|336:176,3,45,0|0|0|0,0:0|0:0|1:0|0:0,0:0:0:0: -272,148,83703,1,8,0:0:0:0: -256,120,83785,1,0,0:0:0:0: -252,88,83868,1,0,0:0:0:0: -256,56,83950,1,0,0:0:0:0: -272,28,84032,6,0,L|316:8,3,45,2|0|0|0,1:2|0:0|0:0|0:0,0:0:0:0: -360,72,84362,2,0,L|408:72,3,45,8|0|0|0,0:0|0:0|1:0|0:0,0:0:0:0: -421,149,84692,2,0,L|464:169,3,45,2|0|0|0,0:0|0:0|1:0|0:0,0:0:0:0: -443,244,85021,2,0,L|473:281,3,45,8|0|0|0,1:2|1:0|0:0|0:0,0:0:0:0: -422,339,85351,6,0,L|240:348,1,180,4|2,1:2|0:3,0:0:0:0: -76,172,85846,2,0,L|255:163,1,180,4|0,1:2|0:0,0:0:0:0: -421,149,86340,2,0,P|435:107|428:56,1,90,0|2,0:0|0:3,0:0:0:0: -228,56,86670,5,4,1:2:0:0: -280,192,86835,1,4,1:2:0:0: -328,96,86999,1,2,0:3:0:0: -180,152,87164,1,4,1:2:0:0: -28,100,87330,2,0,P|16:56|20:8,1,90,2|0,0:0|0:0,0:0:0:0: -0,180,87659,1,0,0:0:0:0: -28,284,87824,1,2,0:3:0:0: -108,352,87988,6,0,P|152:360|196:356,1,90,4|0,1:2|0:0,0:0:0:0: -276,284,88318,1,8,0:0:0:0: -304,272,88401,1,0,0:0:0:0: -336,268,88483,1,0,0:0:0:0: -368,272,88565,1,0,0:0:0:0: -396,284,88648,2,0,L|432:312,1,45,0|0,0:0|0:0,0:0:0:0: -488,252,88813,2,0,L|452:224,1,45,0|0,1:0|0:0,0:0:0:0: -400,164,88977,2,0,L|396:116,3,45,8|0|0|0,0:0|0:0|0:0|0:0,0:0:0:0: -316,64,89307,6,0,L|320:160,1,90,2|0,1:2|0:0,0:0:0:0: -276,284,89637,1,8,0:0:0:0: -248,296,89719,1,0,0:0:0:0: -216,300,89802,1,0,1:0:0:0: -184,296,89884,1,0,0:0:0:0: -156,284,89966,2,0,L|120:256,1,45,0|0,0:0|0:0,0:0:0:0: -176,200,90131,2,0,L|140:172,1,45,0|0,1:0|0:0,0:0:0:0: -196,116,90296,2,0,L|160:88,3,45,8|0|0|0,1:2|1:0|1:0|0:0,0:0:0:0: -92,44,90626,6,0,P|48:44|24:160,1,180,4|2,1:2|0:3,0:0:0:0: -156,284,91120,2,0,B|200:300|244:284|244:284|288:268|332:284,1,180,4|0,1:2|0:0,0:0:0:0: -176,200,91615,2,0,P|176:156|196:116,1,90,0|2,0:0|0:3,0:0:0:0: -264,28,91945,6,0,L|353:39,1,90,4|0,1:2|1:0,0:0:0:0: -453,159,92274,2,0,L|364:148,1,90,2|4,0:3|1:2,0:0:0:0: -268,196,92604,2,0,P|260:268|328:348,1,180,2|0,0:0|0:0,0:0:0:0: -364,248,93098,1,2,0:3:0:0: -176,200,93263,5,4,1:2:0:0: -72,228,93428,1,0,1:0:0:0: -152,92,93593,1,0,1:0:0:0: -256,64,93758,1,0,1:0:0:0: -336,200,93923,5,0,1:0:0:0: -440,228,94087,1,0,1:0:0:0: -360,92,94252,1,0,1:0:0:0: -256,64,94417,1,0,1:0:0:0: -176,200,94582,5,2,1:2:0:0: -168,228,94664,1,0,1:0:0:0: -168,260,94747,1,0,1:0:0:0: -172,292,94829,1,0,1:0:0:0: -192,316,94912,1,0,1:0:0:0: -220,328,94994,1,0,1:0:0:0: -252,332,95076,1,0,1:0:0:0: -280,320,95159,1,0,1:0:0:0: -300,296,95241,2,0,L|308:248,3,45,2|0|0|0,1:2|1:0|1:0|1:0,0:0:0:0: -312,172,95571,2,0,L|304:127,3,45,0|0|0|0,1:0|1:0|1:0|1:0,0:0:0:0: -256,64,95901,6,0,P|208:56|164:60,1,90,4|0,1:2|0:0,0:0:0:0: -76,116,96230,1,8,0:0:0:0: -60,224,96395,1,0,0:0:0:0: -60,224,96477,1,0,0:0:0:0: -160,184,96642,1,0,0:0:0:0: -160,184,96725,1,0,1:0:0:0: -63,26,96890,2,0,L|76:116,1,90,8|0,0:0|0:0,0:0:0:0: -136,272,97219,5,0,1:0:0:0: -168,268,97302,1,0,0:0:0:0: -200,264,97384,1,0,0:0:0:0: -232,260,97466,1,0,0:0:0:0: -264,256,97549,1,8,0:0:0:0: -384,136,97714,1,0,1:0:0:0: -376,168,97796,1,0,0:0:0:0: -380,200,97879,1,0,0:0:0:0: -392,228,97961,1,0,0:0:0:0: -416,248,98043,2,0,P|464:260|512:260,2,90,0|8|0,1:0|0:0|0:0,0:0:0:0: -231,105,98538,6,0,L|188:116,2,45,0|0|0,1:0|0:0|0:0,0:0:0:0: -376,56,98868,2,0,L|420:64,1,45,8|0,0:0|0:0,0:0:0:0: -384,136,99032,1,0,0:0:0:0: -384,136,99115,2,0,P|340:128|304:92,1,90,0|0,0:0|0:0,0:0:0:0: -303,18,99362,2,0,L|207:26,2,90,0|8|0,1:0|0:0|0:0,0:0:0:0: -452,88,99857,5,0,1:0:0:0: -465,116,99939,1,0,0:0:0:0: -466,147,100021,1,0,0:0:0:0: -456,177,100104,1,0,0:0:0:0: -436,201,100186,2,0,P|416:213|389:216,3,45,8|0|0|0,0:0|0:0|1:0|0:0,0:0:0:0: -320,188,100516,2,0,P|300:176|273:173,3,45,0|0|0|0,0:0|1:0|1:0|0:0,0:0:0:0: -204,200,100846,2,0,P|192:220|189:247,3,45,8|0|0|0,0:0|0:0|1:0|0:0,0:0:0:0: -188,320,101175,6,0,P|143:322|100:310,1,90,0|0,1:0|0:0,0:0:0:0: -76,292,101423,1,0,0:0:0:0: -76,292,101505,1,8,0:0:0:0: -76,292,101587,2,0,L|72:248,1,45 -12,68,101835,2,0,L|6:24,2,45,0|0|0,0:0|0:0|1:0,0:0:0:0: -104,140,102164,2,0,L|171:132,3,45,8|0|0|0,0:0|0:0|0:0|0:0,0:0:0:0: -224,124,102494,6,0,P|236:164|232:216,1,90,0|0,1:0|0:0,0:0:0:0: -288,296,102824,1,8,0:0:0:0: -288,296,102906,1,0,0:0:0:0: -288,296,102988,2,0,P|328:284|380:288,1,90,0|0,1:0|0:0,0:0:0:0: -404,304,103236,1,0,0:0:0:0: -424,328,103318,1,0,1:0:0:0: -448,188,103483,2,0,L|440:140,3,45,8|0|0|0,0:0|0:0|0:0|0:0,0:0:0:0: -424,72,103813,5,0,1:0:0:0: -324,112,103977,1,0,0:0:0:0: -324,112,104060,1,0,0:0:0:0: -324,112,104142,2,0,P|280:116|232:104,1,90,8|0,0:0|0:0,0:0:0:0: -160,28,104472,1,0,0:0:0:0: -216,208,104637,1,0,1:0:0:0: -216,208,104719,1,0,0:0:0:0: -216,208,104802,1,8,0:0:0:0: -352,240,104966,1,0,0:0:0:0: -384,244,105049,1,0,0:0:0:0: -416,248,105131,6,0,L|460:240,4,45,0|0|0|0|8,1:0|0:0|0:0|0:0|0:0,0:0:0:0: -272,288,105626,1,0,1:0:0:0: -264,320,105708,1,0,0:0:0:0: -256,352,105791,2,0,L|204:356,5,30,0|0|0|0|0|0,0:0|0:0|0:0|1:0|0:0|0:0,0:0:0:0: -156,332,106120,2,0,L|104:336,5,30,8|0|0|0|0|0,0:0|0:0|0:0|1:0|0:0|0:0,0:0:0:0: -56,312,106450,5,4,1:2:0:0: -4,188,106615,1,0,0:0:0:0: -168,220,106780,2,0,P|127:232|79:228,1,90,8|0,0:0|0:0,0:0:0:0: -112,124,107109,1,0,0:0:0:0: -272,216,107274,2,0,L|264:316,1,90,0|8,1:0|0:0,0:0:0:0: -400,268,107604,1,0,0:0:0:0: -428,132,107769,5,0,1:0:0:0: -428,132,107851,1,0,0:0:0:0: -428,132,107934,1,0,0:0:0:0: -428,132,108016,1,0,0:0:0:0: -428,132,108098,1,8,0:0:0:0: -332,84,108263,2,0,P|288:80|232:88,1,90,0|0,1:0|0:0,0:0:0:0: -112,124,108593,1,0,1:0:0:0: -148,264,108758,1,8,0:0:0:0: -16,236,108923,1,0,0:0:0:0: -264,126,109087,6,0,L|272:216,1,90,0|0,1:0|0:0,0:0:0:0: -452,224,109417,2,0,L|460:320,1,90,8|0,0:0|0:0,0:0:0:0: -360,232,109747,1,0,0:0:0:0: -348,56,109912,1,0,1:0:0:0: -416,140,110076,1,8,0:0:0:0: -256,112,110241,2,0,P|212:120|160:112,1,90,0|0,0:0|1:0,0:0:0:0: -348,56,110571,6,0,L|331:150,1,90,0|8,0:0|0:0,0:0:0:0: -208,328,110901,2,0,L|191:239,1,90,0|0,1:0|0:0,0:0:0:0: -184,216,111148,1,0,1:0:0:0: -178,194,111230,1,0,1:0:0:0: -68,272,111395,1,8,0:0:0:0: -56,136,111560,1,0,1:0:0:0: -178,194,111725,6,0,P|219:203|267:199,1,90,4|0,1:2|0:0,0:0:0:0: -364,148,112054,1,8,0:0:0:0: -384,256,112219,2,0,P|406:291|443:322,1,90,0|0,0:0|0:0,0:0:0:0: -488,224,112549,1,0,1:0:0:0: -304,232,112714,2,0,L|208:224,2,90,8|0|0,0:0|0:0|1:0,0:0:0:0: -208,328,113208,6,0,L|112:320,1,90,0|8,0:0|0:0,0:0:0:0: -26,184,113538,2,0,L|116:192,1,90,0|0,1:0|0:0,0:0:0:0: -304,232,113868,1,0,1:0:0:0: -116,192,114032,1,8,0:0:0:0: -224,132,114197,1,0,0:0:0:0: -208,328,114362,6,0,B|272:360|320:312|320:312|340:368,1,180,4|8,1:2|0:0,0:0:0:0: -304,232,114857,2,0,P|300:184|308:140,1,90,0|0,0:0|0:0,0:0:0:0: -384,64,115186,1,0,1:0:0:0: -307,143,115351,1,8,0:0:0:0: -256,48,115516,1,0,0:0:0:0: -456,24,115681,6,0,B|482:101|420:136|420:136|440:184,1,180,4|8,1:2|0:0,0:0:0:0: -384,64,116175,2,0,P|340:56|296:64,1,90,0|0,1:0|0:0,0:0:0:0: -211,171,116505,1,0,1:0:0:0: -439,181,116670,2,0,L|448:84,1,90,8|0,0:0|0:0,0:0:0:0: -372,296,116999,6,2,L|304:292,1,67.5000025749208,2|0,0:1|0:0,0:0:0:0: -136,252,117329,6,2,P|196:260|212:172,1,168.75,0|0,0:0|0:0,0:0:0:0: -192,148,117659,1,2,0:3:0:0: -164,132,117741,1,2,0:3:0:0: -132,124,117824,1,2,1:3:0:0: -100,132,117906,1,2,0:3:0:0: -72,148,117988,2,0,L|52:56,1,90,2|8,0:3|0:0,0:0:0:0: -36,244,118318,5,0,1:0:0:0: -76,344,118483,1,0,1:0:0:0: -184,352,118648,1,0,1:0:0:0: -244,264,118813,1,0,1:0:0:0: -244,264,118895,1,0,1:0:0:0: -244,264,118977,2,0,L|288:260,3,45,2|0|2|0,1:2|0:0|0:0|0:0,0:0:0:0: -332,328,119307,2,0,L|376:324,3,45,2|0|2|0,1:2|0:0|0:0|0:0,0:0:0:0: -412,252,119637,5,4,1:2:0:0: -256,192,119719,12,0,122274,0:0:0:0: -256,192,140735,6,0,L|228:156,1,45,4|0,1:2|0:0,0:0:0:0: -152,132,141065,2,0,P|129:129|104:136,1,45 -48,192,141395,2,0,P|40:236|52:280,1,90,8|8,0:0|0:0,0:0:0:0: -196,352,142054,6,0,L|308:340,1,90,8|8,0:0|1:2,0:0:0:0: -336,280,142549,1,0,0:0:0:0: -404,324,142713,1,8,0:0:0:0: -404,324,142878,1,8,0:0:0:0: -292,120,143373,5,0,1:0:0:0: -212,104,143538,1,0,0:0:0:0: -140,140,143702,1,0,0:0:0:0: -120,220,143867,1,0,0:0:0:0: -144,296,144032,2,0,P|184:320|228:316,1,90,10|8,0:0|0:0,0:0:0:0: -372,212,144691,6,0,P|327:209|290:232,1,90,10|8,0:0|1:2,0:0:0:0: -348,288,145186,1,0,0:0:0:0: -452,220,145351,1,10,0:0:0:0: -452,220,145516,1,8,0:0:0:0: -328,36,146010,5,2,1:2:0:0: -264,88,146175,1,0,0:0:0:0: -184,108,146340,1,0,0:0:0:0: -104,88,146505,1,0,0:0:0:0: -44,36,146669,1,8,0:0:0:0: -44,36,146999,1,8,0:0:0:0: -44,36,147329,6,0,L|24:84,1,45,8|0,0:0|0:0,0:0:0:0: -52,156,147658,2,0,L|71:204,1,45,8|0,1:2|0:0,0:0:0:0: -144,236,147988,1,8,0:0:0:0: -144,236,148153,1,8,0:0:0:0: -316,64,148647,5,0,1:0:0:0: -380,116,148812,1,0,0:0:0:0: -408,192,148977,1,0,0:0:0:0: -380,268,149142,1,0,0:0:0:0: -316,320,149307,2,0,L|224:316,1,90,10|8,0:0|0:0,0:0:0:0: -64,248,149966,5,10,0:0:0:0: -144,236,150131,1,0,0:0:0:0: -188,168,150296,1,8,1:2:0:0: -192,88,150461,1,0,0:0:0:0: -140,24,150626,2,0,P|120:16|96:20,1,45,10|0,0:0|0:0,0:0:0:0: -260,132,150955,2,0,P|280:140|304:136,1,45,2|0,0:0|0:0,0:0:0:0: -476,48,151285,6,0,L|484:160,1,112.5,4|0,1:2|0:0,0:0:0:0: -464,236,151779,1,0,0:0:0:0: -436,308,151944,2,0,P|380:320|324:308,1,112.5,8|8,0:0|0:0,0:0:0:0: -76,308,152604,6,0,P|132:320|188:308,1,112.5,8|8,0:0|1:2,0:0:0:0: -256,88,153263,1,8,0:0:0:0: -256,168,153428,1,8,0:0:0:0: -256,168,153922,5,4,1:2:0:0: -256,248,154087,1,0,0:0:0:0: -324,128,154252,1,0,0:0:0:0: -188,128,154417,1,0,0:0:0:0: -332,212,154582,2,0,L|388:204,1,56.25,10|0,0:0|0:0,0:0:0:0: -492,152,154911,2,0,L|436:144,1,56.25,8|0,0:0|0:0,0:0:0:0: -324,128,155241,5,10,0:0:0:0: -180,212,155406,1,0,0:0:0:0: -332,212,155571,1,8,1:2:0:0: -188,128,155735,1,0,0:0:0:0: -256,248,155900,1,10,0:0:0:0: -256,248,156065,2,0,L|256:304,2,56.25,0|0|0,0:0|0:0|0:0,0:0:0:0: -180,212,156560,6,0,L|124:204,1,56.25,4|0,1:2|0:0,0:0:0:0: -20,152,156889,2,0,L|76:144,1,56.25,0|0,0:0|0:0,0:0:0:0: -188,128,157219,2,0,P|212:72|192:16,1,112.5,8|8,0:0|0:0,0:0:0:0: -132,72,157713,1,0,0:0:0:0: -180,212,157878,6,0,L|236:208,1,56.25,8|0,0:0|0:0,0:0:0:0: -360,252,158208,2,8,L|304:248,1,56.25,8|0,1:2|0:0,0:0:0:0: -168,292,158538,2,0,L|160:356,2,56.25,8|8|0,0:0|0:0|0:0,0:0:0:0: -180,212,159032,1,0,0:0:0:0: -144,140,159197,6,0,P|104:128|36:148,1,112.5,2|0,1:2|0:0,0:0:0:0: -12,220,159691,1,0,0:0:0:0: -36,296,159856,2,0,P|60:316|92:324,1,56.25,8|0,0:0|0:0,0:0:0:0: -215,264,160186,2,0,P|189:273|168:292,1,56.25,8|0,0:0|0:0,0:0:0:0: -228,344,160516,6,0,L|284:340,1,56.25,10|0,0:0|0:0,0:0:0:0: -328,276,160845,2,0,L|384:272,1,56.25,8|0,1:2|0:0,0:0:0:0: -428,208,161175,1,8,0:0:0:0: -440,128,161340,1,8,0:0:0:0: -400,60,161505,1,2,0:0:0:0: -328,28,161669,1,0,0:0:0:0: -212,76,161834,6,0,P|200:120|208:164,1,90,2|0,1:2|1:0,0:0:0:0: -300,308,162163,2,0,P|312:264|304:220,1,90,2|0,1:2|1:0,0:0:0:0: -140,236,162493,2,0,P|184:248|228:240,1,90,2|0,1:2|1:0,0:0:0:0: -372,148,162823,2,0,P|328:136|284:144,1,90,2|0,1:2|1:0,0:0:0:0: -104,316,163152,5,2,1:2:0:0: -78,297,163235,1,0,1:0:0:0: -60,270,163317,1,0,1:0:0:0: -54,239,163399,1,0,1:0:0:0: -58,207,163482,1,2,1:2:0:0: -74,180,163564,1,0,1:0:0:0: -98,159,163647,1,0,1:0:0:0: -127,149,163729,1,0,1:0:0:0: -158,150,163812,2,0,L|208:160,1,45,2|0,1:2|1:0,0:0:0:0: -344,184,163976,2,0,L|294:194,1,45,0|0,1:0|1:0,0:0:0:0: -140,236,164141,1,4,1:2:0:0: -140,236,164471,6,0,L|232:252,1,90,4|0,1:2|0:0,0:0:0:0: -344,184,164801,1,8,0:0:0:0: -380,284,164965,1,0,0:0:0:0: -368,104,165130,2,0,P|324:104|284:128,1,90,0|0,0:0|1:0,0:0:0:0: -356,360,165460,2,0,P|400:360|440:336,1,90,8|0,0:0|0:0,0:0:0:0: -432,208,165790,5,0,1:0:0:0: -292,260,165954,1,0,0:0:0:0: -344,184,166119,1,8,0:0:0:0: -204,236,166284,1,0,1:0:0:0: -204,236,166366,1,0,0:0:0:0: -204,236,166449,2,0,L|216:328,1,90,0|0,0:0|1:0,0:0:0:0: -120,208,166779,2,0,L|131:118,1,90,8|0,0:0|0:0,0:0:0:0: -204,236,167108,5,0,1:0:0:0: -32,216,167273,1,0,0:0:0:0: -130,118,167438,1,8,0:0:0:0: -110,298,167603,1,0,0:0:0:0: -110,298,167685,1,0,0:0:0:0: -110,298,167768,2,0,L|121:208,1,90,0|0,0:0|1:0,0:0:0:0: -304,40,168097,2,0,L|315:130,1,90,8|0,0:0|0:0,0:0:0:0: -328,236,168427,5,0,1:0:0:0: -184,148,168592,1,0,0:0:0:0: -314,129,168757,1,8,0:0:0:0: -197,254,168921,1,0,1:0:0:0: -197,254,169004,1,0,0:0:0:0: -197,254,169086,2,0,P|220:292|260:312,1,90,0|0,0:0|1:0,0:0:0:0: -409,210,169416,2,0,P|365:211|328:236,1,90,8|0,0:0|0:0,0:0:0:0: -488,232,169746,6,0,P|487:192|464:149,1,90,0|0,1:0|0:0,0:0:0:0: -314,129,170075,1,8,0:0:0:0: -409,210,170240,1,0,0:0:0:0: -332,40,170405,2,0,L|240:36,1,90,0|0,0:0|1:0,0:0:0:0: -68,144,170735,2,0,L|157:140,1,90,8|0,0:0|0:0,0:0:0:0: -314,129,171064,5,0,1:0:0:0: -332,40,171229,1,0,0:0:0:0: -324,216,171394,1,8,0:0:0:0: -306,305,171559,1,0,1:0:0:0: -257,178,171724,1,0,0:0:0:0: -168,160,171888,1,0,1:0:0:0: -384,164,172053,1,8,0:0:0:0: -473,182,172218,1,0,0:0:0:0: -306,305,172383,6,0,L|216:312,1,90,0|0,1:0|0:0,0:0:0:0: -60,172,172713,1,8,0:0:0:0: -120,260,172877,1,0,0:0:0:0: -168,160,173042,2,0,L|172:68,1,90,0|0,0:0|1:0,0:0:0:0: -309,216,173372,2,0,L|306:306,1,90,8|0,0:0|0:0,0:0:0:0: -120,260,173702,5,0,1:0:0:0: -152,256,173784,1,0,1:0:0:0: -184,252,173866,1,0,1:0:0:0: -309,216,174031,1,8,0:0:0:0: -103,168,174196,1,0,1:0:0:0: -135,164,174279,1,0,1:0:0:0: -167,160,174361,1,0,1:0:0:0: -292,124,174526,1,0,1:0:0:0: -87,76,174691,1,8,1:2:0:0: -119,72,174773,1,0,1:0:0:0: -151,68,174855,1,0,1:0:0:0: -276,32,175020,6,0,L|368:40,1,90,0|0,1:0|0:0,0:0:0:0: -448,108,175350,1,8,0:0:0:0: -292,124,175515,1,0,0:0:0:0: -292,124,175597,1,0,0:0:0:0: -292,124,175680,2,0,L|308:216,1,90,0|0,0:0|1:0,0:0:0:0: -328,320,176009,1,8,0:0:0:0: -408,248,176174,1,0,0:0:0:0: -220,300,176339,6,0,P|176:304|128:292,1,90,0|0,1:0|0:0,0:0:0:0: -16,120,176669,1,8,0:0:0:0: -120,152,176834,1,0,1:0:0:0: -120,152,176916,1,0,0:0:0:0: -120,152,176998,2,0,L|124:200,1,45 -212,176,177163,2,0,L|239:215,1,45,0|0,1:0|0:0,0:0:0:0: -292,124,177328,2,0,P|302:79|283:30,1,90,8|0,0:0|0:0,0:0:0:0: -344,192,177658,6,0,P|372:156|376:104,1,90,0|0,1:0|0:0,0:0:0:0: -212,88,177987,1,8,0:0:0:0: -272,228,178152,1,0,0:0:0:0: -272,228,178235,1,0,0:0:0:0: -272,228,178317,1,0,0:0:0:0: -292,124,178482,1,0,1:0:0:0: -180,180,178647,1,8,0:0:0:0: -200,284,178812,1,0,0:0:0:0: -292,124,178976,5,0,1:0:0:0: -288,92,179059,1,0,0:0:0:0: -280,60,179141,2,0,P|248:24|208:14,1,90,0|8,0:0|0:0,0:0:0:0: -22,65,179471,2,0,P|67:71|112:68,1,90,0|0,1:0|0:0,0:0:0:0: -212,88,179801,1,0,1:0:0:0: -22,65,179965,1,8,0:0:0:0: -180,180,180130,5,0,0:0:0:0: -180,180,180213,1,0,0:0:0:0: -180,180,180295,2,0,P|184:224|172:272,1,90,0|0,1:0|0:0,0:0:0:0: -76,216,180625,2,0,P|72:172|84:124,1,90,8|0,0:0|0:0,0:0:0:0: -380,240,180954,2,0,P|384:284|372:332,1,90,0|0,0:0|1:0,0:0:0:0: -276,276,181284,2,0,P|272:232|284:184,1,90,8|0,0:0|0:0,0:0:0:0: -374,129,181614,5,0,1:0:0:0: -300,352,181779,2,0,L|204:348,2,90,0|8|0,0:0|0:0|1:0,0:0:0:0: -448,180,182273,1,2,0:0:0:0: -448,180,182438,1,2,1:2:0:0: -276,276,182603,1,10,0:0:0:0: -276,276,182768,1,2,0:0:0:0: -96,200,182932,6,0,L|88:108,1,90,0|0,1:0|0:0,0:0:0:0: -96,200,183262,1,8,0:0:0:0: -12,68,183427,2,0,P|72:24|164:68,1,180,0|0,0:0|1:0,0:0:0:0: -140,272,183921,2,0,P|92:284|52:271,1,90,8|0,0:0|0:0,0:0:0:0: -176,156,184251,5,0,1:0:0:0: -208,152,184334,1,0,1:0:0:0: -240,148,184416,1,0,1:0:0:0: -308,64,184581,1,8,0:0:0:0: -296,240,184746,1,0,1:0:0:0: -312,268,184828,1,0,1:0:0:0: -336,284,184910,1,0,1:0:0:0: -368,292,184993,1,0,1:0:0:0: -400,288,185075,1,0,1:0:0:0: -464,184,185240,1,8,0:0:0:0: -468,152,185323,1,0,0:0:0:0: -472,120,185405,2,0,L|464:76,1,45,0|0,1:0|1:0,0:0:0:0: -388,96,185570,6,0,P|360:132|316:148,1,90,4|0,1:2|0:0,0:0:0:0: -224,46,185899,2,0,P|268:43|308:63,1,90,8|0,0:0|0:0,0:0:0:0: -296,240,186229,1,0,0:0:0:0: -308,64,186394,1,0,1:0:0:0: -296,240,186559,2,0,L|312:332,1,90,8|0,0:0|0:0,0:0:0:0: -464,184,186888,6,0,P|420:180|372:188,1,90,0|0,1:0|0:0,0:0:0:0: -296,240,187218,1,8,0:0:0:0: -136,292,187383,2,0,P|94:277|54:249,1,90,0|0,1:0|0:0,0:0:0:0: -21,159,187713,1,0,1:0:0:0: -104,8,187877,2,0,L|124:96,1,90,10|0,0:0|0:0,0:0:0:0: -124,96,188207,6,0,P|152:132|196:148,1,90,0|0,1:0|0:0,0:0:0:0: -287,46,188537,2,0,P|243:43|204:63,1,90,8|0,0:0|0:0,0:0:0:0: -216,240,188866,1,2,0:0:0:0: -204,64,189031,1,0,1:0:0:0: -216,240,189196,2,0,L|200:332,1,90,8|0,0:0|0:0,0:0:0:0: -40,240,189526,5,2,1:2:0:0: -128,192,189691,1,0,0:0:0:0: -216,240,189855,1,8,0:0:0:0: -304,192,190020,1,0,1:0:0:0: -392,240,190185,2,0,L|400:332,1,90,2|0,0:0|1:0,0:0:0:0: -464,168,190515,2,0,L|456:76,1,90,8|0,0:0|0:0,0:0:0:0: -392,240,190844,6,0,P|364:272|312:292,1,90,2|0,1:2|0:0,0:0:0:0: -220,140,191174,2,0,P|248:108|296:92,1,90,8|0,0:0|0:0,0:0:0:0: -324,96,191421,1,0,0:0:0:0: -356,104,191504,2,0,L|340:16,1,90,0|0,0:0|1:0,0:0:0:0: -256,276,191834,2,0,L|272:364,1,90,8|0,0:0|0:0,0:0:0:0: -392,240,192163,5,0,1:0:0:0: -356,104,192328,1,0,0:0:0:0: -220,140,192493,1,8,0:0:0:0: -256,276,192658,1,0,1:0:0:0: -305,191,192823,1,0,0:0:0:0: -212,56,192987,1,0,1:0:0:0: -200,220,193152,1,10,0:0:0:0: -200,220,193482,6,0,P|156:228|108:220,1,90,0|0,1:0|0:0,0:0:0:0: -88,116,193812,1,8,0:0:0:0: -16,192,193976,1,0,0:0:0:0: -16,192,194059,1,0,0:0:0:0: -16,192,194141,2,0,L|28:288,1,90,2|0,0:0|1:0,0:0:0:0: -188,309,194471,2,0,L|200:220,1,90,8|0,0:0|0:0,0:0:0:0: -216,112,194801,5,2,1:2:0:0: -216,112,194883,1,0,1:0:0:0: -216,112,194965,1,0,1:0:0:0: -361,25,195130,1,8,0:0:0:0: -294,180,195295,1,0,1:0:0:0: -294,180,195377,1,0,1:0:0:0: -294,180,195460,1,2,0:0:0:0: -256,16,195625,1,0,1:0:0:0: -384,127,195790,1,10,1:2:0:0: -416,132,195872,1,0,1:0:0:0: -448,140,195954,2,0,L|452:84,1,45,0|0,1:0|1:0,0:0:0:0: -416,216,196119,6,0,P|412:264|432:312,1,90,4|0,1:2|0:0,0:0:0:0: -304,268,196449,2,0,P|308:220|288:172,1,90,8|0,0:0|0:0,0:0:0:0: -216,112,196779,2,0,L|120:104,1,90,0|0,0:0|1:0,0:0:0:0: -52,248,197108,2,0,L|141:255,1,90,8|0,0:0|0:0,0:0:0:0: -304,268,197438,5,0,1:0:0:0: -416,216,197603,1,0,0:0:0:0: -408,340,197768,1,8,0:0:0:0: -332,180,197932,1,0,1:0:0:0: -332,180,198015,1,0,0:0:0:0: -332,180,198097,2,0,P|360:140|400:120,1,90,0|0,0:0|1:0,0:0:0:0: -484,284,198427,1,10,0:0:0:0: -304,268,198592,1,2,0:0:0:0: -416,216,198757,6,0,P|428:172|420:124,1,90,2|0,1:2|0:0,0:0:0:0: -344,52,199086,1,8,0:0:0:0: -332,180,199251,1,0,0:0:0:0: -164,236,199416,2,0,P|152:192|160:144,1,90,0|0,0:0|1:0,0:0:0:0: -236,72,199746,1,8,0:0:0:0: -248,200,199910,1,0,0:0:0:0: -156,328,200075,6,0,L|56:320,1,90,2|0,1:2|0:0,0:0:0:0: -164,236,200405,1,8,0:0:0:0: -256,292,200570,2,0,P|300:296|344:284,1,90,0|0,1:0|0:0,0:0:0:0: -432,220,200899,2,0,L|460:308,2,90,0|8|0,1:0|0:0|0:0,0:0:0:0: -392,120,201394,5,4,1:2:0:0: -396,32,201559,1,0,1:0:0:0: -316,72,201724,1,0,1:0:0:0: -256,6,201888,1,0,1:0:0:0: -228,91,202053,1,0,1:0:0:0: -139,87,202218,1,0,1:0:0:0: -179,166,202383,1,0,1:0:0:0: -113,226,202548,1,0,1:0:0:0: -197,253,202713,5,4,1:2:0:0: -193,342,202877,1,0,1:0:0:0: -272,302,203042,1,0,1:0:0:0: -332,367,203207,1,0,1:0:0:0: -359,283,203372,1,2,1:2:0:0: -448,287,203537,1,2,1:2:0:0: -407,208,203702,1,2,1:2:0:0: -472,147,203866,1,2,1:2:0:0: -387,121,204031,5,4,1:2:0:0: -360,100,204114,1,0,1:0:0:0: -344,72,204196,1,0,1:0:0:0: -336,40,204279,1,0,1:0:0:0: -340,8,204361,1,0,1:0:0:0: -316,28,204443,1,0,1:0:0:0: -284,32,204526,1,0,1:0:0:0: -252,28,204608,1,0,1:0:0:0: -228,8,204691,2,0,L|184:20,7,45,4|0|0|0|0|0|0|0,1:2|1:0|1:0|1:0|1:0|1:0|1:0|1:0,0:0:0:0: -112,56,205350,5,4,1:2:0:0: -100,84,205432,1,0,1:0:0:0: -96,116,205515,1,0,1:0:0:0: -100,148,205597,1,0,1:0:0:0: -112,176,205680,1,0,1:0:0:0: -124,204,205762,1,0,1:0:0:0: -128,236,205844,1,0,1:0:0:0: -124,268,205927,1,0,1:0:0:0: -112,296,206009,2,0,L|71:313,3,45,2|0|2|0,1:2|0:0|0:0|0:0,0:0:0:0: -192,312,206339,2,0,L|175:353,3,45,2|0|2|0,1:2|0:0|0:0|0:0,0:0:0:0: -256,264,206669,5,4,1:2:0:0: -256,192,206751,12,0,209306,0:0:0:0: diff --git a/osu.Game.Tests/Resources/valid-events.osu b/osu.Game.Tests/Resources/valid-events.osu new file mode 100644 index 0000000000..ed3614b4ed --- /dev/null +++ b/osu.Game.Tests/Resources/valid-events.osu @@ -0,0 +1,12 @@ +osu file format v14 + +[Events] +//Background and Video events +0,0,"machinetop_background.jpg",0,0 +//Break Periods +2,122474,140135 +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Sound Samples From ee6a90c48da5c6a1a9612358f68a0011e3095a4d Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 5 Aug 2019 20:43:30 -0700 Subject: [PATCH 0522/2815] Fix back button hover sounds playing in unclickable area --- osu.Game/Graphics/UserInterface/BackButton.cs | 4 ++-- osu.Game/Graphics/UserInterface/TwoLayerButton.cs | 13 ++++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/BackButton.cs b/osu.Game/Graphics/UserInterface/BackButton.cs index 48bf0848ae..c4735b4a16 100644 --- a/osu.Game/Graphics/UserInterface/BackButton.cs +++ b/osu.Game/Graphics/UserInterface/BackButton.cs @@ -22,8 +22,8 @@ namespace osu.Game.Graphics.UserInterface Child = button = new TwoLayerButton { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, Text = @"back", Icon = OsuIcon.LeftCircle, Action = () => Action?.Invoke() diff --git a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs index c74d00cd1e..75d3847417 100644 --- a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs +++ b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs @@ -15,6 +15,7 @@ using System; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Game.Screens.Select; namespace osu.Game.Graphics.UserInterface { @@ -28,7 +29,9 @@ namespace osu.Game.Graphics.UserInterface private const int transform_time = 600; private const int pulse_length = 250; - private const float shear = 0.1f; + private const float SHEAR_WIDTH = 5f; + + private static readonly Vector2 shearing = new Vector2(SHEAR_WIDTH / Footer.HEIGHT, 0); public static readonly Vector2 SIZE_EXTENDED = new Vector2(140, 50); public static readonly Vector2 SIZE_RETRACTED = new Vector2(100, 50); @@ -56,7 +59,7 @@ namespace osu.Game.Graphics.UserInterface c1.Origin = c1.Anchor = value.HasFlag(Anchor.x2) ? Anchor.TopLeft : Anchor.TopRight; c2.Origin = c2.Anchor = value.HasFlag(Anchor.x2) ? Anchor.TopRight : Anchor.TopLeft; - X = value.HasFlag(Anchor.x2) ? SIZE_RETRACTED.X * shear * 0.5f : 0; + X = value.HasFlag(Anchor.x2) ? SIZE_RETRACTED.X * shearing.X * 0.5f : 0; Remove(c1); Remove(c2); @@ -70,6 +73,7 @@ namespace osu.Game.Graphics.UserInterface public TwoLayerButton() { Size = SIZE_RETRACTED; + Shear = shearing; Children = new Drawable[] { @@ -82,7 +86,6 @@ namespace osu.Game.Graphics.UserInterface new Container { RelativeSizeAxes = Axes.Both, - Shear = new Vector2(shear, 0), Masking = true, MaskingSmoothness = 2, EdgeEffect = new EdgeEffectParameters @@ -105,6 +108,7 @@ namespace osu.Game.Graphics.UserInterface { Anchor = Anchor.Centre, Origin = Anchor.Centre, + Shear = -shearing, }, } }, @@ -119,7 +123,6 @@ namespace osu.Game.Graphics.UserInterface new Container { RelativeSizeAxes = Axes.Both, - Shear = new Vector2(shear, 0), Masking = true, MaskingSmoothness = 2, EdgeEffect = new EdgeEffectParameters @@ -144,6 +147,7 @@ namespace osu.Game.Graphics.UserInterface { Origin = Anchor.Centre, Anchor = Anchor.Centre, + Shear = -shearing, } } }, @@ -188,7 +192,6 @@ namespace osu.Game.Graphics.UserInterface var flash = new Box { RelativeSizeAxes = Axes.Both, - Shear = new Vector2(shear, 0), Colour = Color4.White.Opacity(0.5f), }; Add(flash); From 11aa3544c49ca6c6d4adc86216eae8ac693c038b Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 5 Aug 2019 20:57:17 -0700 Subject: [PATCH 0523/2815] Fix shear width naming --- osu.Game/Graphics/UserInterface/TwoLayerButton.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs index 75d3847417..b56dd201c2 100644 --- a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs +++ b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs @@ -29,9 +29,9 @@ namespace osu.Game.Graphics.UserInterface private const int transform_time = 600; private const int pulse_length = 250; - private const float SHEAR_WIDTH = 5f; + private const float shear_width = 5f; - private static readonly Vector2 shearing = new Vector2(SHEAR_WIDTH / Footer.HEIGHT, 0); + private static readonly Vector2 shearing = new Vector2(shear_width / Footer.HEIGHT, 0); public static readonly Vector2 SIZE_EXTENDED = new Vector2(140, 50); public static readonly Vector2 SIZE_RETRACTED = new Vector2(100, 50); From ac0abe0692cd81496b6fd5b8839ede050202752d Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 6 Aug 2019 14:21:34 +0900 Subject: [PATCH 0524/2815] Use newly exposed UpdateState --- .../Containers/OsuFocusedOverlayContainer.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index c33e980a9a..eff853abcf 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -55,15 +55,6 @@ namespace osu.Game.Graphics.Containers samplePopOut = audio.Samples.Get(@"UI/overlay-pop-out"); } - protected override void LoadComplete() - { - base.LoadComplete(); - - // This must be added after the base LoadComplete, since onStateChanged contains logic that - // must be run after other state change logic has been completed. - State.ValueChanged += onStateChanged; - } - /// /// Whether mouse input should be blocked screen-wide while this overlay is visible. /// Performing mouse actions outside of the valid extents will hide the overlay. @@ -101,8 +92,10 @@ namespace osu.Game.Graphics.Containers public bool OnReleased(GlobalAction action) => false; - private void onStateChanged(ValueChangedEvent state) + protected override void UpdateState(ValueChangedEvent state) { + base.UpdateState(state); + switch (state.NewValue) { case Visibility.Visible: From dd701eaa6292586c181b35a9cc79bc1e0e677732 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 6 Aug 2019 14:10:03 +0300 Subject: [PATCH 0525/2815] Safely cancel the completion task on restart or immediate exit --- osu.Game/Screens/Play/Player.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 8dc16af575..2f7f793bbf 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -255,7 +255,7 @@ namespace osu.Game.Screens.Play private void performImmediateExit() { // if a restart has been requested, cancel any pending completion (user has shown intent to restart). - onCompletionEvent = null; + onCompletionEvent?.Cancel(); ValidForResume = false; @@ -275,12 +275,8 @@ namespace osu.Game.Screens.Play sampleRestart?.Play(); - // if a restart has been requested, cancel any pending completion (user has shown intent to restart). - onCompletionEvent = null; - - ValidForResume = false; RestartRequested?.Invoke(); - this.Exit(); + performImmediateExit(); } private ScheduledDelegate onCompletionEvent; @@ -306,8 +302,6 @@ namespace osu.Game.Screens.Play scoreManager.Import(score).Wait(); this.Push(CreateResults(score)); - - onCompletionEvent = null; }); } } @@ -471,9 +465,9 @@ namespace osu.Game.Screens.Play public override bool OnExiting(IScreen next) { - if (onCompletionEvent != null) + if (onCompletionEvent != null && !onCompletionEvent.Cancelled && !onCompletionEvent.Completed) + // proceed to result screen if beatmap already finished playing { - // Proceed to result screen if beatmap already finished playing onCompletionEvent.RunTask(); return true; } From 2a68bb2749bf36bc632a1881f124dbbdcc592c96 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 6 Aug 2019 14:11:43 +0300 Subject: [PATCH 0526/2815] onCompletionEvent -> pushResults --- 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 2f7f793bbf..d0ef3bc104 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -255,7 +255,7 @@ namespace osu.Game.Screens.Play private void performImmediateExit() { // if a restart has been requested, cancel any pending completion (user has shown intent to restart). - onCompletionEvent?.Cancel(); + pushResults?.Cancel(); ValidForResume = false; @@ -279,12 +279,12 @@ namespace osu.Game.Screens.Play performImmediateExit(); } - private ScheduledDelegate onCompletionEvent; + private ScheduledDelegate pushResults; private void onCompletion() { // Only show the completion screen if the player hasn't failed - if (ScoreProcessor.HasFailed || onCompletionEvent != null) + if (ScoreProcessor.HasFailed || pushResults != null) return; ValidForResume = false; @@ -293,7 +293,7 @@ namespace osu.Game.Screens.Play using (BeginDelayedSequence(1000)) { - onCompletionEvent = Schedule(delegate + pushResults = Schedule(delegate { if (!this.IsCurrentScreen()) return; @@ -465,10 +465,10 @@ namespace osu.Game.Screens.Play public override bool OnExiting(IScreen next) { - if (onCompletionEvent != null && !onCompletionEvent.Cancelled && !onCompletionEvent.Completed) + if (pushResults != null && !pushResults.Cancelled && !pushResults.Completed) // proceed to result screen if beatmap already finished playing { - onCompletionEvent.RunTask(); + pushResults.RunTask(); return true; } From dda078277a80aa9a465c9edf7ea7b12df3e04f4d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Aug 2019 23:05:12 +0900 Subject: [PATCH 0527/2815] Minor variable name changes --- osu.Game/Screens/Play/Player.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index d0ef3bc104..96b8073d11 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -255,7 +255,7 @@ namespace osu.Game.Screens.Play private void performImmediateExit() { // if a restart has been requested, cancel any pending completion (user has shown intent to restart). - pushResults?.Cancel(); + completionProgressDelegate?.Cancel(); ValidForResume = false; @@ -279,12 +279,12 @@ namespace osu.Game.Screens.Play performImmediateExit(); } - private ScheduledDelegate pushResults; + private ScheduledDelegate completionProgressDelegate; private void onCompletion() { // Only show the completion screen if the player hasn't failed - if (ScoreProcessor.HasFailed || pushResults != null) + if (ScoreProcessor.HasFailed || completionProgressDelegate != null) return; ValidForResume = false; @@ -293,7 +293,7 @@ namespace osu.Game.Screens.Play using (BeginDelayedSequence(1000)) { - pushResults = Schedule(delegate + completionProgressDelegate = Schedule(delegate { if (!this.IsCurrentScreen()) return; @@ -465,10 +465,10 @@ namespace osu.Game.Screens.Play public override bool OnExiting(IScreen next) { - if (pushResults != null && !pushResults.Cancelled && !pushResults.Completed) - // proceed to result screen if beatmap already finished playing + if (completionProgressDelegate != null && !completionProgressDelegate.Cancelled && !completionProgressDelegate.Completed) { - pushResults.RunTask(); + // proceed to result screen if beatmap already finished playing + completionProgressDelegate.RunTask(); return true; } From ffb6794b4ebea7651ed29418f5339964e4208ca2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Tue, 6 Aug 2019 16:11:13 +0200 Subject: [PATCH 0528/2815] formatting inspection --- osu.Game/Online/Chat/ChannelManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 30135d5b8b..b5d8879e50 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -222,6 +222,7 @@ namespace osu.Game.Online.Chat } var channel = availableChannels.Where(c => c.Name == content || c.Name == $"#{content}").FirstOrDefault(); + if (channel == null) { target.AddNewMessages(new ErrorMessage($"Channel '{content}' not found.")); From 3cfbbac5dc9ae96335a476b829b46de88b4f2299 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 6 Aug 2019 17:47:31 +0300 Subject: [PATCH 0529/2815] Expand requests --- osu.Game/Online/API/Requests/GetUserRequest.cs | 7 +++++-- .../Online/API/Requests/GetUserScoresRequest.cs | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetUserRequest.cs b/osu.Game/Online/API/Requests/GetUserRequest.cs index 37ed0574f1..18fb4bb7db 100644 --- a/osu.Game/Online/API/Requests/GetUserRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRequest.cs @@ -2,18 +2,21 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Users; +using osu.Game.Rulesets; namespace osu.Game.Online.API.Requests { public class GetUserRequest : APIRequest { private readonly long? userId; + private readonly RulesetInfo ruleset; - public GetUserRequest(long? userId = null) + public GetUserRequest(long? userId = null, RulesetInfo ruleset = null) { this.userId = userId; + this.ruleset = ruleset; } - protected override string Target => userId.HasValue ? $@"users/{userId}" : @"me"; + protected override string Target => userId.HasValue ? (ruleset != null ? $@"users/{userId}/{ruleset.ShortName}" : $@"users/{userId}") : @"me"; } } diff --git a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs index d41966fe1b..7b4d66e7b2 100644 --- a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.IO.Network; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Rulesets; namespace osu.Game.Online.API.Requests { @@ -10,12 +12,24 @@ namespace osu.Game.Online.API.Requests { private readonly long userId; private readonly ScoreType type; + private readonly RulesetInfo ruleset; - public GetUserScoresRequest(long userId, ScoreType type, int page = 0, int itemsPerPage = 5) + public GetUserScoresRequest(long userId, ScoreType type, int page = 0, int itemsPerPage = 5, RulesetInfo ruleset = null) : base(page, itemsPerPage) { this.userId = userId; this.type = type; + this.ruleset = ruleset; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + + if (ruleset != null) + req.AddParameter("mode", ruleset.ShortName); + + return req; } protected override string Target => $@"users/{userId}/scores/{type.ToString().ToLowerInvariant()}"; From 91b9a496f5c1e3a60fee8136473ee64098c94d20 Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 6 Aug 2019 18:29:07 -0700 Subject: [PATCH 0530/2815] Remove now unnecessary .gitmodules and .travis.yml --- .gitmodules | 0 .travis.yml | 2 -- 2 files changed, 2 deletions(-) delete mode 100644 .gitmodules delete mode 100644 .travis.yml diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ce353d9b27..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,2 +0,0 @@ -language: csharp -solution: osu.sln \ No newline at end of file From 616de1830a4c4200fba87a0ef8758e9cea0a6907 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2019 12:20:49 +0900 Subject: [PATCH 0531/2815] Less sheep --- osu.Game/Screens/Select/FooterButton.cs | 6 +++--- osu.Game/Screens/Select/FooterButtonMods.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index 15088f0eb3..c1478aa4ce 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Select { public static readonly float SHEAR_WIDTH = 7.5f; - protected static readonly Vector2 SHEARING = new Vector2(SHEAR_WIDTH / Footer.HEIGHT, 0); + protected static readonly Vector2 SHEAR = new Vector2(SHEAR_WIDTH / Footer.HEIGHT, 0); public string Text { @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Select public FooterButton() { AutoSizeAxes = Axes.Both; - Shear = SHEARING; + Shear = SHEAR; Children = new Drawable[] { box = new Box @@ -83,7 +83,7 @@ namespace osu.Game.Screens.Select TextContainer = new Container { Size = new Vector2(100 - SHEAR_WIDTH, 50), - Shear = -SHEARING, + Shear = -SHEAR, Child = SpriteText = new OsuSpriteText { Anchor = Anchor.Centre, diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index 58d92dbca6..29b1364944 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Select { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Shear = -SHEARING, + Shear = -SHEAR, Child = modDisplay = new FooterModDisplay { DisplayUnrankedText = false, From c16e84e5e64dda14c07afe74ba14adb940b8438c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 7 Aug 2019 06:35:57 +0300 Subject: [PATCH 0532/2815] Fix unability to get local user with specific ruleset --- osu.Game/Online/API/Requests/GetUserRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/GetUserRequest.cs b/osu.Game/Online/API/Requests/GetUserRequest.cs index 18fb4bb7db..acf9aefa19 100644 --- a/osu.Game/Online/API/Requests/GetUserRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRequest.cs @@ -17,6 +17,6 @@ namespace osu.Game.Online.API.Requests this.ruleset = ruleset; } - protected override string Target => userId.HasValue ? (ruleset != null ? $@"users/{userId}/{ruleset.ShortName}" : $@"users/{userId}") : @"me"; + protected override string Target => userId.HasValue ? (ruleset != null ? $@"users/{userId}/{ruleset.ShortName}" : $@"users/{userId}") : ruleset != null ? $@"me/{ruleset.ShortName}" : $@"me"; } } From 6ed9c983ff1b8f37e981764370d2f9b255a024ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2019 12:36:00 +0900 Subject: [PATCH 0533/2815] Rename shear variable --- osu.Game/Graphics/UserInterface/TwoLayerButton.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs index b56dd201c2..aa96796cf1 100644 --- a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs +++ b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs @@ -31,7 +31,7 @@ namespace osu.Game.Graphics.UserInterface private const float shear_width = 5f; - private static readonly Vector2 shearing = new Vector2(shear_width / Footer.HEIGHT, 0); + private static readonly Vector2 shear = new Vector2(shear_width / Footer.HEIGHT, 0); public static readonly Vector2 SIZE_EXTENDED = new Vector2(140, 50); public static readonly Vector2 SIZE_RETRACTED = new Vector2(100, 50); @@ -59,7 +59,7 @@ namespace osu.Game.Graphics.UserInterface c1.Origin = c1.Anchor = value.HasFlag(Anchor.x2) ? Anchor.TopLeft : Anchor.TopRight; c2.Origin = c2.Anchor = value.HasFlag(Anchor.x2) ? Anchor.TopRight : Anchor.TopLeft; - X = value.HasFlag(Anchor.x2) ? SIZE_RETRACTED.X * shearing.X * 0.5f : 0; + X = value.HasFlag(Anchor.x2) ? SIZE_RETRACTED.X * shear.X * 0.5f : 0; Remove(c1); Remove(c2); @@ -73,7 +73,7 @@ namespace osu.Game.Graphics.UserInterface public TwoLayerButton() { Size = SIZE_RETRACTED; - Shear = shearing; + Shear = shear; Children = new Drawable[] { @@ -108,7 +108,7 @@ namespace osu.Game.Graphics.UserInterface { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Shear = -shearing, + Shear = -shear, }, } }, @@ -147,7 +147,7 @@ namespace osu.Game.Graphics.UserInterface { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Shear = -shearing, + Shear = -shear, } } }, From 24f9872315e094d1526aca7226878f42911a1e59 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 7 Aug 2019 06:45:39 +0300 Subject: [PATCH 0534/2815] Fix unnecessary scores loading --- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index a6cc2b0500..ec2697447a 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -132,7 +132,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Scores = null; - if (beatmap?.OnlineBeatmapID.HasValue != true) + if (beatmap?.OnlineBeatmapID.HasValue != true || beatmap?.Status <= BeatmapSetOnlineStatus.Pending) return; loadingAnimation.Show(); From b1c78c43f382a6aae24e2ddcb8ac102013243999 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 7 Aug 2019 06:57:46 +0300 Subject: [PATCH 0535/2815] Simplify target string --- osu.Game/Online/API/Requests/GetUserRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/GetUserRequest.cs b/osu.Game/Online/API/Requests/GetUserRequest.cs index acf9aefa19..31b7e95b39 100644 --- a/osu.Game/Online/API/Requests/GetUserRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRequest.cs @@ -17,6 +17,6 @@ namespace osu.Game.Online.API.Requests this.ruleset = ruleset; } - protected override string Target => userId.HasValue ? (ruleset != null ? $@"users/{userId}/{ruleset.ShortName}" : $@"users/{userId}") : ruleset != null ? $@"me/{ruleset.ShortName}" : $@"me"; + protected override string Target => userId.HasValue ? $@"users/{userId}/{ruleset?.ShortName}" : $@"me/{ruleset?.ShortName}"; } } From b9384d7a53cb5e59074ff2b538b90084664c3d6b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 7 Aug 2019 07:00:50 +0300 Subject: [PATCH 0536/2815] Remove unnecessary null check --- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index ec2697447a..4bbcd8d631 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -132,7 +132,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Scores = null; - if (beatmap?.OnlineBeatmapID.HasValue != true || beatmap?.Status <= BeatmapSetOnlineStatus.Pending) + if (beatmap?.OnlineBeatmapID.HasValue != true || beatmap.Status <= BeatmapSetOnlineStatus.Pending) return; loadingAnimation.Show(); From b064df91a7065b1b28091208f3638418b1b4df40 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 7 Aug 2019 08:33:55 +0300 Subject: [PATCH 0537/2815] Initial implementation --- .../TestSceneLeaderboardScopeSelector.cs | 37 +++++ .../BeatmapSet/LeaderboardScopeSelector.cs | 157 ++++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneLeaderboardScopeSelector.cs create mode 100644 osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardScopeSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardScopeSelector.cs new file mode 100644 index 0000000000..f4c0d32946 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardScopeSelector.cs @@ -0,0 +1,37 @@ +// 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.Overlays.BeatmapSet; +using System; +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Bindables; +using osu.Game.Screens.Select.Leaderboards; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneLeaderboardScopeSelector : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(LeaderboardScopeSelector), + }; + + public TestSceneLeaderboardScopeSelector() + { + LeaderboardScopeSelector selector; + Bindable scope = new Bindable(); + + Add(selector = new LeaderboardScopeSelector + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Current = { BindTarget = scope } + }); + + AddStep(@"Select global", () => scope.Value = BeatmapLeaderboardScope.Global); + AddStep(@"Select country", () => scope.Value = BeatmapLeaderboardScope.Country); + AddStep(@"Select friend", () => scope.Value = BeatmapLeaderboardScope.Friend); + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs new file mode 100644 index 0000000000..c74c7cbc2b --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs @@ -0,0 +1,157 @@ +// 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.UserInterface; +using osu.Game.Screens.Select.Leaderboards; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osuTK; +using osu.Game.Graphics.UserInterface; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Sprites; +using osu.Framework.Extensions; +using osu.Game.Graphics; +using osu.Framework.Allocation; +using osuTK.Graphics; +using osu.Framework.Input.Events; +using osu.Framework.Graphics.Colour; + +namespace osu.Game.Overlays.BeatmapSet +{ + public class LeaderboardScopeSelector : TabControl + { + protected override Dropdown CreateDropdown() => null; + + protected override TabItem CreateTabItem(BeatmapLeaderboardScope value) => new ScopeSelectorTabItem(value); + + public LeaderboardScopeSelector() + { + AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; + + AddItem(BeatmapLeaderboardScope.Global); + AddItem(BeatmapLeaderboardScope.Country); + AddItem(BeatmapLeaderboardScope.Friend); + + AddInternal(new Line + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + }); + } + + protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(20, 0), + }; + + private class ScopeSelectorTabItem : TabItem + { + private const float transition_duration = 100; + + private readonly Box box; + + protected readonly OsuSpriteText Text; + + public ScopeSelectorTabItem(BeatmapLeaderboardScope value) + : base(value) + { + AutoSizeAxes = Axes.Both; + + Children = new Drawable[] + { + Text = new OsuSpriteText + { + Margin = new MarginPadding { Bottom = 8 }, + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + Text = value.GetDescription() + " Ranking", + Font = OsuFont.GetFont(weight: FontWeight.Regular), + }, + box = new Box + { + RelativeSizeAxes = Axes.X, + Height = 5, + Scale = new Vector2(1f, 0f), + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + }, + new HoverClickSounds() + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + box.Colour = colours.Blue; + } + + protected override bool OnHover(HoverEvent e) + { + Text.FadeColour(Color4.LightSkyBlue); + + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + + Text.FadeColour(Color4.White); + } + + protected override void OnActivated() + { + box.ScaleTo(new Vector2(1f), transition_duration); + Text.Font = Text.Font.With(weight: FontWeight.Black); + } + + protected override void OnDeactivated() + { + box.ScaleTo(new Vector2(1f, 0f), transition_duration); + Text.Font = Text.Font.With(weight: FontWeight.Regular); + } + } + + private class Line : GridContainer + { + public Line() + { + Height = 1; + RelativeSizeAxes = Axes.X; + Width = 0.8f; + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(mode: GridSizeMode.Relative, size: 0.4f), + new Dimension(), + }; + Content = new[] + { + new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientHorizontal(Color4.Transparent, Color4.Gray), + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Gray, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientHorizontal(Color4.Gray, Color4.Transparent), + }, + } + }; + } + } + } +} From 87974850ddcfdbd55cb332b72016c21f880c91a6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 7 Aug 2019 08:42:43 +0300 Subject: [PATCH 0538/2815] Initial implementation --- .../Online/TestSceneLeaderboardModSelector.cs | 63 ++++++++ .../BeatmapSet/LeaderboardModSelector.cs | 136 ++++++++++++++++++ osu.Game/Overlays/Mods/ModButton.cs | 2 +- osu.Game/Rulesets/Mods/ModType.cs | 3 +- osu.Game/Rulesets/UI/ModIcon.cs | 27 ++-- 5 files changed, 213 insertions(+), 18 deletions(-) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs create mode 100644 osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs new file mode 100644 index 0000000000..eb7fe5591d --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs @@ -0,0 +1,63 @@ +// 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.Overlays.BeatmapSet; +using System; +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Taiko; +using osu.Game.Rulesets.Catch; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Bindables; +using osu.Game.Rulesets; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneLeaderboardModSelector : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(LeaderboardModSelector), + }; + + public TestSceneLeaderboardModSelector() + { + LeaderboardModSelector modSelector; + FillFlowContainer selectedMods; + Bindable ruleset = new Bindable(); + + Add(selectedMods = new FillFlowContainer + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + }); + + Add(modSelector = new LeaderboardModSelector + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Ruleset = { BindTarget = ruleset } + }); + + modSelector.SelectedMods.BindValueChanged(mods => + { + selectedMods.Clear(); + + foreach (var mod in mods.NewValue) + selectedMods.Add(new SpriteText + { + Text = mod.Acronym, + }); + }); + + AddStep("osu mods", () => ruleset.Value = new OsuRuleset().RulesetInfo); + AddStep("mania mods", () => ruleset.Value = new ManiaRuleset().RulesetInfo); + AddStep("taiko mods", () => ruleset.Value = new TaikoRuleset().RulesetInfo); + AddStep("catch mods", () => ruleset.Value = new CatchRuleset().RulesetInfo); + AddStep("Deselect all", () => modSelector.DeselectAll()); + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs new file mode 100644 index 0000000000..99c51813c5 --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -0,0 +1,136 @@ +// 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.Containers; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Mods; +using osu.Framework.Bindables; +using System.Collections.Generic; +using osu.Game.Rulesets; +using osuTK; +using osu.Game.Rulesets.UI; +using osu.Framework.Input.Events; +using osu.Game.Graphics.UserInterface; +using osuTK.Graphics; +using System; +using System.Linq; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics.Sprites; + +namespace osu.Game.Overlays.BeatmapSet +{ + public class LeaderboardModSelector : Container + { + public readonly Bindable> SelectedMods = new Bindable>(); + public readonly Bindable Ruleset = new Bindable(); + + private readonly FillFlowContainer modsContainer; + + public LeaderboardModSelector() + { + AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; + Child = modsContainer = new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Full, + Spacing = new Vector2(4), + }; + + Ruleset.BindValueChanged(onRulesetChanged); + } + + private void onRulesetChanged(ValueChangedEvent ruleset) + { + SelectedMods.Value = new List(); + + modsContainer.Clear(); + + if (ruleset.NewValue == null) + return; + + modsContainer.Add(new ModButton(new NoMod())); + + foreach (var mod in ruleset.NewValue.CreateInstance().GetAllMods()) + if (mod.Ranked) + modsContainer.Add(new ModButton(mod)); + + foreach (var mod in modsContainer) + mod.OnSelectionChanged += selectionChanged; + } + + private void selectionChanged(Mod mod, bool selected) + { + var mods = SelectedMods.Value.ToList(); + + if (selected) + mods.Add(mod); + else + mods.Remove(mod); + + SelectedMods.Value = mods; + } + + public void DeselectAll() => modsContainer.ForEach(mod => mod.Selected.Value = false); + + private class ModButton : ModIcon + { + private const float mod_scale = 0.4f; + private const int duration = 200; + + public readonly BindableBool Selected = new BindableBool(); + public Action OnSelectionChanged; + + public ModButton(Mod mod) + : base(mod) + { + Scale = new Vector2(mod_scale); + Add(new HoverClickSounds()); + + Selected.BindValueChanged(selected => + { + updateState(); + OnSelectionChanged?.Invoke(mod, selected.NewValue); + }, true); + } + + protected override bool OnClick(ClickEvent e) + { + Selected.Value = !Selected.Value; + return base.OnClick(e); + } + + protected override bool OnHover(HoverEvent e) + { + updateState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + updateState(); + } + + private void updateState() + { + this.FadeColour(IsHovered || Selected.Value ? Color4.White : Color4.Gray, duration, Easing.OutQuint); + } + } + + private class NoMod : Mod + { + public override string Name => "NoMod"; + + public override string Acronym => "NM"; + + public override double ScoreMultiplier => 1; + + public override IconUsage Icon => FontAwesome.Solid.Ban; + + public override ModType Type => ModType.Custom; + } + } +} diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index fa1ee500a8..7b8745cf42 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -96,7 +96,7 @@ namespace osu.Game.Overlays.Mods } } - foregroundIcon.Highlighted = Selected; + foregroundIcon.Highlighted.Value = Selected; SelectionChanged?.Invoke(SelectedMod); return true; diff --git a/osu.Game/Rulesets/Mods/ModType.cs b/osu.Game/Rulesets/Mods/ModType.cs index e3c82e42f5..1cdc4415ac 100644 --- a/osu.Game/Rulesets/Mods/ModType.cs +++ b/osu.Game/Rulesets/Mods/ModType.cs @@ -10,6 +10,7 @@ namespace osu.Game.Rulesets.Mods Conversion, Automation, Fun, - System + System, + Custom } } diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 86feea09a8..962263adba 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -11,11 +11,14 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osuTK; +using osu.Framework.Bindables; namespace osu.Game.Rulesets.UI { public class ModIcon : Container, IHasTooltip { + public readonly BindableBool Highlighted = new BindableBool(); + private readonly SpriteIcon modIcon; private readonly SpriteIcon background; @@ -96,27 +99,19 @@ namespace osu.Game.Rulesets.UI backgroundColour = colours.Pink; highlightedColour = colours.PinkLight; break; - } - applyStyle(); - } - - private bool highlighted; - - public bool Highlighted - { - get => highlighted; - - set - { - highlighted = value; - applyStyle(); + case ModType.Custom: + backgroundColour = colours.Gray6; + highlightedColour = colours.Gray7; + modIcon.Colour = colours.Yellow; + break; } } - private void applyStyle() + protected override void LoadComplete() { - background.Colour = highlighted ? highlightedColour : backgroundColour; + base.LoadComplete(); + Highlighted.BindValueChanged(highlighted => background.Colour = highlighted.NewValue ? highlightedColour : backgroundColour, true); } } } From 1d42f0959af01834fbd4f86bd08c84b2f6b14932 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 7 Aug 2019 08:46:27 +0300 Subject: [PATCH 0539/2815] ModIcon improvements --- osu.Game/Overlays/Mods/ModButton.cs | 2 +- osu.Game/Rulesets/UI/ModIcon.cs | 23 ++++++----------------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index fa1ee500a8..7b8745cf42 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -96,7 +96,7 @@ namespace osu.Game.Overlays.Mods } } - foregroundIcon.Highlighted = Selected; + foregroundIcon.Highlighted.Value = Selected; SelectionChanged?.Invoke(SelectedMod); return true; diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 86feea09a8..5bb1de7a38 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -11,11 +11,14 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osuTK; +using osu.Framework.Bindables; namespace osu.Game.Rulesets.UI { public class ModIcon : Container, IHasTooltip { + public readonly BindableBool Highlighted = new BindableBool(); + private readonly SpriteIcon modIcon; private readonly SpriteIcon background; @@ -97,26 +100,12 @@ namespace osu.Game.Rulesets.UI highlightedColour = colours.PinkLight; break; } - - applyStyle(); } - private bool highlighted; - - public bool Highlighted + protected override void LoadComplete() { - get => highlighted; - - set - { - highlighted = value; - applyStyle(); - } - } - - private void applyStyle() - { - background.Colour = highlighted ? highlightedColour : backgroundColour; + base.LoadComplete(); + Highlighted.BindValueChanged(highlighted => background.Colour = highlighted.NewValue ? highlightedColour : backgroundColour, true); } } } From a99d6536c22011b34879899017d789b296ad6fba Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 7 Aug 2019 08:49:04 +0300 Subject: [PATCH 0540/2815] CI fix --- .../Visual/Online/TestSceneLeaderboardScopeSelector.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardScopeSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardScopeSelector.cs index f4c0d32946..cc3b2ac68b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardScopeSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardScopeSelector.cs @@ -19,10 +19,9 @@ namespace osu.Game.Tests.Visual.Online public TestSceneLeaderboardScopeSelector() { - LeaderboardScopeSelector selector; Bindable scope = new Bindable(); - Add(selector = new LeaderboardScopeSelector + Add(new LeaderboardScopeSelector { Anchor = Anchor.Centre, Origin = Anchor.Centre, From cf9a5baafae910e287196d8fe702b44e2c1f2b86 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 7 Aug 2019 14:51:24 +0900 Subject: [PATCH 0541/2815] Explicity dispose osuGame instances --- osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs | 3 +++ osu.Game/Overlays/MusicController.cs | 4 ---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs index 08afe90de8..663447d0b4 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs @@ -51,7 +51,10 @@ namespace osu.Game.Tests.Visual.Menus AddStep("Create new game instance", () => { if (osuGame != null) + { Remove(osuGame); + osuGame.Dispose(); + } osuGame = new TestOsuGame(); osuGame.SetHost(gameHost); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 91ae92320a..abbcec5094 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -316,10 +316,6 @@ namespace osu.Game.Overlays private void next(bool instant = false) { - // beatmapSets doesn't get populated until loading has completed. - if (!IsLoaded) - return; - if (!instant) queuedDirection = TransformDirection.Next; From 487979b0164091f9c2928204e660d218ca9faada Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 7 Aug 2019 09:31:07 +0300 Subject: [PATCH 0542/2815] Add Testing --- .../Visual/Online/TestSceneUserRequest.cs | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneUserRequest.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserRequest.cs b/osu.Game.Tests/Visual/Online/TestSceneUserRequest.cs new file mode 100644 index 0000000000..18d6028cb8 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneUserRequest.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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mania; +using osu.Game.Users; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Taiko; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Tests.Visual.Online +{ + [TestFixture] + public class TestSceneUserRequest : OsuTestScene + { + [Resolved] + private IAPIProvider api { get; set; } + + private readonly Bindable user = new Bindable(); + private GetUserRequest request; + private readonly DimmedLoadingLayer loading; + + public TestSceneUserRequest() + { + Add(new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new UserTestContainer + { + User = { BindTarget = user } + }, + loading = new DimmedLoadingLayer + { + Alpha = 0 + } + } + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + AddStep(@"local user", () => getUser()); + AddStep(@"local user with taiko ruleset", () => getUser(ruleset: new TaikoRuleset().RulesetInfo)); + AddStep(@"cookiezi", () => getUser(124493)); + AddStep(@"cookiezi with mania ruleset", () => getUser(124493, new ManiaRuleset().RulesetInfo)); + } + + private void getUser(long? userId = null, RulesetInfo ruleset = null) + { + loading.Show(); + + request?.Cancel(); + request = new GetUserRequest(userId, ruleset); + request.Success += user => + { + this.user.Value = user; + loading.Hide(); + }; + api.Queue(request); + } + + private class UserTestContainer : FillFlowContainer + { + public readonly Bindable User = new Bindable(); + + public UserTestContainer() + { + AutoSizeAxes = Axes.Both; + Direction = FillDirection.Vertical; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + User.BindValueChanged(onUserUpdate, true); + } + + private void onUserUpdate(ValueChangedEvent user) + { + Clear(); + + AddRange(new Drawable[] + { + new SpriteText + { + Text = $@"Username: {user.NewValue?.Username}" + }, + new SpriteText + { + Text = $@"RankedScore: {user.NewValue?.Statistics.RankedScore}" + }, + }); + } + } + } +} From 606e977e2d27427229ba09ef62b064b9c4fd826c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2019 16:07:41 +0900 Subject: [PATCH 0543/2815] Fix twitter link shortener being used --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 52fc29cb98..c4d676f4be 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ If you are not interested in developing the game, you can still consume our [bin | ------------- | ------------- | - **Linux** users are recommended to self-compile until we have official deployment in place. -- **iOS** users can join the [TestFlight beta program](https://t.co/PasE1zrHhw) (note that due to high demand this is regularly full). +- **iOS** users can join the [TestFlight beta program](https://testflight.apple.com/join/2tLcjWlF) (note that due to high demand this is regularly full). - **Android** users can self-compile, and expect a public beta soon. If your platform is not listed above, there is still a chance you can manually build it by following the instructions below. From 669c2462eca3cae95a5944e2581585eeb7d5312c Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 7 Aug 2019 16:25:38 +0900 Subject: [PATCH 0544/2815] Don't consider the type --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index c92c0ba22d..24843f8c32 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -293,7 +293,7 @@ namespace osu.Game.Beatmaps.Formats if (!Enum.TryParse(split[0], out type)) { - Logger.Log($"A beatmap {type} event could not be parsed and will be ignored: {split[0]}", LoggingTarget.Runtime, LogLevel.Important); + Logger.Log($"A beatmap event could not be parsed and will be ignored: {split[0]}", LoggingTarget.Runtime, LogLevel.Important); return; } From c0d3d47c2995dc581f716b4a17ecad8f2d742064 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 7 Aug 2019 16:49:53 +0900 Subject: [PATCH 0545/2815] Don't consider type for storyboards either --- osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 512eea0822..1dcafb4669 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -84,7 +84,7 @@ namespace osu.Game.Beatmaps.Formats EventType type; if (!Enum.TryParse(split[0], out type)) - Logger.Log($"A storyboard {type} event could not be parsed and will be ignored: {split[0]}", LoggingTarget.Runtime, LogLevel.Important); + Logger.Log($"A storyboard event could not be parsed and will be ignored: {split[0]}", LoggingTarget.Runtime, LogLevel.Important); switch (type) { From c6d481238d91e63051c920ea6f1d670f28e673cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2019 16:56:22 +0900 Subject: [PATCH 0546/2815] Move QuadBatch to a local variable to stop resharper complaining --- osu.Game/Rulesets/Mods/ModFlashlight.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index cb0c2fafe5..7247bafd23 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -155,12 +155,13 @@ namespace osu.Game.Rulesets.Mods private Vector2 flashlightSize; private float flashlightDim; - private readonly VertexBatch quadBatch = new QuadBatch(1, 1); private readonly Action addAction; public FlashlightDrawNode(Flashlight source) : base(source) { + var quadBatch = new QuadBatch(1, 1); + addAction = v => quadBatch.Add(new PositionAndColourVertex { Position = v.Position, From 7bcec31ea370d87a1a58958c0a405f3ff780de0c Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 7 Aug 2019 17:08:41 +0900 Subject: [PATCH 0547/2815] mention that the event was the type --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 24843f8c32..b1261855f2 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -293,7 +293,7 @@ namespace osu.Game.Beatmaps.Formats if (!Enum.TryParse(split[0], out type)) { - Logger.Log($"A beatmap event could not be parsed and will be ignored: {split[0]}", LoggingTarget.Runtime, LogLevel.Important); + Logger.Log($"Unknown beatmap event of type {split[0]} could not be parsed and will be ignored.", LoggingTarget.Runtime, LogLevel.Important); return; } diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 512eea0822..584388333d 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -84,7 +84,7 @@ namespace osu.Game.Beatmaps.Formats EventType type; if (!Enum.TryParse(split[0], out type)) - Logger.Log($"A storyboard {type} event could not be parsed and will be ignored: {split[0]}", LoggingTarget.Runtime, LogLevel.Important); + Logger.Log($"Unknown storyboard event of type {split[0]} could not be parsed and will be ignored.", LoggingTarget.Runtime, LogLevel.Important); switch (type) { From b0e06597159d5259965c6b716f0a35c092645827 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2019 17:33:30 +0900 Subject: [PATCH 0548/2815] Dispose instead --- osu.Game/Rulesets/Mods/ModFlashlight.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 7247bafd23..4f939362bb 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -155,13 +155,12 @@ namespace osu.Game.Rulesets.Mods private Vector2 flashlightSize; private float flashlightDim; + private readonly VertexBatch quadBatch = new QuadBatch(1, 1); private readonly Action addAction; public FlashlightDrawNode(Flashlight source) : base(source) { - var quadBatch = new QuadBatch(1, 1); - addAction = v => quadBatch.Add(new PositionAndColourVertex { Position = v.Position, @@ -194,6 +193,12 @@ namespace osu.Game.Rulesets.Mods shader.Unbind(); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + quadBatch?.Dispose(); + } } } } From 38df49995ce77e2176b491375144a80227579f58 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2019 18:29:50 +0900 Subject: [PATCH 0549/2815] Fix cursor scale not matching osu-stable --- .../UI/OsuPlayfieldAdjustmentContainer.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs index f7888f8022..9c8be868b0 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs @@ -13,13 +13,15 @@ namespace osu.Game.Rulesets.Osu.UI protected override Container Content => content; private readonly Container content; + private const float playfield_size_adjust = 0.8f; + public OsuPlayfieldAdjustmentContainer() { Anchor = Anchor.Centre; Origin = Anchor.Centre; // Calculated from osu!stable as 512 (default gamefield size) / 640 (default window size) - Size = new Vector2(0.8f); + Size = new Vector2(playfield_size_adjust); InternalChild = new Container { @@ -41,7 +43,19 @@ namespace osu.Game.Rulesets.Osu.UI { base.Update(); + // The following calculation results in a constant of 1.6 when OsuPlayfieldAdjustmentContainer + // is consuming the full game_size. This matches the osu-stable "magic ratio". + // + // game_size = DrawSizePreservingFillContainer.TargetSize = new Vector2(1024, 768) + // + // Parent is a 4:3 aspect enforced, using height as the constricting dimension + // Parent.ChildSize.X = min(game_size.X, game_size.Y * (4 / 3)) * playfield_size_adjust + // Parent.ChildSize.X = 819.2 + // + // Scale = 819.2 / 512 + // Scale = 1.6 Scale = new Vector2(Parent.ChildSize.X / OsuPlayfield.BASE_SIZE.X); + // Size = 0.625 Size = Vector2.Divide(Vector2.One, Scale); } } From da15e19912783b3848d9315ed365013c712dfa2c Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 7 Aug 2019 18:40:58 +0900 Subject: [PATCH 0550/2815] return on storyboard side --- osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 584388333d..f950437e75 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -83,8 +83,12 @@ namespace osu.Game.Beatmaps.Formats storyboardSprite = null; EventType type; + if (!Enum.TryParse(split[0], out type)) + { Logger.Log($"Unknown storyboard event of type {split[0]} could not be parsed and will be ignored.", LoggingTarget.Runtime, LogLevel.Important); + return; + } switch (type) { From ec13143ab3ffffebc718b97e3cd1964d4b88905a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2019 18:45:56 +0900 Subject: [PATCH 0551/2815] Add further xmldoc --- osu.Game/Skinning/LegacySkin.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 19f04c9b7a..3eda76e40f 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -20,6 +20,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.UI; using osuTK; using osuTK.Graphics; @@ -360,6 +361,10 @@ namespace osu.Game.Skinning } } + /// + /// A sprite which is displayed within the playfield, but historically was not considered part of the playfield. + /// Performs scale adjustment to undo the scale applied by (osu! ruleset specifically). + /// private class NonPlayfieldSprite : Sprite { public override Texture Texture @@ -368,7 +373,8 @@ namespace osu.Game.Skinning set { if (value != null) - value.ScaleAdjust *= 2f; + // stable "magic ratio". see OsuPlayfieldAdjustmentContainer for full explanation. + value.ScaleAdjust *= 1.6f; base.Texture = value; } } From 15a592e25eec64efd0d7e53ae9d52356a4dbf6ec Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 7 Aug 2019 19:25:40 +0900 Subject: [PATCH 0552/2815] Just assert doesn't throw and don't catch at LegacyDecoder --- .../Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 10 ++-------- osu.Game.Tests/Resources/valid-events.osu | 12 ------------ osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 9 +-------- 3 files changed, 3 insertions(+), 28 deletions(-) delete mode 100644 osu.Game.Tests/Resources/valid-events.osu diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 49a6f646ba..535320530d 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -484,20 +484,14 @@ namespace osu.Game.Tests.Beatmaps.Formats } [Test] - public void TestDecodeInvalidEvents() + public void TestInvalidEventStillPasses() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; - using (var goodResStream = TestResources.OpenResource("valid-events.osu")) - using (var goodStream = new StreamReader(goodResStream)) using (var badResStream = TestResources.OpenResource("invalid-events.osu")) using (var badStream = new StreamReader(badResStream)) { - var goodBeatmap = decoder.Decode(goodStream); - var badBeatmap = decoder.Decode(badStream); - - Assert.AreEqual(goodBeatmap.Breaks[0].Duration, badBeatmap.Breaks[0].Duration); - Assert.AreEqual(goodBeatmap.Metadata.BackgroundFile, badBeatmap.Metadata.BackgroundFile); + Assert.DoesNotThrow(() => decoder.Decode(badStream)); } } } diff --git a/osu.Game.Tests/Resources/valid-events.osu b/osu.Game.Tests/Resources/valid-events.osu deleted file mode 100644 index ed3614b4ed..0000000000 --- a/osu.Game.Tests/Resources/valid-events.osu +++ /dev/null @@ -1,12 +0,0 @@ -osu file format v14 - -[Events] -//Background and Video events -0,0,"machinetop_background.jpg",0,0 -//Break Periods -2,122474,140135 -//Storyboard Layer 0 (Background) -//Storyboard Layer 1 (Fail) -//Storyboard Layer 2 (Pass) -//Storyboard Layer 3 (Foreground) -//Storyboard Sound Samples diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 7999c82761..17e278c48e 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -43,14 +43,7 @@ namespace osu.Game.Beatmaps.Formats continue; } - try - { - ParseLine(output, section, line); - } - catch (Exception e) - { - Logger.Error(e, $"Failed to process line \"{line}\" into {output}"); - } + ParseLine(output, section, line); } } From 994c1f21ec5d9391787d73a79b4e5846e2627c08 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Aug 2019 19:33:16 +0900 Subject: [PATCH 0553/2815] Output stacked positions for conversion values --- .../OsuBeatmapConversionTest.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs index 392170f0a8..8f975b14a2 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs @@ -8,7 +8,6 @@ using osu.Framework.MathUtils; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.UI; using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Osu.Tests @@ -31,22 +30,22 @@ namespace osu.Game.Rulesets.Osu.Tests { case Slider slider: foreach (var nested in slider.NestedHitObjects) - yield return createConvertValue(nested); + yield return createConvertValue((OsuHitObject)nested); break; default: - yield return createConvertValue(hitObject); + yield return createConvertValue((OsuHitObject)hitObject); break; } - ConvertValue createConvertValue(HitObject obj) => new ConvertValue + ConvertValue createConvertValue(OsuHitObject obj) => new ConvertValue { StartTime = obj.StartTime, EndTime = (obj as IHasEndTime)?.EndTime ?? obj.StartTime, - X = (obj as IHasPosition)?.X ?? OsuPlayfield.BASE_SIZE.X / 2, - Y = (obj as IHasPosition)?.Y ?? OsuPlayfield.BASE_SIZE.Y / 2, + X = obj.StackedPosition.X, + Y = obj.StackedPosition.Y }; } From 42de5934f6f93c95049f254d748256ca9eb7d8b0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Aug 2019 19:33:35 +0900 Subject: [PATCH 0554/2815] Fix incorrect hitobject indices --- osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs index 7bb1f42802..a9f8b87fb4 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs @@ -211,14 +211,14 @@ namespace osu.Game.Rulesets.Osu.Beatmaps if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.Position) < stack_distance) { currHitObject.StackHeight++; - startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[i].StartTime; + startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[j].StartTime; } else if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.EndPosition) < stack_distance) { //Case for sliders - bump notes down and right, rather than up and left. sliderStack++; beatmap.HitObjects[j].StackHeight -= sliderStack; - startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[i].StartTime; + startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[j].StartTime; } } } From b8ccba02f2017dea054b3fce5413e90dff08670c Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 7 Aug 2019 19:33:54 +0900 Subject: [PATCH 0555/2815] Log to runtime instead --- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 17e278c48e..8365fb17d3 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -43,7 +43,14 @@ namespace osu.Game.Beatmaps.Formats continue; } - ParseLine(output, section, line); + try + { + ParseLine(output, section, line); + } + catch (Exception e) + { + Logger.Log($"Failed to process line \"{line}\" into {output}", LoggingTarget.Runtime, LogLevel.Important); + } } } From bc3a340286afb799b3af13197fd10f950b0e19f2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Aug 2019 19:35:39 +0900 Subject: [PATCH 0556/2815] Fix incorrect path position being used for old stacking algorithm --- .../OsuBeatmapConversionTest.cs | 1 + .../Beatmaps/OsuBeatmapProcessor.cs | 8 +- .../old-stacking-expected-conversion.json | 278 ++++++++++++++++++ .../Testing/Beatmaps/old-stacking.osu | 40 +++ 4 files changed, 326 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking-expected-conversion.json create mode 100644 osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking.osu diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs index 8f975b14a2..e9fdf924c3 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs @@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Osu.Tests [TestCase("slider-ticks")] [TestCase("repeat-slider")] [TestCase("uneven-repeat-slider")] + [TestCase("old-stacking")] public void Test(string name) => base.Test(name); protected override IEnumerable CreateConvertValue(HitObject hitObject) diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs index a9f8b87fb4..bb19b783aa 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; +using osuTK; namespace osu.Game.Rulesets.Osu.Beatmaps { @@ -208,12 +209,17 @@ namespace osu.Game.Rulesets.Osu.Beatmaps if (beatmap.HitObjects[j].StartTime - stackThreshold > startTime) break; + // The start position of the hitobject, or the position at the end of the path if the hitobject is a slider + Vector2 position2 = currHitObject is Slider currSlider + ? currSlider.Position + currSlider.Path.PositionAt(1) + : currHitObject.Position; + if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.Position) < stack_distance) { currHitObject.StackHeight++; startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[j].StartTime; } - else if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.EndPosition) < stack_distance) + else if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, position2) < stack_distance) { //Case for sliders - bump notes down and right, rather than up and left. sliderStack++; diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking-expected-conversion.json b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking-expected-conversion.json new file mode 100644 index 0000000000..b994cbd85a --- /dev/null +++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking-expected-conversion.json @@ -0,0 +1,278 @@ +{ + "Mappings": [{ + "StartTime": 32165, + "Objects": [{ + "StartTime": 32165, + "EndTime": 32165, + "X": 32, + "Y": 320 + }] + }, + { + "StartTime": 32517, + "Objects": [{ + "StartTime": 32517, + "EndTime": 32517, + "X": 246.396057, + "Y": 182.396057 + }] + }, + { + "StartTime": 32605, + "Objects": [{ + "StartTime": 32605, + "EndTime": 32605, + "X": 249.597382, + "Y": 185.597382 + }] + }, + { + "StartTime": 32693, + "Objects": [{ + "StartTime": 32693, + "EndTime": 32693, + "X": 252.798691, + "Y": 188.798691 + }] + }, + { + "StartTime": 32781, + "Objects": [{ + "StartTime": 32781, + "EndTime": 32781, + "X": 256, + "Y": 192 + }] + }, + { + "StartTime": 33248, + "Objects": [{ + "StartTime": 33248, + "EndTime": 33248, + "X": 39.3960648, + "Y": 76.3960648 + }] + }, + { + "StartTime": 33307, + "Objects": [{ + "StartTime": 33307, + "EndTime": 33307, + "X": 42.5973778, + "Y": 79.597374 + }] + }, + { + "StartTime": 33383, + "Objects": [{ + "StartTime": 33383, + "EndTime": 33383, + "X": 45.798687, + "Y": 82.79869 + }] + }, + { + "StartTime": 33459, + "Objects": [{ + "StartTime": 33459, + "EndTime": 33459, + "X": 49, + "Y": 86 + }, + { + "StartTime": 33635, + "EndTime": 33635, + "X": 123.847847, + "Y": 85.7988 + }, + { + "StartTime": 33811, + "EndTime": 33811, + "X": 198.6957, + "Y": 85.5975952 + }, + { + "StartTime": 33988, + "EndTime": 33988, + "X": 273.9688, + "Y": 85.39525 + }, + { + "StartTime": 34164, + "EndTime": 34164, + "X": 348.816681, + "Y": 85.19404 + }, + { + "StartTime": 34246, + "EndTime": 34246, + "X": 398.998718, + "Y": 85.05914 + } + ] + }, + { + "StartTime": 34341, + "Objects": [{ + "StartTime": 34341, + "EndTime": 34341, + "X": 401.201324, + "Y": 88.20131 + }] + }, + { + "StartTime": 34400, + "Objects": [{ + "StartTime": 34400, + "EndTime": 34400, + "X": 404.402618, + "Y": 91.402626 + }] + }, + { + "StartTime": 34459, + "Objects": [{ + "StartTime": 34459, + "EndTime": 34459, + "X": 407.603943, + "Y": 94.6039352 + }] + }, + { + "StartTime": 34989, + "Objects": [{ + "StartTime": 34989, + "EndTime": 34989, + "X": 163, + "Y": 138 + }, + { + "StartTime": 35018, + "EndTime": 35018, + "X": 188, + "Y": 138 + } + ] + }, + { + "StartTime": 35106, + "Objects": [{ + "StartTime": 35106, + "EndTime": 35106, + "X": 163, + "Y": 138 + }, + { + "StartTime": 35135, + "EndTime": 35135, + "X": 188, + "Y": 138 + } + ] + }, + { + "StartTime": 35224, + "Objects": [{ + "StartTime": 35224, + "EndTime": 35224, + "X": 163, + "Y": 138 + }, + { + "StartTime": 35253, + "EndTime": 35253, + "X": 188, + "Y": 138 + } + ] + }, + { + "StartTime": 35695, + "Objects": [{ + "StartTime": 35695, + "EndTime": 35695, + "X": 166, + "Y": 76 + }, + { + "StartTime": 35871, + "EndTime": 35871, + "X": 240.99855, + "Y": 75.53417 + }, + { + "StartTime": 36011, + "EndTime": 36011, + "X": 315.9971, + "Y": 75.0683441 + } + ] + }, + { + "StartTime": 36106, + "Objects": [{ + "StartTime": 36106, + "EndTime": 36106, + "X": 315, + "Y": 75 + }, + { + "StartTime": 36282, + "EndTime": 36282, + "X": 240.001526, + "Y": 75.47769 + }, + { + "StartTime": 36422, + "EndTime": 36422, + "X": 165.003052, + "Y": 75.95539 + } + ] + }, + { + "StartTime": 36518, + "Objects": [{ + "StartTime": 36518, + "EndTime": 36518, + "X": 166, + "Y": 76 + }, + { + "StartTime": 36694, + "EndTime": 36694, + "X": 240.99855, + "Y": 75.53417 + }, + { + "StartTime": 36834, + "EndTime": 36834, + "X": 315.9971, + "Y": 75.0683441 + } + ] + }, + { + "StartTime": 36929, + "Objects": [{ + "StartTime": 36929, + "EndTime": 36929, + "X": 315, + "Y": 75 + }, + { + "StartTime": 37105, + "EndTime": 37105, + "X": 240.001526, + "Y": 75.47769 + }, + { + "StartTime": 37245, + "EndTime": 37245, + "X": 165.003052, + "Y": 75.95539 + } + ] + } + ] +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking.osu b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking.osu new file mode 100644 index 0000000000..4bc9226d67 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking.osu @@ -0,0 +1,40 @@ +osu file format v3 + +[Difficulty] +HPDrainRate:3 +CircleSize:5 +OverallDifficulty:8 +ApproachRate:8 +SliderMultiplier:1.5 +SliderTickRate:2 + +[TimingPoints] +48,352.941176470588,4,1,1,100,1,0 + +[HitObjects] +// Hit circles +32,320,32165,5,2,0:0:0:0: +256,192,32517,5,0,0:0:0:0: +256,192,32605,1,0,0:0:0:0: +256,192,32693,1,0,0:0:0:0: +256,192,32781,1,0,0:0:0:0: + +// Hit circles on slider endpoints +49,86,33248,1,0,0:0:0:0: +49,86,33307,1,0,0:0:0:0: +49,86,33383,1,0,0:0:0:0: +49,86,33459,2,0,L|421:85,1,350 +398,85,34341,1,0,0:0:0:0: +398,85,34400,1,0,0:0:0:0: +398,85,34459,1,0,0:0:0:0: + +// Sliders +163,138,34989,2,0,L|196:138,1,25 +163,138,35106,2,0,L|196:138,1,25 +163,138,35224,2,0,L|196:138,1,25 + +// Reversed sliders +166,76,35695,2,0,L|327:75,1,150 +315,75,36106,2,0,L|158:76,1,150 +166,76,36518,2,0,L|327:75,1,150 +315,75,36929,2,0,L|158:76,1,150 From bde89adcb72b85fb76476065e017b8533ea79338 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 7 Aug 2019 19:45:29 +0900 Subject: [PATCH 0557/2815] show exception message --- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 8365fb17d3..d6d6804d16 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -49,7 +49,7 @@ namespace osu.Game.Beatmaps.Formats } catch (Exception e) { - Logger.Log($"Failed to process line \"{line}\" into {output}", LoggingTarget.Runtime, LogLevel.Important); + Logger.Log($"Failed to process line \"{line}\" into {output}: {e.Message}", LoggingTarget.Runtime, LogLevel.Important); } } } From 2ab288902759280a2258a07fdd2dbb501d2f398b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2019 19:56:17 +0900 Subject: [PATCH 0558/2815] 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 e64102c401..c4cdffa8a5 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -63,6 +63,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index cf325b77be..02bf053468 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -15,7 +15,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 66f398a927..5b43a6f46f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From 411916d4a31b39725b58cbc549fd0a9001e691f5 Mon Sep 17 00:00:00 2001 From: DTSDAO Date: Wed, 7 Aug 2019 19:32:48 +0800 Subject: [PATCH 0559/2815] Move static method to Game class --- .../Graphics/Containers/LinkFlowContainer.cs | 2 +- osu.Game/Online/Chat/MessageFormatter.cs | 52 ------------------ osu.Game/OsuGame.cs | 55 +++++++++++++++++-- 3 files changed, 52 insertions(+), 57 deletions(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index ab4629cfff..ca9ffd06c6 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -85,7 +85,7 @@ namespace osu.Game.Graphics.Containers { RelativeSizeAxes = Axes.Both, TooltipText = tooltipText ?? (url != text ? url : string.Empty), - Action = action ?? (() => LinkUtils.HandleLink(url, linkType, linkArgument, game, channelManager, showNotImplementedError)), + Action = action ?? (() => game.HandleLink(url, linkType, linkArgument)), }); return drawables; diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 73396ce4d2..c875150091 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -3,9 +3,7 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text.RegularExpressions; -using osu.Framework.Logging; namespace osu.Game.Online.Chat { @@ -290,54 +288,4 @@ namespace osu.Game.Online.Chat public int CompareTo(Link otherLink) => Index > otherLink.Index ? 1 : -1; } - - public static class LinkUtils - { - public static void HandleLink(string url, LinkAction linkType, string linkArgument, OsuGame game, ChannelManager channelManager = null, Action showNotImplementedError = null) - { - switch (linkType) - { - case LinkAction.OpenBeatmap: - // TODO: proper query params handling - if (linkArgument != null && int.TryParse(linkArgument.Contains('?') ? linkArgument.Split('?')[0] : linkArgument, out int beatmapId)) - game?.ShowBeatmap(beatmapId); - break; - - case LinkAction.OpenBeatmapSet: - if (int.TryParse(linkArgument, out int setId)) - game?.ShowBeatmapSet(setId); - break; - - case LinkAction.OpenChannel: - try - { - channelManager?.OpenChannel(linkArgument); - } - catch (ChannelNotFoundException) - { - Logger.Log($"The requested channel \"{linkArgument}\" does not exist"); - } - - break; - - case LinkAction.OpenEditorTimestamp: - case LinkAction.JoinMultiplayerMatch: - case LinkAction.Spectate: - showNotImplementedError?.Invoke(); - break; - - case LinkAction.External: - game?.OpenUrlExternally(url); - break; - - case LinkAction.OpenUserProfile: - if (long.TryParse(linkArgument, out long userId)) - game?.ShowUser(userId); - break; - - default: - throw new NotImplementedException($"This {nameof(LinkAction)} ({linkType.ToString()}) is missing an associated action."); - } - } - } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 846677a0a9..43f3f1f5b5 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -195,19 +195,66 @@ namespace osu.Game private ExternalLinkOpener externalLinkOpener; - public void HandleUrl(string url) + public void HandleLink(string url, LinkAction linkType, string linkArgument) { - Logger.Log($"Request to handle url: {url}"); - Action showNotImplementedError = () => notifications?.Post(new SimpleNotification { Text = @"This link type is not yet supported!", Icon = FontAwesome.Solid.LifeRing, }); + switch (linkType) + { + case LinkAction.OpenBeatmap: + // TODO: proper query params handling + if (linkArgument != null && int.TryParse(linkArgument.Contains('?') ? linkArgument.Split('?')[0] : linkArgument, out int beatmapId)) + ShowBeatmap(beatmapId); + break; + + case LinkAction.OpenBeatmapSet: + if (int.TryParse(linkArgument, out int setId)) + ShowBeatmapSet(setId); + break; + + case LinkAction.OpenChannel: + try + { + channelManager.OpenChannel(linkArgument); + } + catch (ChannelNotFoundException) + { + Logger.Log($"The requested channel \"{linkArgument}\" does not exist"); + } + + break; + + case LinkAction.OpenEditorTimestamp: + case LinkAction.JoinMultiplayerMatch: + case LinkAction.Spectate: + showNotImplementedError?.Invoke(); + break; + + case LinkAction.External: + OpenUrlExternally(url); + break; + + case LinkAction.OpenUserProfile: + if (long.TryParse(linkArgument, out long userId)) + ShowUser(userId); + break; + + default: + throw new NotImplementedException($"This {nameof(LinkAction)} ({linkType.ToString()}) is missing an associated action."); + } + } + + public void HandleUrl(string url) + { + Logger.Log($"Request to handle url: {url}"); + LinkDetails linkDetails = GetLinkDetails(url); - Schedule(() => LinkUtils.HandleLink(url, linkDetails.Action, linkDetails.Argument, this, channelManager, showNotImplementedError)); + Schedule(() => HandleLink(url, linkDetails.Action, linkDetails.Argument)); } public void OpenUrlExternally(string url) From 37e430177384e047ce2ce932b280a22583c7d223 Mon Sep 17 00:00:00 2001 From: DTSDAO Date: Wed, 7 Aug 2019 20:16:22 +0800 Subject: [PATCH 0560/2815] Fix code format --- osu.Game/Graphics/Containers/LinkFlowContainer.cs | 11 +---------- osu.Game/OsuGame.cs | 2 +- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index ca9ffd06c6..b03bc4e3aa 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -22,21 +22,12 @@ namespace osu.Game.Graphics.Containers } private OsuGame game; - private ChannelManager channelManager; - private Action showNotImplementedError; [BackgroundDependencyLoader(true)] - private void load(OsuGame game, NotificationOverlay notifications, ChannelManager channelManager) + private void load(OsuGame game) { // will be null in tests this.game = game; - this.channelManager = channelManager; - - showNotImplementedError = () => notifications?.Post(new SimpleNotification - { - Text = @"This link type is not yet supported!", - Icon = FontAwesome.Solid.LifeRing, - }); } public void AddLinks(string text, List links) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 43f3f1f5b5..82bf8abf94 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -231,7 +231,7 @@ namespace osu.Game case LinkAction.OpenEditorTimestamp: case LinkAction.JoinMultiplayerMatch: case LinkAction.Spectate: - showNotImplementedError?.Invoke(); + showNotImplementedError.Invoke(); break; case LinkAction.External: From 9f72e92e0e35dc4c71ba7d6d184334fbd5b732ca Mon Sep 17 00:00:00 2001 From: DTSDAO Date: Wed, 7 Aug 2019 20:27:39 +0800 Subject: [PATCH 0561/2815] Fix code format --- osu.Game/Graphics/Containers/LinkFlowContainer.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index b03bc4e3aa..66911d9615 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -8,8 +8,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using System.Collections.Generic; using osu.Framework.Graphics; -using osu.Game.Overlays; -using osu.Game.Overlays.Notifications; using osu.Game.Users; namespace osu.Game.Graphics.Containers From c2b3c28c798e2994402ba2fbacd72d9093a99e94 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 7 Aug 2019 16:15:53 +0300 Subject: [PATCH 0562/2815] Use IsBreakTime for checking if currently in a break Rather than iterating over all breaks to find which is in current time --- osu.Game/Screens/Play/Player.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 96b8073d11..e7398be176 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -66,6 +66,8 @@ namespace osu.Game.Screens.Play private SampleChannel sampleRestart; + private BreakOverlay breakOverlay; + protected ScoreProcessor ScoreProcessor { get; private set; } protected DrawableRuleset DrawableRuleset { get; private set; } @@ -134,7 +136,7 @@ namespace osu.Game.Screens.Play } } }, - new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) + breakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -411,8 +413,7 @@ namespace osu.Game.Screens.Play PauseOverlay.Hide(); // breaks and time-based conditions may allow instant resume. - double time = GameplayClockContainer.GameplayClock.CurrentTime; - if (Beatmap.Value.Beatmap.Breaks.Any(b => b.Contains(time)) || time < Beatmap.Value.Beatmap.HitObjects.First().StartTime) + if (breakOverlay.IsBreakTime.Value || GameplayClockContainer.GameplayClock.CurrentTime < Beatmap.Value.Beatmap.HitObjects.First().StartTime) completeResume(); else DrawableRuleset.RequestResume(completeResume); From d3657d82cd042e13c1ea848c003b7e094483575b Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 7 Aug 2019 16:28:16 +0300 Subject: [PATCH 0563/2815] Simplify final check for break time --- osu.Game/Screens/Play/BreakOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 93267e5f2a..ea9bba9c95 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -153,7 +153,7 @@ namespace osu.Game.Screens.Play // If the current break doesn't have effects, IsBreakTime should be false. // We also assume that the overlay's fade out transform is "not break time". var currentBreak = breaks[CurrentBreakIndex]; - isBreakTime.Value = currentBreak.HasEffect && time >= currentBreak.StartTime && time <= currentBreak.EndTime - fade_duration; + isBreakTime.Value = currentBreak.HasEffect && currentBreak.Contains(time); } private void initializeBreaks() From ba269a49eef92af0c1ab44dcc73950ed7a8156b4 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 7 Aug 2019 16:59:35 +0300 Subject: [PATCH 0564/2815] Expose break fade duration and add it in the calculation --- osu.Game/Beatmaps/Timing/BreakPeriod.cs | 4 +++- osu.Game/Screens/Play/BreakOverlay.cs | 22 +++++++++++++--------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/osu.Game/Beatmaps/Timing/BreakPeriod.cs b/osu.Game/Beatmaps/Timing/BreakPeriod.cs index 856a5fefd4..5d79c7a86b 100644 --- a/osu.Game/Beatmaps/Timing/BreakPeriod.cs +++ b/osu.Game/Beatmaps/Timing/BreakPeriod.cs @@ -1,6 +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 osu.Game.Screens.Play; + namespace osu.Game.Beatmaps.Timing { public class BreakPeriod @@ -35,6 +37,6 @@ namespace osu.Game.Beatmaps.Timing /// /// The time to check in milliseconds. /// Whether the time falls within this . - public bool Contains(double time) => time >= StartTime && time <= EndTime; + public bool Contains(double time) => time >= StartTime && time <= EndTime - BreakOverlay.BREAK_FADE_DURATION; } } diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index ea9bba9c95..444b1bd210 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -17,7 +17,11 @@ namespace osu.Game.Screens.Play { public class BreakOverlay : Container { - private const double fade_duration = BreakPeriod.MIN_BREAK_DURATION / 2; + /// + /// The duration of the break overlay fading. + /// + public const double BREAK_FADE_DURATION = BreakPeriod.MIN_BREAK_DURATION / 2; + private const float remaining_time_container_max_size = 0.3f; private const int vertical_margin = 25; @@ -172,25 +176,25 @@ namespace osu.Game.Screens.Play using (BeginAbsoluteSequence(b.StartTime, true)) { - fadeContainer.FadeIn(fade_duration); - breakArrows.Show(fade_duration); + fadeContainer.FadeIn(BREAK_FADE_DURATION); + breakArrows.Show(BREAK_FADE_DURATION); remainingTimeAdjustmentBox - .ResizeWidthTo(remaining_time_container_max_size, fade_duration, Easing.OutQuint) - .Delay(b.Duration - fade_duration) + .ResizeWidthTo(remaining_time_container_max_size, BREAK_FADE_DURATION, Easing.OutQuint) + .Delay(b.Duration - BREAK_FADE_DURATION) .ResizeWidthTo(0); remainingTimeBox - .ResizeWidthTo(0, b.Duration - fade_duration) + .ResizeWidthTo(0, b.Duration - BREAK_FADE_DURATION) .Then() .ResizeWidthTo(1); remainingTimeCounter.CountTo(b.Duration).CountTo(0, b.Duration); - using (BeginDelayedSequence(b.Duration - fade_duration, true)) + using (BeginDelayedSequence(b.Duration - BREAK_FADE_DURATION, true)) { - fadeContainer.FadeOut(fade_duration); - breakArrows.Hide(fade_duration); + fadeContainer.FadeOut(BREAK_FADE_DURATION); + breakArrows.Hide(BREAK_FADE_DURATION); } } } From 167a81f40a3561b34f3bfb2e5c6acadb93f891c1 Mon Sep 17 00:00:00 2001 From: jorolf Date: Wed, 7 Aug 2019 18:12:11 +0200 Subject: [PATCH 0565/2815] add special skin hit "animations" --- .../Resources/special-skin/hit0-0@2x.png | Bin 0 -> 4020 bytes .../Resources/special-skin/hit100-0@2x.png | Bin 0 -> 5653 bytes .../Resources/special-skin/hit300-0@2x.png | Bin 0 -> 5004 bytes .../Resources/special-skin/hit50-0@2x.png | Bin 0 -> 3861 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit0-0@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit100-0@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit300-0@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit50-0@2x.png diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit0-0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit0-0@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9d2ed3a7fa3d5bc0aa43098e9247fa3f6a7c6f80 GIT binary patch literal 4020 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit100-0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit100-0@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..777af3bd415263824913549c5c4c708c4d6e450a GIT binary patch literal 5653 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 Wr=9MwmK5XvL>cIqJgU`pj`<%3F8Egf literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit300-0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit300-0@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7ee45a7057ca5f1e11d9bea30375c92e139cf01e GIT binary patch literal 5004 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 F_J2M}_?iF! literal 0 HcmV?d00001 From c77e6074f647574b09403131874ba9566ea1e807 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 7 Aug 2019 19:58:26 +0300 Subject: [PATCH 0566/2815] Disallow adding to score after the player has failed --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 2e863f7edb..fd851f2cbb 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -316,6 +316,9 @@ namespace osu.Game.Rulesets.Scoring /// The to apply. protected virtual void ApplyResult(JudgementResult result) { + if (HasFailed) + return; + result.ComboAtJudgement = Combo.Value; result.HighestComboAtJudgement = HighestCombo.Value; result.HealthAtJudgement = Health.Value; From d5b26b86dafbe9b870b50dd23b74f68ad101471d Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 7 Aug 2019 22:18:10 +0300 Subject: [PATCH 0567/2815] Fix storyboard not showing on disabled user dim --- osu.Game/Graphics/Containers/UserDimContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 03de5f651f..dd5134168f 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -76,9 +76,9 @@ namespace osu.Game.Graphics.Containers /// protected virtual void UpdateVisuals() { - ContentDisplayed = ShowDimContent; + ContentDisplayed = !EnableUserDim.Value || ShowDimContent; - dimContent.FadeTo((ContentDisplayed) ? 1 : 0, BACKGROUND_FADE_DURATION, Easing.OutQuint); + dimContent.FadeTo(ContentDisplayed ? 1 : 0, BACKGROUND_FADE_DURATION, Easing.OutQuint); dimContent.FadeColour(EnableUserDim.Value ? OsuColour.Gray(1 - (float)UserDimLevel.Value) : Color4.White, BACKGROUND_FADE_DURATION, Easing.OutQuint); } } From 40a33b338201d543c27e97f7769e1731d5461352 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2019 10:41:23 +0900 Subject: [PATCH 0568/2815] Move IsLoaded check to more correct place --- osu.Game/Screens/Play/BreakOverlay.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 444b1bd210..78690f156f 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -40,7 +40,8 @@ namespace osu.Game.Screens.Play isBreakTime.Value = false; CurrentBreakIndex = 0; - initializeBreaks(); + if (IsLoaded) + initializeBreaks(); } } @@ -162,8 +163,6 @@ namespace osu.Game.Screens.Play private void initializeBreaks() { - if (!IsLoaded) return; // we need a clock. - FinishTransforms(true); Scheduler.CancelDelayedTasks(); From 99f5ca07ce8562e87fa4dae3de9a24c77805cacf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2019 10:42:54 +0900 Subject: [PATCH 0569/2815] Remove redundant comment --- osu.Game/Screens/Play/BreakOverlay.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 78690f156f..2d014db210 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -154,9 +154,6 @@ namespace osu.Game.Screens.Play CurrentBreakIndex--; } - // This ensures that IsBreakTime is generally consistent with the overlay's transforms during a break. - // If the current break doesn't have effects, IsBreakTime should be false. - // We also assume that the overlay's fade out transform is "not break time". var currentBreak = breaks[CurrentBreakIndex]; isBreakTime.Value = currentBreak.HasEffect && currentBreak.Contains(time); } From 7d42561da93c7f816de3f71e6de32f1d46848250 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2019 12:58:20 +0900 Subject: [PATCH 0570/2815] Remove linq usage in BreakOverlay update --- osu.Game/Screens/Play/BreakOverlay.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 2d014db210..6fdee85f45 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -138,7 +137,7 @@ namespace osu.Game.Screens.Play private void updateBreakTimeBindable() { - if (breaks?.Any() != true) + if (breaks == null || breaks.Count == 0) return; var time = Clock.CurrentTime; From ffed642929e7ec23ba8e3c60c252a9d0522c9327 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 8 Aug 2019 07:08:51 +0300 Subject: [PATCH 0571/2815] Implement MatchBeatmapPanel --- .../Screens/Multi/Match/Components/Header.cs | 16 ++++++ .../Match/Components/MatchBeatmapPanel.cs | 49 +++++++++++++++++++ .../Screens/Multi/Match/MatchSubScreen.cs | 2 + 3 files changed, 67 insertions(+) create mode 100644 osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs diff --git a/osu.Game/Screens/Multi/Match/Components/Header.cs b/osu.Game/Screens/Multi/Match/Components/Header.cs index 73994fa369..629a19e5e8 100644 --- a/osu.Game/Screens/Multi/Match/Components/Header.cs +++ b/osu.Game/Screens/Multi/Match/Components/Header.cs @@ -29,6 +29,10 @@ namespace osu.Game.Screens.Multi.Match.Components public Action RequestBeatmapSelection; + public BindableBool ShowBeatmapPanel = new BindableBool(); + + private MatchBeatmapPanel beatmapPanel; + public Header() { RelativeSizeAxes = Axes.X; @@ -55,6 +59,12 @@ namespace osu.Game.Screens.Multi.Match.Components RelativeSizeAxes = Axes.Both, Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.4f), Color4.Black.Opacity(0.6f)), }, + beatmapPanel = new MatchBeatmapPanel + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Margin = new MarginPadding { Right = 100 }, + } } }, new Box @@ -114,6 +124,12 @@ namespace osu.Game.Screens.Multi.Match.Components beatmapButton.Action = () => RequestBeatmapSelection?.Invoke(); } + protected override void LoadComplete() + { + base.LoadComplete(); + ShowBeatmapPanel.BindValueChanged(value => beatmapPanel.FadeTo(value.NewValue ? 1 : 0, 200, Easing.OutQuint), true); + } + private class BeatmapSelectButton : HeaderButton { [Resolved(typeof(Room), nameof(Room.RoomID))] diff --git a/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs b/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs new file mode 100644 index 0000000000..916115f11b --- /dev/null +++ b/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.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.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Direct; +using osu.Game.Rulesets; + +namespace osu.Game.Screens.Multi.Match.Components +{ + public class MatchBeatmapPanel : MultiplayerComposite + { + [Resolved] + private IAPIProvider api { get; set; } + + [Resolved] + private RulesetStore rulesets { get; set; } + + private GetBeatmapSetRequest request; + + public MatchBeatmapPanel() + { + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + CurrentItem.BindValueChanged(item => + { + var id = item.NewValue?.Beatmap.OnlineBeatmapID ?? 0; + + if (id != 0) + { + request?.Cancel(); + request = new GetBeatmapSetRequest(id, BeatmapSetLookupType.BeatmapId); + request.Success += beatmap => + { + ClearInternal(); + AddInternal(new DirectGridPanel(beatmap.ToBeatmapSet(rulesets))); + }; + api.Queue(request); + } + }, true); + } + } +} diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index 5469d4c8c8..16c6b412fa 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -148,12 +148,14 @@ namespace osu.Game.Screens.Multi.Match if (tab.NewValue is SettingsMatchPage) { + header.ShowBeatmapPanel.Value = false; settings.Show(); info.FadeOut(fade_duration, Easing.OutQuint); bottomRow.FadeOut(fade_duration, Easing.OutQuint); } else { + header.ShowBeatmapPanel.Value = true; settings.Hide(); info.FadeIn(fade_duration, Easing.OutQuint); bottomRow.FadeIn(fade_duration, Easing.OutQuint); From a4459972b6f00d3e533a769468cecf4a7e5462ad Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 8 Aug 2019 07:15:30 +0300 Subject: [PATCH 0572/2815] Remove ViewBeatmapButton --- .../Visual/Multiplayer/TestSceneMatchInfo.cs | 2 +- .../Screens/Multi/Match/Components/Info.cs | 8 +--- .../Match/Components/ViewBeatmapButton.cs | 46 ------------------- 3 files changed, 2 insertions(+), 54 deletions(-) delete mode 100644 osu.Game/Screens/Multi/Match/Components/ViewBeatmapButton.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchInfo.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchInfo.cs index 3f0c0b07b7..a6c036a876 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchInfo.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchInfo.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Multiplayer typeof(Info), typeof(HeaderButton), typeof(ReadyButton), - typeof(ViewBeatmapButton) + typeof(MatchBeatmapPanel) }; [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Multi/Match/Components/Info.cs b/osu.Game/Screens/Multi/Match/Components/Info.cs index a185c4db50..74f000c21f 100644 --- a/osu.Game/Screens/Multi/Match/Components/Info.cs +++ b/osu.Game/Screens/Multi/Match/Components/Info.cs @@ -28,7 +28,6 @@ namespace osu.Game.Screens.Multi.Match.Components private void load() { ReadyButton readyButton; - ViewBeatmapButton viewBeatmapButton; HostInfo hostInfo; InternalChildren = new Drawable[] @@ -80,7 +79,6 @@ namespace osu.Game.Screens.Multi.Match.Components Direction = FillDirection.Horizontal, Children = new Drawable[] { - viewBeatmapButton = new ViewBeatmapButton(), readyButton = new ReadyButton { Action = () => OnStart?.Invoke() @@ -91,11 +89,7 @@ namespace osu.Game.Screens.Multi.Match.Components }, }; - CurrentItem.BindValueChanged(item => - { - viewBeatmapButton.Beatmap.Value = item.NewValue?.Beatmap; - readyButton.Beatmap.Value = item.NewValue?.Beatmap; - }, true); + CurrentItem.BindValueChanged(item => readyButton.Beatmap.Value = item.NewValue?.Beatmap, true); hostInfo.Host.BindTo(Host); } diff --git a/osu.Game/Screens/Multi/Match/Components/ViewBeatmapButton.cs b/osu.Game/Screens/Multi/Match/Components/ViewBeatmapButton.cs deleted file mode 100644 index 8d1ff21124..0000000000 --- a/osu.Game/Screens/Multi/Match/Components/ViewBeatmapButton.cs +++ /dev/null @@ -1,46 +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.Game.Beatmaps; -using osuTK; - -namespace osu.Game.Screens.Multi.Match.Components -{ - public class ViewBeatmapButton : HeaderButton - { - public readonly Bindable Beatmap = new Bindable(); - - [Resolved(CanBeNull = true)] - private OsuGame osuGame { get; set; } - - public ViewBeatmapButton() - { - RelativeSizeAxes = Axes.Y; - Size = new Vector2(200, 1); - - Text = "View beatmap"; - } - - [BackgroundDependencyLoader] - private void load() - { - if (osuGame != null) - Beatmap.BindValueChanged(beatmap => updateAction(beatmap.NewValue), true); - } - - private void updateAction(BeatmapInfo beatmap) - { - if (beatmap == null) - { - Enabled.Value = false; - return; - } - - Action = () => osuGame.ShowBeatmap(beatmap.OnlineBeatmapID ?? 0); - Enabled.Value = true; - } - } -} From 77df6a0cb7ef7736808811d4c2033d5882d8aa6a Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 7 Aug 2019 21:16:36 -0700 Subject: [PATCH 0573/2815] Remove unused direct placeholder screen --- osu.Game/Screens/Direct/OnlineListing.cs | 9 --------- osu.Game/Screens/Menu/MainMenu.cs | 2 -- 2 files changed, 11 deletions(-) delete mode 100644 osu.Game/Screens/Direct/OnlineListing.cs diff --git a/osu.Game/Screens/Direct/OnlineListing.cs b/osu.Game/Screens/Direct/OnlineListing.cs deleted file mode 100644 index 8376383674..0000000000 --- a/osu.Game/Screens/Direct/OnlineListing.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.Screens.Direct -{ - public class OnlineListing : ScreenWhiteBox - { - } -} diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index fc8ddd8bd4..499b5089f6 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -14,7 +14,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Overlays; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Charts; -using osu.Game.Screens.Direct; using osu.Game.Screens.Edit; using osu.Game.Screens.Multi; using osu.Game.Screens.Select; @@ -65,7 +64,6 @@ namespace osu.Game.Screens.Menu buttons = new ButtonSystem { OnChart = delegate { this.Push(new ChartListing()); }, - OnDirect = delegate { this.Push(new OnlineListing()); }, OnEdit = delegate { this.Push(new Editor()); }, OnSolo = onSolo, OnMulti = delegate { this.Push(new Multiplayer()); }, From a345955f45c51d4ecaeb9cc728db1f30ce40d1f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2019 13:57:04 +0900 Subject: [PATCH 0574/2815] Add mentions linking ScoreProcessor apply/revert methods together --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 2e863f7edb..ba2375bec1 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -313,6 +313,9 @@ namespace osu.Game.Rulesets.Scoring /// /// Applies the score change of a to this . /// + /// + /// Any changes applied via this method can be reverted via . + /// /// The to apply. protected virtual void ApplyResult(JudgementResult result) { @@ -357,7 +360,7 @@ namespace osu.Game.Rulesets.Scoring } /// - /// Reverts the score change of a that was applied to this . + /// Reverts the score change of a that was applied to this via . /// /// The judgement scoring result. protected virtual void RevertResult(JudgementResult result) From ac2060f1cf4d1befc59d2b56531d58736f0dd021 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 8 Aug 2019 14:44:04 +0900 Subject: [PATCH 0575/2815] Throw exceptions and let LegacyDecoder handle them --- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 153 ++++----- .../Formats/LegacyStoryboardDecoder.cs | 6 +- .../Objects/Legacy/ConvertHitObjectParser.cs | 323 +++++++++--------- 3 files changed, 223 insertions(+), 259 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index b1261855f2..02d969b571 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -5,7 +5,6 @@ using System; using System.IO; using System.Linq; using osu.Framework.IO.File; -using osu.Framework.Logging; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Beatmaps.ControlPoints; @@ -292,10 +291,7 @@ namespace osu.Game.Beatmaps.Formats EventType type; if (!Enum.TryParse(split[0], out type)) - { - Logger.Log($"Unknown beatmap event of type {split[0]} could not be parsed and will be ignored.", LoggingTarget.Runtime, LogLevel.Important); - return; - } + throw new InvalidDataException($@"Unknown event type: {split[0]}"); switch (type) { @@ -323,90 +319,79 @@ namespace osu.Game.Beatmaps.Formats private void handleTimingPoint(string line) { - try + string[] split = line.Split(','); + + double time = getOffsetTime(Parsing.ParseDouble(split[0].Trim())); + double beatLength = Parsing.ParseDouble(split[1].Trim()); + double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1; + + TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple; + if (split.Length >= 3) + timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)Parsing.ParseInt(split[2]); + + LegacySampleBank sampleSet = defaultSampleBank; + if (split.Length >= 4) + sampleSet = (LegacySampleBank)Parsing.ParseInt(split[3]); + + int customSampleBank = 0; + if (split.Length >= 5) + customSampleBank = Parsing.ParseInt(split[4]); + + int sampleVolume = defaultSampleVolume; + if (split.Length >= 6) + sampleVolume = Parsing.ParseInt(split[5]); + + bool timingChange = true; + if (split.Length >= 7) + timingChange = split[6][0] == '1'; + + bool kiaiMode = false; + bool omitFirstBarSignature = false; + + if (split.Length >= 8) { - string[] split = line.Split(','); - - double time = getOffsetTime(Parsing.ParseDouble(split[0].Trim())); - double beatLength = Parsing.ParseDouble(split[1].Trim()); - double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1; - - TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple; - if (split.Length >= 3) - timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)Parsing.ParseInt(split[2]); - - LegacySampleBank sampleSet = defaultSampleBank; - if (split.Length >= 4) - sampleSet = (LegacySampleBank)Parsing.ParseInt(split[3]); - - int customSampleBank = 0; - if (split.Length >= 5) - customSampleBank = Parsing.ParseInt(split[4]); - - int sampleVolume = defaultSampleVolume; - if (split.Length >= 6) - sampleVolume = Parsing.ParseInt(split[5]); - - bool timingChange = true; - if (split.Length >= 7) - timingChange = split[6][0] == '1'; - - bool kiaiMode = false; - bool omitFirstBarSignature = false; - - if (split.Length >= 8) - { - EffectFlags effectFlags = (EffectFlags)Parsing.ParseInt(split[7]); - kiaiMode = effectFlags.HasFlag(EffectFlags.Kiai); - omitFirstBarSignature = effectFlags.HasFlag(EffectFlags.OmitFirstBarLine); - } - - string stringSampleSet = sampleSet.ToString().ToLowerInvariant(); - if (stringSampleSet == @"none") - stringSampleSet = @"normal"; - - if (timingChange) - { - var controlPoint = CreateTimingControlPoint(); - controlPoint.Time = time; - controlPoint.BeatLength = beatLength; - controlPoint.TimeSignature = timeSignature; - - handleTimingControlPoint(controlPoint); - } - - handleDifficultyControlPoint(new DifficultyControlPoint - { - Time = time, - SpeedMultiplier = speedMultiplier, - AutoGenerated = timingChange - }); - - handleEffectControlPoint(new EffectControlPoint - { - Time = time, - KiaiMode = kiaiMode, - OmitFirstBarLine = omitFirstBarSignature, - AutoGenerated = timingChange - }); - - handleSampleControlPoint(new LegacySampleControlPoint - { - Time = time, - SampleBank = stringSampleSet, - SampleVolume = sampleVolume, - CustomSampleBank = customSampleBank, - AutoGenerated = timingChange - }); + EffectFlags effectFlags = (EffectFlags)Parsing.ParseInt(split[7]); + kiaiMode = effectFlags.HasFlag(EffectFlags.Kiai); + omitFirstBarSignature = effectFlags.HasFlag(EffectFlags.OmitFirstBarLine); } - catch (FormatException) + + string stringSampleSet = sampleSet.ToString().ToLowerInvariant(); + if (stringSampleSet == @"none") + stringSampleSet = @"normal"; + + if (timingChange) { - Logger.Log("A timing point could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important); + var controlPoint = CreateTimingControlPoint(); + controlPoint.Time = time; + controlPoint.BeatLength = beatLength; + controlPoint.TimeSignature = timeSignature; + + handleTimingControlPoint(controlPoint); } - catch (OverflowException) + + handleDifficultyControlPoint(new DifficultyControlPoint { - Logger.Log("A timing point could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important); - } + Time = time, + SpeedMultiplier = speedMultiplier, + AutoGenerated = timingChange + }); + + handleEffectControlPoint(new EffectControlPoint + { + Time = time, + KiaiMode = kiaiMode, + OmitFirstBarLine = omitFirstBarSignature, + AutoGenerated = timingChange + }); + + handleSampleControlPoint(new LegacySampleControlPoint + { + Time = time, + SampleBank = stringSampleSet, + SampleVolume = sampleVolume, + CustomSampleBank = customSampleBank, + AutoGenerated = timingChange + }); } private void handleTimingControlPoint(TimingControlPoint newPoint) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index f950437e75..3ae1c3ef12 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -10,7 +10,6 @@ using osuTK; using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.IO.File; -using osu.Framework.Logging; using osu.Game.Storyboards; namespace osu.Game.Beatmaps.Formats @@ -85,10 +84,7 @@ namespace osu.Game.Beatmaps.Formats EventType type; if (!Enum.TryParse(split[0], out type)) - { - Logger.Log($"Unknown storyboard event of type {split[0]} could not be parsed and will be ignored.", LoggingTarget.Runtime, LogLevel.Important); - return; - } + throw new InvalidDataException($@"Unknown event type: {split[0]}"); switch (type) { diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index d70c1bf7d3..442e37edf5 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -10,7 +10,6 @@ using osu.Game.Beatmaps.Formats; using osu.Game.Audio; using System.Linq; using JetBrains.Annotations; -using osu.Framework.Logging; using osu.Framework.MathUtils; namespace osu.Game.Rulesets.Objects.Legacy @@ -41,208 +40,192 @@ namespace osu.Game.Rulesets.Objects.Legacy [CanBeNull] public override HitObject Parse(string text) { - try + string[] split = text.Split(','); + + Vector2 pos = new Vector2((int)Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE)); + + double startTime = Parsing.ParseDouble(split[2]) + Offset; + + ConvertHitObjectType type = (ConvertHitObjectType)Parsing.ParseInt(split[3]); + + int comboOffset = (int)(type & ConvertHitObjectType.ComboOffset) >> 4; + type &= ~ConvertHitObjectType.ComboOffset; + + bool combo = type.HasFlag(ConvertHitObjectType.NewCombo); + type &= ~ConvertHitObjectType.NewCombo; + + var soundType = (LegacySoundType)Parsing.ParseInt(split[4]); + var bankInfo = new SampleBankInfo(); + + HitObject result = null; + + if (type.HasFlag(ConvertHitObjectType.Circle)) { - string[] split = text.Split(','); + result = CreateHit(pos, combo, comboOffset); - Vector2 pos = new Vector2((int)Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE)); + if (split.Length > 5) + readCustomSampleBanks(split[5], bankInfo); + } + else if (type.HasFlag(ConvertHitObjectType.Slider)) + { + PathType pathType = PathType.Catmull; + double? length = null; - double startTime = Parsing.ParseDouble(split[2]) + Offset; + string[] pointSplit = split[5].Split('|'); - ConvertHitObjectType type = (ConvertHitObjectType)Parsing.ParseInt(split[3]); + int pointCount = 1; + foreach (var t in pointSplit) + if (t.Length > 1) + pointCount++; - int comboOffset = (int)(type & ConvertHitObjectType.ComboOffset) >> 4; - type &= ~ConvertHitObjectType.ComboOffset; + var points = new Vector2[pointCount]; - bool combo = type.HasFlag(ConvertHitObjectType.NewCombo); - type &= ~ConvertHitObjectType.NewCombo; + int pointIndex = 1; - var soundType = (LegacySoundType)Parsing.ParseInt(split[4]); - var bankInfo = new SampleBankInfo(); - - HitObject result = null; - - if (type.HasFlag(ConvertHitObjectType.Circle)) + foreach (string t in pointSplit) { - result = CreateHit(pos, combo, comboOffset); - - if (split.Length > 5) - readCustomSampleBanks(split[5], bankInfo); - } - else if (type.HasFlag(ConvertHitObjectType.Slider)) - { - PathType pathType = PathType.Catmull; - double? length = null; - - string[] pointSplit = split[5].Split('|'); - - int pointCount = 1; - foreach (var t in pointSplit) - if (t.Length > 1) - pointCount++; - - var points = new Vector2[pointCount]; - - int pointIndex = 1; - - foreach (string t in pointSplit) + if (t.Length == 1) { - if (t.Length == 1) + switch (t) { - switch (t) - { - case @"C": - pathType = PathType.Catmull; - break; - - case @"B": - pathType = PathType.Bezier; - break; - - case @"L": - pathType = PathType.Linear; - break; - - case @"P": - pathType = PathType.PerfectCurve; - break; - } - - continue; - } - - string[] temp = t.Split(':'); - points[pointIndex++] = new Vector2((int)Parsing.ParseDouble(temp[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseDouble(temp[1], Parsing.MAX_COORDINATE_VALUE)) - pos; - } - - // osu-stable special-cased colinear perfect curves to a CurveType.Linear - bool isLinear(Vector2[] p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) - (p[1].X - p[0].X) * (p[2].Y - p[0].Y)); - - if (points.Length == 3 && pathType == PathType.PerfectCurve && isLinear(points)) - pathType = PathType.Linear; - - int repeatCount = Parsing.ParseInt(split[6]); - - if (repeatCount > 9000) - throw new ArgumentOutOfRangeException(nameof(repeatCount), @"Repeat count is way too high"); - - // osu-stable treated the first span of the slider as a repeat, but no repeats are happening - repeatCount = Math.Max(0, repeatCount - 1); - - if (split.Length > 7) - { - length = Math.Max(0, Parsing.ParseDouble(split[7])); - if (length == 0) - length = null; - } - - if (split.Length > 10) - readCustomSampleBanks(split[10], bankInfo); - - // One node for each repeat + the start and end nodes - int nodes = repeatCount + 2; - - // Populate node sample bank infos with the default hit object sample bank - var nodeBankInfos = new List(); - for (int i = 0; i < nodes; i++) - nodeBankInfos.Add(bankInfo.Clone()); - - // Read any per-node sample banks - if (split.Length > 9 && split[9].Length > 0) - { - string[] sets = split[9].Split('|'); - - for (int i = 0; i < nodes; i++) - { - if (i >= sets.Length) + case @"C": + pathType = PathType.Catmull; break; - SampleBankInfo info = nodeBankInfos[i]; - readCustomSampleBanks(sets[i], info); - } - } - - // Populate node sound types with the default hit object sound type - var nodeSoundTypes = new List(); - for (int i = 0; i < nodes; i++) - nodeSoundTypes.Add(soundType); - - // Read any per-node sound types - if (split.Length > 8 && split[8].Length > 0) - { - string[] adds = split[8].Split('|'); - - for (int i = 0; i < nodes; i++) - { - if (i >= adds.Length) + case @"B": + pathType = PathType.Bezier; break; - int sound; - int.TryParse(adds[i], out sound); - nodeSoundTypes[i] = (LegacySoundType)sound; + case @"L": + pathType = PathType.Linear; + break; + + case @"P": + pathType = PathType.PerfectCurve; + break; } + + continue; } - // Generate the final per-node samples - var nodeSamples = new List>(nodes); + string[] temp = t.Split(':'); + points[pointIndex++] = new Vector2((int)Parsing.ParseDouble(temp[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseDouble(temp[1], Parsing.MAX_COORDINATE_VALUE)) - pos; + } + + // osu-stable special-cased colinear perfect curves to a CurveType.Linear + bool isLinear(Vector2[] p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) - (p[1].X - p[0].X) * (p[2].Y - p[0].Y)); + + if (points.Length == 3 && pathType == PathType.PerfectCurve && isLinear(points)) + pathType = PathType.Linear; + + int repeatCount = Parsing.ParseInt(split[6]); + + if (repeatCount > 9000) + throw new ArgumentOutOfRangeException(nameof(repeatCount), @"Repeat count is way too high"); + + // osu-stable treated the first span of the slider as a repeat, but no repeats are happening + repeatCount = Math.Max(0, repeatCount - 1); + + if (split.Length > 7) + { + length = Math.Max(0, Parsing.ParseDouble(split[7])); + if (length == 0) + length = null; + } + + if (split.Length > 10) + readCustomSampleBanks(split[10], bankInfo); + + // One node for each repeat + the start and end nodes + int nodes = repeatCount + 2; + + // Populate node sample bank infos with the default hit object sample bank + var nodeBankInfos = new List(); + for (int i = 0; i < nodes; i++) + nodeBankInfos.Add(bankInfo.Clone()); + + // Read any per-node sample banks + if (split.Length > 9 && split[9].Length > 0) + { + string[] sets = split[9].Split('|'); + for (int i = 0; i < nodes; i++) - nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); - - result = CreateSlider(pos, combo, comboOffset, points, length, pathType, repeatCount, nodeSamples); - - // The samples are played when the slider ends, which is the last node - result.Samples = nodeSamples[nodeSamples.Count - 1]; - } - else if (type.HasFlag(ConvertHitObjectType.Spinner)) - { - double endTime = Math.Max(startTime, Parsing.ParseDouble(split[5]) + Offset); - - result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, endTime); - - if (split.Length > 6) - readCustomSampleBanks(split[6], bankInfo); - } - else if (type.HasFlag(ConvertHitObjectType.Hold)) - { - // Note: Hold is generated by BMS converts - - double endTime = Math.Max(startTime, Parsing.ParseDouble(split[2])); - - if (split.Length > 5 && !string.IsNullOrEmpty(split[5])) { - string[] ss = split[5].Split(':'); - endTime = Math.Max(startTime, Parsing.ParseDouble(ss[0])); - readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo); + if (i >= sets.Length) + break; + + SampleBankInfo info = nodeBankInfos[i]; + readCustomSampleBanks(sets[i], info); } - - result = CreateHold(pos, combo, comboOffset, endTime + Offset); } - if (result == null) + // Populate node sound types with the default hit object sound type + var nodeSoundTypes = new List(); + for (int i = 0; i < nodes; i++) + nodeSoundTypes.Add(soundType); + + // Read any per-node sound types + if (split.Length > 8 && split[8].Length > 0) { - Logger.Log($"Unknown hit object type: {type}. Skipped.", level: LogLevel.Error); - return null; + string[] adds = split[8].Split('|'); + + for (int i = 0; i < nodes; i++) + { + if (i >= adds.Length) + break; + + int sound; + int.TryParse(adds[i], out sound); + nodeSoundTypes[i] = (LegacySoundType)sound; + } } - result.StartTime = startTime; + // Generate the final per-node samples + var nodeSamples = new List>(nodes); + for (int i = 0; i < nodes; i++) + nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); - if (result.Samples.Count == 0) - result.Samples = convertSoundType(soundType, bankInfo); + result = CreateSlider(pos, combo, comboOffset, points, length, pathType, repeatCount, nodeSamples); - FirstObject = false; - - return result; + // The samples are played when the slider ends, which is the last node + result.Samples = nodeSamples[nodeSamples.Count - 1]; } - catch (FormatException) + else if (type.HasFlag(ConvertHitObjectType.Spinner)) { - Logger.Log("A hitobject could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important); + double endTime = Math.Max(startTime, Parsing.ParseDouble(split[5]) + Offset); + + result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, endTime); + + if (split.Length > 6) + readCustomSampleBanks(split[6], bankInfo); } - catch (OverflowException) + else if (type.HasFlag(ConvertHitObjectType.Hold)) { - Logger.Log("A hitobject could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important); + // Note: Hold is generated by BMS converts + + double endTime = Math.Max(startTime, Parsing.ParseDouble(split[2])); + + if (split.Length > 5 && !string.IsNullOrEmpty(split[5])) + { + string[] ss = split[5].Split(':'); + endTime = Math.Max(startTime, Parsing.ParseDouble(ss[0])); + readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo); + } + + result = CreateHold(pos, combo, comboOffset, endTime + Offset); } - return null; + if (result == null) + throw new InvalidDataException($"Unknown hit object type: {type}"); + + result.StartTime = startTime; + + if (result.Samples.Count == 0) + result.Samples = convertSoundType(soundType, bankInfo); + + FirstObject = false; + + return result; } private void readCustomSampleBanks(string str, SampleBankInfo bankInfo) From de4ad1f62586fb3c55cd8067ac9672f284c8cfbf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 8 Aug 2019 14:44:49 +0900 Subject: [PATCH 0576/2815] Fix bad log message --- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 442e37edf5..e990938291 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -216,7 +216,7 @@ namespace osu.Game.Rulesets.Objects.Legacy } if (result == null) - throw new InvalidDataException($"Unknown hit object type: {type}"); + throw new InvalidDataException($"Unknown hit object type: {split[3]}"); result.StartTime = startTime; From cbcdc2890088bb151ae6c444d4ae95e005a63f6b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 8 Aug 2019 09:04:24 +0300 Subject: [PATCH 0577/2815] Fix hard crash when clicking on a preview button --- osu.Game/Screens/Multi/Match/MatchSubScreen.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index 16c6b412fa..13a21a4516 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.GameTypes; @@ -18,7 +19,7 @@ using PlaylistItem = osu.Game.Online.Multiplayer.PlaylistItem; namespace osu.Game.Screens.Multi.Match { - public class MatchSubScreen : MultiplayerSubScreen + public class MatchSubScreen : MultiplayerSubScreen, IPreviewTrackOwner { public override bool DisallowExternalBeatmapRulesetChanges => true; @@ -44,6 +45,9 @@ namespace osu.Game.Screens.Multi.Match [Resolved] private BeatmapManager beatmapManager { get; set; } + [Resolved] + private PreviewTrackManager trackManager { get; set; } + [Resolved(CanBeNull = true)] private OsuGame game { get; set; } @@ -184,6 +188,8 @@ namespace osu.Game.Screens.Multi.Match Mods.Value = Array.Empty(); + trackManager.StopAnyPlaying(this); + return base.OnExiting(next); } @@ -237,6 +243,13 @@ namespace osu.Game.Screens.Multi.Match } } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.CacheAs(this); + return dependencies; + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 8668bce25d23d69d23c1bc3c0e15a741b9862b1a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 8 Aug 2019 09:52:42 +0300 Subject: [PATCH 0578/2815] Fix preview can be played on start --- osu.Game/Screens/Multi/Match/MatchSubScreen.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index 13a21a4516..3136326cdd 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -231,6 +231,8 @@ namespace osu.Game.Screens.Multi.Match private void onStart() { + trackManager.StopAnyPlaying(this); + switch (type.Value) { default: From bcd443a3aa8178cc68481b96d3055d18a94014e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Thu, 8 Aug 2019 08:56:52 +0200 Subject: [PATCH 0579/2815] remove /j --- osu.Game/Online/Chat/ChannelManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index b5d8879e50..9acf6bd39f 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -213,7 +213,6 @@ namespace osu.Game.Online.Chat PostMessage(content, true); break; - case "j": case "join": if (string.IsNullOrWhiteSpace(content)) { From 0bed3bfece7397236bc891a34e5fe8ab7c343f86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Thu, 8 Aug 2019 09:02:09 +0200 Subject: [PATCH 0580/2815] formatting inspection 2 --- osu.Game/Online/Chat/ChannelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 9acf6bd39f..4f6066cab1 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -221,7 +221,7 @@ namespace osu.Game.Online.Chat } var channel = availableChannels.Where(c => c.Name == content || c.Name == $"#{content}").FirstOrDefault(); - + if (channel == null) { target.AddNewMessages(new ErrorMessage($"Channel '{content}' not found.")); From 6d5a7041fdf7873c36eff28d8e624e68fc0088ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2019 17:10:06 +0900 Subject: [PATCH 0581/2815] Move system user colour assignment to ensure consistency --- osu.Game/Online/Chat/ErrorMessage.cs | 2 +- osu.Game/Users/User.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/ErrorMessage.cs b/osu.Game/Online/Chat/ErrorMessage.cs index a8ff0e9a98..87a65fb3f1 100644 --- a/osu.Game/Online/Chat/ErrorMessage.cs +++ b/osu.Game/Online/Chat/ErrorMessage.cs @@ -8,7 +8,7 @@ namespace osu.Game.Online.Chat public ErrorMessage(string message) : base(message) { - Sender.Colour = @"ff0000"; + // todo: this should likely be styled differently in the future. } } } diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index b738eff4a6..a5f3578711 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -187,6 +187,7 @@ namespace osu.Game.Users public static readonly User SYSTEM_USER = new User { Username = "system", + Colour = @"9c0101", Id = 0 }; From a3dbaef4cabb1fdd37c96cdd9dd5ad88ef1b62df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2019 17:29:50 +0900 Subject: [PATCH 0582/2815] Adjust background gradient --- osu.Game/Screens/Multi/Match/Components/Header.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Match/Components/Header.cs b/osu.Game/Screens/Multi/Match/Components/Header.cs index 629a19e5e8..7f2b1278b7 100644 --- a/osu.Game/Screens/Multi/Match/Components/Header.cs +++ b/osu.Game/Screens/Multi/Match/Components/Header.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Multi.Match.Components new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.4f), Color4.Black.Opacity(0.6f)), + Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.7f), Color4.Black.Opacity(0.8f)), }, beatmapPanel = new MatchBeatmapPanel { From 1b559c15852901bd2c7819b166479cbb76f69475 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 8 Aug 2019 12:01:33 +0300 Subject: [PATCH 0583/2815] Use async loading --- osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs b/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs index 916115f11b..dc889eeb94 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs @@ -39,7 +39,8 @@ namespace osu.Game.Screens.Multi.Match.Components request.Success += beatmap => { ClearInternal(); - AddInternal(new DirectGridPanel(beatmap.ToBeatmapSet(rulesets))); + var panel = new DirectGridPanel(beatmap.ToBeatmapSet(rulesets)); + LoadComponentAsync(panel, p => { AddInternal(panel); }); }; api.Queue(request); } From 08a92c38d728e030b64bc0135607ce25ce01f5cd Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 8 Aug 2019 12:04:44 +0300 Subject: [PATCH 0584/2815] adjust naming --- .../Screens/Multi/Match/Components/MatchBeatmapPanel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs b/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs index dc889eeb94..b927e38edb 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs @@ -30,12 +30,12 @@ namespace osu.Game.Screens.Multi.Match.Components { CurrentItem.BindValueChanged(item => { - var id = item.NewValue?.Beatmap.OnlineBeatmapID ?? 0; + var onlineId = item.NewValue?.Beatmap.OnlineBeatmapID ?? 0; - if (id != 0) + if (onlineId != 0) { request?.Cancel(); - request = new GetBeatmapSetRequest(id, BeatmapSetLookupType.BeatmapId); + request = new GetBeatmapSetRequest(onlineId, BeatmapSetLookupType.BeatmapId); request.Success += beatmap => { ClearInternal(); From e9b5c91690a424be0f26b8474fc27e03d326add8 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 8 Aug 2019 12:08:51 +0300 Subject: [PATCH 0585/2815] Fade out existing panel on beatmap change --- .../Multi/Match/Components/MatchBeatmapPanel.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs b/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs index b927e38edb..f73059d069 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs @@ -19,6 +19,7 @@ namespace osu.Game.Screens.Multi.Match.Components private RulesetStore rulesets { get; set; } private GetBeatmapSetRequest request; + private DirectGridPanel panel; public MatchBeatmapPanel() { @@ -30,16 +31,23 @@ namespace osu.Game.Screens.Multi.Match.Components { CurrentItem.BindValueChanged(item => { + request?.Cancel(); + + if (panel != null) + { + panel.FadeOut(200); + panel.Expire(); + panel = null; + } + var onlineId = item.NewValue?.Beatmap.OnlineBeatmapID ?? 0; if (onlineId != 0) { - request?.Cancel(); request = new GetBeatmapSetRequest(onlineId, BeatmapSetLookupType.BeatmapId); request.Success += beatmap => { - ClearInternal(); - var panel = new DirectGridPanel(beatmap.ToBeatmapSet(rulesets)); + panel = new DirectGridPanel(beatmap.ToBeatmapSet(rulesets)); LoadComponentAsync(panel, p => { AddInternal(panel); }); }; api.Queue(request); From 460cf141de6ce895cfa69f3dd00cdc62a0d50205 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 8 Aug 2019 12:18:26 +0300 Subject: [PATCH 0586/2815] Add testing --- .../Multiplayer/TestSceneMatchBeatmapPanel.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs new file mode 100644 index 0000000000..46fd43508b --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs @@ -0,0 +1,40 @@ +// 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.Beatmaps; +using osu.Game.Online.Multiplayer; +using osu.Game.Screens.Multi.Match.Components; +using osu.Framework.Graphics; +using osu.Framework.MathUtils; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneMatchBeatmapPanel : MultiplayerTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(MatchBeatmapPanel) + }; + + public TestSceneMatchBeatmapPanel() + { + Add(new MatchBeatmapPanel + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + + var playlist = Room.Playlist; + + playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 1763072 } }); + playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 2101557 } }); + playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 1973466 } }); + playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 2109801 } }); + playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 1922035 } }); + + AddStep("Select random beatmap", () => Room.CurrentItem.Value = playlist[RNG.Next(playlist.Count)]); + } + } +} From 9ab132520cdb6af2063ca8de0d87a56785d44dd6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 8 Aug 2019 12:25:46 +0300 Subject: [PATCH 0587/2815] Testcase improvements --- .../Multiplayer/TestSceneMatchBeatmapPanel.cs | 35 ++++++++++++++----- .../Screens/Multi/Match/MatchSubScreen.cs | 2 ++ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs index 46fd43508b..db2b61cdd9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs @@ -8,16 +8,21 @@ using osu.Game.Online.Multiplayer; using osu.Game.Screens.Multi.Match.Components; using osu.Framework.Graphics; using osu.Framework.MathUtils; +using osu.Game.Audio; +using osu.Framework.Allocation; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMatchBeatmapPanel : MultiplayerTestScene + public class TestSceneMatchBeatmapPanel : MultiplayerTestScene, IPreviewTrackOwner { public override IReadOnlyList RequiredTypes => new[] { typeof(MatchBeatmapPanel) }; + [Resolved] + private PreviewTrackManager trackManager { get; set; } + public TestSceneMatchBeatmapPanel() { Add(new MatchBeatmapPanel @@ -26,15 +31,29 @@ namespace osu.Game.Tests.Visual.Multiplayer Origin = Anchor.Centre, }); - var playlist = Room.Playlist; + Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 1763072 } }); + Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 2101557 } }); + Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 1973466 } }); + Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 2109801 } }); + Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 1922035 } }); + } - playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 1763072 } }); - playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 2101557 } }); - playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 1973466 } }); - playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 2109801 } }); - playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 1922035 } }); + protected override void LoadComplete() + { + base.LoadComplete(); - AddStep("Select random beatmap", () => Room.CurrentItem.Value = playlist[RNG.Next(playlist.Count)]); + AddStep("Select random beatmap", () => + { + Room.CurrentItem.Value = Room.Playlist[RNG.Next(Room.Playlist.Count)]; + trackManager.StopAnyPlaying(this); + }); + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.CacheAs(this); + return dependencies; } } } diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index 3136326cdd..c89c32759d 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -206,6 +206,8 @@ namespace osu.Game.Screens.Multi.Match if (e.NewValue?.Ruleset != null) Ruleset.Value = e.NewValue.Ruleset; + + trackManager.StopAnyPlaying(this); } /// From 3ebfa0505c21bec9ef00fe643b05dbd52336444b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 8 Aug 2019 18:26:03 +0900 Subject: [PATCH 0588/2815] Don't share single scheduler across all model managers --- osu.Game/Database/ArchiveModelManager.cs | 29 +++++++++++------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index efb76deff8..52d3f013ce 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -31,10 +31,21 @@ namespace osu.Game.Database /// /// The model type. /// The associated file join type. - public abstract class ArchiveModelManager : ArchiveModelManager, ICanAcceptFiles, IModelManager + public abstract class ArchiveModelManager : ICanAcceptFiles, IModelManager where TModel : class, IHasFiles, IHasPrimaryKey, ISoftDelete where TFileModel : INamedFileInfo, new() { + private const int import_queue_request_concurrency = 1; + + /// + /// A singleton scheduler shared by all . + /// + /// + /// This scheduler generally performs IO and CPU intensive work so concurrency is limited harshly. + /// It is mainly being used as a queue mechanism for large imports. + /// + private static readonly ThreadedTaskScheduler import_scheduler = new ThreadedTaskScheduler(import_queue_request_concurrency, nameof(ArchiveModelManager)); + /// /// Set an endpoint for notifications to be posted to. /// @@ -336,7 +347,7 @@ namespace osu.Game.Database flushEvents(true); return item; - }, cancellationToken, TaskCreationOptions.HideScheduler, IMPORT_SCHEDULER).Unwrap(); + }, cancellationToken, TaskCreationOptions.HideScheduler, import_scheduler).Unwrap(); /// /// Perform an update of the specified item. @@ -646,18 +657,4 @@ namespace osu.Game.Database #endregion } - - public abstract class ArchiveModelManager - { - private const int import_queue_request_concurrency = 1; - - /// - /// A singleton scheduler shared by all . - /// - /// - /// This scheduler generally performs IO and CPU intensive work so concurrency is limited harshly. - /// It is mainly being used as a queue mechanism for large imports. - /// - protected static readonly ThreadedTaskScheduler IMPORT_SCHEDULER = new ThreadedTaskScheduler(import_queue_request_concurrency, nameof(ArchiveModelManager)); - } } From 566d874641922a3b680b648542aecc7702a0236b Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 8 Aug 2019 15:25:07 +0300 Subject: [PATCH 0589/2815] Prevent failing when reverting to a hasFailedAtJudgement --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index fd851f2cbb..5ca33ff0bc 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Scoring /// /// Whether this ScoreProcessor has already triggered the failed state. /// - public virtual bool HasFailed { get; private set; } + public virtual bool HasFailed { get; protected set; } /// /// The default conditions for failing. @@ -309,6 +309,7 @@ namespace osu.Game.Rulesets.Scoring } private readonly Dictionary scoreResultCounts = new Dictionary(); + private Judgement hasFailedAtJudgement; /// /// Applies the score change of a to this . @@ -317,7 +318,12 @@ namespace osu.Game.Rulesets.Scoring protected virtual void ApplyResult(JudgementResult result) { if (HasFailed) + { + if (hasFailedAtJudgement == null) + hasFailedAtJudgement = result.Judgement; + return; + } result.ComboAtJudgement = Combo.Value; result.HighestComboAtJudgement = HighestCombo.Value; @@ -365,6 +371,17 @@ namespace osu.Game.Rulesets.Scoring /// The judgement scoring result. protected virtual void RevertResult(JudgementResult result) { + if (HasFailed) + { + if (hasFailedAtJudgement == result.Judgement) + { + hasFailedAtJudgement = null; + HasFailed = false; + } + + return; + } + Combo.Value = result.ComboAtJudgement; HighestCombo.Value = result.HighestComboAtJudgement; Health.Value = result.HealthAtJudgement; From 537973fc458f50a260e1e8d31f217a146b02cd88 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 8 Aug 2019 15:59:29 +0300 Subject: [PATCH 0590/2815] Add test for disabling user dim on storyboard --- .../Background/TestSceneUserDimContainer.cs | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index f114559114..bffee7f1f7 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -149,10 +149,10 @@ namespace osu.Game.Tests.Visual.Background } /// - /// Check if the is properly accepting user-defined visual changes at all. + /// Check if the is properly accepting user-defined visual changes in background at all. /// [Test] - public void DisableUserDimTest() + public void DisableUserDimBackgroundTest() { performFullSetup(); waitForDim(); @@ -165,6 +165,28 @@ namespace osu.Game.Tests.Visual.Background AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); } + /// + /// Check if the is properly accepting user-defined visual changes in storyboard at all. + /// + [Test] + public void DisableUserDimStoryboardTest() + { + performFullSetup(); + createFakeStoryboard(); + AddStep("Storyboard Enabled", () => + { + player.ReplacesBackground.Value = true; + player.StoryboardEnabled.Value = true; + }); + AddStep("EnableUserDim enabled", () => player.DimmableStoryboard.EnableUserDim.Value = true); + AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f); + waitForDim(); + AddAssert("Storyboard is invisible", () => !player.IsStoryboardVisible); + AddStep("EnableUserDim disabled", () => player.DimmableStoryboard.EnableUserDim.Value = false); + waitForDim(); + AddAssert("Storyboard is visible", () => player.IsStoryboardVisible); + } + /// /// Check if the visual settings container retains dim and blur when pausing /// From 88b9942b2ac601d33c40a41f8d3625b7788eabf5 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 8 Aug 2019 17:07:06 +0300 Subject: [PATCH 0591/2815] Move EnableUserDim check to defualt value of ShowDimContent --- osu.Game/Graphics/Containers/UserDimContainer.cs | 2 +- osu.Game/Screens/Play/DimmableStoryboard.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index dd5134168f..b7da5592a8 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -69,7 +69,7 @@ namespace osu.Game.Graphics.Containers /// /// Whether the content of this container should currently be visible. /// - protected virtual bool ShowDimContent => true; + protected virtual bool ShowDimContent => !EnableUserDim.Value; /// /// Should be invoked when any dependent dim level or user setting is changed and bring the visual state up-to-date. diff --git a/osu.Game/Screens/Play/DimmableStoryboard.cs b/osu.Game/Screens/Play/DimmableStoryboard.cs index 45dff039b6..10ddaeb354 100644 --- a/osu.Game/Screens/Play/DimmableStoryboard.cs +++ b/osu.Game/Screens/Play/DimmableStoryboard.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play base.LoadComplete(); } - protected override bool ShowDimContent => ShowStoryboard.Value && UserDimLevel.Value < 1; + protected override bool ShowDimContent => base.ShowDimContent || ShowStoryboard.Value && UserDimLevel.Value < 1; private void initializeStoryboard(bool async) { From bedb744a2e067a53d6aa5fdcbc1c6ede16245b9e Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 8 Aug 2019 17:11:26 +0300 Subject: [PATCH 0592/2815] Add parentheses --- osu.Game/Screens/Play/DimmableStoryboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/DimmableStoryboard.cs b/osu.Game/Screens/Play/DimmableStoryboard.cs index 10ddaeb354..8d1f703791 100644 --- a/osu.Game/Screens/Play/DimmableStoryboard.cs +++ b/osu.Game/Screens/Play/DimmableStoryboard.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play base.LoadComplete(); } - protected override bool ShowDimContent => base.ShowDimContent || ShowStoryboard.Value && UserDimLevel.Value < 1; + protected override bool ShowDimContent => base.ShowDimContent || (ShowStoryboard.Value && UserDimLevel.Value < 1); private void initializeStoryboard(bool async) { From a3d90da7d4663ee1e0653bdcb9d70143bc89ae9b Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 8 Aug 2019 17:29:52 +0300 Subject: [PATCH 0593/2815] Remove unnecessary check --- osu.Game/Graphics/Containers/UserDimContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index b7da5592a8..6696a5bfe0 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -76,7 +76,7 @@ namespace osu.Game.Graphics.Containers /// protected virtual void UpdateVisuals() { - ContentDisplayed = !EnableUserDim.Value || ShowDimContent; + ContentDisplayed = ShowDimContent; dimContent.FadeTo(ContentDisplayed ? 1 : 0, BACKGROUND_FADE_DURATION, Easing.OutQuint); dimContent.FadeColour(EnableUserDim.Value ? OsuColour.Gray(1 - (float)UserDimLevel.Value) : Color4.White, BACKGROUND_FADE_DURATION, Easing.OutQuint); From 0fcc6c1676089fd2dd963f937bf317b68252336e Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 8 Aug 2019 22:13:48 +0300 Subject: [PATCH 0594/2815] Add DimLevel property --- osu.Game/Graphics/Containers/UserDimContainer.cs | 6 ++++-- osu.Game/Screens/Play/DimmableStoryboard.cs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 6696a5bfe0..023964d701 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -36,6 +36,8 @@ namespace osu.Game.Graphics.Containers protected Bindable ShowStoryboard { get; private set; } + protected double DimLevel => EnableUserDim.Value ? UserDimLevel.Value : 0; + protected override Container Content => dimContent; private Container dimContent { get; } @@ -69,7 +71,7 @@ namespace osu.Game.Graphics.Containers /// /// Whether the content of this container should currently be visible. /// - protected virtual bool ShowDimContent => !EnableUserDim.Value; + protected virtual bool ShowDimContent => true; /// /// Should be invoked when any dependent dim level or user setting is changed and bring the visual state up-to-date. @@ -79,7 +81,7 @@ namespace osu.Game.Graphics.Containers ContentDisplayed = ShowDimContent; dimContent.FadeTo(ContentDisplayed ? 1 : 0, BACKGROUND_FADE_DURATION, Easing.OutQuint); - dimContent.FadeColour(EnableUserDim.Value ? OsuColour.Gray(1 - (float)UserDimLevel.Value) : Color4.White, BACKGROUND_FADE_DURATION, Easing.OutQuint); + dimContent.FadeColour(OsuColour.Gray(1 - (float)DimLevel), BACKGROUND_FADE_DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Screens/Play/DimmableStoryboard.cs b/osu.Game/Screens/Play/DimmableStoryboard.cs index 8d1f703791..2154526e54 100644 --- a/osu.Game/Screens/Play/DimmableStoryboard.cs +++ b/osu.Game/Screens/Play/DimmableStoryboard.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play base.LoadComplete(); } - protected override bool ShowDimContent => base.ShowDimContent || (ShowStoryboard.Value && UserDimLevel.Value < 1); + protected override bool ShowDimContent => ShowStoryboard.Value && DimLevel < 1; private void initializeStoryboard(bool async) { From 565034e658ace24fd3d2ab493bc0df5496a9cdfb Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 8 Aug 2019 22:14:51 +0300 Subject: [PATCH 0595/2815] Remove unnecessary using directive --- osu.Game/Graphics/Containers/UserDimContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 023964d701..2b7635cc88 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -6,7 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Configuration; -using osuTK.Graphics; namespace osu.Game.Graphics.Containers { From 7e9c100c9b25dd450e290da9d74ac068010f0044 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2019 12:29:33 +0900 Subject: [PATCH 0596/2815] Apply new resharper refactors --- osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs | 2 +- osu.Game/Beatmaps/Legacy/LegacyMods.cs | 2 +- osu.Game/Graphics/UserInterface/Bar.cs | 2 +- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectType.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs index a3cd455886..e4a28167ec 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// /// Keep the same as last row. /// - ForceStack = 1 << 0, + ForceStack = 1, /// /// Keep different from last row. diff --git a/osu.Game/Beatmaps/Legacy/LegacyMods.cs b/osu.Game/Beatmaps/Legacy/LegacyMods.cs index 8e53c24e7b..583e950e49 100644 --- a/osu.Game/Beatmaps/Legacy/LegacyMods.cs +++ b/osu.Game/Beatmaps/Legacy/LegacyMods.cs @@ -9,7 +9,7 @@ namespace osu.Game.Beatmaps.Legacy public enum LegacyMods { None = 0, - NoFail = 1 << 0, + NoFail = 1, Easy = 1 << 1, TouchDevice = 1 << 2, Hidden = 1 << 3, diff --git a/osu.Game/Graphics/UserInterface/Bar.cs b/osu.Game/Graphics/UserInterface/Bar.cs index 2a858ccbcf..f8d5955503 100644 --- a/osu.Game/Graphics/UserInterface/Bar.cs +++ b/osu.Game/Graphics/UserInterface/Bar.cs @@ -110,7 +110,7 @@ namespace osu.Game.Graphics.UserInterface [Flags] public enum BarDirection { - LeftToRight = 1 << 0, + LeftToRight = 1, RightToLeft = 1 << 1, TopToBottom = 1 << 2, BottomToTop = 1 << 3, diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectType.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectType.cs index c9f7224643..eab37b682c 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectType.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectType.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Objects.Legacy [Flags] internal enum ConvertHitObjectType { - Circle = 1 << 0, + Circle = 1, Slider = 1 << 1, NewCombo = 1 << 2, Spinner = 1 << 3, From ca33efead43eafe29d0fbc0861e4918cae261f68 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 9 Aug 2019 03:58:06 +0000 Subject: [PATCH 0597/2815] Bump ppy.osu.Game.Resources from 2019.702.0 to 2019.731.1 Bumps [ppy.osu.Game.Resources](https://github.com/ppy/osu-resources) from 2019.702.0 to 2019.731.1. - [Release notes](https://github.com/ppy/osu-resources/releases) - [Commits](https://github.com/ppy/osu-resources/compare/2019.702.0...2019.731.1) Signed-off-by: dependabot-preview[bot] --- 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 c4cdffa8a5..40c15a1162 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -62,7 +62,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 02bf053468..8ee325c2ac 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -14,7 +14,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 5b43a6f46f..b46438f766 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -104,7 +104,7 @@ - + From 58e98e53d276c96eb362f75b84cb9579bb3d4cb0 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 9 Aug 2019 03:58:06 +0000 Subject: [PATCH 0598/2815] Bump NUnit3TestAdapter from 3.13.0 to 3.14.0 Bumps [NUnit3TestAdapter](https://github.com/nunit/nunit3-vs-adapter) from 3.13.0 to 3.14.0. - [Release notes](https://github.com/nunit/nunit3-vs-adapter/releases) - [Commits](https://github.com/nunit/nunit3-vs-adapter/compare/V3.13...V3.14) Signed-off-by: dependabot-preview[bot] --- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 9acf47a67c..4100404da6 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index df5131dd8b..013d2a71d4 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index bb3e5a66f3..92c5c77aac 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index 5510c3a9d9..82055ecaee 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 659f5415c3..50530088c2 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index dad2fe0877..257db89a20 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -7,7 +7,7 @@ - + WinExe From 88fa06efbadc23501209021ad4df1436bd4acb7f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Aug 2019 12:29:58 +0900 Subject: [PATCH 0599/2815] Refactor as proposed --- .../Rulesets/Judgements/JudgementResult.cs | 5 ++++ osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 29 ++++++------------- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/JudgementResult.cs b/osu.Game/Rulesets/Judgements/JudgementResult.cs index 195fe316ac..0b3c3943c3 100644 --- a/osu.Game/Rulesets/Judgements/JudgementResult.cs +++ b/osu.Game/Rulesets/Judgements/JudgementResult.cs @@ -42,6 +42,11 @@ namespace osu.Game.Rulesets.Judgements /// public double HealthAtJudgement { get; internal set; } + /// + /// Whether the user was in a failed state prior to this occurring. + /// + public bool FailedAtJudgement { get; internal set; } + /// /// Whether a miss or hit occurred. /// diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 5ca33ff0bc..dd8aae27d8 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -309,7 +309,6 @@ namespace osu.Game.Rulesets.Scoring } private readonly Dictionary scoreResultCounts = new Dictionary(); - private Judgement hasFailedAtJudgement; /// /// Applies the score change of a to this . @@ -317,17 +316,13 @@ namespace osu.Game.Rulesets.Scoring /// The to apply. protected virtual void ApplyResult(JudgementResult result) { - if (HasFailed) - { - if (hasFailedAtJudgement == null) - hasFailedAtJudgement = result.Judgement; - - return; - } - result.ComboAtJudgement = Combo.Value; result.HighestComboAtJudgement = HighestCombo.Value; result.HealthAtJudgement = Health.Value; + result.FailedAtJudgement = HasFailed; + + if (HasFailed) + return; JudgedHits++; @@ -371,21 +366,15 @@ namespace osu.Game.Rulesets.Scoring /// The judgement scoring result. protected virtual void RevertResult(JudgementResult result) { - if (HasFailed) - { - if (hasFailedAtJudgement == result.Judgement) - { - hasFailedAtJudgement = null; - HasFailed = false; - } - - return; - } - Combo.Value = result.ComboAtJudgement; HighestCombo.Value = result.HighestComboAtJudgement; Health.Value = result.HealthAtJudgement; + // Todo: Revert HasFailed state with proper player support + + if (result.FailedAtJudgement) + return; + JudgedHits--; if (result.Type != HitResult.None) From 33f4b628a5fb5316f0be69d52f9856e8d3ccef4e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Aug 2019 13:42:02 +0900 Subject: [PATCH 0600/2815] Make HasFailed private set --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index dd8aae27d8..a39b432e20 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Scoring /// /// Whether this ScoreProcessor has already triggered the failed state. /// - public virtual bool HasFailed { get; protected set; } + public virtual bool HasFailed { get; private set; } /// /// The default conditions for failing. From d47a8c0826c06af5595d01e8a946df9141065169 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Aug 2019 14:04:51 +0900 Subject: [PATCH 0601/2815] Remove unused combo result count statistic --- .../Scoring/OsuScoreProcessor.cs | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index cf0565c6da..dc4e59294b 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.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 System.Collections.Generic; -using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; @@ -22,8 +20,6 @@ namespace osu.Game.Rulesets.Osu.Scoring private float hpDrainRate; - private readonly Dictionary comboResultCounts = new Dictionary(); - protected override void ApplyBeatmap(Beatmap beatmap) { base.ApplyBeatmap(beatmap); @@ -31,22 +27,6 @@ namespace osu.Game.Rulesets.Osu.Scoring hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate; } - protected override void Reset(bool storeResults) - { - base.Reset(storeResults); - comboResultCounts.Clear(); - } - - protected override void ApplyResult(JudgementResult result) - { - base.ApplyResult(result); - - var osuResult = (OsuJudgementResult)result; - - if (result.Type != HitResult.None) - comboResultCounts[osuResult.ComboType] = comboResultCounts.GetOrDefault(osuResult.ComboType) + 1; - } - protected override double HealthAdjustmentFactorFor(JudgementResult result) { switch (result.Type) From a9c4b5ac4e9c9c25527c4e71fb550809f7962d81 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Aug 2019 14:04:56 +0900 Subject: [PATCH 0602/2815] Add tests --- .../Visual/Gameplay/TestSceneFailJudgement.cs | 60 +++++++++++++++++++ osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 4 +- 2 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs new file mode 100644 index 0000000000..bb0901524f --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs @@ -0,0 +1,60 @@ +// 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.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Scoring; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneFailJudgement : AllPlayersTestScene + { + protected override Player CreatePlayer(Ruleset ruleset) + { + Mods.Value = Array.Empty(); + + var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty()); + return new FailPlayer(ruleset.GetAutoplayMod().CreateReplayScore(beatmap)); + } + + protected override void AddCheckSteps() + { + AddUntilStep("wait for fail", () => Player.HasFailed); + AddUntilStep("wait for multiple judged objects", () => ((FailPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.Count(h => h.AllJudged) > 1); + AddAssert("total judgements == 1", () => + { + int count = 0; + + foreach (var stat in (HitResult[])Enum.GetValues(typeof(HitResult))) + count += ((FailPlayer)Player).ScoreProcessor.GetStatistic(stat); + + return count == 1; + }); + } + + private class FailPlayer : ReplayPlayer + { + public new DrawableRuleset DrawableRuleset => base.DrawableRuleset; + + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + protected override bool PauseOnFocusLost => false; + + public FailPlayer(Score score) + : base(score, false, false) + { + } + + protected override void LoadComplete() + { + base.LoadComplete(); + ScoreProcessor.FailConditions += (_, __) => true; + } + } + } +} diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index a39b432e20..1e7cd3810d 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -193,7 +193,7 @@ namespace osu.Game.Rulesets.Scoring score.Statistics[result] = GetStatistic(result); } - protected abstract int GetStatistic(HitResult result); + public abstract int GetStatistic(HitResult result); public abstract double GetStandardisedScore(); } @@ -421,7 +421,7 @@ namespace osu.Game.Rulesets.Scoring } } - protected override int GetStatistic(HitResult result) => scoreResultCounts.GetOrDefault(result); + public override int GetStatistic(HitResult result) => scoreResultCounts.GetOrDefault(result); public override double GetStandardisedScore() => getScore(ScoringMode.Standardised); From 5486c7736277d3d16b9ab1a45dc4051f24a5e7ce Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Aug 2019 15:25:42 +0900 Subject: [PATCH 0603/2815] Remove explicit autosize --- osu.Game/Skinning/LegacySkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 3426ddaf16..5357a75e00 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -140,7 +140,7 @@ namespace osu.Game.Skinning if (texture != null && animatable) { - var animation = new TextureAnimation { DefaultFrameLength = frametime, AutoSizeAxes = Axes.None }; + var animation = new TextureAnimation { DefaultFrameLength = frametime }; for (int i = 1; texture != null; i++) { From 5073bce2dcbf8a5bce4f33b06036822524494082 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 9 Aug 2019 10:47:52 +0300 Subject: [PATCH 0604/2815] Basic request implementation --- .../Requests/GetUserKudosuHistoryRequest.cs | 21 +++++++++++++++++++ .../Requests/Responses/APIKudosuHistory.cs | 20 ++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs create mode 100644 osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs diff --git a/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs b/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs new file mode 100644 index 0000000000..e90e297672 --- /dev/null +++ b/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.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.Collections.Generic; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Online.API.Requests +{ + public class GetUserKudosuHistoryRequest : PaginatedAPIRequest> + { + private readonly long userId; + + public GetUserKudosuHistoryRequest(long userId, int page = 0, int itemsPerPage = 5) + : base(page, itemsPerPage) + { + this.userId = userId; + } + + protected override string Target => $"users/{userId}/kudosu"; + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs new file mode 100644 index 0000000000..949ce200d2 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs @@ -0,0 +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 Newtonsoft.Json; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIKudosuHistory + { + [JsonProperty("id")] + public int ID; + + [JsonProperty("createdAt")] + public DateTimeOffset CreatedAt; + + [JsonProperty("amount")] + public int Amount; + } +} From cf2a9db8e048abc2e788c8ee210a2d7b958c9010 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 9 Aug 2019 11:14:38 +0300 Subject: [PATCH 0605/2815] Implement basic layout for kudosu history --- .../Kudosu/DrawableKudosuHistoryItem.cs | 56 +++++++++++++++++++ .../Kudosu/PaginatedKudosuHistoryContainer.cs | 51 +++++++++++++++++ .../Profile/Sections/KudosuSection.cs | 4 +- 3 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs create mode 100644 osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs new file mode 100644 index 0000000000..1671073242 --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -0,0 +1,56 @@ +// 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.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Chat; + +namespace osu.Game.Overlays.Profile.Sections.Kudosu +{ + public class DrawableKudosuHistoryItem : DrawableProfileRow + { + private readonly APIKudosuHistory historyItem; + private LinkFlowContainer content; + + public DrawableKudosuHistoryItem(APIKudosuHistory historyItem) + { + this.historyItem = historyItem; + } + + [BackgroundDependencyLoader] + private void load() + { + LeftFlowContainer.Padding = new MarginPadding { Left = 10 }; + + LeftFlowContainer.Add(content = new LinkFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + }); + + RightFlowContainer.Add(new DrawableDate(historyItem.CreatedAt) + { + Font = OsuFont.GetFont(size: 13), + Colour = OsuColour.Gray(0xAA), + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }); + + var formatted = createMessage(); + + content.AddLinks(formatted.Text, formatted.Links); + } + + protected override Drawable CreateLeftVisual() => new Container + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + }; + + private MessageFormatter.MessageFormatterResult createMessage() => MessageFormatter.FormatText($@"{historyItem.Amount}"); + } +} diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs new file mode 100644 index 0000000000..29b1d3c5aa --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs @@ -0,0 +1,51 @@ +// 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.Online.API.Requests; +using osu.Game.Users; +using System.Linq; +using osu.Framework.Bindables; + +namespace osu.Game.Overlays.Profile.Sections.Kudosu +{ + public class PaginatedKudosuHistoryContainer : PaginatedContainer + { + private GetUserKudosuHistoryRequest request; + + public PaginatedKudosuHistoryContainer(Bindable user, string header, string missing) + : base(user, header, missing) + { + ItemsPerPage = 5; + } + + protected override void ShowMore() + { + request = new GetUserKudosuHistoryRequest(User.Value.Id, VisiblePages++, ItemsPerPage); + request.Success += items => Schedule(() => + { + MoreButton.FadeTo(items.Count == ItemsPerPage ? 1 : 0); + MoreButton.IsLoading = false; + + if (!items.Any() && VisiblePages == 1) + { + MissingText.Show(); + return; + } + + MissingText.Hide(); + + foreach (var item in items) + ItemsContainer.Add(new DrawableKudosuHistoryItem(item)); + }); + + Api.Queue(request); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + request?.Cancel(); + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/KudosuSection.cs b/osu.Game/Overlays/Profile/Sections/KudosuSection.cs index a17b68933c..9ccce7d837 100644 --- a/osu.Game/Overlays/Profile/Sections/KudosuSection.cs +++ b/osu.Game/Overlays/Profile/Sections/KudosuSection.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.Graphics; using osu.Game.Overlays.Profile.Sections.Kudosu; namespace osu.Game.Overlays.Profile.Sections @@ -13,9 +14,10 @@ namespace osu.Game.Overlays.Profile.Sections public KudosuSection() { - Children = new[] + Children = new Drawable[] { new KudosuInfo(User), + new PaginatedKudosuHistoryContainer(User, null, @"This user hasn't received any kudosu!"), }; } } From 093359c13bc05d590e227a9db7615f4dc287be57 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 9 Aug 2019 11:19:14 +0300 Subject: [PATCH 0606/2815] fix incorrect json property --- osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs index 949ce200d2..271dcc320e 100644 --- a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs +++ b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs @@ -11,7 +11,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("id")] public int ID; - [JsonProperty("createdAt")] + [JsonProperty("created_at")] public DateTimeOffset CreatedAt; [JsonProperty("amount")] From b0eb2c6e28cec2831e02f3f63125532041232365 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Fri, 9 Aug 2019 19:21:08 +0930 Subject: [PATCH 0607/2815] Remove iOS BASS libraries and properly reference --- .../osu.Game.Rulesets.Catch.Tests.iOS.csproj | 8 -------- .../osu.Game.Rulesets.Mania.Tests.iOS.csproj | 8 -------- .../osu.Game.Rulesets.Osu.Tests.iOS.csproj | 8 -------- .../osu.Game.Rulesets.Taiko.Tests.iOS.csproj | 8 -------- osu.iOS.props | 18 +++++++++++++----- osu.iOS/libbass.a | Bin 1717480 -> 0 bytes osu.iOS/libbass_fx.a | Bin 621624 -> 0 bytes osu.iOS/osu.iOS.csproj | 6 ------ 8 files changed, 13 insertions(+), 43 deletions(-) delete mode 100644 osu.iOS/libbass.a delete mode 100644 osu.iOS/libbass_fx.a diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj index 37e7c45a4e..7990c35e09 100644 --- a/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj +++ b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj @@ -13,14 +13,6 @@ - - libbass.a - PreserveNewest - - - libbass_fx.a - PreserveNewest - Linker.xml diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj b/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj index 24abccb19d..58c2e2aa5a 100644 --- a/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj +++ b/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj @@ -13,14 +13,6 @@ - - libbass.a - PreserveNewest - - - libbass_fx.a - PreserveNewest - Linker.xml diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj index 9930a166e3..c7787bd162 100644 --- a/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj +++ b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj @@ -13,14 +13,6 @@ - - libbass.a - PreserveNewest - - - libbass_fx.a - PreserveNewest - Linker.xml diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj b/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj index d2817b743c..3e46bb89af 100644 --- a/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj @@ -13,14 +13,6 @@ - - libbass.a - PreserveNewest - - - libbass_fx.a - PreserveNewest - Linker.xml diff --git a/osu.iOS.props b/osu.iOS.props index b46438f766..82c60d0aed 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -3,7 +3,8 @@ {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} Resources PackageReference - --nolinkaway -gcc_flags "-lstdc++ -framework AudioToolbox -framework SystemConfiguration -framework CFNetwork -framework Accelerate + --nolinkaway + -lstdc++ -lbz2 -framework AudioToolbox -framework AVFoundation -framework CoreMedia -framework VideoToolbox -framework SystemConfiguration -framework CFNetwork -framework Accelerate true @@ -24,7 +25,7 @@ false Default - $(DefaultMtouchExtraArgs) -force_load $(OutputPath)\libbass.a -force_load $(OutputPath)\libbass_fx.a" + $(DefaultMtouchExtraArgs) -gcc_flags "$(DefaultMtouchGccFlags)" false cjk,mideast,other,rare,west @@ -44,7 +45,7 @@ NSUrlSessionHandler Default - $(DefaultMtouchExtraArgs) -force_load $(OutputPath)\libbass.a -force_load $(OutputPath)\libbass_fx.a" + $(DefaultMtouchExtraArgs) -gcc_flags "$(DefaultMtouchGccFlags)" false cjk,mideast,other,rare,west @@ -62,7 +63,7 @@ NSUrlSessionHandler Default - $(DefaultMtouchExtraArgs) -force_load $(OutputPath)\libbass.a -force_load $(OutputPath)\libbass_fx.a" + $(DefaultMtouchExtraArgs) -gcc_flags "$(DefaultMtouchGccFlags)" false cjk,mideast,other,rare,west @@ -86,10 +87,17 @@ NSUrlSessionHandler Default - $(DefaultMtouchExtraArgs) -force_load $(OutputPath)\libbass.a -force_load $(OutputPath)\libbass_fx.a" + $(DefaultMtouchExtraArgs) -gcc_flags "$(DefaultMtouchGccFlags)" false cjk,mideast,other,rare,west + + + Static + False + True + + diff --git a/osu.iOS/libbass.a b/osu.iOS/libbass.a deleted file mode 100644 index c34e6a0a0cfb30a240d70443fa0276f6e2774f90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1717480 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 z�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

n1vJMuGM~kIoXjExl$^E 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 PzG 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

h`#jTBZQRdTM3sKKP!c16W2fH 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 ydCELbndd3bprQ_nijA<#w z2|vn@8c1|qlxAABy6AS}R#(^7HYy5Q?b6D&)@q$eItGI?rCq!0wq5x=U-w5c1kiRr z`*}RRe|*E^a_^jbe!kE9_j#XlUwr?`?;B$(xT?H+##k#dJO{kALB{%PW2|{|%hCp$ zz{_(<+v}xOau1r&SvF;-E6p;-O{<6UJY1!Nub*zT|w{zA!$X7w+rv@%Tj(CXjZ?MPBAqbpHHv=Y006sh3}2 z&-shl^_Ndo6C3p(e|Is8k9zmX-u*AV`{%v;7VrL$cYofyzv#3>bv#TRuT$ucUL6mP zwagjh8(db&{Q1{^;l`W$2Q#pajVD>g22ihly(A{`tIn zulC3~%EvQ*{^!52aQ+QIWB%eV-grG@nR;pcrG5*>yiOgOwsEJpbz~Wf=5J7c7RubX zeE!W}`uufYSU!Kn%A1a=BS#(8^sde{WIW}^ZLQ;ELg6Tfpys6Ws39_Opf3frw)zn z$hj3vp@+{O8>SiR5KQ-Zb&RDB!8FR{|IMF&-OV@qKe!LBQQdGE{TQ}y_N8q4QD z52x||2G;Q)b;P}Q$E!o>HM(H_bGXNrT4q()9dFVrt2T8F<2sS+Qm*6u{c(w9DE?>v zwIt4YJG5eJSJl3&y8dwZ@IjNv<^zMW-S6?-5^paUJDU%h+k5IJX7iR=(Pd2bg0&~i z>9$N}_)5Ju$!@&|=b}qv?S-YLywkG3`Akzkx6jyr+W~rm?daOz$g(;@g<34D+;&E5n>@ zGWxK=a-N5JZQpMe4O0kBD(iQ3VNKl`O z(aCo*gs%PK0|0I0BJ(awUw({D5nUF*d9rJ+*%@4b)TZS0^jTHZ1-;{V{;Efrz1^sS5d!t7czGgdU;O9!~<^6O*YL1;r5k`H$mMA z(p7f_X{NKaFhT8+FBpec#J3hgb~YaYuA;lDL-!P7jMrFC9XKS~L-ucBTuZr6VDL$v z`^Lt&Y7#r4`K<+wsV?4Du&Rc4FJVlB80&KyBYf(5;R7 z&T`rAp`;J(q;DynTeA7jFy}{NyzL~@@RL*5p5GMfNlzGc zU;5Tjiy9A&s%&UI)rzg{YH6$-I;L^Mr~rA#G+sW+O22aI1L=!K8SbX>*;50Jubf(u z{>xFpbnB@b(iiidG_El3D;uvF6(aqC^ev-;FH#mfZF6W|crHE@Oz(gBf%NVlfs-#IS6)2Pd#`uUS3?zTB;(D6Vd>_g#vGu% z-ouBVR9jZ=Yh9tvEnUXmSqPZV<^SRE;YneW%tw$B!Wqray~e=zz^VAog63p2FxLF- zIDELbih0+Z3%9R!{(d*bpKF`Bwo>jKI0oTP`ALoL|dOyWAjY~9& zW!o#<)_8{4+5Chd=Ah~-Pa8GV98{a-X%`GK2d^8D_N9n9_*VncuCFi$F`SPmI~Y1b zS2!Z=>?(6mcv{}ZzXr|0D+i?Qsx$|u4oEvYYz}H%W#RsAHPV|6;c-1|HKs+0?;@RQi zO$*^oYX2Yq+m+eh8`95gzV(uW(2_ z5nZ=8!~O5bt9)_tEAKaL>av>);+gd0h%)NWQu-qr63r~6FB7>sJUwD8^wOzzY3F{*ujq zst&rO@5>oOkogQS{%{=m!q}^LABx@cs}Q_P{g@eJEcRtxmVG<hFxG9aYqW9lwjf+|E$8xe#{XA(>A1mVZFB-Z<_J5o zHz|D=-82}#EoZ@xBe5oQ)+YFN3wU)~u8QXnm#yDsUjDc6=?~LkJEQ%$emtGx?LYSG z6b}RY|opg$Sw${x}HRqUuP!J&E4l zL7#+Q-{#%|Zus&^I@Ymt5qyY_F7i$COBb2QZ{fSIi)|k?vuo z)_2XB?CKwba+%l(=oF^$>!&8sL8?y74hzQD#>S){{hs)E^*?+ORy;yne8KslZ z$tN=}7P`<#U@l$B(aSeKUfPe{P_YcV09{^syxvu=USHbt5ITRfwsbD#(3OgQA6uf4 z5;jv*`Swuuzz*7{{EJSxGfIQ5-B3CS8^NZW+K|m48^cP!TnwQvokU+lW%WIPZuvlZ zFFNAOr{V{weUx#8d4GWZPir`YU9qPWX_#KxlfJkVNv|p~=T0B@tbT-kp5fGOs9ST@ zaXviI%$7bDRo@T)0o;vNmLlfr?l5}O2HFP3y|UM(?|+O-I?+X3(i3KIE#zu#e6wgZ z?ksL^c(b^j{`Bm+rF6!QTT0Jwx~Q~a(~{EtJI*SJ|7_viMr>HC@r~k!9qq*>yKXJ5 zCv81>zq+HcwAjmk<*tqrwAO3w*jZdd-G4)#D|bCYonLb5dx3na?-tV4?#<-cuw!iL zi~R0uch}g`7x=CASL}MTSWo_Cw7pb)P46tO-?gN)n0C(CacgN0^)24fQEJ?DsH8Ee zf1ji5eDW{eadAm`H_*o`H@SIkpd7|luhsBIv7Y*_Cg17lu};1-$iHXTMWv1DbxwVL z{n#kIR|22u4IL#jV`nkaII*-hePL-)Ggg*%H+-=)rU4jCI#gPho=}SH7*k5n zhw04Ym`T$}yRbBR*W%Lj#*R{C*O<~o-aXJ*!F*3It>3Y-^sO|o+;pJ>AL+N}s;wD^ zN~U3F(QV_x(sK64*0{sH7J@_UnoZ)7fW z;rm9w-5Dd1Wj?LWW(*Dw@O&lMEl*CyA0eGecwHGs*28C@xgGK!I6TGUHRO#}dwE3% znj85^eBTHDqx(sBd?S7uzNP!IGj`>WGin3>Wv|Nkya9a+|3&|Awef_PSNN?yoIyK& zeY~IH`*j%GYx2jr^&;cT{8Qp}@=qCjCI+9$KwnA~=~bl1xW~9x%eNBS#vMOPEK|4# zUOkbzRgdZ~uN&RQt=pR)$;DpchWKQI_^5^9&#^Lys{tTbh z{4yVvd{*sULnC?bc>LWDbPer^0`N=}a*;siGwR>lw(nP{WUlSEQdP zY3@P|oyBm&w&I4yG4O~*rEo*0cw%G5p@VAf!_%GM*rL)&+%@0Fo@>#d5#2$9&}HVk zm&?EOg%g`5eb2(z&sD|c&-ec;y|y0jx344E^0y}FFLuo21UMeUe}!&76;qMOG;lo7yCYB?kX_G zZiCG@g7qZEnn_F(vuSOy^c~ z#Q!d}Od3%#=K9<}(5G;GM-RTZq8ZKxvRm$)6`(Ab|h69IyT-yhHp- z>owKj_3`AC{YN+!z7KaDNH${z`#|dnA1vPv{Gq?xCCsPze2eD~3W1l`Ak(zYBAoYS zvW;Bw;cat##cEj0KS6fT4ImCZrjr(67*0}!-d=sT5jfWiDLA-Yi_Qeb2F+A(tCfV?`r**SAv^CiF z*WDg3Rl|4Ycw=(=6OE9|$zA8S}fUnh@MSn`sS?hDv zoo$7Y2Hq=PM%|CWuOjeS@i+O3B&#(Kw*q7FH?3DhQvK@{-ynY+T-N$rIe%k{P3p35 z4iOIb+dXxOKHdZ`@nIwT;}d0k0G-iU)Aip|N#B}oR6L-pEMw_=hx|Q4?-g&5PI)}u zARZ@MKz6|KY=`o-;b6M+AAB1^yrTsjR66NmbWc6Yh7ey~?5uZ=DM{zm+LC1D&=1r% zGgA)z2eEfteej{)KXncNPmv3eBkGp#D1&Z+Oo0B?=c#t4FqtdBoDbwxg46!r_0wxP z@0ni4dmj4FKaclJr|qv(rkwZe;CDIix#@p__pGAtul^_ehx=6U@;;t@IDeV;G(Mjf z`C}XD^PyxvA6n+|p}*ER;X_OK9R^-LA9@P8+1SutY;1V5Xj-jTb@X?{M40=(&O^kye8ia4iei0wh`la}Wbg1Xy6XG3;eLz>cd}^xsYIK&% zrpB8uS2{AdAqL-&PGs3lU6PR#d7f+|s~n!Q`(^Qik747%e~O7lH|Aj!ZQeVjDK`~f zn#eR4o+XdU!H=Gz-KX^K**HGg^pfw>x2L3EeJj1^`(x6#@_YAF*qgnM-q_eyRQbuN z=E6Gg{##rd@XxJ>7cTPn?4tCc&uFcG3A|O~9>X|I?X!v44!``5!v{wKccn?!mfiMq(mqIElfQjpS-sN96if3% z%1xw>7r4vD*u%ZtH!u4_Ft^C>^VZSu&+?d+kBavW@GHsxAe<8pp*J}E*Vh4um+@cp zIY$R9*ZBtXewy^P{&~p{q3;%<`H-0R?j>F68|6&i{ zz=7HT@iCGaetZn_CaSm_+0y6@tf90yF*m~D7(PbPn${f@1M@t*-1VW!CqC<)n}F$U z=9+HTcRkjPb4fo}l)Nn+K3qFlJQbdJEd5+MpX>mCy_I|ku1>ho4j!XfV%ix&l=1O81YUoY-@;*o4{5#XBaZ>EE!u(KKo55m zV(86*@oRgeH-|L0$UOOIM8A_`t{x}-*Izd+kJI`f{GPxVJKu4AMH|6!AD$8Me(Xu! zI6OW@zdj6J^{U5{9pdpSJKGr>^v}JVxBLDh#j!?`e~WHcSqNlV=g04-Sg;u$4P7R9 zKtosXJIeU#V$-^+@Ed+jI!#J+dq?5QP1sGqEQk+7evl#iuFnn0WbkfA}x0m#>GYyOh;=^pc6YRj$LMk z=Ox#XNvARoC(7;`Lp<0Z+J6^s#>@jTTjk%VyDn>bRRmA7Lm40v3nDhqhoTj4s zp%|-f@~RIZ+Lp}{p)VTCSbIbvXy2Ao-|Ki*-{s4Bnf%c*J10sR!RiyhL3C0_zG%wT z8wATIpp#?yap2j<#OXM==HPTPn=iZzToTzl#wDc-$FsjqV4RGJ*qN=!-)86#f2G~r zMLa{-lk{aQWflLRu~wVrWAY$BHf9C=n!x4r7iT>k+fF!bSmRPZvGwk^%+2>!(XSvn z!WGg>`u#{VoVaLwL0adQPU6aaSMSldq?f2K(swW6z5HRm-h$utSz@f8V=dCrkIK`D z!G4Z8eenseyx?)4UC*R~7S8TP`2uB@0&+(O) z|Lz-83-($L+l_imXSVx9r(XE>iNwbuvn4ZZXqGsUT%@xJ8sq}!_0CVPA}z$F7<>_I$H`BZC61*b+PPsOlQkuU$?9d=IUN}We2f% zkD1Qf9y4~!WAvk=%HGa%(_^&%81_;JaVwAEuXfW+%VYTY9|MOvB0L+@#B=sL<@T=R zhRJ}BjMeIZk7wbXhAWlLpU=33M|0w?-)t;!uSjlYUjz9Y?q}$m%_9Jk|MuUG# zSE&^Ef%M{1fZwO5|FTqpZX-R4xZh%+ zVM|eE2X$`ARgz{J-QTKb39$g7+&zV=+!odZ$fLPHACmqqe)9%#X{K>qQFP$Y4K^Wg z3tE|tT_JR&sF|C85gpxM+wpmY)^5Zn!U^FUV>X$bjjR=3g8$Jk{=2)DQ^z`51Y9%b zPTIR#el^%svilJDznnbW54_D>`gr8~G-GECDn)m^Q5?K!r{h0Q?0BryQd^17C|t63 zbUN!3@IC0Yxe(ZtDbfcg&PQ=w!db<3DXwcI*C*+pVrvp!T#jP$%j0oo(2v9p*REHb z!!Y{42Rr6%@?Gea@ndl!%QiSR%_k`H68xatrtxEDguCcWwXZQQ#n&j_M(^t2o#G#N z#o7xW!zVr_eM6t0@=WS8#GI%;#o1_&z?dC-N*jpr7@Pi5>A_8x5HGTa`QBX|Mc(1m z)e@~J2`7}U{Rl76mYyqke%pJlIc#QHUp2Yu>wK~A{h;(aku{zdPXt~hNfg4Ug1rK)(t+_zH z13ujU9G}Xcvma>!)a|!nGly{-I56JUK!Cj$ErkvA`@?kXLmFY3j;(TEqFe~r-O9a% zarp50|Mw*Qe|Jw(6?1hza!Nesf3hd(kH0y3Pmk z8OwXn&;B=DU(WY<_IcCs;(!-m@@d0j=OhB`tS$sPdbVHNn_H_{Yd+h(0h;ApH$D<0DF@z zuL6DpedXUq4-J?3%Ikn<2AKEh&hR~7cVwuWS)2zoSFu|Axc<}eqiIj8 z^bqZH9SN;C{;D#2S~5v(H0^!i^YQt+r$oxy_%PY9|FwR6HeojoD#V@-^yR#j9NuMR zq1Tjr4ko&rwz0kX>?v&DUikptI@C|gALOT%4lUZ14Es4{M9UTYcIEL@Fhgs~_#5Q!*|iGzAI}dW7|KUs?Cdb?&Eb)B=MdSr zvPD#fKYnLlLwwswb@AIy3ORmD*_QL1m_FgY=-_zxzw}jOB3ymCPN#g4^VH_>P_E^q zQZ-^gs*RJJ59qQ4b zzXzDP?Pb4jE|SL1PyeK7jbE43*yMv9lO9t--W9dS6&i#OKLf8$cB z)(CFuO98p>%Nn)s)6<*Bpr^5pPLDq%{wZva+4<4X(Kynoa-5g2tfjd9lcRIN)vd)n z%N}*sCc)t+8%G9NygQBkF4)7ptwXDF<6ClkQZKwt}k4N8Q z_xIQJVYKnbSB^~^<#A*ag(u#=_ zu`)^3>FrTljNVX%tPk1QynGoatY)PMnH6`&d%LqY{&>D@7mA0bJj|FLW|HS}-w2(Q z(>^?km@fET5?aMa9cE2WcD3dyvHCFwcPu*_l&|D~(`I{*l}CrFKp*I*Ih-vxbvLtu8XZJOt9A#Ch}V`KF7kDzHc# zx!(P8nS20dL+nGX(1lyC6%Pj34?Nt8j}9<@ z;ytvzvk>d1F3Jy$&B_`38GMrLd8IDll>BKYK zkLoN0^jX%hIOBl#-g-&xkY#Pf6IQnsRc9r9W(c(KBy|s_UezZbl41>99(zr`2OcZG zrsDOa>uDWJ@YxJZeLfq_^z+&K;j`s&dJ~!RclY(vi{|yE^WixObW8C5qsd^GbeETA za~4UKIC|RciqW}9M@!CheuFhd;tA=?O!n6%?ItI$%4uEp zrP;*pd*xK79(g&Jd@r5fR@fJ4E3}d}k2K#;`|!Ua_rwp``-Fd)@#!oZ?SWkj&bw#X z$e(Zh)^hS$kzw7|#HQia#Akzwb8~pA8HSC}G&~f4HaOOi2l%Zia|AvUA0z*re12Lt z5ge>6IzDr$SZ~ogYe&6Sd=CEqrpwqnr86CB?*iAP9}J{3rI}bx_};6%jji?>xtWRe zU2)TPU@-pUcMc!k1j~XJ>ht)GH_g*}4(pwhkaMaVJvr*kK|I*ymLChOHX;jK4(ux) zKKK#r0MU`)jGWDdV^8N^B)#5V-&JL{AE@EIpFhC+An%{yz3hjXUOj%fip^t7-}T;A ztln6fcK0Qvj}Y(j&!j2leWdNuj)ND;Y`%kZlc>*G$>(yC?P2EoM8+xo3663UEY$y) zxy|Xjcrbas1O9wRW$I(=yDH4K10m=&Y(JBW#m(&1TKeuQj%*_er3zPycLPdVoRz>ko}ku{7*a(N?su*36*N+%2_ zvKDdd(S=_Ao5(j$W3(BgY4535A7kx3&|h!h(rrCj-wl&r@=oUppuV5Br{qP7u&mZ?-&_@&4yownc3r&o@puG^Qjg&N2lYs{$w-o{@@!PBMF|nV7 z_h*;2hpyJEvkuOnPVxLl$>W@PaK&)vteqTtanC5rFMgo2rUZk(S6S}TB~EtA&yo%G zqG&~R(cY%}D1Ulc`7f?x0X1`P9cLaHHkh^e(7bhj(n8cL z+i74wM$!jsVnx@1)7r7mS^LRVJnhWyVg^R#2CbYdDDRtA^{{7meA?wVdl7 zQ#a-M!@uZ^^SnkpiB3*`0Us*YK6uq$t`@G1Tr0SC??t!e>Z40CER;;9(8R=FbFX+V>*bo0@7X zoX&6Q3f16|=tq0@Y$uJqe&AQsG#{wSHWy+YtWU`g~YFUcB-uSh3>CE&nUU(a`nwWe0f+D8Jfah=+x=z%(Bzf|Ae=P+hlX! zv9j5FgY5aB{o3!*rpA0aKt&r%M;5E!pB62emhFx=p(&V3nnR39YrL z{W}Yxa(W?VKxYcM^irP(KF~`u^s*j$Ar=OD`75Un>s@-u+*$Z6zkPZMJp#^J8+&BC z&LREq)Yl{nYsb=#&JU7Iw5(c(_lcGwvH|HwB(bjWDD&;aDrDMw#Me#dU96GrQ7ZpAPXF&L z3{GTv!pzl~{2tC6YTvFuk0H`4=$G(b^Xt=||K4CD_t7zP@b0tlKn~{G-Ri=LD}iAK&p zIg&D_0~|2G30x4}S9NSG{73KMgZs|Es}N4OcAc;D4o0729(IM}ZTT})ueqxr826Ij zryrZX1Om0)iZ^$4^t%dOypLtKcHP&3oX^f6E+v&iSLhu~EMkS79Y5I4o~F8F8~;po zq3=MuPgamme2kcr+6we;lYFjA<$T@SX+NILzvz`Q3B(&JKeXo9g|Wm&*8ev;wBE#C z;p5GN=1F>2`CP=t5cBo)k-B0B?MPqt^=a|ShrG1D`ATN_wjC@=H6!R`MsBC z;lNnN5Tw0OqV-pb?HUa%J5(mg{F2Wk*B0)etaO-G?k?TV$;(#*ZM*Vm0e$vpE=+nT zCOwip!Cw9f=)9R{=?2r`4~pH<8CIhEFO!}i&5Ag*L|j>0;Y*|m4!3f5c(KP1IOpd{ zUteG0y;}ubs`ubq=SuMWVCRweIu%?Ef;+#uu)PqExjI`7yyI1V2WUfdE_xF`*{ie; zSC7*^QA{);uJ?KCU=Ke!HRS+Vj1P@k(~*xPef{9ZR!jDD+(Xljz|bmFnxd6=8e15>}xqpTzQj54{& zALj_(jb&`oS2?=cnJ2Y9lzCu{%+V*uWu(ud|0b7V3up5$uqQiaH#_|anzpyC%q; zKQo2C**76)ySpYcKMCe%0`or+II@=3J2lr?(Vm zGwunRH+wd7@hmYh?mPcCbvk(ior~cu%aV2L2%BZE_@Hb+H3+2$jVLL+&AQ3V(zuZC)l(Dn~}iU zl5c=D1bACidV=0-jZkX^tBoU)OovcYFr z^FtOmJi@7ix&tA9T{od3a7dDq0t`xsjMZ z=8yUM6zfGAqu-|xeToBL^(z?P&e;W=MIqmw*C+I9?ATcj{iv==zb@q8vroIS%C9Tp zjZg8Dao}EMS`TD~eKn{4RhhL1)Ia$Q&`TZLI0?Kp4(T8A{b~=H)+?>pb)0J_yAM7B zK3+jSXTM15nyz4SU5+|x1;fD|OLLLj*K+Y3_~ncl|8b0T=I;{WHgabQa>q&QNI}PI z9a~O3Y`pjWeBqnNQ#7|ePZ0~~l#^c(W} z9H!4hz41CUh&+D%S)qG#PN5HMhg(S;p~KwibrN2hj=p;(nE9TEI9mwNHL} zcAjih(}AADoCXr$YzkjwcDtv;!Gr0K;2+|>XfgsHcKBOTI#)J-F7e)Bn{@EqWJSB; zeRmhmp*^Q8`NS{%^0K>C&Y2g|Rp(gJgj40??34Y-xT;R(vXi!xV|JB5U$-D&*ozdZd~oOWluQqDAVrd?6l2imlB)3bspxw=Alo&qj}Vx z{V!9u{GpM}>q}LfMK^-9FOhcD>Wht`nb4bJ*$<+9UDQ@!=dPp$6w_GPO( zzF)DLGt``TmS>BSHmfxDQ8T?KH z`=RB&QRYEdsK3AdMD%C(u6Se0^nAMd+tc!W=a9K?_a)qW?g!o-7I0`vRV-8307geJJ90FG2 z3qE{9Nf$k_niJY&a>LJV<#o_JRHS2K6{G#PCG$EV|R{X9~PgU!`-a9>L?g$G{oMwI9-Vr+)e^+zHW^ zv6^zi9eAI|V{T9_=_q5kqZ z*slTpy70N$gOAR7)gG{zV>|inx%85v&eXpcQ@;OT>W9=jWk9{6VZpt>AI*h}$Rn6W zs6Rygu20UjSq6?b#`zKcn4Por82d;r(bR$O7{eZ=-oCj(wrOo4#6Ip%!^=aR?61js z=d^b&XWf=IQp{gRHoqmqe8s`*M7FyUKFz##UkGnK>C>0Lb23*n*OW6Ytb3DIO+AC2 z-jZ|n!?Ne3r}lATCY8=W)Pd9JO2F2)uT(H)fTag7qd6P zOIsG8od0atyW*A!`)OX8@U<6Hub1!I0B!ouhO?$Vo41LZ;d>sFBCF3R1v@wA0@##- zpUd}ODn?eH?#KWu%`p95X!MGjP5a%IyZ?piO>nYVL92R1e=yi9W!h+m&mzwQVI9eR*XA-_Q7r`MMl zn-dFt8|S6TUl}w_2g2YD>mIe?@#uOsbDbeSa13U7rX2cuuVBl*J7=v{cK4td?{wx` z!z)E}Wv9-yPgCdkujS0Ldx-sd#reL$1Du)Y(#S>VKU+AjW0vfZ+9)=Jg=|otgV-qW zpGm4yzF65KYZ+rOp2=BHuXpqy^w@6MNFic)rAH}`A=XMdQXf8aN@jWKH5%6}Cr(gj z!9`Q+bK%U!T$Ozr{8neN&CS0+n~sj(f&Bumm+PoL-gJ06OQp`IPrz4_=q8+9u2`gR zA`2q$uG_f3h29wAS^OK_0{N8f*4az%7F<2ik(2Oc#oTKD67@%mymNd6jI~$K$UNym zk}=x1S|E+K`p#3EcnrQQJ;=w6pOatyF8}wxIrV4ruW%Rs-S=&@Y!6Qze$06Kc{b;p zmeuuqSzVNkJmb|dmpZUtdQ&l%Kc^@ox{}<{JtP^!`k0>UA2CMj$>nWG#!0s6JZkZL z<*TMn=_L{BlutvtOvtO>@2}QOMM=tzOz+p6>276)cp#)k`1Zoq4bHKX#b@wf+HpUrT=Ji*x^hk2el@r*TXsUA9>t zjf=O+=PWynJ_U?R*H+fm@oLa@0%ihi9?ItaX& zy{w$4r!)S+cfmxu8-8+lm26D-h>d<5cguy){ecyJ4)XC{=^^s^`A*2m_S3f+`k(%`SFdLQCw+?fp~u$Nso=XV&RkM~Regnnea ze$4-XepGq=hjv#&vp4Se7RlJ@+{6Dl~$6E_69v#W%*Sx`}t$N zWSE`nrXAvOG6ltr$gWa*`}k&(U)Gm<;;}w|vdIHIb(;|Wo^E6_JXU@;(UoL|8zF)yKPeA$(dMPc1*c%7?2j4|O(f1vC<7uA)0yih~+kijy|$?2tVr;dE5{;CQ#8 zaS3OM-BKFFxnnxhPw?2g+Kp8M*Y0nrJ^9=8;ePu34PbeHl=JysApgL;jO9tu1^G*# z#Lv1B+6~6rdPJ{Ulh+(bw)yl5O};3b%AqCn=o1+CL}=zTzSkokCW;qp$k4A~z<74(8V)AcLP_0adrM6VSdy$a98PeiZuxpz1hF$%qkNvh)#ou0|v>38hvE-RKD z{%LrmXi{l2d6ta+SI#X}8E5=nz5sNH9?+|JQl-bxd&0K(6|qGF(yOq?lpc<|bnWMN z(vv#31|AFTfP3%~_^8(gXF2o;4_6`&E&A&3-51Z(HwSKn&O^*oOKk;qGvBsIS8(n@ zrnr=J_Z;BN6#11F9wT3fe4i`JhhMVLz;7QXBh(wKMHdC$W5BzX=*gwsU7npupTtW) zL;jkZ#!m@)d7XUVlx%{BcJ2Cv+b^Bv`~=^=pbp;gEmP-RN12W%{m!O+@k{Amf{)<- z3vg5N(Z_{-;OWqjR=@rc&lEZ(cT(&ht76TMzJ@rzAehSZfaB4?;92r1qT8C@rNFO^ z{`)$!c)8MUCr$c^prMQ3X0d=2><<=+fB z7kq>H8fVil^3NJV>`uJ7NBdV4zc>N9FgA7b4xQ=1A;uT~CNX@>=?boA%jO{I$<%(G z!G%$|&JjZAaPU%oK2Hx(+Ohm_^^8mUPU0K1cYnX!oc@2&jw3UCeTng@{#Uc$C-rNt zeYv4{fJkm-fgk<4*i@Mv5d_- zyNTbQ>uxsGVZL^f>@Oc* zv^T4&rP(KicBb>5Y7chqGgz19uCJx9|)vPQOqbnM1DPBf$^+q{iy< z^30SK;IHhy72vRQrya*0 zVQ-!L-S*;n53x3McYAS2iuYN3A4z;0tS4!`N;;GL<0`AOHpDNiVHyfvXRpCi66!9jd}95Kk3 zA@@GTn5Q!4%Net1TRdKHS;N>XGSF$q?YYa$*0-g5Nau>cUvEzS(_8AF`t~tmsstDD zX2HfegM@y&uxZLE_Fw%LeE-+r!=9QNaP6+^M(3KCi|@u;^O}GBwY7>>!N(+@=_`@! zR*qsj{F?eAJ;Q}xs{_A@9{i5>J;f#-500^Wj1j*1bg)r)2dvaK`e{^p!v)LOW8d(2 z>4MdKy9j@(?*9IxD^_=WeLU}<_w)#UfB!sw`}*QXIiq}w=Dn}J{lzoA`p)#~)4jaD zF$3y5rK~=i`qoh2D$`c@{YQzRXWix8yc=Jj--4ZdfOYUv`0QiKmm$CM)sfF8y^roX zuS*E4jJdMYxh3W&oV&TwyXU-bpH5C7kNB(Ybl+d%y+1eM!%Z-g415^gtvHCfnBqmQ zMiF{^7jNfv-t-ao-avHsmiU*iC3BQFSU2Dk5}=Xx|00<;!Wa57VniW7~hi9JBz)h z;$0E;)hds8mnFVMo+kL#fj++F*!F#Kk-Bt=dn&cc{onsV3zGmD$ z$~SAJXBV|rqBzJPXKkr{ljfW*eD&J5toE(+l|`MGB!7JsXDn(>MR9ZLqtiF^Lba>4 z(a`wpBSCvzN3dmmM4{keiO$7VDC8rw*|rRF|6w#l^@vR3iAd<@vf7_Vf+Q2e){7~jZ>(YH>;h6-++ zpG+RVf7+wx^bZ^gp}(@;kk>fi6HgBEXiImUoeX_`+xaftvUSe6TYa7SZN-EE#f!X?%^Mw`c94c>H(!meuNrkG1l394{2C2*uYqsOZn-;>1idc zYu(RtrJk`hDwomEWnbldwPEaOo?WykYXPe<um4|}N1vcD@<5wrO^W3BYYs{4^+ZR#8A@y00sVENhDS}&FlI{^Go&dJuy zcCYkk(ua%iPB?y`pa$IP1gG-SE!`NSFPq`n2w%b#AS3%TF)9 zF5URmGCfXhtfDV1mdT07{q{}#Fw)mz*>7;R)^{pnPowX+<&YPBI=ISJVe3mjS`}-4 zPI_On(S3IJ8k@VFeb(~!G-3O9Z?v1qJEuE?50vrtzoV~P=&RaTi4Q{eX87Ar;dOeB z@vJ_7_x=91W#@c9vQXY&}B`?dV zjDCl+8_`cTcSF{eFOVd@PI%j8t&cX{uR=}ewA@2acS3gm*+wT=9u zGvyDHe-dLGg1oH4Z=*4)tZ0REdHL?Zf0sCW*Rch49>Nf0>1gT%k9+Hx>xmuie2*l} zjgLy^ALl#;e?O1G=jYR}=1Dl&N?yTCIIP&Bf$vW#d;i-q99MbyF@~4D*Lrb>XPb%! z?iyelDSsd)`^~lYc2c$;yi%IJI|0tKrUe~v2RFMd(kJly{i~Ip?OqXUF06!36)#-F zHIi#1XVx{cLv{gYI&~dYb%g8lweWNRO5;Fa=&hL*3O`*7}mC z=kB2obK&2?l=cMP>FC??&u#3~-tV>^#Sk23Z#4Q^PwR})tP8XEyyY8RgKXdo%vZ3d ze5LG8>Qwo8)K!&f>rs2EM>@RdQuAV^e0#|4$6Yf%mIEX?le{`-QqOmZHspU;Wo|31 zk)7-9N1CwBwf*ZE&$0HWPbQsjWjc1HVSoAr#uPNe^2$HfZa;}Dj1D?15N9U}FrGkq zsBEwL1kx~D&<76#k+e6=?a zJMpNv8R$-HADRQEw)^NkNIB$suKTER(1-jx)wdh-`q$uM@ECnxZQvgAgKxO&$}`*g z*Mp9=hn$0I|D-+SbHPu45BWUrnK_L*{toN`^xz}*kpGZ+h07iEcRBrx(T@;Uby=T< zFX$-;rH@o12Uh@#Fg!=F7@>3DfuZQbKp)blKY~m>5!pIZcuO9g{c{R-={R&E<<+^Z zhVo<3GxZ&$4VyMNzI~OCMQcjpcUqV9*A}GR%D=g9D|=eGe188=>Jxj*=u>o}Ge2&h z%KIbw6|d4-qTlE8_Zqw2Pp00HT;+74J?#6~hi$T=58@9}j4=tGuqL23pWxlu9vsT+ z)Y$F?4v~(nUBc6wfU#irb;|hr+3|(duA%%HW#vauM!2nSr#x?alNKNs*6bP%;Gf4t6`3H9wmAAZC>bjbkX3!FCQz0bZb zaLAD@zyle8d~oqzu@AF9(cHyZ*@b=b=d%axcdxVGpZt6I{Xf}vUZ8x7-Ck&+j$`dR zUqkvD)vdnJUYLF7uTn;9_QF3W-Rqy|O8qYPGrh9~8OybiYav%NysD17_Lyt0`GL*I zD0o-ZVD|BWXy4;XwGc(lWpmYV>Eos|NuR*w(@o#{A3C#1!G9C3KVbv7|0{4&w$}`v z!`kN)!AH!qdsf%1{Ay_8G~wh#)?wM(Qb8TJkneM(X`izAim#_5XQN-oH#5!qW)EGB z-pz0S41IXsjJwKyqu0N-RP6&Voj*1EDr6Nf*$m9IFG)J<1K^LI7qZV52d2|zZCRW2 zCAzk(&71h0DQiP*>O1UyAK($MiJttqsAgV#`qdoWP}Ywd%KGswuOCb4$EoVark@l$ zH@W9?Urn9jrAxhW&!P`H_shra2R3ai{S~~|Khr{eYAE~dz`^U&&(W`ZI9y5lf`Qgk zZ>9ZHHa%Yyd_W5a4ii;|xpRDA4bGZ!Z6tnq*DB_yGWlF9_3o(g>eZUY?5*p8?_Grp zyn5wJ5`PG3Y^0w<`p2mI0Qf5SXia^$o@pb-n2v=X_@HXF##fFXRp5ux6bl1JvYw;+ zI%4`v?{c2m4%{2YKJ4E&&Ucr1x6*4@G(4QSxQl!Xk3&1+pT}O;-TRC&-+#vY_9o9O zE_1(IIQ_DKvCPP?vbS?1vXpBWzrP0&Y~+e@y|=z!-X}XO5V5j{9MnZ$qotod(yF8v!bFh%hq9B`MShb zN2d#BXb)TdjsJ$fY98&D9_eYKx$VJ^q0gen63Z4MzO)5jrf{x@b(T+qTfTf>VcfHN zQudwLe$zOceZBLI!^PNt?s~ra{oi1sB`3c9?EG)!Wamg%`+NEy?f^fs+5D_etnZqD z-stOWrmE?Hf9?c)H<+~V41*t0tD)KP49Lh_fb=(9%ryom=ev_?iPN zcklen)SZP*{B~>z8=js3PdRI1os;&#XH7Ud2QtK=@7U32P0Xde!SS;FPYG9-lrAIP zaBp)z-;_V1{G5QK?c#(5Jb-<-a8`3&Q9f4IAOh?GcgK279)80)0qeVj2O4KRV@Og) zIyh(hGJfn(aBOC#t#DP@m@6KkjU=`TX9i3o{qTeryXB{Wu4*SxzWmGp>~_v6%3<5C zE8GXYNQVw)+Iw_HfX)igIRTP2;&VDHpqV^66F_=_=u$R^aJT%7fV0#W_K1Xd53W0B z1Sn4(=^D#m)_0}v*!&i)mY)$Io+a3bcFNNSo(-_Ki8avwKHO&qIkAALe);6!KRB&^ z%g&p}-1$69eiuu9RBRr)qA%+-XQB(gtS{^QI{NS!Jwm#O>a0-AwN0Oao9aFI8uUi` zs@SjJI>0md*BweSA6eIS(fEX$lmEy!gM|Y&{W{(`1+#AJ`5AS{Kc9py{BsKI+{||Y3(06zz9{rr}_Dq4~5yO-bE7aK^y6BSpW zxdM(8;Ooj$O>J3RIr#wcd#Bn$w%v^mF(C%Ou~Wozu`e!}9bb{mo@T{{b!CXv zk#AD8Gm8F(Q`h9s`D)u~FHRZl2^P;A!8?r;I5|EVwOw_HxJ_qV{eB~j|6dLtj*f(H z3BLld#kuS0zwpE1eVME^6?}*>7V$IDpvF^?acHwo_ZP3Ipe@JW;O)O3$9%+@zdGh; zEc0_7eVmy){{H)jIdcDf9c}vi?b$!<_(%NT)am>Vblw0zJ$nDWVg=ga@qRs^>BZlj!hc zp&0KX=dq(){tI?Oa zd^sW+EnjaS<;vKOvi_t#pg;0KO$0ZFqMI7@l*v51`oa2wzK<|ga6$%2CpGE4NBFPQ z7hldBlb$-0{#2LsNiyGw;h|rvtoFjHZ)l9|J^%IU;e#vCn^%ygc$MWm8^d{V(t9#| zi^6O@a6bJCfjbe_6m>>>kTqg-;hyF6FUou}r^ARHIdLrWs&lA~y%}D(6ubfktheY} zhi}r3^1?UK!=QE3T6m2#(WllPB**5)I9r1EqVZRFr~8k+`-)gw;YU0RSAWkrg+JuE zJ|*~E)BSsJdnG=W=SkDN?&hwx!0~7nm`N9LbY13O@c_oY8@zY+Qa#!1;n(Ao5$*~< zMNccdbkPpjAN@z(Ywk23eqG|x!X?38w%k_ay-7f;yHTy(z2#r`nx^Nkq(DZYpNvc9+dKzi(th7m)fxsI{#>Eu@|ko@m^Ha}iESGL`zrKQ1qOY4F3-crww$4e(1queCQ1sI3UJP;fzI1@qp@}{Rh zPRzt^#;}BSz{g7u5>vO?iKBU(_m2>Rc7~Hr-=H{?I+7oN7doR=v1ZO%nD`CfKlZ=R z!SfV-ht~ThNT>g85YjtytKhx2_VX0cui~$zgFTEt$Yfod4k4!<-!5{*_wTmw{&@af z{7=qVrGNODi#PH=DgI2lLnU~xb3tWm4a9ND=Vr=E_g;ff(R8?e-f`qVo}ZT?^k%6m zr?&8SLyxtF{8kx1zK%SSe_1=*{ZsPEznOy<~k z+l#?VGtRo3c(Ljko7~#<57Z%Cd5AlDE%G*-e^60T z2u1_pCRhr`k2s&$!E?B#sY~d8oeUELmk;2Ge3)wu*G#TDu2+#!`{8vjeNp-BOW}x@ z!7nd;2|@6^m3Z>FmkVDw2NF0%JMVVSTnXd1?DWoGLDtQ9VY9pM0r~J+73GgbTT7|ui;5}o@=jYl z@kapT#hXaeo;Ts%3F=n@Jrnt@-yb18N?H*4tlxi&|I6TuAQmNBj}7gQ^&ar9oIW|j zg*^k3$*xWEUq7W>HrZu^6D!GESw@>D z)+OwJ;8}i`=5t#Md&NU2i~Sr``Vy!Aj1PTGaIcTA2Y<8OBQ~!u&gFRx^YFUeUYMR` zPenYKOK_fGl5}vncdj>H^i7A})yAXbeTM#7W`%PmmE^PNLh>Ed+VmUg(~kaIukV%a z*jUEH>!JUFdY;Nlj)OnFbE#W>3nbcd!Q}lpt@qb?G|;z?G{LzZ`?hvDYuQ3=Q=M>b z{CD7Hd}v%PTuH9iamaUYt>m)!XMYAN`0FEizrM$(*yEWAboLH#-PT!~=mFSkR#y7; z*1~$myHNTRHqH)U!QS`owcZ@v$#2oG_M|MtR(Kh?C7MzGwq0))Ri^wK11-DSi!D3e zEVl5y%a$EHvtFwG_>;k5V~c-sj-O->Y0H6$mHzEJ^4G~WR{RV)*(B|&Pz(*V&LV{p3p<>%91`sIf7l3%V*U-8Sq>D_P0 z4jZgG;S11ht@a=7_0}gWV%Flsu4%rN{(_gzygT1PP+655!+0hn^?Y@A9d!t&L@$yD zCIt@yH$_LXx3z93`DQwG?&x(n#e)y$TSBxYUs*oreZ#>iA9rp3@^3dNeF*b8y;JSY zbH3dG?L|lM4Fx~085lmPd?&*n;Lfq^rUY}~ z+f8SIGsm-;w$sKyn`tX)4vnLG$YyGe_1jGAc_)3OnR{DII5NwTcS`#TY0RZ#JFVe) zO_}W!;{9*f1E~8KdFR_s_rVj(ZKo4O=j;*i?I&Wi`s}AAl=~q2X+H1&x9q1j=12BZ zf_YoR^GxO}3lnPQN`PBQ?r|>PezFqoTJ8ebMxMNqEqyH8s2aOZc;U;(=e@mMyO#-@ zGe79lL(s<+Us5L>Zy0XWt;-BJUqtbRzAQ%Y45*2M@@Ncy|91@~U4p{9b1hlqmD71?`1jf%DS2O_o@s|HIzfhgVgd`QQ5_ClErwqee|> z(1S*Z8ry?L8!;^h4H_ZZD6x~lP!1Y2K!g-xO9TB)l%Od_8#OvqV>y=8W|XIsnW58+ zr8QR6j7{5A>6Fs6J%mIvNYZqEOv_A}^80-EUOQ*;>hyW$x}N8c=UiF&?t8uM^>VL! z-RpJp?r3M@(4Ggk4ZWv0L(dGQH~9KEcdj$HnUFnmUm3hZ{`;ZDYvqRL_Cjx^{RBvt zudDr3@b%WH7k_xDSMkyRYOiJ~GEMu8QzG#IvM-~Qa{FB5*n5ulg0VkB zw902j9y~zbG(8gEj{85>L>j+Q8*cpe%aKiA&qh{4f4dgDZxeSjdNSVYdaLdO(KUn(0FW%s2Di)FAKCUespNBl)kqX^(R`{xn``roFd-Wzgs@d}9XPKZHO0 z4#HpY5O-09z5TP&HLS#Qr?DjPAs(fM?AGE6wUR)5z1OvDaRx)8$F=*OIOf z#%`k;JGkF%qD^k?Y#dPeq{iTdIQxHz^E%3`1zDL9)}H0t_Xir7yKnI3ZMC20qnxK~ zA+0H8)d}_SEoF^Em#eK14~ym0hvY10m^cT{_;2W=@Q3EbHbt0UWbQ)xkAFTni}tH< z%5xg;TTfiy;(V_B`+og+{4ij^NrU4&e_mav^}OnE>v>ln`}o;D z-=)X5Bqqd^{?8m>kAtfR{_$P*$kLDdYiP%fvCLe9=A6pV@48se@nxIFThfiQp5y!O zW<1q~`xO?iNp5JKYJhjB&1|PkG}hI6tmI5B{_SzpSMl$UpX@Oce)pB&*B&!HZg_lD z;|2Kc0^;6SraJJx&60zETlPJqxXpO1l(JM?7Y}@ww53r;0qQ}zm+b!0r1u8WxEC2J zKAviYN2eYS|1y4Q_s4~Q(S&aye47ccveK9;Mm!pe+A{7g>{L$Su;lCJfoBL`b2r~U zWpCW^@^$l2h0c~hvIPpxQ z@x~a82^XtvD9sU9FH4*x2Mfvf$H;djai?%kmfdc(CrRhX{JltOw+?lrcc=00^~m;a z&he|=`hN9(wSPa1-)?<(-(KxDv>3mX$yQ6AZIrBsSNwWGp1S??*{a(z>LSWs6Y?oO z{~T?275$_3oSroI5ud!C!(vWcV=%c?u&yfbNO++n1<4j&v)el#Z2+@8)u>KvlOc}RU@ROb$7G1ttu@<#IU zlgc^0RL%+OzQ9TFUPjox+jIP`K!1-z9_2HQJ$(5s+!ecDdr7@}X~akRZk*;vb^cT9 za9*0cd8zyAJ0G7!zo#@!@cojG)X3urohOvvx5m?>`f$I&`-=8gd}B}bq878}=HH#t zGA)fZiaz$cue2;7KGN>qHKVamG3Hi`&X}oRK)z>m&^~Q>U5@*7#^nBYT`1FxV#?t& z%!N(mdx84?w$AiSTs3oaIeV~0OFl`b8lufkUDe3^fbJsG8o?u@3s@KMzo9*yIRVX6 zov5Eo-_klFJz$>u4(`h$ecBVKcc?#>f60U7y@1UBv(6B9)7m$lR^(2G&g%JX{+so0 zjK0tM!PE^c?mCIi8@p*C>@Kw*;yvqxI4<6@Z3^KooKe4R`XgIrT__nLe`&5i?=COR zRmo0sBj4CEi+i-Zv4k%>RFBp8vHMNmZ3=Qva^vJXv}o(xTde<%b6fOm_xFUMdu}lI zbi7&=<8=H|$BAP~Fkwt4X7IB~y-Vw#?wFbOGsQukyLSPxXRm?tSh3cj+Zn6KUhC(1 z=vLzMdC0cul>ZcDozD9gA`go~jYDtUZ!Dm>z?Ln+ z(LwGFXjus&2h{c_nQ~8qAGE$BKB#k$-O_)|-l)e$z8(L=o1M&0=yzLEhLML{&t$qr*cSdPNeZ^Ts_~Y}&$W^}&l1{Gswn~vH(g8F_n-YufzZjm^m^4GY0iRq5t$7-y zxs*GfKO>zXO8TkC37}*LV}qeT^_hER(0TsIv;1g{NON|Y$CV$&hyRQc*46$EKbi+| z!ipole;Z{pfqoPD;;uCx#9i{egf%nfeGkx=PUE}s2mSkh#`9tB)=Jq&ew2?GhWJv5 zJC(Ti5>Kh(Ab&~|aZaHGVQAU~7iuh}ITG@{Pw&{FxJ~$vk`xk{Q=d*1(Fr$qTl5e1bg8crZbmcF#j_*KcA{_@RVfUhqpvP~}ZN;ni1yk*}fS zdEZ~rxk2&M`HQ!Wq)_Jb7mtl9o|enewYi5u>DBzkBa0uv@Lr?e{+{#?p?|60_3vwe z_ZREF1hs)nr5E9@?@##aW5@Tu$LkWwbDxO=Zau+kP6PYlX}|tFt#m*q=BtHgu9Z<$U$kSI>R+s~`E4 z&zI7pLd-F{d8Yg|?h?NTLg?^i@FaPXekr}j;cSh{biBN21IUZFxrbx)=2e}e?mg66 z+eO#cSxU(vrL~=coF@1YE{fCcmA9^8u zICmG$xnMN)!Nx?(O8R!>pT=90UuHiJ`xhp2uZHXAa{Ry#7rAZ1dsm8EUf!II|Ncho zGB;e#y&3ZkNjLt9x!;F*mjypI&y4flp3InV{N1Wg=^=|XzS`^NdkSH1HoD?X&vkxF z-ww}UzUyc7N&2>P8S*xClDw6EF*dGqbX~algWJ1C=eF1C!y#kTg?_)leVcoxIWRex z;vx=K zVQB4hluoa;CFG5J-b&{Ub%(pw?6U};^%p2Xi`WmL@aDeiJ~ncRr__%8_@!4koWqvv z41&5J!~2dnV?$_t>p|zVv*eGkS?;rWKl-Bd8h>A#yI|L=M1EO z7CM}%`LTWG{`mP{`g&K6)LQWnha2t}gj3)1b5OFzFW);?qK~fpse8x8n=22GN>(yo zu%L{-|1Cq)x4*4BYTh(!v+R9X;LA0}jc=#aKQS_&_UzA@qMt3e!J=t5Z>JL8m(R$y zXG837RK1`(xnnu$A&j9j_MB!srSem{ot3+}n}%=M6F!KpuJ6g|eg(xB0<%EvGu8X< zGwagKE3uE;`<9i~tP9Z#_5GKYITJ_kV{9wG8soiRf0TPDE*RDRI<2QPQ&v&J`TD=c zN{?Wt{RY;p!D5rbcQ9+r6KRz8(Qmli1r^Pt|)d&ukLM9FhLFO5pAkV%Gb@$ErYXHQP5|8iK7(lw_ z=Zzj+sC=PMt%cUSr}P@#lhuemqkhKe?{&cI#!eG3Vn$~}MD*N{5aw*dCG|B1M{{?Ua3_|f<{-N<|Pr_6!(PZ_`Bafo02gCD=b22EJ@ z$;>GtO|sMX+QfI_F|DP+Zbo>Lf-f7Va1*Y~mm z;drVOPMqsdcG90TClz4dhW2!%N)M!8p2-hS@7Yh+L7d9 z{MO_;Sdo#7>X!>G*(lvT{v-NW#%1bn3%HA0GV?j?vWZ)=?^N-35`UEVeR(K(skj~^ zob>-kL52M?s5aec_U5zy7QOQcS8n=p4_UTgKKG^h`(e;4UAgJYJ?iFJ$t!(V5k7wD z5a~MA+>y#e>C(3lv=3Z&%S*SHPV)ZBo7sQU4PPeZ+eI!fsLx^kk-LE@n*)+>g=G!Q zad6L+k#h=T%ek|RoV$a(6~RA}dD?%keBUZN_)G0e65DF}2M_MP9p~lxPSc_s)y3_UJ7B_4L%)@v~Dxn?jsRIS>Arj_e3I zD_f`2zNfQKYr3=ewdtD6M{ZrXyng0|$UW{JZ9Px(-zl>U1Lu7~e?-6(WlY8yYY5WWs0m??oo0F@8yh_rwRvXDWH{<$?0%%Yz=|f!&Xs zNgnw6lXyuw)S2W$(=AC|Pi_2k{3jVwY59GII*^^;mesCYJwM7GW;2Flj%&dk(t(W5 zBRxm$$9G3>es-bdPTNNhO1jhbpDniBY5Q+yT5Ug_dG9l|eSIs&Zu@q7XWW~ib}s!= zd3Nu&<=tJOZA0^!ll9g?W{zIUcw++J4m^*wyT2sQ(*3m-G6k8hJ~gHO>2V!Naq638 zDYz+4$&6~^yN~$f*N^iYE6!po&Z&eeIzt^88Gw#oU+kTSmuB>pZvG3SJ4*DB8{K>J z$?b`Dv0wAMzHFGOHe~dpX5M)^{Rq9))sKq8oKq)^FB|wC;J|s%<>XI$#5LzD9Y}Ki zi~j>MqKZ6JARj`YWJH)}pVuVovMl+Kt@drnhu{A2Wci?P)n1Qo^NGdVMn18aekZ~F zF>P*Yw>w99Fq1eIF}EnaAe(yB+(Rb1i^guVRw3Q;A3Z-bnhp)mgcTJ4Q)2x{SuT=vJxj9iPY>sgd&SkKwNln+ z(hbz#=UVYeH&EDz2`64@1m(w<|E?UtPoOL*M<~Yy8mlav>aL-@(iYY?y4~-*?`ws3 zPbUM$h}Y3Q|D<2xHO3E7x9TG@nO~HhzE`PvKlfYB^xaC^Q-MI@v!p94B)ydRM)aX4 z11SmZFH@fX0ym9=PbVw&-Uw#|I*Qrbhw<~~_iaIauP8=b=+}OGq)wmhAWp^<<6|N8 zn}ZSP>v^BwR<)jXy0+?%Wg_ADfsZMTp!yZH)ytvFLdmhr*J-20#8r%)J(ej{{Qu3d zO#h9J(}n4yjn;sLw9_`8{rsLZo*~bETb*P2@}Tm@c;;GYji)r8QClsO?$Q9S#<|0? zGrs>)$|9@9`&LPMg72eI4}l2l#C$6}{dUfz4qTF;&mKt&Z*kIeXKnqHQv!<@jXXH} zamSq_t$%mQ&5Jvy2I@CWd3fdg(OHWpOr2jpHr1*B!4$Pa_nQjpw~+x)TtZx7zRRRL z>Q?r;-;~w2!_<#wn(>4irn50EwV?^W+LuwWpSwTjOfdbqctv?V%WAL6pWmPB&cOe! ze((3^{(Ok|&YKfCy}sYcps&TOcl-Ljt?O&Az&GCh@U~lR#iO>XaDPoWwcV#dwO#)` zpGI%@+wXfu)=i`BF0lG=bOKig@ZHe~7Wn!B{iiz*GS`~-(0HK8qBS0{=Rq_c(7c5| z9_WF8PbUwQR(bDh(lwARK_DWo3u05&G5f>o65Hxly`5=!#@j{ zobT|*0cDhl^graeyWT0Ce<607?~wlY=gxO*jk4dH9^Y?Mz6_E4h{6ZH{J2W*_%r4^ zqR56z3FFI%EM&wCXj|9U-1Ps``Hoi7z8l$40;(Tx;dwkS^yAr9Kdy1R`f>cZdj5a? zvnLxOmY$zO+1?`ErG@jp-JDTNU?>aL72P=t-BIo9`BOva`O@iE{&ac)`Z_w;bm{B& zuIxl#f6CSA-Fv84PQ37@`Z4TYM~B}tJ%H~2&H8&UoKSx=cCSrWJ3gaMA8WkSlL3Fp z6@th2*~-&Y(u;g|$M|VZQU>UIWBwY{sru>@=R?)*KTMzR`ed@*7lyt0&w>-i_}`>l zKdjHLwc_!|^Is*L+WlXHcDpY=MZ0HiWx-sl-A_58-7^m|-tN_=ZM|P@+Sc>CKBP_i zdVb0YZCZ1xc3al>Af)f7(3Ul(KV4g%YR2?S$S3?W-j+44ufR@ixq!BOrt!MU2X^nq zM0XdlXALvX_ttpb*2(87?mwdqOD8WNjNgXU&qtu`HvIJeKpXD6-a8%H2&xTh6=FOu zC$wR|e^;B;SUppDqJPh~+OYcf3ibOZv|;)RR}Vk24XaH$E1#II@i}|Z&f^=7)0u<2 zP;FV`a^?ha-!xrqnR!9C&8mM^-Dw<7+dpm`uDUC*+AM1pC+XQUNZ+a2toHh!)MlCc z?_T-u?z}%b+X8!xn+kn_)edmqr!&~6>!UQLc9@&;`=vbmriaj7nQu-H(RWd%_TI|? zw89*^z3Y>QnIP%GoJp1_{D;jI))7aG(H#oWwcRmtlrbK1rvkGh;N) zp>6cD+9&YzPz`n!(9&tu7b~qTw22b@m0)k{z8ZV~cXi)AQg)q>->Cc*A}ed5E3r9B zJNL`Z*OwNO2EF$dP-Wtmh4f>cmDIk6;xg{6(0muVZWeP0>IavZHgrAV)P|NoPbMDj zBO1{6(jQTuACK=}_Uvy)mc7Ip6lIdqz#0JO<#fjKCdw+ExE2+9nb7PsbK`+1>(F7XML#&8dA08lUSrN&%E*-k@bal-K_~I-BAz4&hPFq% zb0oSa!=KNWyelW&XOefhl4~LEJq$lNw2=Lml6hw;CvSgen2|}t+#q?~L#O1oDO*3* z*g4SP&4sTd-@bg&oWbej3w!Gi`Z9%Yj}253E*N^6JyG!kPq1KEp?HiOd6=}?eeK`; z5A?NlE?MnZ|ZAf;#qHAJd-nLk`q}*PRw@GR(60nj%jJswZ_t!+-3JA zSFg~SA#ZPkbk#igDUJPcjIZ4NahfNPz2dwZ|KojMG^ww0_Wb0&ubVzz?`a`?g1$xa z89j^rHi^_3>|a-#W1b_HcxZ?_6sM`bu7jtw&ab{1zR#N!;Y=rS=V7P40J`_mi9F=) zWAO7XSxGza>NCckINp~=+Unxmam_n(3A5-rPtMUtWN2SOFx-^*>YPiM3wUQFme@2< z%UW#}=?RC>8Th7-=H#4}zedLJJ@v)!a&EhAq~(?KIZtsDYucU6+4eJMo5tF9|44f0 zra^s!hVR(#XKXMUdWCOCcRoJ&FyCW&1o^~wX{Irkz`1ks&KN{z0nl$!bswI-|1yEI z{Mz5)?}O4DrtZo)ch&PF85^GG{J>*F6YiQhdNq4n&f(lr=Jn5ylpu4;o#%)2owA89 zUpktOzlk56IU4-#<)eWOvqw|dzcTrXPmG3SKlxJjZgbA*F81a>8Xldv5PusAMpN;V zzUmX~FMo*r#g~p=bQk>u-|b1=!1rXX53(l)Imi1ZZE)N@=-T%hs6TKh9e3?XKOZ;G zuABYf1>F0Q!F#>E$y&R;0Qa;F0e3HrZ=Zs_?vXkEPJvJqcIFrEb^+GzG_p>6rp{;o z)dbR?{^;`~TJzEVnpEmHU32g3iK&0nn|s%O>ok4a=dRX~bo@+QHEVPN-|cbOOYf|D z+TFYEY}h=awG!=F^yfsq`)VddZGI}E4r;lhLHFrbN6j6r@Rs_}Lgz8v<;Qv*cUHu? z|F840p?7HCh15qS{qI!giDBDK--O6?o*9mHKR+ZtnS4vFlKPwAJT;t2JR$nys*ra# zUjcryptGs-OybES{(R!sJ%r+e^@Pg>H-ft3F35Ml)#i2g<^=BcTf@8Txg$GI;;B0F zrTYwOB99H^BU|&r4MXp--%58T6%tPs_BXL_s(|N8p7oB2FLRG9d*#ZZ9okercJDp= zozYV0Jf5Y?6!WY#>Y~oa29!Qj9mcfGaeenO_tk$42tP%*{DZ4u@mh_4wRlBWaI5Hd2hgk_cr>Ip&2uxtYte>ZSlX!gEgNV?$c6 z&^OORtP#FT8$CQh0Q>MLrlw$~;h5L9N%1mP@rA79X5%(f&f)6~JGJ=mb z4IK_`8#;`h?%QX_H<7QTzcBuC6;AOIr|&NhfBBZb2y}r(7ZR>W;mrG@(8bWrysMda z`t~J+E49Lv;jbKjtP_mCs{%ij#!qT&bK>DQj(t!E5AY3&A@w_oOZzKoxob_jF8xo2 zbVIMKEB5~*WgVa_jxXo2x#tCL`|ZG&hUczo`LDU5qVIoiye-6dH|0HnGEOt)t2JPi zqxasYhL)2CFaFQ$XCEk2(yc==?8eZKkmm<|9m(FKCcWgt)=vr;?6#Qk(}%5}6!X3h zn~NE|A{n3Zq&13b2^XXt6@b#`#`CRpSp59Iy?!#4be(SfWFl@Kwtf<#Q?Dl9W0za{ zj~P4ux$7q%>`v-_O=r;k?Ddmh6DCYskzS;Em3`3v)9WW~nkc{rNX`TY5nPZ*tVr}9R7x!ThCG{53V7o-gY8yW^C#Jx36cg-TinpQXR%dDSF zc!IT)AD|<#uZevTi9r3ijMaWU@&tC?-liWzzwWM8YQ1VwsL5fiG>z|Cr7_n(Wz+13 zSdMO;!W_&L=3ZD&d2I@M!4$2ho^s73&YDT=VY6oPnNzND{(!!o_D|o!_b%XZf6e4= z+N6E<#BZMg?IEJBy3M^kT2r{k8lSMw;BDI+8IhilwCk+n?HlaaE3eGByI4(4*^ zmYnYRfwRAc9-cs#qulhZq7>d4{9&ctfAIJrE8~$TpgV=JDcch*>lVO zH}x<2w&{K7v#Hd14!SbmOLTR1H%{t5g?)6=+uJ6kMy7I>r7W`Ps?R)refs=~8boo?hjRUkcrm=+f3cF(ohkk=+$(+-Ty_KF@o~$B$;b_B5s|{-0 z5vMO{V9#Esdv4;O=3YXK!!<{cLOXi*b)7A8?;;~rAozbM`Mo21Df3%A4DryC0|RU4!a|Juj-4jW-43ndeo2OoF_x`w%+ zOx*`V8`Zp-+*+`&;Ler&ZuCg@l?N%ev-tLu)5$(@r+K*cZsy=(jN5+DI8=KTa*A@y zC!WdNEk5}L<_}Vx#9tAIbcZT*gN;1LKzDtfvZC!it8hm`x=Zk>_qC`00_KaJVr>37 z6Rs}s@X%_WYr$0KF75GsyUx5z^Gg|#r{33lmK*;Cfu^Cmd5(bgdlaVS6z};96RtR= zX{em%D5(9O>AWk4`pradVSRU+<{i~Xt8Csy?<ROrrrs8hUJ898 zZ@TUM&*V+c03Fnx*0YJv=R-R!x(lv=J}ezwT=ScS^bMM~*kbyyRE47tdl=fE-_+dw zr_mE=d;6OfZyRY^fSjWpGnN{lU)vvqr*+&$sR8Awey$jJG~V7IMxHzqP8_-|qDKEZdgLfqTM{vVy; za`!bO+f&%v?0pA!IWqMV%w@9AnLWu>D?3MDMBZym?5uonB%82V#G(B?Q`z6c_=WTR zo4%3Geg(-@&SsLnh7rAMIMg{$cey#<9_V!Xew|GT zgi;2$?|vY_odBBSNI@5vz@7@n{gym)sN5gKe8IV??D=MGbabK{&ff#wtj zE4*~4M)9LGmw9PM1n@m;(ky=HSv}D5_QcWmuP2=c!G$`H@s4}uNO{t}!_-x?sLyvs zf_$soJBwuONq=xd8gn{cK2qxEjihYw?hFp_{NKq_vz4bb@{|tG^DR4*ug2r@H7*x8 z6Mc=T6U`f>#^A?>(t$McnHJ~%TE9*b*i#qEUq*L)zw(AYcW_dOyC}|nh`GI=j%0$G zS5&?<=kPxJEoJZSx7?7+JQj0!CVzqY+eXq`T86auZxVWp)(o}h8~w$-w?p$1nTbuO zu0PdVv@!A3%uKibRF}G&gFD>$-rvLUH+5vbKk3(_&5JE&OgMu%KJlW{A|CYiFa$!S z1MH1C=&XbXSGF)G`Oe6s*BTS=ktfLm);mYvg!h!En^qOLdGgMv_o7?Me;+!K^)1-9 z&mVPp4PNy0Irh7ov+T?%Fju>2NaIG6&hhWR`t8I^Tj_xIS7^;Gb!DF`_cV`m^TMlL z{*sJPnfp8zyo)pTq+9mN$9pEdI#;i>inrcy=NIRlHq9(e2V$knL(x|>c>66uVP=h9?9!Z3=A267O6e5uz>k;07w?R;ljjuT5r0f{?I%zM z$u`$cdCkO3pd14A0m|Ygx80piMx3s_vCrYe_D23sE)&1~X|7^EW9s>o$9&3TK4meV zGMZ1hxMgD6WYBMuarm_%e&8x2TkfWO)HWsKU!;6qq z+|SLdxaG>$q8 zIOn$jJ#xWg;Ey;n!P?ASkByw?#&gYhJn6)<_u_3sy0cF4>;V;*A5RMLq!LdG@uZq~ zCY>Ul-@kh7gEr#P-F14$R-U)=?8g%%o;2bK5>FcOqSFul@l(w z@>h&wo_FiU&u7~8vyiRNGvD-`(Kpb0-=trkx@s2deXK2_GwL4fiFZBk>Qrf~(0dm= zKQe_obuzG5*=6#qvkrQm%(MUPi&UNi_0NyIMSQz)o3!xxk>Cb+!^O1Gv<1H!8N_WW zI@+Ygdq#$Mp2G8F^PI`^6!Scp=d;XnD$f(mbJ~WpMkknO=^1IO&f=YKjJ&z(59lFp zjHIK}rLzAdQ2#HUj7S9aW&_Pu>`AHBYRQ~dQo?8aV=eDAr9Y5#O^?(eD(G=2NX z;HQSO&N+YKDU#IP=4X1bB zIFKG=+@$uq@9B|R!<#>P_B$@G-?HJF(M;2RFQ@*?;mvaKrrTyWyMFw3oJL)1yeu9p zF!87^Cw_M(JbsPqS8?fC@*wkN+7oeR5~scupg5;tpT5D1SMli%bT^JiXO3pzcNpFq z7SG~01Ak}XXZ&2$qZ9aE#EpmEH0^Wp%Y~ztgzmeBlX|JMP zz9UUa&%b^D*av>T#J37F@s|@uH=4HL%Ph6WTUb9zvp}NmmAUdMQ1@ad-4O-1o4Cwg&`$>xovFDem1D@ILbfspEU77#mo8pDMm*9^T{oH%2CN zZux%kd~6GI!SVfP;(4E^lf0bdeet}{|H*q&lDuyBcR%4?|1yO_vVf9LDR+GC{o zV0Ui^sJ#~dcg{w%qA>XBsG)4L6>rE}FVy}^k#~nT$%CFlD_#CmpsTYp zX3VT*&WnEC-z%uI3i7Y>`Rd1+-^&PuZ)DHOIjjwG)<47juBdxfG8I3xp?wemXq|&W<0@oJTW#<;1&Y(ry+Vx`4F#=ODFLnzbPMAJ*`h zi`BeOpfi4M5WQk@Bz`vi?6jGRPv_y`*>fk~v7he+zg>@Rvf;UHBO4#(zMksFfokSW zrK4oyH}q1Pdskx0mmeN@gSnOr_*D{r}h1xW3N1?)c)3NLo#%($dLeG{*4$(87V<>( zdt&_uu**~(z+*ZqqkZ`P{`w%a_9Z)@_$g}6`F@GH$F;Q6Q|*Vm)BSyWZt{Ck>Ms+o z>OV*y*f6)2O@`G@suPT|k9G% zRuU!*zofw@I_q*TbTwghJ~uVi_`dcEFT<`9TJ!P#Uf~NVD|_BvYaJTToNC@)^O*j; zy?s`@hP-u9sLG?-eiMfyilMjCI7)f;^Q$(l{W~G}Y2hos8Cm$swvmOjlQ*AZK8w5u zV)6YC#@)XEfYTX2&=_S;4*OAemzn#!)kZdKaqs)oy_r{%pC0(Kh5LSWf7NBss~Bem zS@TWX-;~fih}yB{-1R-Nh0I~Ub=P-BFQMEt56=3Y&ZSabk_YcFug&~n=)uG+#@a8| zpEo*l<-Ls2G_LaRUHt(%*1PDCU!d>*!HDkelG`KD>Wk#|qU-kh$RuQF2J_;>w7)ds z+svA13cQ@od|Wznc^$-`$GGff(t982N<+4C9`+U8>Bn65zQR-PyU}=^y^~iKm;Z6b z@q4|dQm*-EAX(ppLR}&qZGgWAZU+*KHn{IEg(@AK5%m5#Bsd z$-GPz`HQ*z#bZN!tMFf?-<1d0Z^|5boUxVe+juG^oXAH`<`7>N@6-2SQ$h`iM%+`h z-W8_(u|}Y?iMof-LDuBpFO@m_l4IMVXIJ3q{~%G1e*_BrPVoLAiY z3$Y9H9__*Q*8-dB~8l*~Dwz2BA2CevS(@Be^$Rs6^) z`jNx|bng$+JIOn7Pi|nG`qF~~X)$;)%J;jRv{rtt(%J=ue%UZKhxoRz0->sQT`sJ=WE^W8#T>hl|B%d?V zMyIl`Ub0VP8GWzt{d(`)@2cn28JqhiUiZ*D?00>~ssH7#sFPn!xa^^KV&RA0nN|OV zUz3Jk1+H884*7T|NZ(95{T1`J*#F>HUfRm`b4TVr@m7$!n7W0&Mro%GG^Z7)ziWi> z?9aM!pk+bO{cfS!`dRG39He|zUg{H7-ZwE;(_YfEam!e!aY2P!H=37s?_Id=(SbLZ zv!Af45r1BJsXhbsCGNOKb-D>&+7yrPKk{AObGKzca!dC9cM!!>srVVgU3H#jzKRa8)!%xUp5V@IB=<#f7cyj_Vd{BLn_r1eCroWoh?SKE!@4tNAt=dU(f}wMU zlt1zGgR?g$D&T3CuS2|#It}nX^ubre*MTtd`cIsf3B%*~Kj+{>uB?-cmAsvbzLghY zZ(_sKL*=ojhi+s4$@ywWi{Ek2JfzVkpWzNz@!ufzt-T!Itbf2=3vqb2-~Lnw@+Y0` zEmtRDoD6SIWDl@(sfnu|=Dq^PM(!PT;>#b5+Sf_DNp;sekv)>V zxnb@1)OVrr@8+v)|BHV*_DUB1_1!1wLDH8~9B&;kg1!2IZ_qbV#@s)SKf+!Df4DNA zxEhpJ`kSdI?DM?yyT=X|97>K)+UR+{F2giL(j+r;GCz6KCaz z#5vc*`8^Y7^BLkSFmc8{B+koCoE;|4sx!oSk%_YqKN~p`bi}zfP#5_7z*yj8DK#lC zr3|OcOs%4OOr%~ItPDOI+#5VUtvv0?v_2Y8+p%vS`|+{&kA1|s%30`CNgX@m@6fT| z9sB*UKOReSGMuv8G`Py|W)~pWaUG=eo@QQWewaY*Ez>2lYtJt#Rw4W1jc5!R; zj+}tgAwxX&ukUWoG-0+b1(&feGzRkAiwW|ag~=7-$N$yd!wOXue(z;ZP&M~)V21DW zT~EgBmE0l5=9wHY29{y`U(agdx}Wf*v#y5w2f*}m15P%k1^c{JyocxYxcR^AFA&G) zy)Z`!Z->dlZ^IfdOeg67syB1qtmwk)xs*6Y=tQ6W>X)OW z?grTWSRUiy4kiSu=(Kt;1t{mGG?OBfz&M8A_&!P`58=b}aEU5xCWqN_FzcsLE*S4u z`x>u^dj3jol)oc0D6@-5Q<$`4F50|GaTam0Y>^kIGgtn0IyV@RQ)OsE-D{8|C#tPAxycj;v??&C*`*FN2D1O z#?)ZE?^CLN|I@9vzMqmlOfjYb!*6Z{_1*k4@`4%LgB$3-n>&KPuXii%#$S*=%-CMi z2P*7Kzq^We-|M>X`ZezTxc!Q_G1?!y=A6{)6*jt``o$bcP)itmkROkiLLb1>n8t(D zB8H!vrh@-Xd5;q2xA-1&?fIv0F3*GRv!nQjCm?So2AoVVhVk7TbQ1ntKY;t|LpJTe z{C9sP$dedGvQ57e<&^9sb2|QvI2Gp4`K7>Gl>~rCgB4&t>S4K|VL{iv)F5o?(#4?s zMGZ~EbnOcb;^w;N8O${pHke}&@!|T*G8i(L2`W7*UtvD~D4}4U!CZr3gYqPIzT4&E z5rc;f4jUXam@wFHu-9OZ!QBSC4R#vbVX(tsyTKNN%?6tcHX5ukSZ%P%V9a2-!7_uT z21^VU7>pRqH<)KI+hCT#kikrYV-$|c=a9i+gM$Y580<6HV{o^@ZiBlFb{X7hu+!iU zgB=Fj4Qg(YzhJAuW`j)z8x1xX+-PvU!8(Jr25Ssf8;lvOG+1G<++dl(QiCN1iwzbU zEHD@`m~SxCV7kGeL1l`+;8FNgc*NjggAy&WKV)#&;Gn^TK}53auiM}*gIxyO3~n{p zYOuv%oxxgzH3q8YL7y-M`6$);L?Lw%06MR-{4-5 zrWxF0u+P}{8tgH++hCW$od!D%?f?}}hrxDZA2--$aI3*)gG~k-4K{#^NBkvRZ|v&~ z)*7ra7&CY(zEb!y+=ZpaUx~qDgHeO0V6DL#gVkU@{Zf^|n88YeWuV?uYOus$vB9XpB7+fw#Oa31 zGni{IY%s@Qw!tieA%mF)(+vU+qB*!X#o6AeN_LzVYSGL4Jb8WRPRqU+`yJR{<~U1C ze{(1O%JxPRE(iCGCLI0AxcwMRQ(?lHe&tU3kL~#;To3N1|G1NWV%)wB`=AMTl%(-@ zXMe!iUSY!3;l9V%cUkr&*sHxNogKy^q5{A6UKcXcpdS_K&7J$RC;s3dEk*r$#|RD7Vyqm3{OQI?V(ii}dS4KGpMMXfQ63oC zr-Lg0egu>?#500bO<^R+UKW!=v>jpe=l?n^m@Z@yMM=8BXpCY zJE60z@U_r=gi*RWK=E4}cnw%<=qf`;4P9X9bVDB{4*Bl_#iwzDEuhOM;5_ULf0s;m z4t70bp8F1i8^@CF71$mAZIa%_m~70VTSu8g8u8K{HJAsgT#p=bws)EG=srX|&__X) z-!54&9ZcG{W7mLDI@W_O9~n&it5=VEhduWigE{Ye zbmw~>wi*n9w-f)y1D<`%V5z|ZQ01C_z}enr%4yF4`GU^GP377L?LhnG+6{elz}ddT z`0s>10`0q3LGQ#U{p|*~f+{~&@H}}3UICqNg$rVrXVH816E8;bRDkLa$_!n!Kl#2K z>_W!=a3X2npMd`{0m3&Kbik{Lr}H-+)*8$O>E%*;ip#Xwf0)0*fB(ho3NdU*eY@_JGP~+b^B%F_X?}?6yM3LFKCoY=!poRSw+( zU2FUoLpNLQ>Chz@y{8aVe)2%2_wYV1KYja>@$JB_*P^qaw_@bK5mftWV{SF|*i~ZWztFM|W7oUaOGl2uAgKKA{kgNf$fUph=gdn&_k+rB9PESk^VKr6&Ta{xU89iGN}K$fBdry%_oL0+ru(=F61dHiJz+OQxq5 zyDE(QM=kq2?0QhVln%$>@Vj1qyWVxS=lbc#ZYT6EQ2E^mc0&94t%2SFU10oILU&m1 zVd!>?KKhR1#IVzQ%RuEn3Mze>pz`1IPBQ)u{O`8t2y`1p{u@B$fB2_f{u2g!f0_)x z6T1$K{BN}EtFX()C>`Z}9u|SB?@*t!J!taN{}ToZ&{?=C|9hd+q5b^#KnLY+{O^Qz zEcZI-T^PNm9aMf>L8T`KD!)ZPNyg`3S76ckKSthTq=&T{jQ_~+_m7g{Hey$8(Phv_ zdcF4+7|a2cze7KCwhyzGqx^LJkocj;K+!wFL1;gJ?a&G6BgTI#bid_Z0lgKY{565f z-+EB_D+ZOnu;u^g+vp<}9f2Oh$bY}V-Jt5V)8L+e_VU|naJRwEe@>=zD|XEo#k1aE zndM%H-EfbW&h>A3>5PC%cQ&YU8GaMJ%H(tBo0J3eAyDPg2_~TZa*0FtL-!f~Ezo-{ z_j2e~jN)$uRW5a)(ia6)E;*L}u{YpHi!O%l$H;#VsPbsrZR*`%!|r5ysn^*`CyU{{N| ziT70*_ez812Fnb#{=f_0U@*(4U-RtC3`PtNcYF4I25Y|W(J^oV?<)sy21~$Fun1fT z=7OZ|&MbrJ;1$>pf6uc|fJ*OfFb`}p_Azho{QyF5Mi5O^N;TfymI87O%h z1@*oNcnx$WsCst5dEoGOJ@-EFV(3Og=Nfbj4*!$quOC$X^?;XyJ3*y04qgd18!R;# z1@->6E)Sawt~XfOmE@1Z-{IRN7TpV7ZqZ%PWft86U24&V&?Od~4qa@~ZU4v`%Fg8b zw*CX-6pKFk_l!#{ItU$x?lOGW^LNMv=s|y60-X&VH~h8(I?D>*1Rb*ITIfuRjzFhd z^r24Vv_?>~|R~0B^zl2x+(s z90TP(49b2lsP^0qmVj|kd=mpN0!u;hS=i8-hVCT|il-JlANNd9@wNV~XJ2Npz+ebe z{Da?0=Bwvh$T5qqhTeryJnf+PuL)GRa!~IpvHTzYzwl$xz0gtUVf^xYp&h!&a&O;( zo(b*Ob2W4Uv|rC<&=F|Ao=c(gt?+r!c@{nPGJIswz0hHcZh+2#_Uo+*dYCjRAKeCb zfESX#RznwoYM;5F%Dsnosyw!Wmw_#y!Z#WF_28}8$3T@+6jc3Xf%2CLUIE?DyG6%A z)mPEW$$H4buE4lE1`oZIbnkeHbX#;2^dPifj;bY@o1WKOo{-)!c17_l;{2l&=#}^4u;dgu^89o=gxJ6gBqnBEA9`sg=KDr%! zz@nR=TP(T+y4j*b&`r=jA0PQTdJ=J}A8G@YzGj1EpyWoD!G6NZy$=*W?=qMRiVt^w zJ(-VU;_bBP9Oxa;o#apLx&H<7Ww~cUZ-w4z`!jwCH^328$ls1|LHE<9Gs)z14s)qfl;2G@i0z!ES({F$K2wR2mtTw|njheaQGp8gg) z#Tx?WfP24&tOPs2PlDN?-ZO|_#k&VoygQyx##@bDheek_w?q5&5rK|F`{kbv-Ddee z+D83Z{_~+*E&qAYEzo{_hM}7*_YiauajKkhK)0N*6P*C1U-g2L3*DgP!cOo~a0e*4 z&;lylM({>33skdW_1Y(z1_WS7FgdpG9tB(kn~BE5R&-JzKr<-uu^{zcx_*8bI-9wz1DN*vC5*Z#O9Uk^fAx zTte98S#<1ajjIQ1#sliZ6B=>;QA1t3mnC z2UT7n@B;8?tK)nG90q5Bds~y`*o9piM)B2wDxV548@d#{2t53hhh0z6pR_poy-ddk2s*#D$ge!IbW&>f)Kf60?pc*=v%`rDh)x1oJG(*m6i?ek4DbkL&fpdDzR zk7}WhHYfcTKp%nj-6PP4q5b&7&|}bkJvz{bpd;`ezZY^IrQJhsH2qQ#deHJewuSa@ z(L160EV>T5*P_dydn`Hxy&Kx^hYo)kIZ7H-u2rDwKNnOv9p>FCw|0YD4Q>Q)z`X`M z7mO39{I!5rK*tOn0ndlt`{iVQck#YG7TpHjgi-j7mVG{U>n%DR`f!uyZ?Gv@o;}zl zp#A!4g6@a*%fA77ujRiMx)*Z`>8b-uz;Y{m6uVMP9`+IJ{dzk32z+YMyP-QVSK+S> z{215_M!@w3D-9Nbv$2mpoQ$u1GxJ;+<-2Nga^1KLyBKu;1-QTuVdxU$FKVz5RQfWl zaEBY=TPs{Qw1dCP@Hfn}-jgu67gYEihVC%fX0R1hJvJM9V`DNsgPW)qV_$8s(qOv5 zqxjMLdJT3P++i?ou-RaP!E%F9gCS6ScIY9KeuLcx+YPoDYy?wzZ>^!L43--#F<1cJ zK)l(8b_@CR6Om5ZZp_ou+g~J8oJ70slgnBM;pBO#tilt>@v8+V5z|fcqQq| zGW6JAdGjZGLDg3rECzcwc=1+)@*g$0v7Yr%_DAr0q4taPDPQpXWql#ZUk-L5=nlhQ zJ)fsv`Fzs74Z0KBcaK4LV8lnIAhF*WwcKkTAUhBK+~8J&8$s1mouP}Zc=EA}TJiMVPrq#ZhmHSigCT>5@uT<-fl61G z!RGs&?Ik8XHP}@d`xy4?O}|x&T_v=i?y(x?<&3|m!2(eHk!$E|gP8_*6GrdVIS65~ z!HB`WRi1r^!8U^}21^V^4Hg<~T{wKAEXAa+MrB)Edd^Pmery+6-j7*xKp4PA4O=U#nJGJS>E z&b+y50C+Z~WI;?j_jO z8vA^M95nL!i)CK?T?XR@8$rFl4!i-(2URXdW1jzh@J8tMpxk#a^~NL325Z0o>8>(Z zY%pRl&tTtYz4vq*%mU{Sp99_owlDGC+i0*HRJ=zkJsdRHY;dE&SY=XPw10;7WzntB z6&BqHU2f5}(CNf;5$_+q%Ns`(7|a6C!#=Ut%Xb%e4Rpz0dU}6Zg_o{EQ2w$&#nb;6 zo_#lX0d(1Cl6qMI?<=+FwmZ>dpqq_c+X!6@-D2oE=&0qt3c8RuZXwU^Z9=Dt}?{ zTJZ29@4fLw$#~Xd*9N_V3g-7h7W7ujeee$I!-OjamEL?X4DP^9{dy}n4{QW)1vUAg z{2jjC^S|@<$K>xGTH-n%6Ap0eB>ES2bGVb_)&RulqKV7z^>h*i=pEdee~0g z6T?pYnF}hOJ)cg7Z^wN-M(M8tmHu*2>F=W~6t3g8r2l;E+M)gNP&RZN+UJWb=r)T! zN<>?seLgt?-D$4HC#W!PZMU@542DF&y51)%CH*VtWExMG53b9kW4TB#CGr zpgS<)gBlQ4ShD9v<`WD*$LY}epnd*tfbNw$@8mvXjMCqmbTwkv3Eczod!Yf`0qv)=7P`Ztv!M$y zyk{}($pV#G4Cw` z3BDxqNoRYy$N352N>Yf{27A z?V$4C3aVaWpvobqDEXcsc4OBkX?;Jq*`jNpgBZoza-E0kLFKFTI>z}Q!G-i^V^;!Q z28zxCi=qAe1fiqQhm8M2h0Oa|?j6uaFnZ4*i1#IXL8ZS3RQg*%rJwJ&j>j9quEwHk zKY@I;=%X~`bo{`+OL}k<=3uA#$TCP{ACGVN8s>vAdLQ5B9Jg64&}Sakcx;4h1=h0e3+2=rl$-q&KV_sV2^?N`!XExH(Fm97$SJ+xnb zh0t}-?Z$r?y4G^K%fnAG5w_OT6W~29;0;O_sPvVB^T0gIe-OLja5DbB zFmlnN%b=SqdiWyR7bc+kywGd+Q7}rpyQdp_kmN5}KOMPm(pijM9rQ+!-wRQ&7TV8O z1iA*g+^=WoYRi2vhkgP(y*CR~zJj3g)rXt%wIe4PUnO=O7Tx<%_#L}X;xB42&tMjA zs)zKCCc}?Sqo0|Uq`RO?FnWLSM;r$cyQC-EqdP%2zo6o&HCSP=*kB}^e$M1i1BQI) z22kZOjDnd5?U%D&maoN7RoQ=xIsx*++U zqvzAkF>>E+uzx+e3LUZN9O#m)WcW}Paa(jJ3VGC`8=;FVx&pe; zqQlSy7Jc{}`f1`+{*IpI^*`%D^*d4U(_lKN{-|QA=dWpshq}j6^%MnFPlX0^r_kO^ zIUmlXoT2@C=!edMF2S$rX)kn^kuEV>T56{GT~HppF5 z)#T@gn0;(*HEp>&fUgS!oO8f-JzWU$U)%wVa(LW8*mLk5pd^x_{jxW{0(!5s#- z8f-LJYp~K_iNW0yymDC&D*Z=;9u9)yyRIO5q&Yv?h}}-;U7+fx0qlhK`K}gv2lOH1 zzXH0$a?gUU!stC^p!lyC6#o@~#VSWo<;{IjffsytfU!8Eghil}>OW7y}uqERl%41f=OXgj;f7n%loSH_Ts_7Q~KY zp-$pi*N^+WbDP_rxzAl+cbvP7pTo~N&gaZ?jOP~f-1)rY{H=K|{gyuL=*3z5vW`f3 z&pDmkkYb+m{{~%>cj~=kFY#XUT=+Wuih0f=1Gkyyb~0IQp8LP-INSy5h8=D~S2fQK z*z4Raf9~&x*7L8e=Ofnh6zbadf3fv^o%MW&^?aZ8{3YwT)q2($Wk1fhtmj`^&xft& zN${BOKgW7DcYU~dnQPgXS{G)SV|49WDJPDa7E$7>tNfxfPB+yoyYB=m;-6Pnw|2$d4~(bOc*do8 z87o~LBP1eoaj_{QZ!G9HEAshk*REMhaoh+Gn@C-1 z^}Q?ZJCQD{Tbr~xo-uBHVBLy)ES_3a2Y=juk_A!Uw4!EtGGR*>S1sdqzNKJ|hpU&} zFLL!41WWH(ziesk18bKr{rrlxbq_47kzLh_&#zl?wVv)>vA*iwsukw(fz|8AshTy* z>Z(>$-RnAucwn_p-oK`*$_vhuiD+G2)$+A#YvucaWb_Xtz3S2R3)if^%2|330%Hvo zbk7=ij(@yp`Fg_hkH>p!AE?9rUbp$zthsM?#94a33331O`|n}@$x=|&?>?-P@Tgt$ z1^(4lQ*c#F?}ugAFRfa>u5RrbJRfhpV)Y97Kh7(uS#_e_1FKJAyNnibnuuMeQ$+6C zlg7F)){MXQk#O=iyI}TpSASymb%oc3!`DP+pu`#=AQEAL;nj&A*9YZSHn`|*E{{7GXDhO@4& zgL>kOp(8qBkFM79DSnZC%nhL{?fHe{Ftl`y;?ZyEQlvwj_w3Q-^D8u1vVJ{%UU)b9 zUo-Yp0)nnSnx(51)zYQM$D#1O@blq4?tUyMG>dx=74o<^YF3cjWi_*} zHE|vvNBkW(o9iwsVBge#1V&YhD;>hqL z08cK{}{S!qb)zZ3JKI|^E1t{V7MadckJoxJ0|y@#bs z?_RggvO6&j@pjm_lbrFN%EbREFZ7hFll1Xww6d;tyPlE9uhD!B#`}mzB9WyplzS`y{`AaH`le< z*_nIpxewp_d;EOw!H+wNK+7OP6V4UmLw|4Kq4XJnW3rnabn;*PIdDI%((Rwp&SEMWD#bf2$zrt_mcI$=MK(3Br72j<^j!{S zu{th2Yt(mTGws_F`8KQH%p&4y_I*N^nYk}LYu9&8W=nAfWpO-SE#sELa&vG&_j`9V zbu%G!@8hm)5#>1+ny4)3UX`6A;yjxxKI+1z)Y2a$NB0yf&Tz%zV=lI7Prk{!pu0Gm z%4j7xiL+GaFARmVxdyxoHYRkm_7Jy!il4`uP56EIobrUt*RA*C9Y5mhfn&z5fVsD+ z`*DvaXudA`n_YO=qm|<9IA^CaWo?lX>aTl0{k2*PKXCo7wqI*CsMl}*biH3^KNM(E zf6YHv8BC_y)n9pM*ZYNmYc~(O_I+H78Mu~xZo9v@@b$n6^}furRcLjA}I^+&?H#^6oh`*3Grb$~N|=14D4EmYxdhabFvnp=Cy%c zrz}A;7nYj>x%j=HyWIRn;ABy>y>A3QFuf7*W>xt|7FPL3;A7rf09G0VYxa0z+vc^hi%(p zaENR@+@qN+wBPfwlG22kP5M}|cv<>q{<}>JLtGrbvsi{Z*pM67tM4A)!6)6_ve`c( zwPP6!!dTqfPyytTb>fMNU0B`gQcZoqEa_xzG=Azsh ze+=#w3aPG*e!V?CSUF{lKi0m+ufJ=vpUv5f_l@R;N3C#%lNSiV-4o=%vBFBUfkvB~ z^+?`~)=&V5sEnn7okd3jUuKOAnC+ZBvkkq%y`$`#{mH3Zc+^d=2aeAnS~Hy6j25K@ zM^5tugw!W+HQu`!vjS>WufNlX>+1RRVDdC>4t7e(cmj2)&ztd-Gknp#-zYlrbS=xu zazz%>U*a0>QG|7&qh)2KB4!|TN?}`W_!=`8`P1H!_027{hXj$^?_Cy_U-MhUqvvZK zVfp2wtkH9bmsW~Kw^J%~S4uVRccu9~IjcZ9{3*AtbhXP=GPks-WTfONtu8SS(23`$ z=MY!6QfyiVecBq9tNinOSb3D*2IQQQgeJc|gboT!z|_8TMPMf{EEI$mU1rWVd_ zZ?%Yqkn6HVr17NjF@*kcn9fyQq5l)WS37^eCgS;Vmt22SnCEYbU|!f!&Ckt?Kf6c~ zOu`&)dc9L_-P6UfKPo_RTo;y=PO7aI;~X7_1koc-l}2I(jc93LVfpJ$L7d-nyqLz$ z3Vxj<}TTaGdxt7TqP%d9dy>ax47BCnQOi&72wTAPVyQ(cd!<+%MC zXKzuK*6KnWYwF^47V#v?YIDEuZLLk!4T)l@m@LL%{C0Hi{>C$nS;l0t?*H+8cAg8x zk4(8X-JU(`MrXmTciiQ^fBC9Me_2&i%gRKXh&F`E`zyWLu`yJA>G6T%cdk4VIp#x8 zh06zyA6LpE$EMKn-lYTOZw>!fN&|M-7Q8N{E~z$BlLVSdtl#D;|BFU)M}EHsLqq0X|I~KkU4AS+=_3cH;h2lXm>PTGtza z=53H8>ri$dTG>n-Y(k41_y7{=_gT2wOf^3ku$YFM*FL8VL_Hq6JSOvHN{JsKpo_6K{#c~`(blZ1+F-PmIcmW#gu}e zA;B4beF_WiOn?-$qZb%6uW3o3KKdME1D<1ST^RahG3i++l^dA>x)y0zVXt8_>uv`{ z3!#|QY0f*#ezEB&dp~Ht{yoU3wBVCBHHFP9b312@OAGFrpbKX&hzrk35Q00WfD14B zz>AU#spPz1`V^FWHn(%uxO7MZKHPwkvlH~eT~iK+cYdErAM&Ea^|&J`w=;Phw4dy_ za880HST#WiT2QL$$Gg1m;c2LEA=Rao%!!noonQ>ly2l7l@Pi>$#BxuxFRPV+b_=?NI@1!40P zDoJ-)f|V$_^B&x_fJYnYuG_pc1Gsv90cM~em^Q_M*`@Zn!dqT-hPSL(65Q)|1$Uob z8r+Q;+WYE*!TJ@q;<=b9e|-4O1beW4f)T$H!>`|DK%c%xqj){lirP@4g+_8E)f%bs zY1DGs72M-r8r<`AL3q{zJYfZDeU)mpp*91)h49-q>4Uoy4B= z9-7~C+6g(pW#9K0B;PNKL-)Sh^TBO#(9OEtIbwr*u9#g?UP4-WLx};>a5_h&A9J06 z%(_Ekm`sq88HWSUPER zBv~;6X>q;$h4CG_q!}$)E1>b%bv}O7W`9!IX1^I47rz=>o6#A*CgC|hv*(4)(6G2v zXLt-Wy-{~T>zMu-W)HwW6A5n5d8T4-aS zOpePDW^prCX9l$)@Se-VwV|-=y3Dq0|60?1EGxotc6(I*WgRBnr&VFK zE5*ZIhNS#YehvP!-p|1<(CnL=+iLx>skH9l30^rk#DcE0*W&DIYdVD?@B< zs?*kLEKPMzBD=x9-ESj(&Q9EN{4o1w^rw@&MWwjIIm(&Ty4`=R9kvL{9xtS_BhSKG z82FWVc7FG3sw9tEXqt`TIm*(EC7hR~HM^V8dJkG$BUQSarQ>d6DPK}p>MVJ(w4x*| zzjb+j_wr#iXga>!Sku7O-fiL6Cqvg*c7Ba`q>CjzpspfQ*L;;zn<5U7Brd!WyC{ z(ON3PH`&AUyZP04n!2t`u#tORC}B6jLN>w@rqYX{56@FRQUnk5H0V*&&g(_&b(?fb zS?r!9LAMl6DURa!7S7v-Gd59xCVSm^QAuaq3|HR8GcM7ho*R9Be)ss?yGVECPAM+- zm@7`A^>=gxo#D4V=6X-!>P{=HzCm4iJ=sG?A!=Fc$g)ldsXDhOVok3cu%>T~TFQ`X z=HC2IwGxAt^v%)hDqVg5TbA_HVU+egvZMfFONSN-=k9vlFBuM&N;K?IkeX+GfAH z6#Ie%yR-o|3cugIyk9O0)UU9Xfs;%s#t8Sz(B)QeGp%J$JA2Ob%RdPar*3fc%RhSO zyLiX)w%Sjw<}+ax@DIXzc9n;zh1TFRbqUH}i9 z&4#XCjfXX;b}V?9>erjE`t|zdxB%$^H%Ih<8&wYVue^FuEkNU3k50lfV%(}n&&GbC zaxQ&W3X6$wu556wbfu_gPx)|9-U)D>{VLZ{J$ai+(iiotEg#zAD8Gq%@>pZ-p;w{x zaQg@BkV-+^?*9>181Wa)+xCMta0`{U%=i@kvtg1c3-2OcQ1*J@J(ZiJtNbI%H74L% zK2eZ*Z49olfjX0LE!P-*g5gt5!tywyNm%pQq8>=GAznz0tEk#q<&D?9B$5}6+5Z3; z9QELVzu?W)lWX9+dJ7z4E9|MQ1*PtiO@6%*HqP$qfYHdn3CKbqi-c&lU!LJNhQ}qq zTNU-j-9WYyStxj0x-mr@A@<8@C^Pn7l))RX)iP#3-B;9OR$EQ?lXQkp%{b`u*#0#T z(Z`K{od}mUd}xm6!Dq^yI6g7RXR-(QjK30gVRKc(Ull`rvZ&j*Rk05E*ZSp8I*Hr7 z;n8iYGyF!>)pDD_Z*XRt=XU{N-&rNud{!9`FdR3@*yZB+4W6JGHslYtVx$|l`|mgl zf9s!3zAA$)n1WXz%oSsL`JQP`V+%C1)Wc!!n&?ct*$5kfbgSu=&dxKAuo*lj$-dQ} znCb{VnXm-fdTux;Vtr}jTH{YL_RH0sG#*<)M_a*h-GLetUx!f{R}$pGnX0dI2e|~y zzsg-sHX0$_!P77=sU~pj1P+!=4K(zvejfKT<2rcwoWb}6M|gb#EI)Aa7u0#Yy)!#Y zo5y1HdDLg>J4>S(K%Xv*bza+8hd#k>o=}Z%F2~R%80el#XW)!(tKixM-`VQ@+?I8D zLd#jjlD!x+{y zOx(A)%y9XCHJ9-mqUgX%F>2jso}<+srLQ#b1#ZAYKW><=l8kHoz_o;7*Yw0|2CkXF zFSZWR6QX{1&r#BPhiW3Eg+yDjr;)S?(v54uBS@z&>M2O;3wttXE>OP_`n`U&YXr22 zoGH$5L&A};CDsGJU{Gg=sEB;yo~R5+sEn*u?yA+w`DktK^Kjd?LO0+xKyJn%0YCJd z2h`dGdp8M`>um~}E!+KzjF{Jnr$n}ivu<&W@8FVdhmOGJpw+4;cz(-PXi?0HwN<8r z)^j+l`#x4|KDe-Y{q4rHip$JRYsP=9ojI=u-c>p94QIE)_KouD6OA=~b74ckRd!an z(IG%DXjX1SYi2v@^X8nxv&u;B&ukHVAN_2(`GD`fBoprE3kCSB*97!)bU}Vf4cb9t zfIA({ilg>gFX~dCd?UC|v<=sIabIdKd?!M9#)OK1F4Y<27O-IYb+|vZ*`Jg}8Vis0 z2}@4%pHCNNkv8#DvgIaj%%k7B_SYc%k9lwH|Yq=aUyf^Qn@p^J{K;eqtXsDTK0qa4i2gnm z#z5C;8^uMP*WKX6-6c~bMJ2R-=D*i8HDFO&+%G?@wyG&mZJHdg zoWh&IqdnM1vLF=cQ7FK&(93N(fj_P5m+y-7WUQ$R=WXZ>o%aN4eEstLb^(!9^vQ(z zZ^yNDT5kl%mK3xqR`n282A~i1{#&+v?2kFS*{?r4IS_ZM zU(RTk1Ae=)i!;KnXFBKCuUmrN3tQd`RA;RW#G(JEjgJPNTKAbh>y$h2jOhh`jB#n; zl_F>udmjz#UbfwDZ7ak0xC2j{9trF!`pkdbvWGE-O9HzwPMW6`G;b7Uk#_-{2{M&a zc}!oJWsn5`?^{9zMskfS#^;IA)zNBOlNat``}sP4QN-~IBIA3giZ12IR>-jaQE+v72ZJ8pT}f2dx5ui@rMT(9YT^2jkorob(Oy2#aZ zTH6paK&PKTBQeYtR4Og0h*7DOgZ97O|9j|mhl@TDiBd81H5yf(1eHF$4-}iVDsVE3 z=9VZlE%3CjJaE$Vqt0uKaQz7Uq+QVe&*y?lE#fp#h`}_w1W7Z!7 ziJ;OMpsweaEdhm|2&@AI?k1`=Qe+O)2VWD9xjsMg^Jbi4td_h(2;C-+ONo;p$x4{eIdp}rR3v*r>NEFaU6PUFlGi4Pd)9|m!0+-ve4VTwm`x< z%s>Y$n{8L*M)eslK+@4lIEOM#fpw-7{}KV8y4st+k&4y-F?DbJ6rDb+??cyVMop+Bz3J+Ko0eh?O&73Wr6kpqFKh9+2} z79eywU^`M&3v#Js!Ch^EhI`5TbVdG4ps4#b7o^TrIFF+48uM`LkZ>Lk4eD(w&ZDSX zGHVixqKZTPz#7gYbSPfb?canI71ftqZ50~s!btRf)29{TCx-zuL-KB3V4fw>&e43yo65)d4XrErS-$ST&8nH-FqYQR%^XxABK*S*Yk7f znA=*)m6Vj`!unIaC3uEbkD+xF_(H9pFN|8I6I)0>WL=J!9TLl^aEqPU%jmAYutm?p zwFCCz75Ul#?u9JKF>$j%*~ZGL(J0ePot*KD)}rn)!`h^I2*|G&<*BNE*pMu88@COy z=CJ<#q#xEoKU~=RD*a3-SZEmh88 zjFv;!BR`$y$M-I~BG32Ni@jd8#x~5FYKA>9=dY0d9Fp2m*!c+#Kef{s-1R4*7^0~iwC1Co7Q#q1kF-Qrd4fU zD%GEhTd}a3hj}?O_ zUzrjAs;}DkF(D^1#TE~|3e3uU6{dV;@_52L6pMG_j_k=-4|AVzsC~hDqGS zyTz$uD(rEru2gs#6S!I2OaWVQ<>g&CGxY$iJJ?raJis-97I|)3ZXWqBRVq32W7N4= zTm=62lg^{kkMT6LZ!)#%$7oZoYrXRb%FG^L?Hu3eldi~X!vpa=U^d9R-XZq4ble8&~=@KJ+mA`$2J?r1y9$NZ1ws8t}c4_P@2h(&`dko_93D7)`MiP7xuiRde}ij+@r1% zRU(+V9Fa+L#azwi=a_^ecxc1ZzHvX~c648_(#Q$W`w91BBqN4I0I>$t`l9ip@1_yL zbtMDWCH;~pLqPNo^52KIcEE&4e)Q-)U zn60O^u;;%lL+6#V7`%l&g~QHi@`Z1}pp?2{+hvt@NSP9ziRbe6r$MJrEz1B6JqBHxZ<^4R|A$Qyw_0+r%5p*?%)`Ds9; zG^55T(1x38s75!?C}eBWlds6L)H|g?M2d1PfM{ozEDSQ{+;DZ~PwZhGjCn5R`-$Mv74Bh^nl11X|@oj99C8Od_ z>j49;!FG$ZQ{rZ(&)V#~*oI9f8n4Jrh;N6EwkU?$!(Cj`n~ml9&Qglg!n+W^Y#?ab;v zU~%u1N=kX(ah~}q5l?OHua`J0mt7Bff)_;9e<~XU5oc}V(blOV*0Rwe5%wtePMhnv zgt46itqfi`j#d*soXYvb^1J=%Gr3vy(pu-2Z9I4j(RO76deVfp$32y2Jx4J_XXmJ_ zxt$|!V5qZ7r1p&pn74u=?IVD^goI+CP^?jAPsWp|_IV<$*08*>pHK@0BH!Sl@rl%$ za*nCJJg2ZGx*OU?EqSO(A{~>)x^txu6lV5Fgy*CCsjdQ%d_7V5%ItTywBRl_u?6}Y z$|PCr#Khnz zZmpl2vlgSV7G4Hw>!~)*i|=XhiEHSI>E!3QSM|7U$lKeh$;>EjE$Vw-9Fb335HDv_ z3nIQ}$AIto-{IDCmEwpGnsgw-u4h|Gx{(evh+e;f_R(i-@6@OE#JhFc#2E;|yRQFdV03#Bb1eNN6d47;xLaicc+ zO+YqrWx%Z`L~-k6y&>9lPhLk@4l!{v?YIB|BVU0fy^%{V8&qmvf&}0bjZnCfO z8-d6gz`G*~k_?M)=b*1XXH{NWD`EA4Xjd&epwe+Xh#eqqqX)Wuv>g~cJCN=6I6}7D zSy~iJz@IH$k$E3WBRlX8c>C|%f7<|({ffK;NOo=QYDo4g^8J^h4!z*9*HJ`Cs4yqS$rLm&+M}U%xD;2MW6#EJ|;K^fVCj0SzUKm7?Xb zYLWEhxR%0hHF7YZ=j$ylZ#JkZ3_FlbovHFnvIYtHr)u}zOJ}0~NHoqe5c4;~z#==* z(S5=3ZqLl}Y&%7?c7+uV@G3U)c- zbD#N>vdF7JdJ-h6abAy0Mf)oON7icBNU15}vGzbp&x41>Jg~M@)XV@*a?qa9)|O^J z5PI;k{0sOw77arWI)EOGZxSYatR#C`2Cb$+^x$Z$C6<~DS$!$Y#gZ>Zz%e9ZD*0!k z=O~MZXp9FXCW8_=BTIv{1PwCaU`9YAvw&(xBxpVg3%)hYf}drW@r?6i1Shz!G#8rF zG9`)=Bn!rYGugCw&RU20!!1XJLGK8lNb(fX0usPXc8dTuWM_f#~c>Oh`y*0g! zBfL4dE1hQ(Nr#JSaMw1@my+F7f}1Yn+?&^Zh66Uu0LmCStVT`xkPh6SVoLe_RQFia zXwf+&mWDdxE1lPU7~jiZ6sB`I$sKV?J(#^_RSHjl)yJe`?bxhIGGHEF&mn4KWvg8U z!#oTPN9Xly{SLX^faemOJ^pWe4}@QyZy6)TW7J;t@gh*SDv#p3ZbVL^nVYC`AwGh; zk9^t(USTzHG5roBxW0%$cpp7d^ic8if$2TZ7LmX{%#R zYDpL47mJUSjYV9(A)gmbK>REgQ!!qnePnz7!JlBVEkl;UEr~7dWz6Sbgwt#Bv#g7O zFA$?N_rBdo<6^tlCdP%9^|381?aqUQaA)b5f76ddBhYA>Wu{(sfy*Yu3ata;cSQT8 zDnhZ^nb`UU7F_-22ZxN01s{fL_F{KihITqdt^uzQ`01jyMV?-;1Z-Q9} zIXL$0hv*^S6|cL9=u4^?3M^5}gs`d-%|rQxwyMbg*l?62tGDb)aI*i?!EDwR?~;$( zxhD<20pqI(ao`Q&tECiiOBnzADIyJhRJavWtRhw)`9yO{l3UYtwy}H zw_nq$uiGiMDXcgi-c=^Gw8uVaL-rQ@u^VEyj!Y5i96nBGMHbc_VpBfV%*|Ts{IpGb zIxJ@X>K2^C^O(&MN<@oWc+TeOj;q+F*ucF7L?8CxEHc2M32kpbfgbR6lf-(TQCN&D zFBW>~Qk!C6AH!!gBK2OMchwW;^?1R<+l^=$GVAEh%kr9b`bIg+GO0~TF(1VEeA<{L zrMfQ5KWjgw#4|!VJY2zzB~(wbV1! zlhM?@*TX|k?^s2i3>v$)d^+6{Up$|Zy2QsBaMmbbUT6&M#lJWxNi;uMtsl4RCJJ?) zlmeelDDDvSBQdVIioHn>uO2<6rxaR$Q$xNDG?~^;b<-1*ys_I##3f>#v$~P9zV7?S zukf8_XvF%p8ya=M1d|orocCUv8<7x!-KKI?9yt?D-8vdcv*6n6W5$?G2GrYgp4o0w zXP?^5*=*|k>$X0b8oM4(Ag-)=Mp-nNX=?GS?cj<$2sa-a|Qzv9<}He0CoG z>FD`K|9*3leRdt&PnMQ*mv%gv^HxmioD#r#?@>)Fh<_3oT<7J9@J`Z~6b zc+>wmAM=9f6x3R&be?OM&%lTth)SFy=c2J+dOl_gH|wqz-`DgM-uA<`*lqkIl(6Ws zbiOm?X|A1JJde@RE$nc!lhAP-F*yofYE?$Lx#?@1O4|>-bf;jO-4?qQ_qkiH;eEB` zEX`x4XjQyp;&pgt_Jzf{N?JCTQ|U}UdqI7c!FC;_40M}YkVVC5p#r? z!w})nQN39Z=XHOe%JOPteSV~TFj6lSjAx-rz=QgbczIH3i=#zgDR5@VA zAP0m+V;kR+*O~GYU@GA2Z>k|VGvVyK?&e{#KiTsB*Ymo6KkQtF1%k z+5b{0K#SK7yOwBq>Fb3TXO;KKKL}FHeb%Z$rtIhe!RnC;Zdrr03J#?MYuX zulwyg?T&>#_x|jI?zWhpyJ9*FF_08-qvu|7kw3vsG2YsS{ATbPdP;>ys3VRW+hPd` zOsJ(16Vm#C|0m6%SrLG+YvK7l(D#kzY{8lWJ%x0CBlI#*zn*k|V?;M=R&2#*k*gv! z8bTa*tc&Lg<}zzDu(32hM*_|50`_~L?Cd}Eqy50@;q4Qp)%o+ff23jrnw5|E$6U~o zJr0!rO<-R4n1|7J;c-_QsA{IT*(sa_Lf-t~c$O)*?sXbk1uF-t>yw`jxZPHXn`U)8 zlu7k;cU6G6spf^D=X~{5G0r}QIy=~Knf=$V&SJ#-mK7O<2ZF+wD|Z8Rf2zr}kX%H)(F_QusdT z2OlXdX}`lg6VI`2Zi#wkZ(&xfe?R1O4ELI|+x&B8`NaXdcu$_K5x-WsrD>S86nHZB zG)_q+ZNet{KYa`JNnOk^!t3cUu~LU&F56;ibz|&x{z{;IV!Vq)C+@q9*!ozNAD&Rm z^(T~MPgv%!6!d&rJhbOTeWbxBpuhTvd;lMqgU{@4=(a=o^{*RRln*wruK7ZZ7_(7d zI}g#13fk&6LjY#*4#m>&u_AcRn8Is`IUg<;D)-^I4@d%EoiANN@Q}%qK1J8T8y((Wm3}FFxQEHq6T#kJ=2y zjB;59{9x24eO(K9k17KuF`FQ!G;-;r2TvB+qC@|~S*y|9I=rRSIU4B$K({-3L3TL_H8Qu~*Q3H`b%A(v8|gFJJ!4<$5W+FV!$Ha9=j+~P*8TXoy|*He z4K46B)PP=RVny@t^{9HpojQGRDi#v=gwq@2~x|gh)J~EO*86c*!PxTmieyYTO|hsIM@Z5%#{-2Po%Lj2o^c|OqA6i!Yeobda> zlxnd;aS*s!HPWxJUE`6aBKMYetposVs6lL6EYAI;)6RDu%!wsj=*D2`hJq&$whG$LS!#U{tjtTRFRTvjmNd29Q-_G!y zo8p7JR^$bLgMLp#9{rfp`tYa~8~l1;{3hPS_lfXNmD@S++W|LZ-3<}6uksEHfDL4H3>D=&}_vD6ukKHpSPH}~2-y{SlEg(v9 zglA8L&m&BNX#{~h`6 zSg7VQvQVrR5nAST%=&JR_n};%mj3Bxp>c{uY=HiE49BD21mZ-^W1&5uERq>WkT_TZ zl)Iu6=i=VGP>QEg13Jg>{1uOAumA&e{1YX<_@zGba6zV9=|AZX(Era@ih||4uKXY5 z`D4zf+z;!|{1f1QfU3{+{Ya#ElLu1!6F-*)eEqRE0_+G7>H8!@L!fAzqtlE$7l-NQ zfV-QTLv})7zv#Qbe*l)pN+w$htXT@^Ld>aZ-%tGk<0l z;m1pX|G5#lCtr2)rVq}r^luJ)=AUHBsJ~OZO{CpxK7^N3CmM3N8MrG8o=_F-zo*eK z{T1gKKPy@l*o7GM7p5J+0G~fIS;T%eEKN+D$Vs=B-ibY!$eQ8KFNeqY+nE}qJM_xk z7~MhK59~qp@1dKFfBjAQxmyZ8U!D9~{^z%7XImWFx3@9!eG8LV=%+^p=cl4!wCDkr zE?z`Tt)Ze6`cA$czbjxfRp%p8sp9rNR5-Pzw~5n9PBgI4qCP4$O?2Xp+I;vK#L6Mi9J^|8K9(4iVr{+G@msnJUt3@m+u~Nt~tB8cH%O zK43vFEa(L%T1xLNT|BpvImBN1wqU(#k@d<8g3MB1asvA9OK#1x5#6;bH7FC!X(umrDrB!EcpGoW2wY~u%*=IvY>X>m@BO* zbzqEWRLM@|#FNsz(kC&lZ@t4xH9d;vBL>>44M@+ar5c5BqEx!uIlFO&=#Gr<&VWs_ zs&qkPN;GRmeY|Kct#-Z$U+)N5jgC}vNo}dYRa-(j8dpjlAOoyCJlV>XRG@@bzN&GA zTA~TGQ$K>}23iBGcMd)^PRv!zS^NZwDG>&(Jinm(@&8yx%fTBur-koCd0IjD&8k)s zol`gFC8e=f{y~6dh>m6eH$byT>!$*9fxX+Be&fSlxDyZh9MG~uN4Fpr4zFS&#k1H% zijY(Mig1ccg1^h9_#nPZ{=4OGO0t8_&@VR0Fk_5JQ5<0=&|3EU@-*C*?yh8LE{JHa z#(sf&<-ohCa^MfhPI&Oau889V*y{Dc-ta(Xbs?QRzW+B>*45njypM#Ws zBS6_q$L1jKq7Wzo;W@K-j!3FL6}iCrRj32Mo2j1V0ne#N z16AvO8Q8n;(Lm+8)ySyPvT61%+jEBUqJRJt3^{IO-n=#(>GmJ=#l_#%LdXHqWN5So$0}1DD~f#H*fO`2Mv0|Q60aRA z%v^wx89=oG;ifb4K0%KbmubD@R^$2{j1Wfac=H>9ckN|p=aRtWeHhofko|K|}6#)eim zd4vY~qwPsU_Qe|P)e5b=f%3``f#1k&Se!TdLwM3~CIx;wqM%WJgJs{hBww!_>n9H) zBHfI3vte7d`h}VuK0Kt)6yj^i-b!H$J5KS;+ADW;QaMC_G8X}}dXh#k9-lZGMWVLZ z2e~aQ9l648&i5oo^^I%ML+`u?u}J8AM$}~?orAjx(yB<;CFmHm5sae!VaTlGYMjo% z`&&8RI*yxBbQp5V7^P7E4HEpH;3dKNirG}s^b_UAjc@e-DTkXtp?vZ!{1!| zmM8PxIK#FWQn72zfajNTg<`~!q1Ytde)8m?-KGx>e_;>0_qtP&zn5+DxqIbbsunk4 zBc1@2NRbuz)6NGa!Ls{1M)3a5-ysSlmGsIbouQ)+sXU)3ZA)Gi_A4pR2L=ccARfqc z*u1sacd@o0pRldb-vWMXR}44;y+Y3+?5Ge*ylk=b!v4?i8NVpXfq3kHYnbL#%;UHL z{-fpiM)cHpLCo(-RJ}ApmkW_>_{EWF2CO#Yt%!{63(F5*7P@Ts6_!_CUM$l0vdf&s za=#f*W1SAG9vpgdUw%(VIax}(-S8DcyGl^p_mebY@u8#nJuMGu{LtVgo?Uw=zvu08 zZog((F^RVsS~y!mE7kZ{pMd&qyp-srUoH4`Sw3_r7Q3q24%3?)g0Bo!AIoYOVX4BHq?Bfm!#D$pZh`53dui=egoV4fZEHjd@sJ^@%z8AGbYI! z?tZlR!SZ|$=1G%F?g#n(oYV}TTW$WYk@HV_UKGtQoLh@Cw3DCC3*9~`ik_*L$PZ8Z zyM*O;-l8^!Amf-_huneCQ9Ln*+E#M;qxO`E%?GGfAnFEk1X9nY07Yxl+o9vM!2gi~ z|3^K}xR5PDaw{w^KSCv3oIP?~^WM-s^%C8M{YCrS4`Q!PX)E5I9pd%lSTsYGD^5v0Q^n*r_+aH#H78%2b z)G;JY1hj&j6Dq^)cLQ1VM~pW-WT?BAdd;*lw@3EoBy14zr;3IRqFyAgOtn~1S`5vI z)jF`+=nUo3K~4aDGp!W#L87G>zgleey)R4tYC-$`8w5 zhE;ieRbD;xn4=Y9ic}A@IabM*rfn&=cc?uy_YL-CLzaQDB z#8a09>Q!DzUIOGjb1iLu#M8+7hmLe)P(FrkCjRkV?OSkF`!a^M&pyyT<9E03tGr!4 zy@1xCH+&iV1BJZr54@~u2ge~3PwXW*h)i?jI`3JxB(OJ&GSjK$l+S#BpuR}!0p-qq zkwv|zz*rEcKXP?XRJo}219XQyE}jy{s@@#P!ylX!x%FJ;ZEaQRnHEHiPhzaIV3AiP za+9xbYsS8@zY)2~pTHjEkh5(b$1+BXC9v|EvI;xR$OvyLit#1tfYvi|vxJ$DV)orm zv>kH%I7VD+Kgn^lc^R}46<0?i{*ShJ-@16Nl>8!#T&J$#e51ta$p&9WfzYS>hURdG zPjBS5z;9Jwok#Qe`!-gGoOusn{k4cC>3wSVSm@^MKySf!JdU`LGh92x8LW@&_km0W zSnOeW_dASv@`uvv6aD$!Tg!XeU)9Cgj+qTIJYHde^snRDT}u zzkGLl3f1-q)Ba713Tf4V!V1wUP@d+AL+e0MyV1f?H)*YWw}xhs^vhK^Pr4RSTEuH) zALcdsm#vO^jgAhqdeF|rn!0IR)T?OVZs4-!x2awu%pZ+oqzCYtK4T)UP@$S zZw<{6{59X%_Ij59e@&fAbwoSw3{W0;rkah|B<6R2nN2!ORJKD`X^IPRv>$Vv8R)W4 zm8DHZqFbdmlBfCIOH}QHO?=eF?*|9xbGsjRK=YV5v6#E)b~k*(R>rhSmP^?GKL*-I zf){6grJXs3Gl(a9>FN<2bI?uJ_q_z|i8!}zaBdy-Xyc@(s3Sk6A|i;unW^+4yw0qf1-*jQ7A{REKfGY5N} zPoTY@s{C=tR?;}UzEHCTI@hcp$&AM z>V+4gu9tdmS?oAWIsl$HpaUeG`>36pxYe25qJy*@A;yLZm5&ffx?@^wC{L-EV!b7j z7ddxA%k1pf(2a^0ZvcQM8`+zmH6A(o*S~%Cc-u`^>r9vAYX^J<`Q3lXrg@9nmG!FU zY@Sq|?;SXz)!(4%#gmut`}g3z44?L$haFA&01LhKHdhjs?*aeWj%+aEJMTyM4tCqc zs*UoT`RZOaxhR|eFU!V6_|q)4EbZ_)4P|Q&ZO1+hz$cC)onmMv`z86X`ZfTINV+&O z(u|sRF{UwP)wEmaj_r7UH20GxNq;?o9H}Ur`ojhAE@U^udZm|fMyL(KVE|W zIZYD7E=S=U7WyCH_X#0Rx{Wqx-2QLRTHHCWq?6jDx_p`|YL7njU}OE@xrwyTns$Ck z)E5~ud|yWBS5`}zhpX>HoNbsN@{;Tb!1EQfTH1T9m)nXn8`4C6o3O>?<+kD1#)%^KL)#*tEWeG8u-`!F zP59xpdTH(^7nAnQ_j8sQ;ByIMNx68u7;P8lVirSYuG+4cxTH~IE%vE50(2XeyD!c1 z(s$@mzx?TyY;OzJa9ZzV`nGa=@qP%a$;+(xwVvBYakdyQ_cHy`Vs0$7;SyIj+DmOP zFCKxrXfA3&-L_!hQa+!iPY;edjXu$k0eUDXRMoG`^NW=`u6wf7 zplYLoe)I~&ar3(;lYfc{U9*vS%}+99gWqxnaMynM7gvO?J7Deg%a2~+Ebz5Iz|z1` zCzd==QnI7ub9f=E5o3G`=$u~`m)u*}DuRsS^8wA-h=P zt5~s_dm48<@FsNgJ&h-nD#qFQwl5w4^42@@FlV8g?@~E zZL7?5^o3#tbyI&eorU;-wSFqt6g~dfmf#qiW}0l7H1n@n@p z6Iq$5#~x@=ocOaj6(GBX^ke7k>W^iB0;V)Lps=er{&HBtPQ2?|}x$NV1Eu zlJ@t@?U%1Evs5iZ>zJs9Lz%zA-#|1SmcMA^9)Lx;624_gx6*WA!!mRJZTM*89E~rM zos5tAt|ENrib!nyA6;0ZxZ`fQxF_8|&~?;HAf#N2X$I|C&d z^`Y;fw08Y-MB1>QF`hnjRruOZC=b_Ddr&_#Ax$^XhrW*J24sT^qNeo<16r>hZ>b@A z(7asx8r>^#x3TVUF`i&h+xJ*ov3Or;Km2+e`u{~~F`_#*Xe9^RIg9%NDtEE>bBxP< zZPWw${dbh4z8Cj@Uh08As9%1gH%6o@t-aKzOn8puksoFUm&+nIC@BN~5hvalEnrlZ!TLnk8uEw3zvfb)69ufAUxrK8X zb|bKf8`vdzNu-6FAeT1z6RzGR5BcmnVOJ!mT5!L75ViHo&!~Obc2-*>8vnRMT zpum8uPP!x~v=c`I?Wr+!Zoe+{iBb!ji^HyjVfmvzl6dcyF6@{aN1p1ro!K|gP7LI! zn_G$*O)^g)PMbObeD-=_8k6Ec4%%XM(GpVJw=TRGWb#keJ zw)b4rF4|Fo+V(--M{U}Q(fBo<7WbKdh6z5j+IQjSM{8=EN?BjrB^oOc)_a51kYm7G zh(`GNyM*kKe!j3EZvme81(eqcU&S2cs$n-*;46^L zX2jkIu7j+FB#`eQ%BtFfxcs?rFTnk zNu%3#01pBb0lft+5uOJl#im}txj*Fr&7xW!@B^8;&e2oV(YQ>$&1ukqvQ5AwV9sfl zFcxzXyM#oupD+6=4`Xo`=w-F2qJ3yAMlS$`kl)5gW8n&qoI)N9Mp?)}2;~AdBVw-z zUhRLAo%HFC+jQQ~{MV3YDiQ6=qSlUR!|a37h1sah2!1?!UbK~}N9qENP1cZ6$s)O? zjtY*xz=WE68*DWNs43TWB zk>1P)i}B7ZMP3w)eV!MmgP&Sqfn`2u*vj>p)3`q7GHi(5bWRcK4bLMYJ(-0p*sId8 zsop0T^j@-M&MG$I&7tv^fRIqJ63n)$_56xE;cs@NDRMgY0vOrCY@^R*E}JSn$rO29 z@PfpHv)_p)4c_z5mfOAOS158k%1>|ItjqvPQ%IWHiV|gnyI{8Wk-GyfA@Pu2wfX#3 z%G*G`2FEgPmp%l|v$S<8YPy0jx;Rd0%n?@|E?xFAtW3l6C%N6u>c$N5*zaQNazsV` zB(Q57^G17h$V1Qm6W+P!2)*WsKAnYrgY%jNrO1uA`m2kN@2^9@smH&;4r-QqM2l8X zPBvv%?OCB^mVB%f*U~kbb61<;MbLmDW?T0)7gBfTrlkH(6l$0FIHm(uaemX?ip~AV z!E!aL%HG;733by;bz3|sxjtQDaYrI&Mz7~9HU~xGrr`;HgNDgAorLsO;fc9}ex_kP=EWhO?ILne)G|Hq2kleui+MUP`L z%|CWhB)qgD&ksy2<;CCmo=dVp|9+0w=7}=zZ@Jj_TIRC5#MtMhWn7>B0=*B1tm7N{ zsV>7-L*JNsL!Z72-)Z=E)#TyHpq^8$fB!0`j*W95SBLjnMC^K*ngAL_=8Njdi_)zi z){uum^(U+m#cqRYIc7Xa83FYxG9%eI@TnE(`%F9UK<+X({qMi?q_`A=dtt{~{f~Or zToAg-^`bIS&EwHS{uQ`0C2wR_VpOcE=E1N&i2E%CbD_<0dPq@QYFQW4Rl85QhqtMw z!6W8H-U5(_TplJdsr*1*&x2|NGMeo$kjbO=hkx0DaopqQ)GVG016e!?Ee=r4mr5P( zpE~URM9bg5&g)4Xb}qy6(bu`%RSreQt`isC_fmXZ&7d&ph0$+`%#&(144PLGy5I^TM7}(~(Cbq&XZ|JXQ3yr%BMlwkx!$@H~RTK{1z>XhNWv6G?UfcHI2x3rYxFb zv00hnZdUI0G%NFwbyMuiyzhD*cwhoV8SJ$j8b&NzNPK|$@v00HE zLL(o^xMAr9YR-+D6-V|N@kw=zEw3i8NAoOexho3x{}a3I)7Zbrt2wxW@@g*N|IoY| zMSiyP>b#n~9-)}#`RcqH?~0*$HS`XOl%emSfX`S!G<|=V*{*HL>v<=a#;MkC+WMHQ z)<1S{t$$n%v&12W7zcmrcI@~99VMwSY=$TPNj*hv4B>3-GtP`AZ(dK8gIW-!bjs%O zK(o=jZ{)*+7i6FIJ_*`0Ts?Fsx~ zj!zLc?e!r!K8nnBM!h^37q zk$fL*&q@IvM~hS^juth4ly}86M1bc;-g-$-dhtk9chq>@1hFo@YqHrmI;n z7DJ;&Jb!p^7pG%cZ{iIUVck}-DDNMwc|FAu8qih_S%TUdka08(c-sNg%kxrmUYo?V z>RxbqtH&V=hGUzyVGSC6$O7gO+o>my!I|G}W4HHKUW@@==XQzPxDmN52a)@^#Ao6Z z`C83r5&L{u_Dv;iL$(cJMN*CnfA7$(9ewAM8T%5#}?g#sB^OeC{wq+3WlLQ`a-+p8L7a z59gkH?>TB!UBCFbc>SB3qylG3%g<1uw5%e5q11-{uY1v|a?? ztoWh-@78Zt*yqO7)wI7^@vDwH&U%Wwzd`r?1NwWYn5hju=e@gTK(tl0J)fA=yuHR{3H; z4i%02*}Gc{thd3w?6At7$-+Ah_9r2?T~RhB^e;;P6FeF3h90SiRd&2H>4aHpYDCrF z!1+IuT+sY>LI37X;aESWfFlr?|F7_VZwl6`>KnSxKd-!5(=$0$$neE-LK6qc8v!AW55yd&ix&PK@P^93vH$^bv6X<5Np z2Jna4@fpwxpWTj4y@(u7E`7ug{r>|oYyF^ZxSs5G%}ti^#<#~y&5`%De>Wc3=yvQ^ zi1XL&n6*95EE(q>#92C*sia*Q%9x7+?}Ab;ZN!Qlu=CxwawBw}3zA(K-q;Rw&R7bqe`}|U`i*3i{5LM- zNv(~?$r{Uk{kxS|(_D{mhmQHbwik^hj><-JcTPEYv^Uq9jSapbl8oTE#shpJ)ZO6%P4dH@W|7&rg$cv`itza6P~i* zMq3izPQW@SEE#g$l)Wr9Ar5ce7f-1B>Z$Q*>>+;j)!KgX*q7Nk-($Xsd@VZz+#4i; zf-jMNQH8yPd}(y$Cohc#&Z)&qBft8G+FHCc^5gygl@2eBIPwE;jgGtc(x@wX-x1rR z+I&VTXEX!$5a)7|cxSzUT4Hp5muq*RUMS=J`_+JsQtBtAPSTZ{Hrr@eEeOB52<(Jk z{Z;MD{yEypO5Id`^_R8e8?P0FmHgFNU{*cEP11N1a1;g4I3PLs?^R84+qZk>?ITb3yk==Bw+{OnGc_Pb;@*HrDu*NluaTks zxDeW%<#?mc<9PH<%^Fe{XuZNs6zu+a`hBqRl;*41rHH5Iv`F*)H3u6%(R@2WuUaSC zHiA|wlQv?}%hCEuDl8k|_0R^iy}_K`)(yHM!&7Z|!6Pkb!rrW*)AOFN6x_sw3x~F> z$oRnBJodxR7hwq?WnPm3Qj+dnX#WOU-;heAmdZPrhm!L~y>qbfU)pH@^(DoUJgXD( zby~v>7jP$T){q)^p6gw{hrrott`)mp{I-O>@^J27@m>b653d`%x^^9_le%?ErEVQ7 zZa=8%<4v#4n^*pC&+UV_LI%!yjMcm!SU=ASDRXk3d)A%>VVeuFS8Hn-_qr%qk&$q8 z3ch`?NhwiP=*^?mIjLo}-MXu`%gJ33wikB7RonFy&d%S?ojdpbxpQ~_M25b$Ol(-; z%mbG&nvSc&3}P9Z;7V=%aq|=u*lDy65j8l{$ng|YHn3k$o!KRS6W>3gTC?X%!HF@ zDSo}LvGW1K-FPW)NL1CGjT|#W;qxkdRCPl1o&pGb3bUW@nm~`A*+Jhx4_4B;* z8Z1>O&wLf~CR!sq)M#5nt-F&O;>TzfyD*oi>hBv*HJX5rfQ(IOry`tV3L1B=QPmvn ze!>LbooUeaI0$>bC!y1>B*pmzY^v(_ha=3-dpf0j=+7*zoy}U#ZcKih`-LfJ(=+}I z?^+RBxB5Ih@zOF3G6fay z|Aj?g9|HWsPv@}J(2||wEOGV(2chEqzrI7KZX8PKUxy|GF?gh=OT&0|QFvy<9cz-E z&W2ujs``Nk_TIXF;dv|)5Vp7U*Rk}&@5ASzkv+PIt2kx=*v*SBqEFYu^!Z2Q>3y_e zqTdwhdKj8E%`vb5njPO6H_W@#IDAR@p*5}ROZYur#deE_^}Be?irZhsob+y6;oC^> zmy!4?5_21MBl8Z>3s`R`DaE}YZDvu`JP&WcLlTTTW2ZH(&FniE^&yt|%$n6bRsD_U z3(sJ!@8seY7sKI>*_scKj;X1$W+xrCp#M%w;{U*HNL9U8v(lIB3|JwDj1?=M8SflB z^R_(ZuA0b!(DsbqDLk7)sR!Zq7xalzgR`MXRTDf8pVbvwV!dx^_ATX16N9rly)7x{ z4CwolcBCr;_M}7Yi}Fm$5vl{ASXIAm$nfqNjvbWtvv72%=FDQlNa9T!v)tElG^9ao_MatA(7}?ehLRH^yn1&frU~W9r zpjgW0Z1PXiSHMd4!2{5Lud4eShCV^~Ukzb36Izz$WBfPdLq_PYioh(Os;^+5uc{@8 z>AlJ>HH>Qme#cg2cug`V!fh&7q5t3Ei^%?IO_|YW8f~HedY=ibEJg&P{S;Rficof!=A#IDojFyJ}YniF4YwK^TH-|f~iU@LqpI14c{ZE&WAYP4w zW4`qtr0vnn7ANxT9q!H1`ecJcbn&I#rNTj?6Unqrn!=N$nSYC>& z7_$uKSj;KBrwJ`Eh@Bh^Iq&B89lpX2Dd>HT`#0L)kGag0x!CMb=eBkjX?{WfWEsy z_FWfR^P8B^(vyoIFFCZP7OzFq&=bC{!<_l&%r{^4o3;IOoYrsS#sqq5M0Hj}d>-~O zS}(p6y>(-U81>#-(7}(rm9fV02HTJN>lEj`InZzSozXeFyygj!JQKBI@9=fB<%@3v zqoyl(vG`tGeb?9NS`p)sZ}!9&pxaM0e_e3gvJce4I;>31Z{6VeOI3jzFZ&$62G+AM z;Y3ciXu-D@5jcf^Sn%Z^<*-&r7;I&#I;+m%+cihyWx^f4-R?hNV^$L2il@Xr28 znjDwjZyet7`Jw$%v3w1`)DC`~EY;tzem6qifV~k;wpDfA6^A#rLny`a@;3o~Cfw=p z>NmrdICQ>g@fC;fqs#!`)OJM-o?Lm(U!2X-qeV@BvnLkou59YDkaDQH|!QMU{z8_?1tmy(Y2Ds%oZSM*jK-tnA*C9ca;(f7TqwUg%NU9%x?oofz`lO0m(5 z86=l8ig%j~4Q;frC*V-R-4P+PNwV{VHk*vDb@(QyXtN1sp()ANXtWKZ$LzBbXVht^?VtGWHLr!20nbZ)6xzwlDH@)ib5_1Br*$ro%-5{Ir zsbAt$*<2Y`Vn{+Ci80jKgu$Ab&M-<1dyTTiu=#|4b@mCryMR5&+-wkKk2B>W+!_8X zZHkk7s9|{H$Q_hVeW&S!eJ891RDTTH4^522zuMB z^T@wCGE%F?^)Z0Y4TC+Aeeq%7Vo&2VaD(uvk2X0p9@eK$Zi1#)+>PDjpWDvG`qVK^ z7Sv*9(>edfm|vIkx4lW{WPR$rP0d)<{8dBBSvR}=Y?a}N?+$3;6fu*WqDC23WIu@4 z8b0+G$ak9+dCXsFzYToM5SdT(d8`9I^>(!80840V>a_;`)*IcyzaxLk@kYvc;~+S^RdcYrb=U3oEXf z+CoC(NyZAi()G>bbLS42?Nhg7-)utrH6?J$Xxu%@3cqR$=Y1l+R>S=w z*w5XiX$$dGVVBygGtTa5SAA**!lo!q2JNO6Vp7glW9HZ25Qf~7QBrtU40n^)RC+{S zrN;mr1if(ECSlAMp5FVv@bozi`?B@C^TeCel+ew^%XjR1%@*z5LLuys?iy6ycx+Ho zWAY%#=_9!#fv&E`d67ygt!xfxJb+rMXn$dMzN!K#RUjpKkI8l6ho<{A?tE$zp=<8k zZ**K|*=@szf7&iX!#$q))I}2QEWk?D{}NY(!OpkvN(ow8Tf}S|R)=BEz8DAoA6DR% zsRipa{ied_g|_p!K{FMva8Ki9MacuA)fY)c%ixXddH>*;3@5Z> zU((tNK38%b@x0jqxp*7>vS632_yOG5m><|-24y+ib$^>A74Z1OiNyJxexFEv1gfe z7F45U&re>$WBzZTZ9S1_!8opNaOEk`V)pL$fLPs`8o2`#&h(b&R{94QKbX<$f+24^ zZbX7R4l_I7)MUa7hQIp3j6HmZn}^$%0{6J$m7x!2fY!Sdp?tU>__wp}hbXt; zJQX;zDlD51nG&F5jcZKJ1C5#G=2Yg7MU+fLkW`k&myJlLhU!TuPK)gJdmYyGQJ zbt`%lds@QL8oUHBRtXEokL&TSHGatU{Eer^9LKBFFFayh(3`ty)e7t#Lp zG4C*))#buMSsZ70*W+DU#lr|?hUB^^)TT3c@pre8BlTd9e9|76L#e-Ctbf@*P84ev zNN91vr=G`mzqTJsPet@e9-^L+iZOKp7etg zD=T{@w9b|3y|`{3^~U?u*J#PF-9ygp*bw(u8iM_Qmn#xmFd23I;@k?mT$jJ_kE)IK_ag8s-0PcS28Sa!Sg)j#q=N9|6}^;HR(R9%w6xIz&?!>yb3Ts2nmE}D|yk{o|8K( zFyNG*t z{SHg;{)#>YkGFNx{?^~mGcop)3d%Hadz5o{=x@BcfnAB6S8)<^)r{8{d+eL>{vg@; zSNox(JDkfi;x0m`Z}?Wc{pkyhqAL`h_>OVn{3^xJBq&2p;iyh*>ZY7L8tYK_+7m&cB&C~ zVV{04a;Tk=MN~&1j%>#W=jn_~7ouv)8q>g4Mnn#C(&x+t;*g)!yy^U(jN=z$v|IGx zX0*2ge7}IaR!}ozXXrPbQ1?PK@@v4Jb?B^&Zq*J-{fGV)(tYhFc73!m#=)8~Yq})s zFZ{K>v!YwT!&^DD!xp;*oWgAv`5W;FVtj#x@}%6rT)~q;Ll>iUiydcoT@=_9Y`TQ| zw2|x~Es#aC#tj#~0LK$nBYTb}J(ugV)&+h%FD@@b9*1whZmj?uRoh7D48h*fh81#> zQ^A`AC9+K&-e#CLRvc! z#7s3c1#^eHEkcVk#rcq?PYilN+H7?Zc!$}!IuYx9nA9T1m8wmB=I9q5qjncKxF+lU zSBT-mhu^woPM=7rRqhmKg8JZl(&qOM^?AO2;-SX-cjZ%&Tr=vFf1llN^C{S$87=HH zd=GOScQ3wI3vXh#GXlGvMOY>vv#_7sTT7y4!hgr4nxfE*A}2xF46Rp{McT* zL$i74Q}2Y_;hQ&wg}W!~>U@`$y^`B4d&P-xNG-VI z;x4qWKizx5I!r4^y1b)%9NK#C?Z!IPEE~RD3M)+M-b1o2^c68^4M)EZSN2u`&i1rE zkGFOKDb-T}yXo2*H}9ezhL&=5o9wd!bZriurrBg)?+My$f2e~^_OTb&6B^S zXe*7d!OqjWZKb^1gj2&9b7|fSoEw!^b#_zfEYUx49`5obwyKXceLXAPTh`u^!Lx+2 z_tU*49sG5xGt{%E%i{|n6Qt$!P5wkpT0mQWt?CF!Rz&lAu)JPk+UZnqH(eyrXHS9o zs?jL=f685`=@8D0W>-Pum^r{LSSp)yhUPr@E+l}-*_G$6ME5mdjTA|bQm4dl<4}g> zyr~R>Mw9qLJ7-PI=bBbomhA2`_1}7P%$Rk{@~-7=zEAvN3`>E1L0!ae*77_tuTYpEJ88xFf4X;p2+TXd9#jC? zitH=l%c1wprj7enwJ+WleEud!{r_pqdlzQ}$9zKjn2(ay{5awV`0c*hTUhW*)J8lt zN#VAGwBTp=9m@~R>yJl0=VNf#a@;HJ>>`0vuLtK3Iik^*@2V6Qo{*Q$s&OY_WGg>Avb&ls?IZj5 z9?PsR^IX7N)h9hJ{I+Ov>sB?_Lw|k5&-U1T6|T@4AP=lq+TUVA9}zY5ab&;ZzfG3| zY9sprXy~6>sk}lP_R4yU>VcV%7mERSy$d;$0((9&r3SqG9c2~$s{NyjqpllU`_zSZ z?TfQkYeBVtMD{D_^``n2rtta|;jiF+6!ib%UX?I&EQ8-O!*;GY9o7XoCem<2DygZo zH*QYGLlZdOB*rMpaAm}p48v8UA>yhMfsxnuVT=gTk5&i8RRN{ui!1aZ_6ZU=L%yX(v|KW0~=Qb z?$sS^T=5MgG>^_wUJusvTMfaQe!ao&o1A^Cnyzs2ztFKUT8@pEv8z5O$HrjIS{&9t z16Q`dy2#T!TDqJG2Wyup>E35!kp}J}F%<6#Aq2Qf>nx6p{umjb+``K5pS6*}wN)P( zv`T3A?XiXC_QxY5AitnKWVTvh<{r>tWZa2+Zo!cehnp=~SqB@BYpY>5&mnjK5rav97QhML5V`WSF0YYp`u za~F6LyIWXGA2bo~#d&M0RsFo4oFx5q2}u{kY(UIG8~2Hb*`WD;;py2S=0L2tYo*Cp zI>ubC#jM8~ZMOTyci=Si5%)6o{`~Le-m7QE&CG_}Z>b}2AC>o_mP=c@L3^LAxc@Jb zAL(H4GgGtoiCaN;Y8F5HZTAdBFHky1Kr+$!y75{#Ez8i>gSQob zi&mLR4mb*T9=qg*we_5jIVhJt$B#bmiOL<&)N^PcjQPULp6INwwpi=}KS*VmLkt58 z*jSNaCr=r+NE?Yo_TwS;KU>v2*vr%99=iR{R@M50*u3-Gmyezs-m3O_mt$>kQedpn zrX0uG3O8-aRk(j_Q_k))hVY=FD@|}L(Y~k4z1kU;-1$?x?_5l1kB+e1S7QZ_@>Lz* zul@2TmSgR{tT_n(vzB86Yt6ElpK_OfSMa6M(ON9Ve~0vcJfbKQq@GXi(GBhPG{dW1 z?$XvxGs;`PoDlh$-PgTC904wjH_CIMbqQ}NEwwI}+wIE9nd#o+o|vx9|8;vbT-OZI zs^-+$eWz}*MpFhD;OAD99RfdRc**uG-vsgwZ>u^Er}&Rl(jMGPkrwPjO=Tj9nob>+ z@3_NzD;q_>@i|p^<$TJ$8`6g=zp)rE@1i4a7*4+Hz73odeqA*T0S}d8u3sHSUW$4h zIKPv3>sm#w2YnKI;2z-QaFZ9aKT4LuZ#m;@Z>@)gdld5s$I}kVKSUD!r)7Nf={s&^ z>nYb%CujV3pj|Q%v-|Mxp+!}@k9x>$*jt#x8DG753Hja`;Dg*~|10{7^;Y%LJJh*j z_wHL5=!2Z^ZSL+rvNJ3B9+w^ZvTfQ+f1USv|BBtaFh8`9>0|rnc%Q#r3C{0UxzG9e z{f^ytA;gk#zjody_DB02ySL8u(;hkWNBbSS_hg54|Hr-Y*882If&Gle_oVz}zmpiC zYcqWOPxd?Qe3M2e-xS>MgkitKTTS4byn{RBEzrv?GdSO8LZjGfcd6^gyPrPV?#Gq0 zBPa7`c0OIr$>ozgX-GAEBKAfW&1T*GtU#r@wl+NrwpQ1eH1c?g0~EseeZO)-Mh_>Ix%n-Uh3d(;56o9kz=~W zK4AH*w7iPEz&_wtwRZ2^4lDPq$3lSD;r{=~|1Vz0?lXk&I##F7>1h1FxAsRn{tvO^ z?{+IL$9v2g+U7qV1)P&_weN55=h`@k|H(M$)?pm5p9eXeuuCTc`vY%4{-etr0{vX) zYuL~6T?Yz^+4aI#Y&!}w5px1i_NdM_iamF zi|Up8+!ocN?Q=2nbq~z%{~M3N{cYMTC9gpC>lXENqusl%!y0_+emzR#;ak)FHU*JtP)ye`oa7V_`bS>(Q5+TEH2LA&(`gL^hn+NvNd$GyL_HqUgqThuj3 zKY2FV(3n4KnZpVRtH#%ReWK%>&mR95jSHcE;}E{yTPZ?&z>j-;z+QcTpYKu~y0_(v zt?unydgLX$x3EKOdJismA7fvW9~7XCmpaC{(=ltj;9lNg$0Uw6OgwaNfZtf9y&wvI z$^FNGb?FxMHoT#LF2{m!&aP49IASYA5^VzTt;SwAchL`c*N+TaKQQM*-}cGdJ(JC- z-#-|j*Dj2bB{O)EV&i(n61G`fH#aNxiQs)B<3~9EeI-iS@5D{AUm*OmH`)vXx;Mt8 z%xFrwi}EB_)6Q`Q$D|uZUn}&Dom-4Bb0Ti^8EC!{9`_ievakOErz;VYZy3EM$dIdW zUVY}tB&Twt&jJ(l$M@7E`VPDfGS#o6Bqs&60G?(8UI~d52Q-1WnhM9I$+XhiMvSv# zcWcBbSb=a{ooMCOP*RvNuqruEv1UVS!YS%Gds4a4OC-<2nh=jt@J@qvI)hh3+G-k6 z%@PMB2a%VOnv}cf^G@6H+n>ib49D1^gK)SqbOzq^EPVad+HPVdc&4z*JdI=8;*XFV zQ*KVUwnP3M^jO4Jq<9Mb&kT&IpWh>sZ$jZH*+YrA z8q3j=;&NxEx2S4^6*p8xbDQ7pjT>R5{1>}oj+O!bG~dbWpN^IfZZTTYd^!CljMJ;Kgb@)11G`9fz9ZsIamMa>+EC#~{&C%lb zAyI4|ByGI3sP^}`Z%OP8ZI`d(th{*>a+Ygjd*e;fh%t417_?%twr|lK5f^>)TQJ7g_StfZ&SZ)D`?6tu;K399`~qzd$jYrfBKsL zU?^5b8cLM@tL)il@YY9!74z<*=x_7h)=ktK&7b(jKbei&T%#VV@Egna;jBV@_va$| zSTrC0itGJ=@2iRb^wq*L6!DnH(tR;$r+r5z@*Db?WLPu41^ssc|F}CJ^yVr5^nEYC zwEWJPNE%yjZT%_#D8bxV9AR4va1(5AmGQM?*goobn<2Q53*0dQJYCYRtIp`$IrGbz zrMPQ1x6^6r#n$2Pm2DL>&-U|kieTaKt5^4 zo3qoMpTNy4lcB~s(&CP}+v+gYhbyuBy6?~SL_~h<=^2@gGsurUv494^?va%^CI8qH z18Bfoq9Xi`#&2V!dC$k5sK}46bW)=Byyyu(-E;4Yp0G8B;^;j}am*fFqo>6mbJwY= zMM;-D=17a1IiWNq^6H{U&Xt{?0KaVB;V#P9{po*Qwd%V3P6uwb=sUo`ToR~{7t8+Q z3E!XK{EMg4nx4)VJz{?s{A*Yvnz#6Q&y;sg={rgDO-G+y=Rb!M{2Mx$vhiB#2rMZK z>va}B7d&DND1**>MBe+k&*nzG{G!Lart_wcJ)QDC_Joi5*b_GBV~-LE9YoDr{axB` z-00^@D{>=U6}d6IRoK4w7AX^9|J78CR*0+m_5}pu!Ave(8s^UyP8qyuus8O{X>wpw^5v)*_brQ-B`M20BF?HzZ8~w79xCpV{YE7p0G%J zLu#+tjiqCvHYed6c?sUQEo&?ow6t;GX)E-P!Jbd#qQ=?vOB(l$8QEA6xvcTn>59gk zc^@I|Pw<|y0O7jlO4_yg$8C8o~3`xPQWK?Os$sy+(P1V2>3Vl78C3*iRCe!-B+9AQ+ao_s^UmuKOJH9^j{oPRR4rr%q%S3~n(H|@-ivM2Ji91XWsh1DMu z{04j-EDVYiqs3NtObZ+5;0Yni8wjlo3(Bn4+W+6Dr;rP?ll&JYR1WXz5cKAsup!lZJPJkEmz_8^N21R zpet@dOP_qh`dH*;EUXGr;wPGq6yWwcxVhd7MgJDnhM3~gcshaJt8o94<~3WN;XMU` zvx$_*t2V42xNZ0#$O2DoRq!(SHtjq9OT+ZtY6?awzD?+)8ybI>|ur(xOt_oDLCA9jXqD7|5h ziL5V#KBSqX-uY{NE zgxq-G#cKZ|8Is}5G2!(Y&^DkL8UDC`1j2vNblh)M7;ksP8wjn(Yu4gD?spijsef-W z)n_VWpouEHq*LtVrQX48ss1)k10^|s!Xl~XwLg&f{`aI}?;`(UOWE0NMR;$8)srwU*Jp}^BAl_Rhd zG~GNqak+~&@L*3jFQ%pd(h+F)Pf5d_(?m^|J~?Y@*bI!rJKp53QCyuyiy%>^z7IMJ z-O4>_{;0u*oHW02Ftub`kWX?H%GCr-ucxOfEw6Ly>Sw51(=agks3CD}#+R7oR>g$Y zL}1qf?_-y6?}&o*o^TuU?Szl?n6JuiI})TvK;w``o4ilQv_SFk^+S z7Xw9eb-@LklNR7M8*8o0)wpP5vAK0?{vf_mC^lbp3$YP*Dl9$T312Vz9YnU3CG_+dvy}RRsGgqisXftNij~$opa&E?)*i52tMrEU(R(AH`=X~OBFoX% zV;po`-n-T#QYZV339ESo}#U z-@Vwi@}~YzVu(Tvm#g@-v32z#)Qij1x_XH++%UxIM2lD$=L-9lPep&xS6FtxdBH!} z3ZAK-@p^c37;deGH!GN{BT~0OuR%0YrM8hR9uuXmS-=Hy53QXd3sNd@NZse-_Qg0m zw3wnqz1PS*lm9VBzGXlADRd?eDZw4gyPdE-X zA1`^dGl&cRQ5KUnqrc@hZH|JBD$p-*zSqT;7x$(p!AjIxvQ%7kf!~|#{3p)O1?DY` zmAft;wYsmVYdy-{Q6nR-CPbR^WJk?VcX-&`z4dm%euBNujNYil*4E;7qWFg_&)>*Jh$u8)z|XsiV6a z+=@9pH*&^`+%YpMa#u^aFKe6k1Pb0qKR^25hS|12S(cZj<)ytTTDRz>z{)C10?q!s zb8t;fL21G-PQIc@JSn71^*>Xli=Wzjaktd?^yTX^1iLp%!8!wJTR&0ZHSp#bervJ>v(ser!Ot1|cM^0YpRJ-Q0EXMPuX@f`Lkhp!sDMpT2}Z$E**{esQcU_%bt_44U+ z^v`Q+IR|Y(umZN9=jQotiz_s?uDF+_;3R!+`*}Ax`D^L}?bZgN(ZYNVGUHPBl#KC} z2DG>M)alLv9_yy= z@e0o8-`(yRw`RL%{C;<%8T*E{+P>kM8l$Ba0ZuFp+P7TL@1EcI0=Vp&NY3l9(a@}f zDXHmrckp10NKr42YK&Zy7XvK6s<9zj5)D-xY8sx6N^2t)ygE2GaOhhlZTyL4< z)_P0WKFbsZy|cQ$)J-|f7oNMY)1Wovib(7c0=LKq9mg&5#QC;(?w3Ks&2#-##c@x! z$eP)>R}TrCF7N46Rhm;%j@|`pNE2Etc-?6ZhwC?w-`ZPxugfAVs-81r)X+xv% zyFjynH3k0~qcH3Bja+ZKI!*Z)8fUt_Y+N5+a~`%cl@V)U4N&ys2*sNuw39)xdrc9> z8_E#;j_A69TFlKak(Zh7kFZ4J&Nur5f1me{6+PmkTZIi_w6kWe=o1sh`y=oY)ugQ# z>v6U+mVTzTSrNZk! zh!|V275tY$)JG1nzZ@zm*T`P>fz)FRXIR`!%3a)eS$m%~6L;53 zP;;Pb_-uW7k35Uco5qtlb+OiJS6~DA{4--K_aZ)QmYItiD2du&zSHIoJ{$ zCm2O3EZs``j!W#3uR!mFall%<#L&MOn4rmIz4K*5-jdX3SpE>Cn4c14| zN*ezT-b`zi03UCu;izYL)D7UnwmIpL21Ux6O`&Xw7Ew7zLs}Dw@3lFc+Sdc?hHo?F z`eE%Q%#NICu##avVT>B?RFX~AoHbq$Q>Q4S;$5`#`(2g!71Op(CCczeGoEYN@b@yv z+k0#oiu4k!*VLLDvCyODDTEC7Sm=v1P%S<7T9MAHII$mF_Uo?$=P%dP)lvq4_Sa+p zcI~`A47^7p=CuFqyuJ+QIG9tzcjRDp3U}ohURQ2}d&!$kt1ZpRE}wxr%Sx+hsA=f` z%u|L%x&}H5AxpP;H`zkDihwjmm;VLV8%z6p66gjj;~Sstp3MDNHMUQvOEVpUc7h4b z26GrR&KESrlx1Ce47xY;b@r6Jd&d@i81Dvox_M3Q4w*w2Xp-RXsM@S+eL7F5%hIen zPOe4|dDpO&v)Y8ZBEc7H6Y5SWqOY7)dJSsol{m2Jj-jxI?M%-Yav>#duEUvukxp&> zQ`6gG=iA$E>te@_2BR%29g@+x*lY5Pu3wX798RUPaN3sDuPF(?75o-iqG{zgj33S4 zp#4^|x;Qv5R953udz@i7E%um3@Rs-(*f{KQ7xqZJyL#_?)nO%HIag*pe@W-WLfbm9 zu5`Bf)dbkpqPed>Rp-*NcUg%tJ2#Xr_sVAbH>b;QVP-elhq-YZP#?hovkW-#k&c#o=^#YN_p{RV2;w(ijH;& z{A$^IfpYDaHio=vzoRz1V|)SjYHhGZ*DEIu5Sq4fVgUI9nf}0YV;+DG-1hI+w100r z>Q`5``#;z2??1x+$a|Afc##><8=vS7cUqAe?5IMg8}={gRVmI2*pwUSwC7l~7wX(I z#XuI%^#6tQDTn9$6`|R|#w!HFXMUSCj<&BWbo#pP|+;AZ&Kn&aj%WKE`Rr zEynZ4p5YINZwRjp7h-GMTWz1TecN`27$(Mu*@5R>KMNb$uD1EwZnTAiUG6Gke>Plz zaI@|EwjbKs+MttDfY%6k|15k)EEF@~W{Mm!Pgo$jnkAkVPl!dx+XC&^QyspYhA4Nz zeGfMl*W>9yVlz zCFd#2!Z}YqowIPx?16~==4Y@acL6`({*E~K_pfb2%-^p6=D)4IpszSVh`oJqXqO0j zr4S#(Jvdc}ZjVBrAYABQYZmed=NSX~I3ada3Gp|$p9{Zvq7cu+Jvj;C;nu@d!MzCQ zf%^_lc?j(ec&oo1pnV3mV()K|Z~T4)?$YF7$n{f%_#@mq$iv-_2=M{nzu>Ci`lAdA z+-LahL74x`pMvs!k8ggiJmWxr1nxVy52m57BJQu@7=FiNKdrCt;HOMSx#1rC9lk@@ z?`8N~)$fNhgeZq&d6Zcl%QNwe5Kqt1%d;NvR)1g4Mf<_=`#;YA>G#mMrPassJP042 zKz{SlHt?H~i{A^-mJp)70Js_b@Lx!v9PkPp{xDuE;0T3n%jOqs)ESH+(e`o|^e?!* z(WnnN{w4N6{l}nP0bhi}pCW>IlBMkKs4}0^#5;!F^AcjQn6QbxuM5gA4rqW;p8Um+%Ap8{F@YJa<3bQM@HR zia75>UD@~^I*!g{ME-w;Ji#fW0O4LtMSO5i-Xp}TaA8RBvvklL zutWy;0^EAIy>Rzt2r;e~;qWMW zes{p{0M{DyeC1b(@?xa7(h$rS@nvv7AY4Dd7Qi}158wTaC~ri9-zdTG#4k#^1@s!= z=R@ekpEc_FoF`!e;;lx!`+)lZOC>yE)YBRE-=h2z_#HC_(;w$OE#7xlln;Pk zJi-xYIO0(e0_NYu{2Cdq0qt`c{ue^R5kCiq;?E(!LwHKU_avMap_g~t$D({D;+zr@ zEHCj%aBQzpGTbqQyAOUNLc$RrBE#(lWPPrb;TI!(5&Q~5!V_=lr01tr!iy4CN_bqt zLlV9wVX1^0C0s4xG70k~oGsxL3CBuklW>HD$r2_=7$c!kLO=4#dTWsIFd*CW2MjP1 z+UYo8C%{915RlAY0?2+lS;DnZvY-Dy^z$f;r>J245?>1XJdBgNe~I#EfiDJ~INO={ z0?_vW4nu!g2$%~xangy~Wc-H_zZ3YVkobv@faAFPDpHO+thp@DQVEZP&T+6CINNQj zgvHT%zUO@?$~Q&}u{JuGZ{n-r7|(ba?f}9~f?rlhIO0bnJr(p5zz>JeiGz{W=BJy- z!8UZo*a@K%kBHIfzJH2xbBqv%m|#ALH{$z)-Jk(a5#=oiU)NRaCC>UGeo@AsfcP&1 zKL>hX{v&<{j`edy!YzR8@9O|rz6JQc0PSHw{Vs)HF1{zu_=yjY;d8s``CNAl?S%B_ zb_=FMJXO+6zC^`SA@`sAB5w)woSstv3h)~j-p;-g;*FH zjF0$i&=YzJaRBX-3)}@dan>90N%DJ~ZI({~J~rfg;+b$PXEMT-p}v=(-bTQ$AHor5 zJjCmeAGTNRZF+uUj)?N++l07$TQEPwFT!0zxI+?dlyI2*?i9Wo3BMsB-x2@hc0C{a zB%B7w@!f#3;1a_8v4E`q8u+vReYflRFuW(qe}JF&_Fz7UTjF&3g?BNp$6;QN3(|>S zlynQ|HNek@(21V{{o);%dr+^Hz)yfqob^Hcko?|`?~ed~BjkJH2jEz*>kzI6^=L=A zx4^Fm;fOOH;^X01udm&q=Qs1PC_jXBcHI%oFY%d@z6JETz^8}MiO&Ro#`MNZcpZMM zuYBOFuT{PD_!k}me*wS3UcvZ@zaiaU$7*`1Q4g5YwIN~f1 z@e49sS#N#5&X;hugqae?O1KmITh8knBz#uFV;0^2SqblxFkZsOJ9WB4!VwZCOSmyf zFR!~?l$Rtyph~;5ak=-x2k_IUE-fe`Vi31 z06!H%Cw>BstmjTPQdH%Z$vo4J%Ah^`{2*| zDV6ZVFZ6PZd`*;}g5R-U1j|AE2pqq^EW`CgxH|ZK9ukhYFC-{q34BWko%jYgrZ+i7PjB~LQ9c!W{Yfdo^oXZQdIjh<;3Grm z#D~E#o+UEeN`zYmzeOS8i08|2Wq=&F#e;PIb21?3sVqS9pM3ziK8+o$$GZ%W{m3n0 zo%BBo$bH7hyYz6IB+UJ#UY-rRFkXI%@$$=Hd5BK|eeF;v9>;!hI`D@?Zh}ldXAyN zd=VcF$8uk`>iflgRz3Wrzl!n$@Y`(-h9|yN(p{jJ0xt=n6VDv3=X0cldk{bS+g#wR z&uJ1C4%gFJT#9`T{GJ^iOo#Y;l5PXN0{Ed2I`M;WoUfLQ(BoOT9qo_FX3>aXJj7>$ zUJD4(OL;EfbkK>jzKCbb@Au*R$-uKhz9;U4W4$LJTrui-Hp-n0zc_>=&UlFbfc&t& z8)Uv?knd*r)rRDo_(7ST1L?ho@5(~bBYyC1J-wY08UR^;?*X#@&fl$zaPJHGl{rfGifDapm z{xK@}J#nX`9|V0YaC-=yxK+|mfj$!W;1D|T0!d#6`bywSL+Hdu{#sA}#1>JW`fJQ< zzYeBPJPGte|H1l#_8tH{0d(T5C*nOp&j!S_RBi$64mxqtiQkmz-$eXI;CRB*zK4+9#2M@V=EVNWAmvCQv0#J3TCMIremz6g%(vkl*|eKtxs{T{uX z+e#oWxkrep_XP7n+$!lOKu-mp96~4F502|}G$7~S##BAN=*^Zc2xmGg#N_o@7vQ%O;fOOF@eK@z@~r}7`9=VK1Zco_C(&Qudl`Ha{2J55UgG?Y z_*ZG@-ze{)??m~{H1J)Z6NjXB2k{EfDR&sP0sCvA&9>Q$Nswu@EqVKK&}f# z(tol1eigo73crOR-xHq)ry$=`Ww@OPHywVHL&6bvO8VQNX92f|(1{O|^i!aZ1U@8$ zPTT;;dilnt%fXJz_8o=xJqf>xkoF~B26`0AJqh%;fxiYias2Hdz6$NhcAScKXL~K( zgnYyAVYGXo{fKA7vHhF~$M!mm{(-lz!j5o(_9MPc_Md5>?*hIhr2i1#2&bT)DgoKg zkIQspknTzNRfMEVe4#_npBs?jrvrWgI9}p52}c6{8~zCr?+M6q`DFZ`qu*Rde9a;8 z6K{ZHxoTv&`XJxm!0%#6xrjrPkyh`8fM<|S5nv7AfDFA~om(f$Q!+5mW(4~caRVIN z>vM#!0zB-{!`uH1`2hUhb_Bx{w>WkB#TP|+KPTpCXOK=Dcipx6GXkDRd_EcfM}V9^ zUIS!0MSzWf`GBmq_a4yWwXFpo^Z>@m1HpKSzXr$f+W>0;*8yGud{)9~fH(t~pC$1Y znNJh)=|^4~L-I*{kEA~g`fI>TL+HdSGWC4D0mydS1o#=?%1rdvEbMcz|6U8ff=u*R z;Owu&r-ROMyLF8ypACEp=z(!eJQv>6ieSPQ!!x_c2A_tKe7rVDNk5 zXW-axP6D!i%OqS4$a*aRWPjO(JTaZEfK11grN_JF1+4$@o0=7jmv{o4*6$_!3gNR+ zj=2c`4g5Ywc;YN4@e4AXYrOv5DhcOFxIw}a2@gpamyL5ptb9WDXnzGOBa_bJhU z{{y@Q;R&ZAJoB|p!nG2fknkf3{SuzfhCG(_zXI|x_?^v$JXYqLc)>(HzL|i`#}q)W zw^l%=*By}S;6_018;by$eu;!{1M<6F6ZLe?6pHeF@GG4dOo#Y6IK~q(Nk4~424p%3 zfQ-*3;Yle3vcYr8->3do)*B$}ttTM;1)vI8 z0LXN={8leV!XL4Zg5QSU2FpQw9US-Zt7Z6u684;;e;+kPyd%zIY(FQ;yTi{kMeHTc zdLV9uV>&lwJYM;IorElzlhCd+jt%#rb zPhAH21N=%5f1o_Xt&iyOj*xJ-gexV?lJKU4jS^m#u+LQe`|c8&CA3SpLBiUpkgNV1 z=`9uI4e+a(3b`t9)+_OwaLjkyqk4P}35Pt2`32$P@!c@^4R{pui~Npwk_=bznEu^K z2^%H+{4tC_gx~UeQT`SD&OL_lC%-3tMuv-X>GR&@r!bCO7)P$)yhnT#9P9spgc~JX zC*dkUw)avA-4Z$wZyC~?`84pu2j_^r#MvIim&xz)C7dhaY6&j_vOUg7yhg(4 z$Mtk0B>VyXtcQlVdV1FvU|$Bm+PT5>h);LR^5y9DHA2E9Kn3leEumAw!?~Cz&mipM z$bT;Qu3XHMz_t7XUnl9wZtO>auL_|Pe=kpucc+9U65cmYZ_fdMY`30(Og~z}2ub(J z@Qo6lmvG*EJ^Wk=M*(tuvPe8mLZgIrGTzT6JSXEnCGlet9+I$3!cqx00W$to5?>-= zu7t%homCQk1;}!p0%U*O1E{r+q~l_wR^IEtIgVST|E8yO|Ji`JD>8p7AlKg*K=PyO z7V7k80m)~%0J$F?3&`{?KdIv<0QucPK=QX+0a?ErC7cS#_?=Jc^U%SWkgvec{$y|- zBJO=kr<+{hSCNOtr-F3ivlr`p-y}ftb3+!3cP@Vhx%4c|pYTgrEcOy-e<3~qj(lKu zgya4P^JaNZ_(daJU_V6sho|-Y9+U8}gew8LuYLGwF%W`2x;#h&w^I zApfUs;CvtWXwZpsUrRii-!uK`SigZs@O!!L5;wrHzI==H_W5d&9zOne;5*^>`J!NW z;-5(R5YW#6KNUhJejJYRy#~m880%#C1qi7dsPCQd+Y(aW#C@Q%{6^4k0{4O*s1M=|aLoUb z-|OL4PJ`U=_uxN%9}Gu4Rnm`uZU;Uxgid@29OF3%$nmfT;n_d1PL#g}zg-9)=r6=4 zFVV}H1<3uY1MqXe5r7;I10+lUsOG=wd+bU!Mi60jvPz_#63*ULPl>LT>g9x1|JIOa0~kmbIP z@3faYXBFgiZVmvJkeouTVTs^{%1^gCpG$8kvgC&fWuom%ipZS{1Pd4&X z2ERQa`60gO4|;y)0PtK2grJeCInvlM?K!y$>7ueh_Uv^V7$an zO8N-UKLUO{gid@Z9OKOgWV~)d;Na^uJXYd|m+9%)e+&7+GOT0Eg6R-1h2!^&0U6%{ ziO(hkp8A~r{gH>!ub)GIeJ=Pt@n|@fKLU{DKZf#AE-*!wcjIzB{(L~jp9{$P83IVT z?l~EM65{_HahwT>pZGR7%5~QPGCv1a==m9+Ey~|mf%^q3g83o78%{xaHp*~rge!qx zaY#7gYvGuWRe)MO%JA6FmM?0 zS%e=9zkUcG==a29CA}8(IN&iMbmC?>)|U_M%z3|ZxgOu(NuvBL{61P9jE{H)=$z*r zpdSZ*81%rrPds6zUN2D+HlY4lPTz09@1c%sQU8H*6Tb|)R!*8T_93QaI*gIw9(7JRs{U`cL|IjbpJ+{|WW^r{H(QeXDeO#5l+)S0U|H zK|1k9IQH+$2*+}p9unoX@T)<%Ks^vY56Ap}B*UKo!~>Z5hXBp+KLB_Z_-;V1Kc#?N zkJbWegjJFE@13-KB_?)NSL zYUu*9e;t$GrQ*92@H-On9q|K_J|6TpfbR>T6W=81vq9eqyf}nTd>S0nOOoNvBV024 z5<*5RU!7Artc~{Gt#pFdm5iP^9O_56Jx#o`h=ox&X+09WK)A z-Tol@8~olb3f4REJ#Z}lG8qo?>hc2kEeQ!n+%4%FL8s}Y*&%e|!{FF&d&+R%AY45B zx`%`#Ef?ZfTP0jZh5D-x0$vzGC;luP`$;|^*V}1;jCU*{+TlpC9{+vmI2SF}&du7_UE*)U zFFcfaJFZ0J8s> zWI8QKCklSXkaUPc*NrxAum4%kR|_D^cT&QAfE*7cf7aV+3fg@u{5JeK*iOXPfzJLv z8T3uSSA!lH55!l(v7Tlk9NTT11Lr32n~rdSb|gNQ;ZQ$LK#qqI2v5G{B*Kq^pB3Q) zd<^jv89q*iyNqxN@QV!zN8E?@VgLSIwr?M_?^p1<5YoQHPlC?=oC^9Uz>k9-=-KVfqqZ?036$4 zlMG)B$a*ORx8Gf}07ic%)LpJF3@<|%xCmYZ%8-n#hd;lEtA0y#B z3C{tNpRNQXKYap_{PYn(^3ynf*S>oeko}=X;)a)WKBpNt_v865>)}!)jFWKuCf(l& zsPV^ulxxKU@;jVEYx1NY5T0_S&w(?aXCyo>p}j=+j{&@Za9{mJ$Ik;&UUdqP@~S<6 zlxr;m+|@pMfvD0*tcy7)+h0k zn{~Q16@2Yx^wZ5jI`KCoJqz@=fgcE=6Hk%!Pe30Ad_V}Dcuz_H2K0E~-9zZaYfJR> zWA6b!fIMF=38qin{Hk6*jazkiQNk?}u9naz!_`SxBH^)Zl8@bnbS_~1MLO@n@8CA1 z0~~)lh?l{!z4l4CRl*VpXK&a0*K|PkuPNL0_;#UO)8IF0doVuYegNT;;b%cO;tWT;C+M;G&Ifut@a~`!C!KgB z%E>q2W|Z^)WAFXrqAJt=@pEPlI1Uh_k|ALYh&Cd+^GhSM8b=rbaYeJTw$?$>21F$^ z*IFNUL~2Q`(5ywRB-eFGEwOCV)P-EPT(x#A+jT`(w#O|MW*Eq2CQ;}6zRtM@2Ce7$ zd|t2bAKyQ|=$Y$%|Gv+CUH5&i`?{|)(}BKUz&{ei|C;WJ35Fr`!Y z6vd$*pM#zStwo(QE-!KapGCXt5VtC#{}ir3I@ME-@pvBL{FA8VXDslin%iTG((2Ht&M=sr5p58ElM%b8fG5Z8fzP#Ax2QTUJO3+;Q0-ctHgV-|M1(2n!1p8ilcpVRA+ zUVv~;L^_4-xM-cQg3@#Ej^8TnYni5QQReGsd0g^3wNpV!g$`iTBe*t$*WkDloQl%w&TjxddH9@8wQ z&TUG6HjacIfw;%E_4J3r+c^DUq;E%fOGG+_-^4}zUB`6#@0I%MGVp%X?_pp0eNTN9 zz8~qQ>cBsYw*%p^NT)E36NNLl{O2fdMtE36c?zfEqVd)uUp2;exk=c43*w@YkHS(PEQ>NyqBK85$Zt+c;|X$Q*D z_%uy64I|C+!2vZ;ceTM@(&NkcyGseZ|^Bj;cBGk z?0}sg{-!kuuR%J6>AoqvoXamm`AUSBMwF-UVq7$S4Lc(h-*$Q6GiPJCyz&HNfu<{TQ{Qr@s_V$3^my1WIyziRE%d&3E}4>(kWbli~41K zN2%w?Fs!TZ;JNfpPdyY)Mf&6KA|CQ;K==-%Q<%ny!a6R03FQYOtc@s7;b>fRKi{A~ z9*pm4$YBTC{u=$EFx5lh_i)j8JKk0L^M|3ZXP_PXyFLA(@Hkx5Z@fn&kEe&*&p`Vp z(bn;Z_9<-GrL;eg=@OKq@pK?e<2jD$W4n}g-%Nu%AnxH^J?&EXHBO(4^ou}c&6`G`twDquzM8R8MeEpKNQyEqJH-WCHdUT z?PsC=188ecMEewueotw?1Nn)sKL@4pOhr76XChPMdrG@UQXp4|OMkDYT?#MY^p}uc zj&MmtI)&%rqJCw(uhiqw3%iZ)gP-sB)I;HcNZ$(z^?LUmp#70fVHyVtOI*Ga<+TW3 zk7$R&SI`dKhY$VHWBd$|qsxf9gnSgHdMNx2u6Fe2HBcJYbzJXU)VmS&ycE$d3O~!~ z6-Zx>@REph3P1dT(ry;hFAzuLxCCJu$5N)1A1L>~Tan%hisf_nI8Y6>W@nQ4l~t)5+6H2X?!m*6&jR&T}eRy8!)f`*wZfx zkLC13NWUN9F%3QG6gK0ceswe|^=Jn`k8On9HTLAA@IR6M`XM~8uzr7o@M)w|7=Lel z(}=x0mmh`lUm$!WqCABUIL?U%cDB`;Um*gM1Xm-&+*kf{WIhXPM?PO=bET z?x+3>Ja5s?7WBOu_d{X)y+z?Fq?4agBJOuR!fvF8{gx=a3>V#R5h%^WC5M#ze!d^x zQ^5U{AL_Yp3YUJYq*wRHIsM151Ap9;PT}o`mHT>)=|?C-d`kSY!l(0yCw`q~n*V2| zzMZ!SyQd>h&Yyegqwsl7Pel3!gqtJMDZCdK^}mkkmQR%W9*YxpZ~Fv#`6oT~QTQdK z>;3|LP=)b8cootqOyfo2Wn6wa%2yz~B%(Zp%W)Ckijc1!e6nNQOAzNoJ_=Jk6n-2R z@of|+^=}|3jqle-l>Q~fKyHpej*j&7kHV*MQT>NOsoWl>4$#jKwt|vf>CC5C8$lO< zUIx{Ds?^&WE9_1{T`Zoy9r3pl^$}lNnQH&4^v5URyz;M@+YX{}pWqwKt2>)9YVxd3FG?uA`=?qZ(m!>lbqGx0l0vI9$(kC(}BnTS4((s^#$O z9Ij!SjLIlIk!b=`9n)B*8m6#8%K51iRPHK10Hq+;cbUU}rmajbFg*{7|57uD&v5t@ z(^%%ChN;99L@pPWV=IS^Of#6~GtFUo<9ztv@r^c2&ROg&74 zOfNI_Gi_y>s8-rZV5(yp%hbx$$TWkgfoVR|9Hv=JZA_OjUCMMZ(=w*w5wS`-qE5^- z)QDw8#*->fpHpJ1n0Qspv{tLC)vYRPX8r-ON_|)@SMyknm$<k&qc$(l6$%S=v8%ReLT6l=xgL}}TK`~=BbtIDv= zNKQ+Xqy)cMtCDPPajziBv8EeEw)m1Ue;K46p z9hP2~T&?Q$sw$~ydUdqxf>>H7?Nr&)q%z6yI;yA>w~D94{5mlxF0EI`#xC9|7L+xL zr&O`ltzxEx!LCu&tMSXNdyu15Og7l+#S5wnBKo{ntV9nI)2#L48r3N=(P-R>ut!YH zFdi1mcB&H%#;s_fAUQ)XW$5vn3aT10_JHX0;NC06%T%&ay+&Olxr*Fkqv(-N#v~f2 zA5lpOjiQu*TfvwmNC`Gt#)U@6ZY*X;6&7(%mMdo@P8OsJv#3 zLch45s7kO-Q1a^wlzKeVmGs(&6zcz(Liawd(3&R{dKgshXQ2|#oTY?|m~Lg-$n+G` z8AZ@R(XJnq?px!+J{$d-QKHmSHcuI!;6jD!o>FM-FSwt0c+t33GR;_yeK^WL{wpQ@ z^%Y7z!RM5C!wX8d8kG8*xn2o3f*KI6drgVY`Hj*}evJ~2-heYlwBy{UjKA}BC0+l9 zLd)J%;*a1VN&GzUwo+~nY){mV6Sf$lv9MVXJp~&CjsKcMO8%wTYf(6OM5)Jxy?`F! zMksX@UvomCB~kDP7G)6<7BWT=VKHM`GoN8x1KC>6xE^>V;|su5jCG(dGOhz%$2bx4 z_#4K{fL~|46ZkF0=$NpLu?Obarh2b#yf#O3g>}bH!yAnKFl~F9^aSD zI8BE=KjS@+|1TNq2f%j0cslS|#%qATW_$|xTgK}X;M>KRzHhIcaWm#nC*$+LU5pbG zF`&3l8s~AqQH+DYTE_JKa`BAIFb5MDF9sgOxDNOZ#vb4yj86g^7+(e+$ryqwm>Jh$ zKHtqah&eru@m1jQjO$ZjvkJ!#5rj#MA0LXdF~&=QA7Z={_%X)kfoC$V8U|l8#x-~0 zdw3bw1215F26z$UtH4VcCmQg5zKrQRtt!HKfPWp%GaR-j#u}_gYZ;dUzs9&4coXAB z;9ADxux|a1aTf4)#`IlIyBHS$?_pdCypM4s@E;jp1^yG`oRP2{G2RON8RHAUUohS~ z3O-$ot9}9-9b@`lr++b?Zh_r^aVc;M=aES4N(O47uDDu__ z+?TNqYx6CPGk^y$J_DS{c=6r%Ze+$wfm0dNm+9UWjt9s)C z@VgLfjLYs7glxvI1LrZO@9~(zSZ{+*A>&LuKOSY=2>f%#r+{ZOUYZ3P7h^Z@e8yG4 z<&3L=f5Es8cp2ky+4vr4#`zAMy)kZ{C=I0rU6#`;`*A3Eb4;Ejyw`wKQR_5g2X zd=mI=#+QNLWqcL*ea5YmU?XF!n~d)UXH4GP1-t_N;n z+z8yvcm|%s|7Pq2zQp)2a2sR#K7h-NJ;2{H7N+7%hH)jZTBFDqz1Oc{{5o(P<0HWR z88-vp##lEE&rZfx;1tHwfrl|J10KQH4Qyn*26!~%YT&VqJ-`nzZUxR_90bl~EIbI` z4955_0O3K#R^SZ(u7ggqgz=e&1>u*BZI1|o zn{gKKbBx`jj;|`)mM=*diT8#<7D7i#`(bg7|#G6$T$dmJLAU3q5Ckdgbm!fgD$0pon&EsPCCg7ACBCxLe|UOWefS&XZJ|G=2uIp5ECCvYQU!(9B10An}s zQO3uC|H`--_$1@=!2e*J_#|xcj5A&E>1134e1Y*+U@zmVz#WVei}9TrjK=|AWjq~N z)G9JI12~%TF)nxp`ak39XQBTyUh_-n|BOAr zuQLt;zr~o|;oQcU-pkv;xN#-)|8V{_(Er1EUWES7IA@f+y>j0LrCf$v}}V81woaT&0IvGEP)|BTna3H_h(5#YNS zmu`ms&v+~Fc*b?W4#qoyCoy)`LjPw>ew+_6HvAU)KVuv4Ova0Wix_VOE@nJqEA)TH zOfj2Ss1J^Q+-46Xf zoCkP2WAaPe#n`wL`hPeccpu|Z;6F011^yG`f_I?*GbVql&lp>GLH}o*5Bzt=%Ye@? z-U<9K#xv@n|1%~(qZY=x_n`kXHUR&J@pRxTj4OddjC091ja3bTAz=Iht{R8xW#^hge7h@-I2IFPGCdPHZV;GZf#l4J=1KSt}fwLJW?uGu( zSho-QKV$tz(El0l1%8zAVc?%LcJGJ&&$t=b#kl?e^nb>#gV6sOlb^*e!ukIQ{hx6V zcm?C+2I&8c3xHQM)_n~9pE3ClyvlgbVd(#ilm86;pRpTwD`Vjk=>Loxf!}3J=h^Qw zKL0uNf5umV4>Hd9EA)THZs1P|tEz+}9_as!j{~1z+zk9T#0-*Gd8FxG3^-=P09rr!ve#h8AN;z`Ez+X$tM>HGYjVyyc+^nb?m{pP=9oCEA; zOy9r!9Ao-U;8l$4fL~@z-`PrbF6z$_;0=t0Q_%kz)8W`}8QXyC7?%OlZ+=nPYT)-6 z*8_jZ*aLikvG5P*|BUs(pD?xoA7ktU_Aqt>f5n)5DNYl{x^oiv9OE;<=NZ%YV_alR z?+*JI`+>hB40*Z?9Ar%ILtSG`?mGSE~ z$Qa}530RjHmoCP0jd9{Hpl>t2umrjdF#gMb_et8elY^S z8iC)4z*{5mjtJZkf&UVLPe$OcBXC;;z8--!5UlWh4T!*ZMBw2O*cyTFjldHka83lC z9DyHh^>X9RvS0xyWbOCs>{2>j~^{Bi_-D*|tezy~Ani3t4f2;3Ec2S)Xb&!`BT z8-b@q;720x6A^e;1ctD!lB0aioSSPy)`G(F!ufL+Pde?}_Ry7?Yn~R6ILErXE}j`IvuG1gdrvm(JY{QIS%7(c+>x<>h6R=jonGpYF*(P;LscvcN3N zoQ-8-A=ol|AtY_iQk298;^&q_8pB&U=ve9~kTX3pm#^XJT;UG@yZbLJD* zWw40W3`Bs=EGt}$w8CSX7KjfDgmGiAtELgo#REVDLdDOjN=|CCpU9 zOeM@z!b~O1RKiRp%v8cmCCpU9OeHK-!a^l1RKh|fEL6fmB`j3JLM1Fz!a^miRKiLn ztW?5EC9G7!N+qmR!b&BqRKiLnMpKE=RAMxh7)>QcQ;E@3VlHTJyhZzDsc~$ zxCbRnMwBoS&rQU06Y<ucy1w{TZrct;<<%*ZXupqi02mKxrKOcA)Z@^=N96*g?Mfu zo?D3L7UH>ucy1w{TZrct;<<%*ZXupqi02mKxrKOcA)Z@^=N96*g?Mfuo?D3L7UH>u zcy1w{TZrct;<<%*ZXupqi02mKxrKOcA)Z@^=N96*g?Mfuo?D3L7UH>ucy1w{TZ!jZ z;<=T0ZY7>uiRV`0xs`ZsC7xS}=T_pmm3VF?o?D6MR^qvpcy1-0TZ!jZ;<=T0ZY7>u ziRV`0xs`ZsC7xS}=T_pmm3VF?o?D6MR^qvpcy1-0TZ!jZ;<=T0ZY7>uiRV`0xs`Zs zC7xS}=T_pm6+BNFS6DvVb-#9q=_h8RzUL2%|2<&AJ(>6D^`ng@Bq+&v!i}+75qHlR z&g|6AoH^>jM<-`xJM<4unKnH;i}sgQ^npL}BfgXAIHr#9*z-E&G3U?j(qI&3W=1gozKW;uyp1Es_Mo%s64$hV1;d*>S(+ReyUWsR~@CrxY(JqBz>Jh}uti7yMbKWqCWtlRKcxii$B<8iK|it8|N z9XKlJ{=0chd*6!_b?9*uYfuOE$A~)Sc9&KF_WoqG>HfR%UfguP0m0Y_`J_6WBSfKx zi{UzEEn3uF1Ib74evoYIIX!Yr!S3!qx)*HY7lb2jtiy^rXuYSuAFG4n6LFPdA*c7V znkR`uE6yiqP8Q&zzmL!;UHyQ`et(}F`@}?b_k=gJ%YZmtw1o&iEg(Ej_+UA+Hgg; z8-?ih3{k;)L7_UCO6@wqDd$NR#)Ilpinx^16$%_}o7^ESZtM^w|7@dUSr&4Of`3SG za>si%vjg$M1P44aU4rY+E?dWgDFqZK3hSTAs+$xL%KU9#~$Cu+IFArN@K@r_aA)1?d~?+o#8m) zyA|%WUNK#cM{dNYD|w%e%qz#uJG0iSl5_q#BB!&7%KloZQ_lAY@~P85@HNVzg0qxf z(U#C%_r%j{y;66&?WXj*ksj5ZZall$J1uXI@336k@Y9vJuN;@tS>>vARu$J4qODcy zy=v_q-w`eKJmUmv*RDqkd$i`9m3(0D*@vx=nuB4 zT5ui1wFB4c>VuVNi)s(}8nu<6sCWCcR%1**$jv`|56%)(kw^lpPu{ zS!_$2Kd~)(vOQ#-oYq$P#7eLD*h;TZ0{NNuidS9oidU8AXp5RmVa_vorB|J|673-F zXRmmpkVksV(I#;n&pj~#`B!?QP>+;HWnS?{r>C`5j9lsMlfKd${n)EsVeG43F>icm zzV5Wm-99PlIi%jw{3`ea`IXZf8fZ2kkDONT%eq$_`_B`? zu;);pXkB9uU4Cqr)?v$5d*+plcL;vX_hS7ML20%BU`a`_1KjX`78Ev&X1<1(w1?!s z|2$Crf#eW(HO~tL)_L`ggBPl8p}>m=g$DNrD%V1X)@gU;IV)VNoQGUTovLDO;e_JJ z9b;6AY)vY37x#Bn73#XB$9>TKfmpK8(Kru!cuFYX@=os12;o`NBW3P`VR>3rkM-vOI#!9Jo zKuU)wITO5E9y3}ONzP*(!iX4;n4W$@@6h;buj(B%rZhep|LBh>uK5~$K85x8a<1HZ z-|w^j5ft422uivsm#S0~vSUwA4N5W6SkY2z51gpOS0MZ)2oH`sLxD4`R2#nMqkCS( zIoys>uKt)&{XF$TC~&M*a^!a&b<)_SV4XKRFnU7-*K-SOj=G8cn#+)9pPXl#oTpr~ zG%MYif?O)(dLFszCn|lWwVvu)F6R?>DNh7Xu&7$?$9qccDz)Bm1bG$SzkERHQ&s&f zj$%iGBMRg9T8^?^Z=lpc~W6rsy*OxF>`A>$*x^Z2wgmH|2SXWVZ{`W#ZXDs@chh}u+ z=Y!K2hXP}+=6C16a>c<|yBS`bu44S|9O{{abJJY^-s2k|7=J}b>c;)AYU=$58y$^~u)K$#wePeiN@@Oh>30t4 z`?ghhGVE|WP6Y*9jaQ|uy;EfvcnVKL`ROG1f9u}t{Wht?CykhjwOgRMra z_aC;5Z%vHp*Sy(#_D6DTLk?riZeR228n67+j;ZnX(Vp{u2C6<9@EuCuA96>}^3~!= z)R^Ar+d@wYT#c6=YQ1BgQJ!^`s3R#x!OP|PKJ*>ggdQIK(06n-tzsy%W3VUSJDz^d zyAP$09;F%=`1CQ2zBTgmDikP`OO_&zTG07)NxR!aQ!F03?|YkT@N^8{bF@cj6XI6k z=|;ImA}vZw`a*7K(8y0iy7(1HvA3-lX=~F@&TZ=(XAkWeDYR|DxExKVv1v#B)IPVP ztw%fB6I1-l3rIsd5*#OJpres47WFSphS1Rlsh0dvNv5nP%2satlkjPq?kD40t+#)I zXIE-l)qRa2-NMwit#O*rn88xpmXYd^5zm{1sb4fB=fd%=nR3pYwqYZYbIP!`I^?tt z*0gOKsS4Fk-WS?(H|5^nJiayIcB&h>;<2jV3%$yy2^j~cwpGSSZK)&og(@GZLoO@j zivI+yq6d`wWo>B86I4PQGEY`YWe(P(M^anu$d{Xu1N}ALjvSLTA@ks0x9O&&wdv!8 zw#tzyZT(Et`ZCmOmg}WHQvXyatH-EOj?}jPQ}u1S@x{ocyPfKz9#CCwU*GiHkZv;N zLW@tJ#mV}%ia1r9e&n=fa6`_S+m`xHF1SH`1xH5ag=*$!g=)N6ZQJ7V+TQ)FsBPPP zTdOsur0v~zVncfe&u!cKqAK*!eRL0wHoVP+t8XatiMy!Acp4+r7Kd72q~6*>HSgqv zYCg-w^Ejt%`$$*Y_IGTpnK8Duo%5Y-JL6`x)qR$ZQI|r0xKGp`=q(dHVwoNH2AyVKzb&-0}N%J?3Uu zpHrTvJ$lg~TiZQXuG>fb?SapZ=im3)@#Evd^Ls^oRQJB%;zimQO!kn>-MA+R?+1GJ z20y$f(Dm9AG+xZ_tQTeNUo_4kd449#KRdtkGh1KRpE{2hOmO&}`1(9&Uzf44tdR8m z`ojFq^Ll!U9LIBVs0bwD2~hn^mcdbreL{YxOQ6)7_X)~9_uN-+y)R1E`<7l543B#v zwZ2In((i;cddiZPhY?8adqwqK@+Eb=)iYu~v><3e@g?dO)qq#MI%re6^U<((~kfN^%osZTF7n)-yw!Cl#ZFeSkgw#Afdu>DaX( z@1ZQP&}-wpdk| zS8OkQp?F1MC{TYjzr)$924y$kJ_y;q@5gn){EqZlkRI$aeq0yK-HmTH!we8oN#;ef z&bZ3{{g?S2{(?xk3%5t|hW&Ni%6hT-aqO>EYeRwT3o9M_+lBZiJ^7sjwNzJ3dp=36 zb`tNGW3ZaX@Gd(BJMJr4)K*NpvfqxuKAXbf{dP>dvftKXM;+dA&w!qs8$215+|b>i zTg6<6cI-u)$AUAQLtW4}rDH)`L99n?ZbFQ~A*EaoeBb5>rU{}oEBo@7Sq{A%*VdNb zp_}@cyca(f%o3i->g&?DKEd2O(GNWq@ihjUp2>nnURzJIuj7&}aumY5bT#eLj1Pd)|v=bLxrS&{i<8N2rf_v1!6U!?TiMf>qQJdI^((_=rKogS9G zK}eIj)B2wc$HyVQXAd5YGWT@LcFfuQ4x!hbh9%VP6|{LSyOZQ{RdEIF&9&9om48?T;7s zUpY1`x0UsO4{GWY9O_p#MeLBT2mV{$A+L9JOb^MI6Ff;tW<&t!Q1GJ@f?H7SIRd_~Z?CLxYfVEI3YwexVfJ(!x%C zwTuUllqAs#)#uD6jGd4c4oj!^h1`9FwAIK@YxA`1m%e=CLO)@eqt-5Ynu2M@6znv% ze=o7*BSx>5cW(>6KZ~()VgFi>eT-_Cv-EmkoG+!Mc0N{?p#>P_>j5jm;^^A>FWNi5 zydD_oqt(0Kc0FMDr}X+7k55vsJrNN}Ti~E|~A4Pha9ho3-;(N~v8X zw$rOe+t1m*!%+CM53a-m6q>_D&fQ+L%-9EL`u6)?yE|t;(m>3RnSX2SFCL>j8~d z8@fG?_D^JKqx})>C}AgjJJrHcwh6epex(?|VTB+=$PN}=6R2~*MZI3PFci-Z~ z;*j?5Ltr9@t9@=m+4d#~uNz<%+lyvo$lDzgz^G7+}JT@Tc^k{xb8 zR+$>jZyor$qH5SU5VILO$R{@9iQ(3^XQ?)Oe|GvkpR_|rI~`2WVcl;Khn@~53$W4t zRkbdrrBY*POB99F367{Ms!o$#>w)bpX8$A`?kD{IuQ^2YO+{#+gttV%H(p{s3#=B=u6`c~0iZN#iL5$jJC zw4g?L6>%a=Igd1G@r1|9g*uXAuv*KpVw-;OD(H_Jy{hz$NLvj}-rg1)XA7;3!_E*Z z_#53jm#N?Ge+Ut6+E z+^{-Z@|+IpjWZnA1CNBHooU~DE3j8M`g`o-u}_en)<~DvEOGtyz<|(7hq~s0Z=Lon zakNLc9{76y53NP&;z^y~*p+oCrqBj^TFwpjv`L+fb|Hmwd^~%hv$FQ3H_^=3()uz!Xp6s+mE%;-CVJKwWlz#uSNgFPW@vCy zXZj825VXSdyJ>}))EV9D$sg8n!Y2`=Uz3ir&=KDKk37q|h?brt!!Y;7ZrlEyH|Z+0 zZm7D`<8MljMtaZLK@8T7B@wzp*mCbaNZN3uHbR<6xJPvjtWm^}-dqifFliE#It^yx z3+mTCo7EMn7}ihLC~S~>d#FBce@NfQjddbYYY5Nj3epoWgT)(W@Lw;{e0|$3ZrZ5P zJH&Nl=@ilh|9Z^e>00cC(tVA8^KX_kAeK%HOISsSwUd-r;2O3TXvvi{AC_XWV#_sX`NnVZbIlU2k z3Z6~wHalS#>2KE!8)8Lke-Vv%WOXVG&MH;h+c6dW#+QpbxvO|~gzUS*}p-)7~Gd0DLhq=1qnu;|F znw=svitB;wYvRUf&?jiky&f3&(T~l`pR#`NWAoC`4_u(9HrC2#vg}0*9erJm-uReE zehYqKFWHnOhuZ&PXj11u)(|vCJ$CBNuvNnfobs`=Y$90~6x%E9f8J@g^Uh)<^@=7lx>__ecJy1(r-o8Cdtch|sAIEtZ5K3XSoqc+BP(9er-_MK zo6&aQL>W$j^uATcf>==*?+*b_ZA}h`H(d|BbWy^o=G4~P@%*d0NYAgSt%G86S`#O^ zeXI9h4=hKVIp&BjHiooJoP?C%)Fuz-6L>xzDhc38AMhPsg_e&N`BqOlT--Gx2ai3FL2S?n=@Qdw8S2<$jyQ%l(bxmkZ8ll|z%PrRf zgDxs*gJM?s;+iJ3CXSd6i|6$~9NHsmXNsfB7lS*FlJEGauWwAfZ_18Wv6{S!-N&o2 zqQ2^l#vLEZJLesTGq}U6Hhaxo&=QXJ*?@5}3a z@o?h)OEC`TymuXQ`F74b>(#8D<$HO=Qs4H~OMP1>Eyh?a#@LjxJ$4mVNO}@#+vyBZ z#HGe_R+QJ6u8=Scc2X_b@>XU=wYa0AYgT|CE3;!Zi0z^n>g;+qswQ?PYykBdSh(aT z*P+NKSI>Fh>U+4EJ@Japsd@k5{A))`k zNin_$!t2@1@>*4|mgV(U$m=PrbDB(*EUk~OP|MOP;!ZYu-`jr*68m10BC&gJ4@+!H z+wOLSQ>%CM$m{A&io7N>!zjEX#6Q?)!X%-nY7m3`eH^&{eT?&imoRSe zF{zOKbKa~GI0ePH48!;sFzy z^U*gje(AW=5?{u#a$IkCqem?By?e}zIM@)=mtp=GeNs$0cm$rMm-*6nJd3&G^i{+x z_SH@L(03kbwJ}=E7n(byVPYPoYuhz9%^{jUG>^h_s4+^#b12mL!MkFOMxH&kADKP> z`}}El{dd&|!SnZr=S_GX3Hq{$JcneTk|~$_myuV=wZKtdwBw)eV>g$Axbka(Lq4)! zMYkJ7Cv>>5UM6+hhl^Z#l-lL1pG}siYk}>)NgeB_q>eb)jXcu8k`6W$lFE!|>+ZApO5_m8q|sS0}IPqjiC zETq~d=#6BTk9MRw`d*RRNn`>`s zaUEw|fnK6!QE5?1B3l7zf)plg5cGD$!5*cS<0*erFj*&Br7@y;jU9SoT6y-$FU#R6 zLG~uSaXd7^@}Rs6fu$8Odh(XQ^CWl{4v4Nv9o0NDdd`Z3YJ)?u6yYvu`?&DwSXfSR#=ZJ^_z5LACI$`TT)zz4 zC$0sYuushKJc8H_*xgJFX67SD@>fBPdt%TU`%X)h_ABzr7@p-JTZND!w5*7k7|ci} ze~JR=yc2`QjBA1WS`98Cr68E17lx9hLLBnAW0WHayLimJBv>Qk#4+M%$@()PJNvsY zNA0G0iM`lBw@=h+TUMw<)A($7>uAq@`3UNp;h2hbb3p4s*CS{NeaoQ!Jc9m=&svL< zqep_7$yN5`h8EYgz+<8Ab3yomkcIw|>-SiZm3z4sNDQHT@@tSCh0l7F`KBc-G5^{x z`_*8Vu+X7%t#hpdcmCdz*ZGa@W}f5h2K(Z?&QI*{rhzmGu4l9QyEM+ooj;}5+2r&a zRtP!w+W+^Hq$)hR~X%{M+=4Wb44dkaM*#&4w561C!8ai z{^NYgDZ27HmzOE!NJ4IxPZPru65Xtz;kCe{EmgY(>~n`X zgmngo34MG1$~NrYgpH(o?{8yxLXld>=c>}F0B6c+s{yMUin@ybe_oT>~Dx1urr3#nAhm|twIhlzxrV!5#QOH47vGrX-}!xB=iy!@l_$f$zNND{<$hz~r<39m zh9@`Gp(le*OYItU>fp~V3PYs!SQR*|HlS}GqNb&CO~o!cDJ(4Pu|jImzn}MNrKf-H z4;G@d2BpXK%Axeb&HgRqWAvE~XVMSTN%SzMFjT$E>C`xMDZg|6+j%W8>(a2oIfcI~ z{CA;#mS3@n!{_v!b_4Ho?X9{Iux(cS#;acS8?S16rB`!Il5Ij?J4q*~^ood6tx9n| zGqhHLmz=%udyyIg6zGLa*Tkn9TPTnnliS%wc`cP}K?A>xL zaO}Vm+I>AZjrrC;>hb{s-RCPbQabYHW!X)*0gEhQz4RPi`LfKFQyY31Nk_73QKk0 zkoCS5z8LMKP`{DvodF*P{os{8F;Cy-zOTY3rayX4q*x#g&*e~>Rnr0dY70U+awIt4_onQ%5Dut zVNG{RcfkP4&KZF@A4D*+hFWSs7u4h@G`tbj7wD zSBotxMPW$a5?Vn-i_Y{4PJLtB#n|K7-lnpNv_7V#zU2~kp~aSuMODiR@miqZqLAX; zt#@v56&9;n4ylBe3Y^8BxL)fLQ$%AeqzTp%Ss$vXmmG*SB%!aP95jzpTsedGhgJ;C zeH1zxG&eE*kgFd~#=s_18(i%5kXbY5nf0 zdEi+=zDsBpjc0>WHJ)p-HJhG7AG-!Q$GeUP1)bYP{((vjWDg2IPlfb{JRT}F1ZlT; zpv7%;+;T;IdC(;(-sTZoR%y^uJuKMT{ouyvCdq{vA0?!XDi*EK!jc}(rk!tei+Ui| zm$6t0)1%tcqHN09;O)@=N?wB{{xz@a+1I@4$Da49O6InSBcWB{{7;>f1^<}|q1)hV zwG}?Hx5i~Do)&S>V|Dth3HEYuSKBUXXUn#@b=kX5C{IT6dW*D8S2$+ybLa`|*zx1+ zvR{k4HE{G?Kv<*9)u(!NXKDEcQiRq1&Y2_5(*u4XC;%WPn3wfPB3F~aHT~Ast zuVYz3q_(4Y%}>3-Yd){zv4Y4oy60J@?0V?A{{Q>U|KIt|=XFHe!ectOIj&d7* zWx{?||0jO)R|EISwhgSrdH-+u&0h@+kn7Crcv!aRlYhMIJ0Bza&cnLUJc&;2;6IOR zKstQp(e|x*nAQJ--#pJ>?CX2`PLa?2)xc@lBj6v(u1zcN+PXag4!g;V3i=8>=gEso z*|qg}QVITFyUAl-)nXT0=3!RLUi0b}QTCdD#T!E&^YCo%@tCJw9z5pbaAx*Ic3U)^ zpTQ&E7^iQmgtxm0k9dK0bMT0lz2?ayJ|fR^@T^zMK3K>ziM{5@vmTz8avk$29$xb$ z@S5j(`aA~D`8;mxXFYB8L0i!!)cz~58NK4w$ZaXDPTdc^%cg@<;2lnvIaU&9dLYXr!MXO;2pp8Aa{iJiTkAf-DXsSwt$%?ot)06tSOBCDButJZH%>hpZcVoKruCx>RE{)|c#u*-vpt z@px!nLtvdy_SUomvN^5>E`)>)+rGUTH~`xNMkr~agLIwT&aLc)NB&IxUF5@X&f8ZW z$FLVP>>=7lSI$l2werYc<{M`oe!%@fZs+69AKy#%$k-fLn!d^*v^*zV4J?HP;t%iB zK9S@Haw7^ONt#HK$UBu}$T%y`liRs|yp&2(A&#tSm~!bk)iYVq?P;jtk=ESKuX{b2 zvCHgOs#9eBGwd=IpH)Sg=*_m=&OiJw&*+|Ym(EzpUbZpVSN4*ieJA+eTK0aA&+5h9 zr%AU;)3&Fnqa8yX55n_kqgMlch2cbs2+U62c@r;SBu;2Mb>=o5z$s(E)F7=J?1m_%bEraD91(MiAbrcKbYG&uvfdv6TV(v4|6+%#vhk4#Xo8w&M+{REh>kQZkKgLRr@%B zEE2R1so>qIP`xZ~3f2iY5g9>vh@+5mlK1mmU@EDbg*V19BFnmU53D6IqU#V=l|xu@ zayw(oX!JktmN)E=WgqGKiDZGu?X-87q}8`)1?z2}rL|@?@UajzD9G%Aym6cn%~duyr7qTJ7B+wb{So&k{OQbi96{Xo{b5Y-M^9=B)_O$iEE5& zqispa%A~?}K{dz$Z`C376Y2D_A8-=J>wS6O`e$!kjA0($c6i_x&&3j<#U_Z@r+B== zh|9jOV`wLPpn=B4Fx%kh>wmjT*dV0lzHh_+Rrc79dDx+a*W2)tm@C3}qLKV!Xco}A zMsi>5s1>5s?CY=WlIze58|m4(-hTGWL$I>$a1}UcUNl^j=LM`~ z+g-{!R(Y+$u{`|TEV&w(eo3+6Tn$Y5h~!fx+d9dLQ|uCl4}w(#_Ym_RU!!~v{Z0t5 zY6yDKSOr_h7q~lBd#pOiaT(`3e%~xPj~_GYCEd+4iq_NmgUu!MY<&Ui&u!pJil=$j zR~N+>G%Y#eD(vyD24XKA4jSCUUAdikTt~_*A>|Uz3uev2_?c5@X9cZy7CL*L#9w@y z+j&Q?_C)3{Py(IRwKr_;}{POItLIZBlO#hDC4s46El~l4iC1_p?!J z;cu#&8XTtEw|kXKm7Z{-Pfb%$D2Rm}R&aSP_Ibg8cgSG5T^F^ne_~#X2zyhCqj4hD zD2{pFey&NZ5mG%r*z#GlLcOa)KRqhZ;O_YCicLWS)@upAvD*-fch{i<*)NK3{24W< zYoa$~CCxznf`7~RxSJtO_)&{~NwZs;%b&Xtjh~v>3;$H?=+#sbIQ42E?czKag^*KE zzP9gUWX?2RgoW^>*nFsyv0@Y5f#Wfc~ zC#`A_Q$A?9@T*x4pj1r*W^KBr!R?@Z_|fc7ae@;oDB~*luexy#qMv$4AH2P{Tz-2` zTDR=oWy_X*vKQ8Z#*S~@cr#I)<|t~8etjzLSQ1jv;_3?mM&vxoO1iY9imd4M8pOI0 zYx94JSn$;_QjwF5FC;~+MgPRYmam_Zx+UcwvZN&9t{y_If}3R}m|Uwau{mnGrA4Z- zL0)X&r$?5T?GBQdCftKZMSXak`hb~gm)m~T-6I<{atb66?{QuYm@hovEZ#$RE}F$m zVK{LWyf!92t}7G#i{1Wj_jYx(Pj~#(E;ZALP|k!bcM|8*iCHw#@fXA~Pk4Oa?rm4a zKZ-si8Z|Aao>Hae>h`v) z@$pkG{Y%7{Mys=^&5u>n9h!R|a(s#XhB)R+=qs9gp)DeG7#P-GsU1Do?x6akYYt`A zwrGUt>gXE5@T<#L-ci@|ZC2N$I7EEn_wa@%H`tPFe!-?;jEID#JrpwRnD#yv- z@&5Eo%1OpLBODGMSD9cNRHPnhsZx7>ln$+^S}Ii8!Bk;F6wQ`(wKzI6S_PY4ZpT06Cv7#}d5y!{tb)tEJGbMo97i^5 zT5(6n@7inY=?#1A$nadD_k6norZ$mQ#^LRr@cX{0bV86K4C@LEX_MEyVLfY}5o=zu zC%ZNAXPdpxX>t0wik^>yPv>?#+^e+&{iomNcJzM!57Qq++^AmphU(w{mQDb^3Fe^3 zz3uos`+ z;?DKibp9j6wQKN(FYJ=_QJ39D_|QSN1%1s0wV2zHQ;ZyaUvs^l`lrsc@+qAk3`n<4 z>74!a@s6gbzjlTek+*l$B~8@#S+`F3CEh)es?+gy@KLN{)XGY?R6}bmPE-7}(n{6v zA4u&Atn!hjpxS;kD8XmXqf+5nqir|J=Zsp|2{)xWajyL6R!C01+3dtJnc-1bQhWEX0L9#GBaALb%b3#fHA$j1x&*@BYiKY~% zEjYB6N*jE%Rx36dYUlhn1&AGtI?Hu5s$q!|^y2oAp=|{%4I;FE8g4=)l$f;FT!c@xM>Gq= zD(oFiRO+192+O%*M+^l#A(Hs>^0QmW2=_*fZxX4R=yX*djFKgtboShi8mH1@=<0ZG zcLhE=sMv_9hHY{UYP{R6^lc~lHrE-}ELAR>YpvaaQIqD0rfqiojz9|Do=>L!{Ki{f z+!a{wEi0|xy}MK#-VgS4jIMs#?y0UaS6ATINTGKfx&js6zg_Gnilbk&`P@IWrmy;HaT5%SLq%h1@pgkdy5cd@6i2Hd{@x_*KVgP-SI@Wp#d@FCUESJ zI76>%7@0(Rgu&rG^AYNDwF{bl&F)JvLa|LVLD&#;!ijNzyR`zIL{ZIC9d?x4f^vi8 z_3&^|hv!CuI}}h|%jtBnTq$cSNgiqKak$@Tem-Cw7>g^j1Nv;_di`shog7~WZL~dK zih3owI@clMTmwH7Fie6E;`E@t{;Qzm{xTcf7*b8281FkA-|Wk|Lon=Z5Zmt*L`P2c z`4c&v%>}668p&x|d#Wa;;M})Q#5zBsm_}K9cBktj{AkOp7EN@EOB@6ZN)`R)lg-k) z-0Wo3w!T0Z8d(?JQ*@07>kj5JX}&|{r$8EMz8Mbu#7aX7b{c>AR(aOp2^IYno>o%J z^Qs43ue!c)sa`QapA$v(x~@Q|MOZ6s5UZ*E!WIoyolg0!_UVuUaf7h29`Sa(XRX>m zse0d*)m?#q%Q*}V^@b|=v~~r~%HcBK5&7h>EAUlI^qV-Th1_>wE`Hvkb)+QCX;GDn;8cy$ zd7Ri$|FVCMzji!w!YAg%3u%ICqk2q&Tp&Tz#y562qc707RKmI`UM z;0PP7QQ0j@o&n$^o=rVcX{>YZhfLOEjaKcdEfo#grdg%J&}qS;#sRy3=2}f_FW$`R zY^pyV5JueVfo)+G6+Jj_u#!HA(~DRb2@AFVOiZ?%>*vP8C zf_@j)%*M|@RH~l;Hsrn5_4#f6T2va$uNYcwq!VA^4hhAd&FRe0|4i0QQe?}J%4KkU zRct5Uv)a0e!kS4vP+^hw3TA*86aMOZ_cuUh{sI5Rz*G1Od zO+g`Txl|W@(Y+Wh@W7L*q$EWzEy(llur+RV@@ z`F)?6OfP`@`~Uv>DM@C|%$zyrInQ}+-{-;jCW@;ahSQ~>zzpH>kt{3HROc{){@w!p zc|d;;w|X8Ws{2W6>B2m$AH*oR^W4j64|iicbMk;%j`udm);i68&;a>Li>K#8Npe9X z65Ern7f)aP!AL^x#^EoK+xad+TR_O7_FNSAANs%eDu9VJTeExNB=Y-v3>QT z8PWFw_eDQC#=&1xt^=Z^ zT|eQz_7%qxk^H-y)EDZ-lGTAzb=rFV|Qd(NbHkL8*8J>Fk??^JqkJF;Ei-KXN+ z=dkGAc#n7Qnh~u##-dY@X<_mrJ#a4Sd;cjweMg_O@m^UR#=;q$I_kdmH;(C}Q>N#) zZygmMT|Z$)`>KE|dgn3Lo;KYGU(|Z?s^k6F2MmbX;uG?tLn)r`4n3kD@Ht|Az&KTG zAAf8;ur_o)G5XT<8PRoty!N+`>D$MTV(qsiaIn8qTW!ot|Ccd4)H`Ntd&f-M)1Iqi zFR`&~%m@!(t-phNiOX&XulK^Yj+_)JNo*{QAdMXx11g!B=%1mZk;9n&erD@`_bdj@NAHYaV5TmwZB1V-cLX1v2W{l>7f{YWC(Zs0O zfqTho=G(IpbQ_k6^zQnXPt|i{^4i}GAf5)j*;eIos1P7weEYYxN?~91zZknCsB9FPr%qiuu&P zrhAp%6O@-TYL5%T5p0?(&!rkH6X8 zW+ktb9zKS zuo_>B10i{$TY6E!A!%_#&=HmY9>2@{zkjSadr!gnXNJBz?fgCJ(MG%a>8p=@-UBHz z^f=-iwKcW8ZkXsrEMB|_&syE`(o7Mu7=AB@4z*?)JeEq_-``|T>FNFAMA|{>SZHPs zwaO74xT1QRfNv|xKnK%oqP7=+3h=Dk_i1~AUMvU?Rpcd-UY0qz-RhoDTOy1i&p=6u zAPQ-qA5fecCrI(MdeL3_jb|l$ujf*@(lP#cLB2f_eVnZ3OD4MJ}y^H#TX{8Nqg3&JyOw3kz^E_w&;o(F;rkRAl_P=K>kW6eOK9X%)i zy`GLRUy=viNtJ^h^RNjc>uXC{W~6Z<2CPxDTv}R^U&(UN z;`v}^so=~kQFDi3EtZmXEGj)3{Swb~dvrZ~hZ4iIpPre3G2nOS-AFAShzg~dPN9U> zbe?BrsU2fOqe;5z0gt`Z%l7nyLikmx%2PB421u=$hz`A_RHX_|^ptLQOsStFx?|(& z3}$-L6k4FMBs#U?e!OTcUE_Ei*4|{iy*<@hQeA3tR+o^h#+8yK$OKIf&r0J;mZOE* zepP+4(t@~D187D_BYMGjLyPA+Ogty%$<}Os6#0$Fwox&yAp9MWe*eCWit+0|pRN1* z=JQiUO3~)ijk!sw@0Pv}&@9onFQGltjMBPUj`=_ixrRUbVYMH<*KdavywbZ7{&4bf z&1|(NveEF6o5^=|E?@^{`n^2z4dS!pmyKV`L+x~gjiukiMbo6A0pKuiMTA$@V=mey zC`OvlHi-2qBh(B$+jFGgXRQ?fg*=8wUI`dePX-L;4}oAC1GLTt$RP0U>K6xB`Ckd> zfr9!RxSzjNqV3dIurnh*3ppx%JL#UD;MS_N;OcUm!!roYvvb#@K<@kws3{?sR+Jkw zW{(DP>IooqmH{#KOmJ1zZ-U-kgw}Z~xU+o633qUNIU)!3Zs2%KxgQ2EfSdi(0f8`=C`-&4G8nM`ap${lRPPconKQ4YwBSjA=qXp@vZ>+{CV?|!iI~(&$ z*%%t9vEoh0Gl;P;Pp8qz!Z?*-Bno-I-bimvBSvq6;~1TTJ8=%9^#$7KFbaoSF>*&} zG(HS|p7mky07hVYIo=u=F~a+?qs#D{-VB(qa>Dfe1#kF95$AtVm)OB2>T@r+==~6V zW3Fp*lVzy)ebhWSq-(51u35?|vIfZCktiq4t)J~0b{rP9Iv{7(uS@>uOn1F=%J8yrh|&F z&cyqhIDZw#OK0)`nf%;*cBj$@ubnR5y+h`Y~aiM!0fG6Yt)&$lk7icp1t4#dH zubpdMADlqZ_Ecoc>9qtCF3EruG&nRUDYFSQU^#9&$!O zv`ZwwQm*fCS{P7mi1ucD+M$bV7jm+Gr|-zbuc)--yV)XrmVd_y!h9>9_^RKYW(2n$ z-%}8tOT3S5cf)oF=_)~X&!-)U#fSD6gbS1ie^@u&#J{UQC^0Y8oA7b zCc5LV{CeEab~({UzZT-xchd69gM4gE=Erno9*)49*G+uxT3-3!K6*FWOQuEMQ}%E^ zw)eg1nXo4lcfQn3JD1(>UQALU*^BYchz)*Bc}wIL#3#gV>F3DQS%*2|=A*}pE2eod zSLz<+Ud-?2JV#2$L5d(5??3zHG&Y_kx?PPUlvz;cgM6Q)Wdf=7kbR!=$wZ|;f1=I- z#Y&rjyoonZ-+uPpmlsn;AKydy%lhAS#OV}7;$|ANAmcQ`Dv|=LNG*;yk-?4ju&A_f zA0o7sBU!N)s!fMlsnOBjgV!Yu z@1`672BO4 zUYEaO%vq)Zc#(Lfo}d0_CRAgrV>D?XUbVb#y+|1f7+WJQFD-_I#H#IBeRKpo)V$n7 zNStC3<6OCB+P~<$}tF)_>{Q;3*evVit1N z85Buz9OOYe-j?pmHahU6>S>uI8}_qzOpCk$Kk)A!=-b;_nztpMEeADsOR1j_P17Hu z;XTOGH7$>*9CWDCXH+^DofcUy%+DqV;w`}S4%eaJNX zmjm@&i)eQytQN`YFa@~Zn=MTET8_7`7Qco~!`YTxbg0Wa`4K1aPR<5RJ4+58$QIj@ z%P-u3U8_>>A1-3gDZF)PqCaECl1oWOoG<77&+%`JxZs)#emWof=2Gy=xxre6Uy>FK zTs7C&{8v1OECP_1Kx5iI8M>S1>U#Z~j{c4N;r?YQ{WHT7PzDXZQ0pgpSE=;%h}zdL z%5EKsU2!M%7j25D&%FP0o_f2z{0+ZR5A1aIqmTxGmoB2ck$OKrSX-pN!9OkF`q}gr z%P|_n?e|^%Cd&S(z6Zratf+flyiu|89EN3{yPaFdjk~{jm2%`VA%%+^(j z-1r|iA3+}5KZ=y$k{f>tIw5Ca8N*k%RNyrIb8Pt+l94@gbyU|ITGw>vNw zW%Q=>4%GLjfbmA3%ORauv=RSDbG*MQo+~B0$t>s58#wrc(B28etwp#@YgCdq%w>#@+?TLOd3#Dyqlrz2X8jFz>rd?I=?@?@c! zws!!nuDvoXGMo`r_NP_KFAxgdDZ~S(RF&=XF1y6+v{H5mw+{O*9gtpXyWeRvhQnFEJv6_>N(j|@M$6o z5m|;Zn%(W*31Ly8LNgmnQEvtxM%AL9te-@!jebi!q)%93UPbSobU9$PL4LMHdSp~3 z`L~4-Y!;wDIL%%|BLa(!(svqd8g&|ReNSRnt+Ph46cYV?+DmzwF5Mtb3qN8fIY!&t zA+I#Vg*eI&9cM*^S&?`~ezd`@(44YQhfgcg2OGW2$?pdD=JUe8u|x70J-V3dbi3=i z*b+mNN4PA_hwjX$CHQdUS@p=zaRh#5AD#UvzH^XG*7dvz>ErDyKH$dOu>St8__4Wf z&UKdvKurmd9FpX`aE1L}C1dA~EzS!sv(vvz@vpkutNv8lfAF_UPZqMOErE%FtHt#UJBIL{VCG12b7?lFoj4ksH0qF|<*K z{Wn=06j~*J2@lhQIfFva%C(+Bz7mfQxIKO26x*QCQ?d`W3b2pGDhMcCp?=@~|NZ*h zZ<_DAT6VfD{i)Y(FfFXcYHE6BtztpD*Ry6C`bkm~z7Z#=Q)J}vbNSsnP-o(FPYZNw zk_lMolRtAMQR!juA;gK3MYEUx0F9P_Op4$=g-YI>JhXHDU^@faO;*}bcF`QPtNvgM za!ZiB0ohxLpvHPsGJ;)}exlSY5JW=R5t>bvE{4~qJdtw`wcdi~YIu4Y@2GUDe%H>O zchPa}ZtC&BDaT2+US7u`qBT14Us-R@5O#da*aA?+-|!vYyBm+ zxU)Ww&eQCvnMU(O{V|4i*4OqO8=Zp4OZE8cz4FgH&=-62x)J#@1hhjyeJ8ufq zLaK`^BkEA|9>;Kls1HI?CJX%*v6v(;JO>PMqS)f$-U5Pmo&GJ|PJR~)S-WROLKXiL zxl)mn!UW^EJ5Nz&czyhk5$WS{*bWaH58LY)k~?qjp5P-&yS+ON_IQhS*_l@NidEZsNiH2OqCCdi!h2Kzg zHGRhv^&JmF+q)ty`<@eEYhOSf!#CX}3rb41mYjzrat-{B z&y$2uTr#gP;>;6DOaeo!59xfWJj+6pWWmEJREgYUKqjkvRWE*oDBK1EIyZU9%FdZTl><8VEY#ZI4_ z^p9(K;`l#n&k!R|RM4K*%M%rC|FSfqjmC>|fnhZ9nTKi)bgw$tk=9uG-Q)I>4JkM6 zT;MiX*``2(Xvl($ito2!)xBZS;;F5Wn{UlVe~YvuhwCW@ncBowX6k_jjq>wWHtt7? zT`CFp%)))Ld+!+zCSG;VOToDR%RL)pFQN(?@o(eBn_LHBxi$3YFF7M89y}19tDn`Q z?|1;eUrRRlPyA}H-vNy7%vOi4G8v|qCmJrAM$_E($(#2!C~{}RT)Xng?o~hSzqEtT z;=j}ewO+`b_7zKJ<>M*$<0#0K5iZNQN1`OBtRT^p4QT}O! zmIdEP)&*dYNsi%nZ}qUT-;}ybXTkQ((k@Gh{>K*7VUMJ9Dvg}`?&>z*wNe{(J*)I{ z40Z>i?WpukJ+}aQerIqg-xJ1fS1#<2z;HtO&lSpyfLeokFt9;rboo zVPyMsS8=z#?D!iZRT*23KCPK%7yqkcO1~WWn#7{&GMhHie;?oTL+`t~_kDHiR)}I` z14F*(Dq5*5V@qJl(Pd70M|!)JBYy*oF!VY^?AM9>OJZa(A4hg6elxOR8pT<2D6^lb zdwJK(h;ak*#;V^%Rsq;4F$#yubT#l)AmXqVwgN#o=y&O8B(66OcgXyRe>WC~lySHP zF+_i9;y1$+wb3?_%h8XkYog~ZFC%M*?ca_HXh8YTIz<~FvOf_2BK}?re8q6sR~}?r z-B%=Q&sJE0h&Ne!7-m13>mymlhQEnhmk{TMy$#5UYWQ)~nq=^0E%CrA^&qTLSELy| z=iwdUk7GUzq5Zg%(T1Zp^uQa3&zv3}zPCK!NI*SiW9XM?t)4#*kJ}Y#7M|XJRjj;t zNM2e?{lWbq5vekQF?4f3U!Pti8Hzah`;d}}dW@)^L-e3pyLQ#Pm*8r1&Bw)f!j*37 z-+we0i;tCFkuG*~c>ixoi{bIfgmhALkrUhts9kIKd5p`vW_knq{Uut`yBA+MU+RTT z=!*1Cw?U*cf9a-o8V4(~F(l#cbZ&QdvA9?KSLyt+E7IrP+x;%X`O;sA20wBlf}Ykm za`5~s($4O0O1F9(&RO|cK(;OC=*kLQNj-a2Imi1gx}yBT{PheJ$x#oxThtzIz}~bW zkZ^T2zEorq8?_3@uSmOarz_HGCq2%72o>BN75kwZ+u8>1MGQXAsLJT${~q{YIPL+$25>!o(It5aJw z{=kIzgTDAG;`{fu*)~&;?9J34XwtQy(|GC=J`=C*)hB2q=(h)_s zHtGB?8kIh0a}%jo5m)hF=g_HV7nJTD{e{(f9t1C$5-*|f5 zxxgd~Y-`n@!3ThzRX3Efp18}@TM=4)U79J^glb920e+6&HaNf+7Pv4@&tSx0J0leK zFhsO)Q&HLC9mG`A=$NNPZ+HRNz^rM};nO*wC=sy{h;QF9%7Gos-2TR0h(>zB5gq(a zB6NSq|FC6zR%mM93BO81Wkqm1I5=5i4(xm)xC4^JPD?}cXTVEwRAUX&hNT=<9mppa z6psE8@H_TMftXwtpwq)x1cS&@c&_g!(Jah9)Tkt}UR>zt;S z8>yFQ*t@d{3#* z70W(;3;L#~{2^eG9ujlK2JXOnvqf2265I(pr}j2`y!711@LYRnr&oJ2<*I90M0yn) zElUsK?6aNE?tTexOgW%x<;+EmhypRyvCIb>O*Ncn$08MX@|9d%-H0kITv9>vAmqAh zeS&3R)-71=ex2^Uyp&Xb(^T-fDbP*ki1jSZdhlP091?5{p|C zxgmIGqM7^TY+sIt{Bt`NeGSfo_!Hk@?BlW&7o;a9BA1?mxJv&Nh$~#dtwBtYsXiHU z0Aa2u`|wfM=glw6w<_`Q&vu%3XD+6h&MvR%3592NdhO#pbv>6tmeMrwJ%4plCS>ty zJ`;0fq#6L7-0pGZ>7u8c>oHz>7|2lCe{Z{zxtR4B!>nicPJHge=kjV7a5+sEFDMkk z>_G7FzG86@JfI1nLf~N(EKHwGQZH7Ll|iv7tQ5uYZxrn@V=$wZD=_w0 zwmk?GGdIzj^7r?gvdKNOb+z%K?vXvH374F|4W;BW*FR z7Yg!$_u-7#K{Hd!I+$*a`>0#(m9}0g>%5{X(yi!ZiOSkwo{BxL$T!Ln4FS=M@QI;& z?3fKlKt=al>P7b?G}=Kg|CDQR{k8$uCklW6))jeiz_ASB%Ww0-tL!rH|He-DV=Evj zT#^3py$iAbwDxGV;`yQ7i1=f5E_i)j9mBO686jXEWI)y|h3sp~AY{$bK}W=J`t88i zP^Ou<*E};L=Oz-;=CV@RCEc!~Y=-COqE_D#c{Y6Xh=IEXXJZG>MwU*t@jJ~OcXEWU zVPo8gI5BsOSE@P?s)pZ4u}j6`5qXmPi2R86hpeB*w+ z4_7Gjs9U2VaAaw9n;0JB!g0fIomn1_CqsNiLEvOqmnsOH_j(aHvQ*LL0ex^esRxllIruJwrZ4R z-qh%dl%HG-?-10Huq%;evh?Xb+OI<&7_pAt zRqmzXeq^c6FNU9jPOV(|0E!sC8@o{;uKhkg0v=DaC2DB-G zu0WqZk9|IjPsgGd4yf~T?h$uG3;UNW8Jc3@Z&siPwmaF$W)s;wkj2CXc<*P)OZ~EAy(FLFM+e>vyKWwv8=1+tB*h% zhlODb(2u&G$by>*z;SwCq@8t)f{qbP^@7;)@y-rT$Fe^{#fzwJlUU^XN0aNS{C}hc zs@0tFEvJ9<%d4Ivt`l;69oNLac9(C>2q0uQS$eY-xL}^_1}EOz%pT}o(QWWo0^7n~ zdI{*36tuFS)p{j^z)$2Xfo-fFLFkoCpjEK`mE!{B6+*9I{*~VqvAlPf|5pArnyIYC zCvL$0-#R9BzMr$WBJsi;1s}j|CR*n_EsL6fUqQ5?=A&1%!UBbs)mSGDzal_3(C{ng z2jEw}l<%vZ?XoC{KQDB8S-P#2Ff8}g3X3?K@Yqm6mTmz?Lc^|5OwE$evv$I;q_nam zbJGTSGH6%BvG9#FmOI?8$S@h!54zXw*WKZ-?`q>I-_ zu@J3Yyy)vivB=Ul7m43-o`EP9TF1KGwncXp9OZr3mHc@W4@tC1v6e)eWOqtLT=p<=dlxLW z*}09wGg-I@*l-s5$?|va?f+lA`X3{YcrrK<$5uP`McE*D9Of9C-h@>Dbf!1-C-2zM zAOrS~*^mO5EG@i9alKjCozC^BN9JAJ>l>b#0$V%_?R@*)8~XpJJ%g^fB8~ix;zgkK z8Id2yT;p?vzZkHO)A%vr56Iobpm*Qcn~jr@tf-NJ-v<@j0}K5czaQxRJr5OyihF-O zat6J9sPR9B=;-I*Tu{LI*WQ+x0GQ)HXGyZ5?c{JOLE*X_9H?Y+Npd+*uS zqTcge+_QE*J>?%Q6Y0*C8a`ExbEux!zwTqe0w>Z=73U3&2p12f8waFEaqKdo5+yBIke*K{( z6&^Kue;ye&M^w_W7h29Dx9EL$F;?JU?C?Q``b7A{_fHQ}eIkv|5sqjRIBrx*2absR zvs(gpP0gJBGG>Lc!_zKEsG^tgrk{AA%T6HPS259Y6no+X%2jk6R>?_-C&*36THF&I zBkEB(k@m?Lg-!kY4I@)#C9%n|s1%f$hhBUgW(~N7lL|P@Y))GlhrIS>j?mbdi&# z=@g^xrOYs@Y)kvJHYQkG{c@U8K2yqLx=P7@n+R;4p<^jT2T5vfe_4J^*+nTQl_Z^L zCmHcOude2gqSvIWMr2nS+rY20LXzVVF~~75{`hKGGtut2cgJE^cv~JM2QMgr+Y(s1 z6Sk56dD^)p@I>qC_LMCoy*-D@PS97(7Q_gs6`l6L!olUPa4_UZmQTdi8RbPz1zjo? zmy#7qM5cbAldOuz`~Bb%NUVQRnXoyhmCLuNKT{tV`iqx>MEBF&a) zIfJW^RotAlu$@P3slf?UX9xM;@HC4#v;1Rlr70i3AN;Gb`lp=bgv@z^(7&U~Z~io5 z;^s~8dCF(+BTNK#m@*q;#_-*y+w@2ozj>SA%x|)58Fr8zV03LP!;TH{50b>UzrAqb z9*P|ODsV$@J{^5CMF-kM|H9g7ndP2&I~MCUu5eZ&dz^DOt6Aw9Sw9=s{xitcNYeQr z)s1Rjq2_26G~2nNCLe(+ zM^^7~g=>`~np~-BQ1P_aDyv?lRPHV6&1*qblWK1yy(giOa-MumT2%Ov0ryB0&V1_% z7YsNyR5<=^E$H>vLF}>Iv=6^0Ptdpd9 zL08zfOvwRea)tlCRCc2OdW^pHH%`Gr*u0>OOqe^@-=I`Z4I(*mx?vwa7YxAv?Qjbv(R6?WTk%x84lR^JC!^e`i`aWOcb=; zSS7ZND3_#%1E}SYbS_Y0QENJ=$XVJ6={R=3PtMfiSJj3`4?KMD{?$3z;RUrwtMES!4-D9}pR+deSHg zjxs`1j1je!q;K2K1tw6A=WJB9(H88h*cHvGTzecv(woO2gEZN1DH0^sCL0l9S;&x$=qA1r^WWx-RJ;4rl1gu; ze*DJ@{?j+VS{}d{?yEt=fAz0zN3lOgpQ(_f+_s8EEK_bWf-^`` zHgtbU%EX=i+?e2_d!^(1&?Z;d6YB}`*zJ*|8!%Gyi?1CiGw5c+f8Skk!|+=?=Tek=ABpN z|I1M1<^=R}$W^;S2WsdDK$ygPQGrXj<7FIK20A@ee=gv%&<-a_EBuMbRFgUaRzLLm z*M5#-M^K&l&)=)nnd4Da<3T3lT?gj>OB4pbJc zzV<*~$zTsxa=@*dn>Vi^U_riOt><3c`T6VI*{k`Bdt(sR)UCwG%vaiu1l_KW(XAVE zSvcShBvp{iInmbw1Zf`V;6GZyCx1VC?`yJI(W_GwnoTLvsMOe8*p^xjJx8J7G*GH+ z?f$R~SuZ}i$G3!K&c(=V8*qn2;q15Cs!4)` zmO6rVY4T47o4 z`Jj5HYpi}x$!sFj`3KMBk0-}hzsLDT35S6Q8o@F6)0dSZMvHw^T00+WcN5TDp|8H9 zrwU32iiZ*DF(p_CNhl>7a?|&ASd4I1*?Z$pJ0nveHPd*UU!X~uWIf6Jc36l{n$YN5 zY-gJ0j=fvCD*ZJ4(U1>W`kM<_DCdfM0cpK-Uubwnt(94cWZn=MNqS%JQ9bSBRgWzo zdUJ+9t+@Kxv_9J}IKw+C`qL99_;75)fMbJ%lNV@|(_ocgAtU_Wk~HF?GrV{}D^94s z&?n~@L}&Q^*m+f^;xWTxW@|E)Gm@%Ufv+c14PL0pR4pw7Wh!5bCR6p_KbF^h>KQ4% z1aFP8qlPi;s())?8eH&JzaETSF8iF5D{_kWLb%s%1^{Of3ft`ho> z?hkx;+4RNRJv+kl%_v=(Rhi{kz> ztn%f${m$^a1CFbFlj7R?kL_CGZQ)9#vTk`?P9*{)}p|DaQmX(Q6#gUAev z7}{6l`MpwYL^^?*^%1Etcv9YAy?qaTKN3_W+lcgeFoa!BZn|X;jm>tMW@R7lcViRv z?0%8?xZFy|`#^O>Q2!6Tw4sj`s~p21+5C?6ju4RM5ot@%2@g^4v0iBb@@{xw{hxvI zF0Eh6%oddvLl!Y9Su)ZU4xl0j^7lD=b$``2qF#SA;;pFL-vHjjiw=3{&9mHGNuHZ5 zZT397Y`S?yWYnxf;qjb?y`#0e5^^-l7%61Tiy;3WpJqS~sDvol@+r&sWAo-KS1%GX z+~gajtRnsIlL>#oD))s2^XF$g8j*e!%!I`tB8^7%#2PjZBX<*yM5GZIZyiOAD|kPO zMHb3w?~F)^L3g=^mY{1Qk^yb2V|;~d=-|f+CYsB>2N)l!&cNb)BB6SQH8%zfmM0ON z!HlPn0ct2J(-4wOKj|r#7O&FPtYE;+5<=+GMwk5ot)LPC2HmTG?R z4JE5cpb6UTh&cH7sqXt67uq4y4?hansnnCr;QIY=hVXMB3olU80agy(cUNP8N5u0e zMBCg{1RP}5xqxu=Tp-PlJqS^!+bp=38CXWehW8<2=mqG@6{zAVAg*ao@E|g-WM#vy zZy~*hYR@mUa8sWOddo9{?_xw6dQo^Mn;P)M20TrPX;R~M+^4U!-=oOubJFS4J%9cM z^|va-+b2#u(Q9WzJkKJ^S(1pXC2Ydjv$P|O7e4@+33*sM*`s#s4!PoJtONPVN+3}M zHj8%TYCDcR7d?g8nmNGg9t8pe8E$JPj9?S=J&-w&Q?B_8oyE)mM|(;hZ*_^trhQhK zD;&94=1iryov4%=rDKJN6{((vxQ<1d#;N*z5qn+CMo$u-n42>fG1!dOky9Re&;59c zZudO*#FAgRC%PvhTTb7ac-Xx>IWO{4PhQK`^<3)enwz_%IDZ{-b=1wZl?!_!(uy`F*yeSWT`zwQ_rzG;N@kF^7!g|U87F(bQ|KE{!x}zj(Qb6WY@zw$UYQE?2Xixqul6Rpc*-S!7nhA48ZMAU9+7RgycD{}q~=Vb|&8I#Ri0zF96b|}s{GcE=_xgPm_VzP_&8bR+zS%A*n{-$6syp` zhBQ56Qk100fTABCcEotn3zXNNt*aJamZvK_9#vt8ApajpD?1j7B72p~MD|(qyx5TMO$DFGrDKQE9>z5ql`@es}t{${4K+DWiNu`We**ths=k^4E*q z02V7N{_FgH`Tt47r^*DRV{Z4M{@aLiEqq94o#6T!^0chAg5hLY8Rl6Or3*l$m6INS%uR!q7-w@}5Hc{#u)lws09PL|b|B|%+VFwyz zu3eBH8GlVL=-CTJ(%OkW`eIpBRy)IFBh z>-Y{c+lZLx9l-o#iho2l_O0S_SIQaW{58Dlz`kWZTt6C_M^|!1BK%ufd^E5jwjvA?lTJX{P)XWQeyaIU}^SO0j_0bK^I-#M8+0 z;lq=VBV{E}x-X&L3Nycpz4sB(>ALN--mgRKUk;*8tJp8Zr(D^mDL)O`0CCE)(%Sh6 zm_MZ_y0hG^h6y9i4DzuFz{TB#9x8q_W3v(WD0WT~kGjsDj`!6C^B_GMEx95cIewac z=R(5UEB?cE=!{i~riav`UK)ssL}yr+E_2ILmwxu2m09ysVO=+ zD%}Ykk+Q+xFLJ;paF(bvF{<}JhkUGFU_=D00U`APk)#j3f_-L%cKS6nO=Pb)kVO&x zMO39)r+5azr{-jVb9kag+>G5C?g0l!G>e3rv?YwV7*uJGdAz3ScXz};<@ zML_|3+ZuQ^|EA31sOHY#UzKkuXK;_~O&IO{@NGaQ%p%@DH&~TT^FgZt?@|ctTM^Cv zj(VZJ?mRcye9Onp3&mNeycx?fcLbck--j;^?f>Ye=lrui3f}WZ1tpdXVNB`2ukW!( zIB;0KKr}!-8>eBA_R8QOA;svJc z86{5%mv^&Yto?Q~fhA9kHJLCzalpZ@G%*eULAB3-J`GuK_| z+2$5X3QFB2XpKEkho~Nva|?~Da0LCa)RulzN@t7n$Y6o^ne}<y3#83o4vR1j=*l`sQD~R6HVGZr}DW&Ft;llgc~A0jY?>g zNu)Oq2cDgUr&4Cz1QvVdnG>9N@?z5H;*=WLK$#GlS5eBPaowg4lBOn7Z;;}$qnvmV z%f7juPv=_;_!S|Z=cev0&vVAIx}_#Nb%-)R28{%~-PHW3QehW44PbUE1*bMyI`kH9 zlO7enypRlN{9blMJAb~LZLDg||@a@9%r_!^aCXet>Ju#1k}rVC)zf;|<7isqltA zo)E9_gju~60Er`HfFn%0PSw8~lg-EqMLa>P22MO7aX??M|jhSinlKn+n|3mt( zV1*-?6^@{-VBOg@3Z}zy+7WrXqW@^9tEYN@t(zjxXl2v=ft{KFEFS!B7X0S0w)?7j zXE!o|yyms@e4ccyZSJ~jJIO5cuI*@>Fzd4uon}~g+u)xlaEK1 z+GhBNo8>9CX8F$Q_-=jIO|6EcGw#*b; ztwWFJ48`8oC4Cvy)&k*;X*G07HBoNk8?m}x0@i`2&5eH_gU6nB=q|~Px`k>@%zTd{ z5+t+%a>5t6y`I6IH3Mp4(ONzBymIthVEA?K zN^w}Edun%A?|heXXZ5a$W=CZEb@tYm!H?Xrrmn_)sixjibv?q)f3RPTQ1tnVW(iSigmy_Mzh@h3zkRv>h%{QSl{(+LPGwm~WT&W9-0hB(dVVe$}|{zM2p+)@wiJ%=X!Q{ zX5|y_@42```K@M+=z{;>aaH|4l^>B^P?P1B6VI=$R_c-7*pKID${%DHN?otEPTs#LlimI$%!O)V+C0qU^ry;Kz?nzk_|wWeey3l;M3|q*Z*Gj$-YRkk0Ubq-^e~=eo*!q z`UCoqBRsj{TFT73Mjvv72M_4$_2`mj>uP=Ij{aJX!tryjs}23Ae>;Wy-*jDVXn-uG zv?E!nPaD!f8=@E}$WmdqyZ`8Ft3W8BJ)r;@$<}yk-B;^IBNg4qUAncj;5s@{k{O<~ zm`*gW#SNY4d`v!iq~*}#L$w|A)zfvWnj++uK<+MXW9!e!^p(-0HD`f)Z|06vF>jZKVmqjt@SVh2@ z*CtSPmFvkxzi>63m?Q3*XNPv+2TrI< z`TvdLr&t?~{~vOYP0IneE)x469Z^nSrbI-*IEcv zT8~*0dTG5+BGR2btmzHmr2~4R(Yh-C5UsS?=2xI;Fot%b(Uy9@(=ORU~6H4w+k_aY+2o1E%!q z0mDcSfDXW=k{+PR|3ufcE6|<>+9uoRs2`U1PeH;siu)4BrwW)v+h>tCPt|;>uBFQH zw_au&_x94Z0NSpAr=0kGr&RvwK-z}v5xz25rR(d<9;EN)&Ev|wy>xwV&=Edj?N2k; zqw5vo6h*%8MEyUKI`Xk!lgwe8M;5O>ZPyBmn5ykwt>ez@++CvCy_j$){9{{auG`ZI z{J$eS_fb4wUjd2#H%?fXN#+mdUL~D;4w?TYj`AX-Ei_=Ndry_h9pU%^y}z2`wVL;u zte?WM%Urn(Rk|VLR@L@);%V z=@ZDhWOo+##Dz~LyW6)-H~No1!S@V9UCT+u;>d?^oa@m){1q~o9e;wJ69;=U&M&6# zFmeR<+ri4 z^mcAkRe+m{HmQ_7MnCa=pn#U`eyt@L7L)#(^KG(LS&uYl12pGOsV8dKD3Z?NeK%jV zBhg-QIHoywN}nMYx3*X0d-5Xh%>^y|fbL8(6h>(-H*bl^mDR#a8PjU;WH7kEEV#zM& zMH~ZKX(Pv!9H%sXq)#F{=1ZPmldM7>?oR2Vf6X4Yd6OwaWb4)3CSP3M?l3g6O*VLT z{NOOTQK~NAimK>D1DDB%z3F1ie!|9+Pn?BriB*W{ln(4ubGP!l(?+Gk(}cXU=-GB< z=PxZI+wxX-pUzeXU&9i#p?O%Pv>9l9_QGn2xGcm!QQid|>Md>c%-X}oR%Cb|?u7qe z@GPCo-MZEDf+KRGQ(Ea4l)9zMJ(TqX-Wd<^SccFyvX^kVOKp{o%O`sM6A1(39ah0x zf%wSi4$MFDNwpVyr;OR5`1?MKgnrFeqxRHfZtQAD@o`{;ki$w>Z4VDirr6p4j?8$R z`Sr+vVV9@;yg}C2?Sx;0UH*L~9TAix^etu7w8%HJK8v)VvQ)7M{|lnatUo^*`E(Xt zLoH0Pwq{(5bHCJ%r`&vRxSabe^1X7U9#sJaEF&o z?WBRR4}ChG7L$U{qcy!b?)2iex##EBB7?*HMn_w6_RLUy2T(zz zVJA!bg=k)9?%`pR$H+Xv##+`>{gt>S9|i445tU`)eYVMh$9k*HY4RJm;(*aIbm!n* z$6!AxhR^a?U?@I$d=9oW!mEBPV8tg7Oj0GjC*r%_V%~8q5N|nlaS#{3WB6My1Pn)C z2ckyfwRMM@5mb<^6OJ^20@5uVOD$&uW()O+s*97=C2k4iW%E`~rEB|%|LU-* zIL4z|B#bI0Xgoz5j~D9x5HRc-36$6$0)wg{f=*9*X+v(PItAx{YNLdP69!&f~3 zx~!DH!tav-R`rqROT5de04u^fWuFeP^5V8Bcii7Lb;PG-zb%V@<%NK`D(>ZDfkEZR z0*0bv0bTa705ZE*V|~a8y%9R9Cu(Jx#obV5b(4Ql+YedROw^M#RbwuYMQW+3Q6AI4 z#&V-~*~z|hd!x<+|GpWzw%O2QnmBTauET9)qhV7}u&#&!pqyBUnC7Ow9^lHX6JZNV z6|!Fs@N-@d=n7v4o;5Ez9M$cHVK)+{=fVCu2KMND=oge>3>F^qYRGHG4ikLq+Y(MT z0eNTUZl%0J(@6^-`;zOUCUsQ%;-1b0hFC_4L&g6M@Y#O|=&Q&UN60fmk#$OMUnH;Y zvY;s&V@SF65s6ZpO_5BWV+1jpidF3pMpd=^v3eEn6=%m>mv_L{cNSj!_kyhaY=A3b zHS>`-{cON+)E(Thi!$Kb+wQ)@9bAnPbiY9rpGSkMixvjAAI*Xt@OQy?cV*%^Wx?(K zvjJVX5ZqSez)Zz&M4g{18zT397hG}liQr-jaSjxicG)BfZrE4hP?^0(BNG5AcN_ z23yLP2Xtk<{OBsXRbE)l3afEM)o#@(Sz$FGyH#Fflk5N8Zj~2KxsG+XBJ-d|=b}L7?B%N$m>l~I7dEvJQ+(px4NXq{q%T=fJdP{%HRj2eyOJ4ZV0r$}~ z6m5O1RP0tmk$ZE~0R7}?=;H21UXd0dDZ*DVBWAI>p1q3Oz{ap@*r$$SC8a7>E3#I} zI>gREe#_E9ese^V6~7-@ph&L05f-e2N-Q90=X4#l#CMBcfKFq!w;p6EPVFjsZ@P-h zo#fQ)SI;{>SL|12k>oIv!pMe|hTV2)4>FM`zC5yF@ro2SrH9|RDYpOCVt+QZX!?u} zu?1wm$_r1v|Nq^7bqVqRdEqNRunTMByuOaBp4oX*1x9+^CFw}JimPLxPrZm}heS6D z`@tpYQ{3skJn~LJV&d|`Dmslk6_yUW?rEo`!v>_%ec?OG@^NftsKInJa$6ZmZG>AY z!Jm1jH3!+IblPuf=fV3`1eGkb_{*sj4Zt@p!MKZ?>HG?v@H04FQ^|NErDaH;NipQX$K$&e09gFEy zB%S_GGL0%%!*71t$u^J{r;V`2E3~ybI7U507vwoyB5HbEMzgm)rB@!k8S-eKKPTlH z?`F~fi=3(dc}+$Q9}@a{sm3j;nxB$oVFaE|`8Kn+1oE?>q5XJEz=3sOfW?=GpL?+4 zVaNG9kewrowR;mHSU;3m+lyYqqBm8Corun(rf1eVR7RNFzH)kg)HcBzh?`ni>WtP+ zcaR-y?%Gm{nc$+6?m94+Lq;nvazS_lgQgBb7M-n_jUFX3Cw-3;=`(*QRkNo~_iq|^!x4+%}`-4y3sjaiM zF@9zOKfAp*s^XHAtVC6`drb;QGl8Q)pN0P0>^%*gl%nDiI#Lq#)#k2OD`@=qYIBE& zv9xS80z(+`F?8n9pZN4O6g`LU{^h8s^D~NaM*W-}4rr01p=8UsZRS@J5 zosRnz!KRNovfL;ma)la^Q)76*Sm=xnnT|Y}cRdf?_#s(8a*bmLq9?e!Y;M>=j3=7( zV>86H;Hc{TabJ$Jfl{*^QQLH`eau~?&tl}Sxi;STuO_-q-?e6`scX$LjE$R{h`bE9 zU`B+Mj@M71hVE!Y4evcIkGDaN8l&$vgmjM4jfN1%HU*4R6Y(s{X0i$S67U_LCh|^D zkBJV8z~c+?c?T?5Q<3>|5n?q!f!kq?eFGXcthbPj^p+1C$4?*!^DW4qCEO}b6b&0W zr>Vm7AkhV}PVeT~ZPrzqV1w=$4FF>XIaFe%(VPd06ght93h1^j{u_VSdytOh$( zSIsl5!wp3HL*i#jKJH5r7tl>OQ*ZNJlD=!>ZjGO8=@@A-O$ary8gtj0XJ{@`t*3 zsc(a-0TicjNvdhnO)#8!=pq;UUES}!{6FwsXHM87lddB#X!b$UDhK-C7lM1~*ihtK zyCnS*b_I6J-HrCJ2l!bWH?y?+ar^<~<0o6g7{%5An}gzkfdx&8NNWi5imyhL&Bbnv z|DA1l;n`Ec6U}6q>vmG)8rkOTkynZbS{%q9|72Pwaru7nYWBzz1N3=~rz`JZp!iyC z457bVH=hsr>34Y6?8&={b0jvQw#_dsXW$|Be$=5)GB%TJqU{DZoVG{w1D>YJX+N9` z2A+&+R2f@Kes_;YaQDAkQWN|W@WRhm{N*Islf-{X+B?7}{uTU;wVqv;{-Z*NOh z-WOTxEo9X}OaO2kN!e+O&INvyO?tFF6>+ac&?#b?O|r%JD5CVuT2#?ZO|_-IJ)kF4 z{F>P*xSV0V2~=fqm$Ni{=QEw)|Ng#TcKnX>*OqdVf9_oE@Sosoih(^)>@hu{e_Q|k zSlw}kll87%t&{LUFTP02qoL8$J013Z@xq7|KDdE)Bkyo=$V;YIYUEM<_M^Wl`1Hdo zhA5t48{(Jmt2-vA)a}Cz4ytYl?^t2#5^_+XdKbpz_a1z^`>3g7w!yH@)Wwu~SzL$5 zd|&ucWFnaA2UeUuH_t?kmOLjLa}1ek64w=k-5eWD=aOH}b0*-*+MGvn;_>OtnVn-< zXX|I> zPstA|89JV9wr(tl=orh;zjhWj?t=WJjc0+m!*0r0m*54T)4}%9kd7y0+ zqGU#3y*5Kb&qdsILY3)Os`$rE{sdKksO2|^sW|{@KUhYdSN){pfn?zCE@YPnE#+d{C zM+au%>5B=-8cpApUL+c(oF#)dC2wvB@A1Aat`p-oSvG_E^q?^Y+Y!o9g3b_y%`vjW zg3DNHutEt_V8`wUG!kVMEPKdx;~A5GwUN$K?Aon28l4!!RLWU~V-&NX?2dFT`4A|l z2F(^KF#wnNysHtE@*whT7K*MGY_kc z8Iepyh;AyY!KgijaV3ih@fG60a}fuB5E!sim=DYY@gw9BueB`2A4Y4RgiK?z+ibfC zgLO&rw?KM<^^(;~5W@LrXXFTsnIIUiFfQe`it;be`TsqX^ zc}GX{dSs8_fcn2I<485u7=Ko_H((xk%B4NCnJ74EW04pS4B;<;r13JtMyr#?SIbX0 z3}YH!j3?V^lpf1>wb_1#`BEf$niEKD%KT9O1Rt&ce(kd`Qn-e&0~Hu6s=bPF29-CU zW)d?u;5%C2oQiIxd+2kxI~03Zhn{`X^eAwa#SMt&cg1a_obrR3P2xOp^=$4AaTY_1 z%S~%ikY~sLJO=~}su5q3#ZZEFmz&I+ zO|K8C;U@mf^=#)0@6^FMKV(x5o;T8VNt)L)6#0?@D`4$C*dOVJv%-+KY~{qs^g_PHChmWM0I8DGf1%NFzp!5D+0? zK)~?Q5HQ-*MuL_Ypeb@Fr5p<$N>M3?P-@Xq8W9mGwW(4Xky=Wr4KG!S9z;sX^Sx%@ z!)(y@oZs*He4f9aftBmJ*1g{Ly4PNN?X~Bnv}NLbPH*3TCx?zhJH%Dv`?nn%lFYjZpW*I?PvRtX>+;X!{57` zZ}SMqOIPl(_wdP&Wqck{PcKe=@F4pWpWquQ#B1Ii{$8&uZaX$S_DhoE-ei3I>zmPC|3crZz z8(TE==FYS)7Ia?Cr+L!&-H6q(9n?PTpz66EPp@^4Kpx-l;JdwVoex?W!<}~*+{zhs z!JZV;zTuxJ-18<+!Ea59=>6r%QT;y9SBB-~KuE0b4acna>*Eci)rzLc&-1^QPOMdPRD;K>I-k)c5G3SFZ9)+*B!Xxcv6>?@auo=dck_?fbX8+^h8p(rQFNE z#!e@BzjEEX&L?X4bpm`beHXLS<%d+VYVq?c2;INn<6D4wd_ljJ)5bh?1@G$m4LS9G z2K_pN&a+C+LCstA|CPFKT>Se6e4p=yRPoXO8g*Y9q( zy1&hxbo|AngJ<%HyW;O%-cyQ$X9`hx)mi<=M5t%&mI&ENj`N()aqc9L$fgc6Qt3E_3Z)mDZMNeaB@7`hbf4q=hV(P0InLd);EcmpsNg7g z9pjJ}=W8oL=;W{bZezZ_x0q9+^Y@=%5w6f3AEjdD_4Q9W&Q}AD(*hnP@QUpZ3f=QQ za|m-mzVgdBFdct!8h#1!8Z(l7ZYxATlQP<9|Rv# zc^hlV_W=0_w-9b8OeTDr@NW;&hhHPE&dvL6@HylEww8Qr9Onjb8o`cFzNgKrS{#%$fO^MmA{xSX8g1^8q1npPPx~j`f z$~XC0+Pj4@3;uxMzUpbOJLdRaE$4xsQZM);g1utx{qrw4Mn=y+!0TG$CdTDU%n3U< zj$M>Va9=7jcK(1d?x=p*u4&^uc^N;06A5vSIz0mS+CjS!jd{2JqhX{w$yQleebPZ0i2y5S%n)t-@J>2qFpS^~r+ zd(umv_qpW_#-4;e>r0d;z1i=!bG2a_1N}|PJ&b-2Y&YzBnOn|)Hm9ZAWsZ|{S)v^2 zsM(XT=c02wcIj-whm^Mud;{DJ{u+GR^h)v{f^)LBJOM5vzcjBk(hm{3Bsnw6NtgB} z?f$LPGM>1Rq?1-U>B6p#!vnM#>0RA)@8h2g;;!nNNGIK$Aph|Gg%~CozE2s7Uq*h7 zhjp(yEi17#lRrK_q#q;5U$ZUU9@5PtuF8{6dWxlc3siq@v-A&5&09|FNA;%cy|kj0hz1OXp!vXHseNIW2|gT0Si&PdCH2sZ;%Q0Mzk4M*CXn@1FGUI^rIpebOply4uq3>fyF?-iuDlo*s^~qer5h z(kspWB=#rJ%RP4KJhM9-XFfXDW0#)IdPwaVXE=akl;1X><+mpdml-ZFeD#xVyJo)N zw7f>#o=+y)C7qe(+B2{R(;O!wEn$}q5Hz2iC2ee8v-DG28PC|-Jn5x(V6W=Q{Ea{M zptoU{R)0xvwET<6zXiSClV5r*A$FXkJ4rv+&|kI0m6A?cYIms4`uK0)ECpN%T(V+!|~)%f3>02U+;X{Eq}ywPRl9cj(s{& zzVuZYRzLRi42~xwVVBM!sQd!bRdD_4NxCB929r)&$0I$+(w+J@cb-0MxX18m!)imv z@C5lZuOBjeHPhAG4c8g2G#p{L=`(J+Rff%mCwjZbQ@Yt{IoaEBI(jFLNBSW48ZH7$ z@b6*te(ch`)=2NR{MF=t6}`igUwS)1{jrI3rTBB%Pn?#m#BCs*w91iQPtbh#gkdeH z^Li>MzmE^P?R;yK(=v(r#|9Jal+GpSeA@L{H{HAz{Lekoj?X62Ngw*0+n&9KOUWny zC--svf1Y@a1AfvghJE_D<@Nk8r=>4xdiP0`Cq2pR2R1q_Q_$l*cImMM9mfd6^Ok-v z>61RsdeDKv)Y1yUQto-SezaPEFQ@->j zg8K6@!;CDqKh9By{InnZ0Q-EyJW%~Jh;-_g9K%srZoj;}fq8|vky(j;ksd)%IW?AU z7wH}(Zl))l^fI%r$G!r+#ABE4e!cTt^m>O!1}SU&Edq<~qxL zU^{v*c4_%ndb{PXBmYkHR!@HEEdJWUCVV89roCzyG7l0o{6|v z%rR=b^&wvUm|-|P>bC3DYNurcaYfNYyQJ&P-io~*UF)$+*AO%x<=)_y<0oC-4XjUY zNR%Vp2m5+(9_L*@uore|^_O(O^7kcwPjoj=e(A0R^?MubX``Qy&@Ua-y_@z(s~qX= z1oig@Yj-o{Z6a>Hr`^)Ct-ihAcUo#FyUJ6ablm{AzUhWLh*SS9K&$^&3~-Nk?GsMR zD&m$6NF1;9A+wLeegu8MW0!vPMmPVn|KYSeb|d=@Hzx8+*I-`@YTm8`XJePvaY|3c z-obI#u5ntb(3RMwWtT3u@()w~c=Sk5`O+f@>>bR=Bi*6bY5yw58F2$hC#`a%QwZwM zez|UcTx+<4c=bmvTK%yx*FBykl(U(*r*jj>BR$UShp<(hv6Jmp*Iu0_^9}r#*J*BiPdh zx&AnTK7?Ib{Um(=`EqPKaFxNe(TF_eJj9r>a7J|2hXvwBzzlu6|4buoQJ<;^8~2)Euiu(viMq%d*~S}t=`qt zyPA5Id+L=wobUES*)pf)L_Y1vPxOQIk(=H6-ZI>Cvzxy9+pG(S+jVmyz4Q)Cw*llq z*^HH-`fncjGT!8R@g3GP#8s0|n%5fXnFRLyW{f9Y@o#AddM5VxzL|6dLF2X@+z$>1bv+nt@qI1-RgW|86W7O+UpkYZdA+-(>qok)i0kG_ zCw+E^+l~V4=Z8?|kVHGA-!}Uw?8nfDJ$C752{FHe+SjVJ^gBskM_i33y>un^67@Is z>F5gV(&`uK!G*3LuPSu?mxTX%5|>h#@Sk)tLH;{${wu(LNyMG?_)mHi<)~keG&?Qh z&?6})-p|q{1Rwph5Y#xYvwBzHw|e6EWWa@SBi(nX+rCUt>3f2I0K1w#W97U9{+9TC zre6g$J~vwVQz(BkaqB(hORpv9xK@x(<8U4AeuB7Vq>GPB=`i)F-v@!Ofd$|p@W4>_ zcqe_!X?csd{X-MSEB!n{`Idvf0jn+j9@5VvZl))_^u8juys1lA*A zE2#P4X{7<{LCq5tpz0Y6o&bZO`fYZxTkhUPtP_e^Cln{jm98Kt{YdaHU;)?xh7AMY zQS|v?Zv6UTZh6ZVJ1rZCdvaK!Jn3;}KZLykUFNY%SKs2cs}hvoiorL)LAN;1Z5z%v zOmSa5n7HsQ&QsDFuhIeR8c%&Wj-Kdl*yH0%`XKGpdAtYIytI=z9sl#7>RC11&A<9F z=1a<5F+7o9`cZ<$O&zHItu)L7)vr-d<7MPm+tT#MV%JX{Kx5F?W)cr^{ zsQZ)sr27+kn_=(U-SVdxmKiQFTxQs6xcYYXS7~S30;gp?ajR};f7RM8T{zM$uMenp zWPrNPzK>1ymZNo_IKtA6Gkg$K`l%z`@<%i{Ei;L$9GNIzdI#YQ>5hOZw;fb}o-^!q zhpTJAnEejtIcLA)WX^Y54ieXPhx3%Q`dRv%+0Q-fv^Zs~zdUy7EP~qG3sgPXhGn4I zJKQk0%&liTw}Xn`0lp6wmb>+iFL%pd z`E~Yrh#OU&C|`OcLHFq;mcGvLEyI1}&*yqLsorThKwKO7rPUA8doA5gP~|*tdb8nr z!zGlHN&hXR9m|M&jB=z^j`T^Zrgj7bXgb4mPt*P}Zo8wD zmrUGwPkGX11o>w;DF2ijK4G}raEW0ZsPywpS5m&(zqyXEd|sOe>ft0%eT7SY~y#I2f?C|CNRrE4=hY{*-&rq==Y8{J*Is8>YM5_0$S`b} zZ0Mij=4&_IX1LdIr{Qc%chK-P!_|h%4eO^k&*l7%aX8y)Sx8*n6z3^vUTdTa?{(|T zHSA|tYPc4ZKUSJvZ@Ay^Rl{AN`eDnxZhh6WoR;T_+jMWDKIuNw-1-iI>M#Fv7f)Gy zCR+X4({SN^%#-_AS54*p5BISjaUb&}TJ4t}Z}w%_r=mxB?9z*>-14dory6#j;rj73 zTJ3n-;@>jd56aKGOm8<_Z(E;Ot+ zoNYJ-RQaP!7aI;T9B1tqX}AH@aV-Ni9;?Baf6Ttya0jUKc$?`dHE#Ql&2iUR6`+o% z6x8}D489Kb0#*L=)vjIy#_o4PrLQu~u6E~{?)Nw?VdDB!C*~RH?KQ5wu7dps>VCc^ zVVCZAzq@|R0JSbVGtYT0_$K=ul}^jKd0bQHIZsJzJV>7;Xgzm`bXxC^8_&K6aqXmw zum7cY6V%RT!-a;WpzcqvC4VyQd+YB`OCRETl3!Z)r_urJg>SGQ#BudPCu5gZJEWQa zV(Z#T6S$9<%RR>2#BoW#N>KefK=uDdOTUEln~8hclU{nI*`LJz1bVs0F5OH}d9|SK zBgb0$!=xWi+$c|a>0<1UF-T(pw2?e_^eg?#wv$!)hHTzc!Ihx~tj4 zl+zuZz5zOQ&U5~6 zu>RuthM%}|v_o3wi}XQj=lC%^@1YI*J?)fUP0)Sd5>WHNeDdhHYeCg>_#xLX?V$SM zRZ#uE{vr4HmyP1SmbkSKCE6>!(Ck~VFG4Ty*rf*$l5Wmk0NMawg^;v9$et2=f1P$p#?l2 zS&&FCT}@EE6_)NG=_-jE=Se4BPEb2Wf@)WurFU-UeKy49deTcr2`V=W{2l3&NuN%e zx}M{FC+>U$?=M1Y9+p1c;69&v%WxB@{L4Tturro`|Dc=&U=q4ccCZFid&Yw5&*2R` ze|?j4AODRcu8?}9d99Jo$DWNoguNI&2)nfG(gO(Ux89`F`Q2+I*A3!&kuH9|OQ#cb z-Rug=pYP9i`?;9(XYkp{`H6m(K4f<8t6Pqs4|?p<`w2SU9iZm@RhE7==~olC!joS5 zG3=V>w_kr|Oq>!>#ncj&nNa$9Cej;IH^`OK-xieyYK~4ZR+Fyx*kP z64Wm%NvD2T$2fU{xaFja_lxv~N8NsC9>H_5M=9sgL_bKcC8%BFLG6F^1GQf7YWVCU zZvIDaW8L`(_kWKh@=FgVs2%-4^;a)Y{q@c_-Fzc%<$W;UWG?(>BA@hrvroZ(0R5WB zF8wM&<9jFRblg+#;QB^fE9v6>Aib5K_HVHC>%ep972xF>r{F&Hqu^U$J@^N(3RJ#H zp!P`ySv{wyXE1RCJoQLli(URn<~+|tUxhvHFX`?C^@E>u>i<0YJDE6#ba8)3vyT(?bv3y;@{giWNp^F6K=G68jVA*tdBs(GKY>vj^y>9CXlQm%f&ue%WN{ z7E#U?;x>5FNiQ(_v)CU)*Lm#HH3XIO>SDJ&Gl%hh!^ONWWO1TB(z^({pKAq`{&`U2 zYrW-LM!pTit@Y%SW?wiq9@b-Dfo}HLrR&VT9s7LrJda&^5JB}hmTpin>lxzCE=sgd zx?_=xeb{%P!rM zp!4BmlY2g}Z{2dPiFPz4&IjqY2x@;jsP+0DQ0;miRJ&G^U+dW?$^Qg#%gG;K-%2ke zD1W)7J3_i~#PRJC7t%==n*9v+VsyU8F5R7=aeKPkbv`Vl9QkD}es3mjKIO#yB>fO}jsJDnA4S(U;=V`_u797IB%L_Df%D>60y8lyoV?`90~R-^M>0->;f~ zd7jqt8gV>dyKtUJZ^f?h{22D_=q=de<6HWzrEY(YFJSyH<$AR=kzab3+2>*3gWln> zOK&Hrzc-Uk{r3>_)w9HHBwf5erJo>ZoIPsk>p}HPHK^Nxw~y-wta0(Jz&Z*F(g$Yy6=# z-%0m+-0hcM9Cz=>@yp|hev$4;Q2QOjafZ7)A`&TCH(vO*aGxjCuM?H4wB(o2=iO)Ns&wn?OU;3Tzy5*0+eu_37^Vp?Z z&0dFnCwiO5F3tO_V*RnpaEoEJVTIwVPq^uJ7|u3a`F+=a&EKaTjK2-kvy8Y!->02u zjVtMe1oiuT!y3cchPkUr$v6pv8b8^q-SPsImqT2i)rs@x4*We>v)b*K3Tyw{QQjv*+*nWhrAJ{` zIR_XgNjhm>YowPFbpDKd z(k<^;n9mJS&xj`zwFFxj#%r~|4t6~^TZ8bo2XxUzS*D0{wVq(k6rpfg6i-01Gk*+qznIm_bL1!QI2#5 z_WX6+*VC^-bQ*SPUTdVgTK*{cQ_zklzw|lkQNM1dJ)7vq*XaKP#BHWM(ke&#X@dGS zd!5^!K!45~>d9J{XpeM|pmwJlb_3Oa=c!BBZ0*@V{$<21^0Y@fE{XR z@8wUq?cx33Ei0*K=~IdJNY5wyopf1-JFWiOZ1w@DtJPD#^y;VG`j;3UAfNho16uvN z<7v0P2m3lLyNKKVbfP}#BW5qh-hqD0W0zj~jGO=I>)CI9hW>vhkzcwVdvpW)D-O>C z(Y4s6b==a^Eq@#NXQL}U`K2oe>c0V`J4t^%+0SViPFz3INvj;`AVK}d`}||`3h(=G z=}BC7PkGY4Hn{d))YqGKT)QFBUg@3$jgPLN#_xVBZwl!TkpDGLdD7`Wa@*%O97#U) z?*O#=H`}o2M{d2RKTrFK%l}cLUg?L-z8(8~^n)I|^gM#v)pw&?&Q+xAw~_1E#zZ;N z>DVj(i}D$-ndqyqORJxx{g!_K`MaUdd+L!sOFioE=V?#i0P`f{YCmxsNhhsxq}LJD z-y{CZZO=QO<9wu^;s2Frk8~kH?H&NCKMz{{6@6GQQeV5L{nFW+-1@IIb>qJYU9O-QA zC7U_V>CY%Sh+SI!Al=jQ2dO6m9q{CrP9dm2_tTzy`mca-be6jJ&>m@(BfW#5{v5E` zZBJL)Gl+V^n-lGk&L*hcy+HNnZmYkB^n0mmr>B1DZa;SGKTUeIV>+nu*@t-bXNKY6 zA6q|MM?VlZ=*NkArK`=p2m3tqOpjf9IzjEq+2WRyLb~V{+PfuDj&v{V+rXvF2YtYv z*rnAE(#e)TO#bfZBu{?n^VFl`I7NFZ=)XyfqqD@lLwlrEj`T6b(Vmr{`gN6+yE>D7 zYRXyRDOdU-vv0>fAN`=mEmL8+UaY@~d-U1F@k_Uw zy$<_M^fr%OdMiQwIQFM*IedPxW&BU6|EGy^q>HhqZKXW=u@s$;U0TO49k%?*)RT+O z_T-n&BB&oTNmoz5l{0>V#9d1|X_X`0)6#VZ)sJt}KHYDfwekyS-+9?QvaN2vtjpl~NL=&QM88NcvUFQ5-2&2W zBW{Z)o%AM4*JgMclpo(VJn@`6eg=VR-;w8C-0(9Ory7OtpQk?QHiE`uD;QhPS^5p6 zUq;*_PkQNtzi`{P+prrbKb{8Fzb6d4wz};aa5dMVR*t?k(Jtx1WSy?M(q^jp}ad99It&GJtt|9ExGPdlY-h%d9hAPS~Xn zl1A;{Yq-bKSAT+WiG7zRz4TV>xqE0g&+l8&o3TstS|h#I@-HX<)95EW`K4D9eE4k< z=}zLOa{7H4aSKT&t#YIn5ahQ}pxQSWRR1RLvE#Xt=ifP0mCZr zSLFAD?}E#ByPsps-R+j=r@eW^MRzC4lTK-~@_y~&TJQ|{pMTZWgF!#@OCM0>rv1vT zx0_+nUdQ<>@kyZCwcwZT{pxhuDZlomeEBicu&B*#&uKsFQQ`{P677**Y4#_vKY?EE zu}jwy)L&`8aodv&s(tEM)t?4x-tK0)-SC9PA2yv%Vx`aKkJ9%xonu&N@qgU4 zI>TDS8pGL!Rfbayw;66V++w)NaD(A`!;^+5K()Wa^bx~D7JtyN-EbeMerhwl*Ys|~ zEY}bIpkbzAhN1b>&wAPUv(HHh7^WB|8%#8kT^u{x+2V`R~s+%rne2Txht! zu->o^R5`VVH5NbHu*z_%VXNUbQ2Dl+-eP)_;ReI?hHF9o`&XM@WqO5S28GFkT>IEP}WMkwDo{y_a_ti1P4pENvS*kO1C4T=*4O7gI$%cMIAeJva zj-#e?46_YO42umfJsw5yL}<2MyZ|_ZhYs?ls(P_&<%;VjH)GhWUnhhE;}B z4J!>R3>O&I8`c@t8m=;2VYtk&+3=uYyWu{=Hp4T9?;D;pJYksL#qB43UmS<*4;ZEx zMh$ZevkkKhOALz*3k~xP>kMlRYYb-_`gZ#^`P;js=Vq<&&92PZmvrdzcHaqKdVNxE zv^i;gmm^8h+>&j+b4h!<_@lvAOwEuie`d<2%UZkoxA;yZolDAy=C1WEN=nbp%vj_r z%{b|+4Cb6mN>27i^YV%-@^|{$d>i~7RFb{hS9;LrFU{EGTjujG^wnl;^KDH^-|NfH zIG9|w`m*wz^wqxP~B$QPu8k5no1D{w7~X(g`1J-s)T6JLyXgMz{D@CGGR2=j3ca z@Ajo<=j`;&Uf(4>D`%~56~4*lh%@QNQwffrp zhptS|DQ!>kr?vY0X&eg9O!KGZYZ`Yt7`rKq)>bC-0NC|%$N3ajje}S4eWoBrC!P3{ z-5Jne%2jUqvYu{yPCDy%;`e^qwYT@;J{6tb+r<-~bK@8Fb#YTR`(fBK!)`gNZ*cRs z4RCSiU}2JT;$}B}N2yy*^PR4}Vw8(p$GKR4x5bZlaUrO7)=qM@{~lLo8&(-EG+b?% zIhkkR)Vmebakow7{+e>K?sLn@uX6jReYT5dzUpFCty}&fE?(-l!mqp7@eudlMBs}&HQ&)ZvwN>_204h6>dFQ-*xp{;APa4`#rb+bN|EIV_5us zH-6JoZv6VE-F#bF^r@bTXI%_zb+LV$TknP!-Sn$;Kc@EVam#7uULX_gyynKY8xFsW z{c&Gx2syVI-_3X`Gp@OEjPW+cmcEBl={w-}7|#T!8ZQQC7(WD78!zq3cd;7Z0 z_ zSbX3!jA!GeoYS%I2~~RdbrzreS)L^r*Y}Wp!T8C~aSjBD#Z8qbFhGF}V+vT=Q< zR-y5%EWVG|cs_iD@hbQo#uvfMjjxB_ZCu|)HOcq__`Swg!}%`K*l{)Y<$IuwzYo{< zlByrlzu-7uGhPg@H@=g3>k;F7;rgyqrPp^Xeam<|e3|i-FY>+O#*5)k7+(PYPvd*x z>y2mk<&= z4;a_?2mQhL0_I?yS30hR@T11t;qMrKAO4>4wKuZQU_3XM_evPggC}tvQ8_!|mmAL; z$eeGy9NxpYz5}MG@sgVy=hMb3;hDyB^7!s%J`ce#-bN_y@-K!p|8$31@i4##rDE_Ro!H!>=@+2M-u8hF@*G z1b(gYMexrUUk?u&-vYnh_%?Vy<8APW@%Q1n_Qd9mGQNx3xIU*}U_1vt%yu;vjVHq=8`tOZryDPZ&oZvh-Pahehd*GP&%rwn89xVa zFkU!@Jx$~K?D}HkCz3|tKx53+u?}Pu|_(Aw#mXF@6Mo!g$&^_EwE& z!cQA-h5z07Le`cn24nu6%DVD0x^S zEaUnN`xlM-;bG$|;5QntpTv7vjq9`KL*i-RMaFl+zhe9lywrFH{7&P^ta-;855OlF z&w*DO&xKDjJ{vyM_#*fm<7?ry#&^TNZal4$_nH_#3IC??Rg-yEVLWdNzY$=(9KPJR zJ}dq`<2CTr#!te3V7&NV-eY8Z8T`MDuZM3jz5)K6@k8+K#+_-7^P=%=_-^A>@Lw6< z3xCac2mE)&Q>ODQ!gvt=N8?%Ww~c4Rj~ma2pERCxAKwLRytImELB`AAoyM2J{oUO0 zmNtW-XS{eOamFj)pESM&o^E_A{NIeX!FwAYH_LH8Z`|SDCEIuhJY-y-6&_%GZH?pH zWPIHHj+1Yk&!Rd*jW2``H=g@|7(T{$DSW)~O87m-o8eQ9XFSOI-+0;A zSpOR@tz-QkPY_@TwD|BdTC?kkPwKF<2zxZXd! z)_4W{DdT#7@J8eN;6FBg4!+g+ie;?-jc0KWx5Icl{AJ^H%US;$&*lE^H^vvj_Z!by z!TR5L_IFwT8($6ov+>gJvHmwc4*st3sqpuWAB2BseBmnA|Hj+kU9NP;STXmE$;OkP zVEu2rihIbbjIV}&%6R^2*8j$9;nx|jgZD9B5C4Mk+%>HKjqClJHyF=Y%lhAV6#nIS z8hD}c74Tb(m;8YBzj3`^@ebp`AF}?B$HVV7J{3O6_>~U{x{zG6zhNEdOzDd z`_aB-JoqEl|Hkv+-!)zVf5P}O_= zBjdT7SpOR@h5yueJ$#$-P4E|tk85H5Z(Q#$`la!-AG7{9o(=!4@pAY9;|t+`FkZ5S z^}lhwujZ)n%4b>s8?S@EXM8REjPZT&e;BX*DeM0fo5!}Y{x@C;PcdE(?_qocyr*%! zr{dGb_rWub^Mu>^obgP!exo3^?s}f}zwxY}vHmyS3Lj{EFMP1^`faTLjUR#+8|OFn zoLh}g-Ol>oxZYne+W3)Qu>LoG4nEO%W-IG|<7M#sjHm5j{cl|F8<=Z+-%G6jjpyxR z{cn6Ne1Y+-ms$TC?|?5cuIJc~8&7Xz{ck)6{vXCG;7=Og1plFU(n2TsHP-*eQ{XMe zGvWMpL@doA_|J?Vfwvkz1Aoc5^E&H)GkTKV`fb{(1!`Pr&^Sm9qX9=Q^tIbHCDfGdy5i-xGGVac4j4f8&|( z&ls(z{w3r3&5Z)%`u&Gt#`T*7CB`$`S^pa^ zg^w~`3%|?wLU@Jo)$qy2+u+lUcfe;EKLf8ZuJ50Gz<56VA>;bqj|SsthXTWzF z*Y{q$VqBjIe$}`>NA$Wl<3OKTZa1#aIq7%86tB-B95$}c9sI?(J_DlRlFE(a?8jJ# z$N7BLImVM0(*MTaf0S#4@c`+Yjh|V-n$vjhw_A zW-0X=ui)Bxqw&%qT;q%v7qTugo;#OywechObG{qj0=MUX)DrvN1nttsUVL`z0=H*+ zvG`9UCE|N~_&^UI>EZW!_}4w$o}0zW+~A3S*~8!TaC=@BOMeCDZoHit9&XRVVrjnY zi7)l=3J?FPhcEWBK!{9X^A<>3!|_@f^FA0FP~;V*gkn;y>lfG!++ z_sbIedJhkK_>CU^B@Zv~a0WuXHypRlonSvyIiaBR)(HiZC)V6Nv8rm)#Cz@=IeE_H z8t0#y@Ax=l;pBT}RZbrEai*f`$&)`?8c9nhR!^KUxrU#`{HU2q{+an-G&64cNE81_ z^FG?Nvg(O5=TuFs@wAwhj&gsxGttbEll5z!lW(r3+dN4vnjCk`np=I3$5J+V#_U;_ zCXYpucqD}orp-!J5I0>^&dpON&YU^9s^Ec|$#cSY(Bm^J<6UQ_qN<7aUJ^z5LuzWO zr%jrBp{14>OX0+tiB@SM@|KxX;w?!;mQ0>`Z_U(?q%EB_XBrKk`4L0eM;da=oFVtr zOq&<)6f1h<0^nl-Ve za$4n-m`P&p%($E}tFm%hM20yvm6NNhXN%5_$1)aXhMfubFb&M2+wYmhNSa(r8vYPJ zW%k?}Vy47S=c>w>rE1o_p&Vzz49hZO@{D_CKY*S*Ltc%MIhqrQ04L0zSi|W)Gc>@N zFniYh{Hd8r4^>W>!Pu;wP&s){P4z6YUmQJc<}^yUIF1J1cWK<*nI98Nh1LJ0;F#%S z3XjFp+L{Mua|E={?T$Iq807rn9H5_^33KOEP3HF(t8#Ke`V-cli2g+N=LY>5pg%Y2 zPpW5mJtja)gzG-_ziaa)gy5tQ=wG2rEZeIl{^j zR*r~rM3f_<91-P+C`Uv&BFYg_j)-zZl!K2c@<%zM$`MtLsB%P=BdQ!x<%lXrR5@-? zjvJKY2IaUxIc`vn8$;YJS3lo)d|L-Kh@J`c&~A^AKcpNHi0 zkbEAJ&qMNgNInnA=OOt#B%g=m^N@TVlFvi(c}PAF$>$;YJS3lo)d|L-Kh@J`c&~A^AKcpNHi0kbEAJ&qMNgNInnA=OOt#B%g=m z^N@TVlFvi(c}PAF$>$;YJS3lo)d|L-Kh@ zJ`c&~A^AKcpNHi0kbEAJ&qMNgNInnA=OOt#B%g=n^RRp#me0fTd00LV%jaSFJS?Aw z<@2z79+uC;@_AT356kCa`8+J2hvoCId>)q1!}57pJ`c<1Vfj2PpNHl1uzVht&%^S0 zSUwNS=VAFgET4zv^RRp#me0fTd00LV%jaSFJS?Aw<@2z79+uC;@_AT356kCa`8+J2 zhvoCId>)q1!}57pJ`c<1Vfj2PpNHl1uzVht&%^S0SUwNS=VAFgET4zv^RRp#me0fT zd00LV%jaSFJS?Aw<@2z79+uC;@_AT356kCa`8+J2hvoCId>)q1!}57pJ`c<1Vfj2P zpNHl1uzVht&%^S0SUwNS=VAFgET4zv^RRp#me0fTc|<;s$mbFHJR+Y*)a{Bl3AfK99)f5&1kKpGV~Lh)a{Bl3AfK99)f5&1kKpGV~Lh)a{Bl3AfK99)f5&1kKpGV~Lh)n0qw;xFK99=hQTaS7 zpGW2MsC*uk&!h5rR6dW&=TZ4QDxXK?^Qe3tmCvK{c~m}+%I8t}JSv|@<@2a~9+l6d z@_AG~kILs!`8+D0N9FUVd>)n0qw;xFK99=hQTaS7pGW2MsC*uk&!hOf8?Wo~CRX1w zb#S-OhWdwdG7~Q@00VJDZsf+y%o}n-7+f<~xdG8AaRUcf;)-q)CiJ^w%&mn(i!$%H zZDiTdLTxax#S?q!eZZfA3G)m`8_qCXWcZ9BTePwCe=t04n9TiYtUSYr;c!E}H(d4y z4D}vtY3RoHJ*_l-hn1zc`H<;EOXR&f)f=-z=5vzFs~1*YlS{*r^pOehKHXEk( z{EfZbGVYl*v1-Ch6?JJDN#xfa;Pd}*+nM!ApYsLMvCVtoHDN;4G!A!SRn(2Tw2Zxf z=NT!c1su8B5r3%<;;##3b{zVzw`%Rm9ubD3`qn=|3W6Qma#&}-d4x_ z|E5!hN=c_rz4_fdmD&-x@R~58a7fvZ3({#Nr#?$iOBu>ro9c5CNA9NkXn$N>MzWuN zAag4doqSQuhW3<16)|RI(6ij_^SZPh0mfwo@w>aZWvsuF_eY~I9v|G;PtXnSgeh~i zk8;Ve%%%+aBa6H`merv8g-1UZUK1v$3`W_Ek1wOf%E-1dR#+Jt?(x@z36ti`i3{-3 zcGOZv(Cl7+s4elA`hzV6-i_hDE-hmfW#pLcpO-Ov*0h-%i#2A#gj;Sc#FI~q=;btv|( zrm|p0Q%NwhDXG!dRN!wM;%_QQZXA-_RFKj*B&De!&^RQ}RFKvUo-Q^SiK zN0T$%=?rXD7dGrnzw5=(6d14q3n{QT^MyZN)>NB!F4X$o(@hQ4mu5-3Yr>18XhTy? zFr#x`UPo1DXJ=zgFq6W@ADw_`9_48w2AUasFEwx#;d_}(JgNO(ubzcy3dY25?w^Dx z@Tc~FvAR69{|oouA(@=ozaa~I^-xGQ-m(M~olYTh zJ}H?dc?Ef+Dx_yqLuTwa8cw84PwNbXT94AZ&U>jndaC1!VjWjR$E7zGrIUSlN^1WR zDRZxd7Ey3PU~Z}?g;MZ>Yuv0OVqIJmh*d{XMQQJ)j@^AKbxhL<>TDX7@m^}*9gn>B zK##_@-*hw__Fd6NMC*I0<%F@Vr&7n0cI*qvSgb#V#1;h_P9&d7?conj#XY>UsiXV( z&d!FNf!~h{5PRB(;-ZX(ohj2ZN0)ZIcdoN@O!@T8&inlxe}Oj^<&}?J+|Z3{O6jPM`$^n%r?a@> zEZ50Kf3UMA7#+2^K~EP;;dz}SQyO+MXjASQKjH3p+cK7$R(CzZjJHwp=@ zZ@iG2bq?Dz-5DQ8=TS;iQF`Ntub)ZH`t^EW>H-?>G@gCo@MT>_X|NX+re^JbFLfNl zrhFH^XlshOAds;h<8VgTm=TPI@?WNA;id8dKM^}fo+!rAMjJ~N_`n>Qif)tg7nm^Uy`W14o?kMmh#=q z5Nb`z)5C6hD0A+F)UsbO>|S7eHwNf~AXV zg2|)q>?k`M8!#MO;lszYR138>ob8$V&2P%@3m(_1%Gcg`sCgM=EOCV9}+lcoT~_#gnop9ghIk?gwj!?@9em*KclUCT;0T4 z>8LK|G23@DQKu*^ras>bbp&<0@{RcviZ^07Bc;B~_&>Tko^t3Hust7F-{_9o5LeSa zjd~)kZulfBcGjtsUqw*!IW17AyCY^z88~VhO5$9P@3%&TRMW z*Sr+_06rJhnqZ)*VP+>z3gG#ShC{v&+8bJvbfRY{jOrX&>0a;B?izn|=9$h;cNC=9 zD5whtPZtKWd`*`(J=2e3YCgjR^1`8{(vAYsHtu42V{W@~6lLXgmQFo>9cEu^X-Ckt zUjCbo#><(wrYDuUNk&d>JQ;6jYy!=^P*F}(!(1kfrpLd^-_YsfT?giEu9;pma97>w z+izd&|B04xr6W7`d_cVoO4FFPsWXf-4{zZmDNoC=#JW$mDw|s{(JG_9?$gx?r|5cN_;ds^eka z3fdSAcKjO8H$C$f6LQnz2lyL$MJHIe@pny04Lke;PtNVLcqlDZg6ntFWt??cjjSFT z>$JRx&D2N#ak{f}$iSm@dyZ!m&%GvAd;#zFz#};gJ7cHi(I+03cioAd_c8vzST8-U zre3;U8g<772LwW=L$4gart#zphy0B@x{NMu9G=|Rt;?{|#^RL5Vt>(1_h(igZfYou z6+>IK^5T$Vi?2k_U3^8%-~caN5#Q$~>tn8n_uZ~5Vrj?8|Gwyo_(vq34d{yaTR1b{ z%lhf!`Y*p+l=7>+d%qNFZ7fP|EDAIhrM+B~&RWgAM5cW}2VS^DZr7-Zjn0cMk%={5 zX-BOJkFWXeL)f)Fo3p3Xy>ezS#psBy==chD4*HSS)JHBSazl8Z^ zE|FC1ggR44zj^ehFuOSF%D6(31Wf&9k2sAVzVJtX~xBvvWvW*#$pPI#`Z%h%F z&GOwu>IybS7YSGCnpafJg(+s!b*HGrRXs|A86R+#$F5VXg?fx*1)Rhcuqk$RNlT7Z z6T2v;G>t0NgwR;n%`aqPPiZXtcSqN(t!jB{;h9d_h^=HGm^%L^`u8R-3N_d1s>0!x z-r2GA@3GD}-n|I}SCo>Zqe&!-pWw|p!*|x(0NWDO#Mv}>@vyTEXOiw8tt+1v#(#g8 zQE{!gRzA(=>ei@hT*=W*+%s^14P8zOwRpR*pbTn3`SbT;4PZf8%F3-OSQ@*aHe4P{ zKBkC{)7o@Q!!L-qH1ER~@)pPP(xqcZ__YpwnbiLW>(7#4Ib(w!sie!Rf|(tkcT0X_ zTyUHY(w6zqs$igI3e}VanMzn`xQ+X@dvGPesZ`EFVf>ERs+2!v!5k}c->LY4vSbaE z1v5!n7KD#wEkU}vhwFlM^1EBh@oH79yNH^ej2qVIff6-lJY$AYQg^(Z%4uF|)=P9R zA@KOSxPhcQQnU7uZUk$kagZ^r@>su=E5w%1r&4cMkG50uo0K&!SWC^dsyTH*u2vHl z^~Arq&88b+P-iEydy>GLM zu#xZ=f);}B5K^;#M*~?$Ok@ib z2J2F@=o2NTUn&+i{3(_sKr6?*>Q>T5&cc{)3xiche@ml?pbuj?DpN-{eTOASOjKc@ z0Tf5$Xv)VHrxxl|kNM-01~M*mEUEEhQGx%fCZ>+Y#oW|PJ$eJ}WA>TKsYcY88ud<( z*x!$oWQP~)tsZqjD&ueEQNFfGM;NPDoy_s8TR*b~R9oOA%EFT2ZN%@osN~9Y~_cD ztPEDfTBBCPTC*oUaKPA%A72=hH+A)K+Y*n+k44B$mZA{KASdytVkoB%bVNzR ziF&ODnEP$w*BZ1WnAS9qlRx#Fiy4`ebPHP~O{b5to$-x-J=KnBxQZooYX9AHMlBxp zVZ#|8V|`v$YX2c=bBF4Xy2h-xq}+cwwGRm#zd5!4ErI*{N~9bgK)RH<|Hj|I+)t5u z2%8=a%2BP|6jeX(lT8iB-e)F?UEgE7DaVI3`5L=64T)`~3}J04W#3ckJ&&<I<&_v7wyrJtLc+8h>?ro_KbhAdo+eqt;a2lphS+q z|0YKXIfm$#zQL`i$B?q7A;pbD*tAI;UBVS?4mSV4Sv}v$EdS3}&y4YxwKyfs2(}!P znjR{QZ8?Ts?Vz8 z97geW>|So3${$uvR4(hqVfmrI9?w`D{gDo*%l+lI-#%Sy#As(u$E`}(bto+%Z5G)F zzEr(`lN-NO zdZg**^3Ld{j^7hgL~S?Cp~JMam{Z>I3UQ6&bPuIl)}Ntly`I4;H2?Ha?R3qgHudWj z9pA$q>$ucM#>AR1*R#_V_!~tP2GdBu@f|cXt`eOqzXX>Gf@) z*FN}LlYcN~e>1~&C`GValt%g<-5Sqy8CuAVi*}fZ?~xUYAl03mLgfKs0!>2;n^JO{ zhL$(onb+jcZA@v7H>ELUer&ByJ8!x*bMDZlp?Ud@L-TGLnqPhS%R^(Ml1hh`Qqs^e ziW*v)+J9)t{oOAdYyY9eb5b9kTc-Nsk(8KB1$hI()cOC-(oVx;AV6Zm^-RRb18Tbe*Xw!wNn^qdLm|ep*j!KJeeXgX+2$iJyB08 zDwC*gsr~mmG+reS6*YeBMR*-5c>X&}1W^sFs$XOHfhWfR-c z?s?=ce`9?9|CF0?_%XS2$^FK7&V2W9FTDnGmcFPvL9RSJ=22&qbX-Q3__n~$4xZo! zut(G7GF_O^j^A23y+r%u2mY*G0XC!ACuhiImUiqWW(>1g@n4uyMs-}Hq4}xB4Y^2n zz6r85&dpG4zR7mC#uNJ!d2Tznx34%|bRBcWh6{7WEDE-{;ybaqVqPX2NR^tlDm#vx zh|Lwb?p%?rgE@LVsW?Z1njLC-&^bwq8`6(3s><-rgxy254|KHp_p$BVF)u8BeC;3Q zwsJPL10$FfbO%3XL@qJ91dhma?+IVw#xEtB~V~Q{_)EMg`{Ysd{&@t@x>n$702e6%U>4Zvs(=nvGxQslyt8= z4iej1C};CvOhawV`q7*2;tMTHrzIu9^w29!MQO1<(D0%WV`x&3%ZKn-rr~A(z!$0y z@#SmTbksN|lk7Ryk(yaI*fdky4vNoOoY&lVHuMU|b993{)08vQWN8OGGd9yiJNg`t z&oqU>a_yxt(~L_xIy*MgkN<~d zug1nLoN(DMG#2H08# zYHGOgV=`WRb{BVi{;=olKF|5~F=zMf(~Gs|ckYjup54cY(UY&GOj4ZP*W1}WADKA2 z=hGIQmY%cwXbTzRJ9ih(arWjM-!>+9M`i4_V09#+y0%SrG?g6Tm&<8 zyrI9Qjy{cN|FiHA!zSyU)U5MI^)@Yy;G^!EK+hr=!mMxu{%@xC-<7)H92sLbkz>ZE z@PsI}Y;S7WP9EU+=UmoVRCt`zFV?eU=*i_{&b>UH&7Y@HGa|pKh%0wdUel=Dfo-Wv z)>9Cv*bwah>(m7+QO|LA?=-f3_k#~P15ySS6@FcJH9A{${^|i|x%<>8<>jJ$lG4yz zB`(T4K7Mi2?BB=A^I3A8rQ^8+j%Qiiu)$40Q9E`grIuk2Jg&IZJNKlHVnFuL^`pnk zd|T?KX1&39Rt0l7wkTbC zIZskk`|qs2TivS*zfy5q8yLnc z({O@^Xw?}IeK0n5k2j3f$)>N3Zs~tB-$YJm9`t{Y}GC8i%Gd4GT05 z4Kxi)YaFUsl1GfUI&-gt4W+(e$u)N>t)%E4WZkdl2#ZxxQ7(@aV)CJw%o{sqal=fm z3rsiv4{z@RA6Hc_@J^CxJB6V$fuRJ5FzA3O1W7PrLIR{|W)ezTCTZS=QZZs}E0%Po zt&oPMJ1J~7187lE@Noo>C}=$vmA6S-I^`jjfK+{i}zLf$pKk&X7->H8tHmx(K^_SuEnTrfs$K0*TywK{dN)R`cPNCyD7_6cOyXV)T;z>dyN02niyM6y~Q-T5%$} zN~C7|b1CMup89V{76BWV`kbSp1Zjfw0J{rR>~}*ox)5PizY6Qw*pTY@K;bS$lw@-u zOA@l13!+}Ixo~1eZ7x*G=7J&#wQcsN{+cBT%R-e}t$0e21if9I!}o-`NW(#s%}(<# z2jhHBUgM%ee4hw+%kbq0*_S*&26XXbEaPldY@&#|N?rUdP&vW;^=2VQQj5lG-fYb_<3B(Frzx+{}OhW{veli}U+ zBejMfH^bW{YrfUC+j2v;cF;+y7RvOJ72Yn}miN&uu;r2AwjD88X{Tp)#29f`$6s3E z&fA$z4L=eCJWmydM@4@GY`x#CGu!^C+c(0y-BsZ~_T5THnxh@`#O|ddx{bW>bE*%4 zs_=8Zb>{)7R-3=7?djMl6lNA=3NrDh?e)7gxcaOx9ejH9OM(2j0u>m%U44k-e=wa43y_!fvGYP@dIB#2lkw8QtmQ%yqoSQ<>x+V zKJ_@jQ%r5qwuw$b<2XR3V*6ucv5YR-IwYu!bIE>8mUxg@GlzGso>JZ6>BnGNs)`A0 zXoKk|*`SYjqM!i({QUDqTRQ9_X`%s5H!xOWYamCVSH{tC*%9ujS~AXct^S-b5Xkfk zgPaeyvz~we_FZUoAP&E;6Iz$Chg@POK80e&pVSL7*Ysq>BY>2ijR@wESGRgS(=s;y zNk*@Rk^Trj9roe)Kq^3wGs^MLXr<*N=}*H8tEUb;Bf?*UI|?!PWLCN}EB%?3-ppAY z_R~~6R_YKVWd%H#y8Oe>$7a&n0{aFgNL#XsK9hRm8IgPOU~FH&OcXKa!F`cq^Wqms z|EAkd@mcrmY#J8Fh7gv#3*oxv7ShGbC%B;9#^4|WsCqkwJ7bX~b}X>dnhT|;n{Pu$ zi}TY@iaIPT#GR~FlarJwacwy`MN>w%^r(CvmiKgpi7$g2P{Sjnz9d+nyya>^u0j_n zflxtfNst#Yqu{tTyZXXF-*538@T{4(^tBM~1+R4~Rc!$iA!amh`AGEw&zes!eKceh zSn!hikRK{uV6}Q=?>XQ+i~X_4d6qoIrsH;y^>qt4?hN!coz+>($m`(4+eAm+mhwM6EB%@l!(lxu7wC(c7$1X98 zce1}FtaLU|sWfbi`|Q5^m5z-_&fcise50UQ^f9*JrQ*UsnNAVEG|b{bn)RFYXVMfK zhyJkixq6Amcij>}YwU@r%Tnr^2KD8NfFn>J$S{(oP+65XV9fdwZt49 z>h925ricy>g@*LQts(L9BIP=@qfZCt%K&Z)U89l<+hy$+3P?;<4z5qek4Z*kTF#T* zeJosTofN0O55>;zfcu|R&)0>nqXss>*#y^uqwJH*&iZ01!{lrK_9-<2XFnP6QT@7+ zQmQ3USeW(LB~-jqTT_NY-&b{Z8AZAgHuR89CvUUcD|F4;lVEIvUAzkP2KzF$xtFP> zJu3AH=7=?C|9c@@XL|2)Z1`Opns%tsY@Y0J;HQaB8fc|bR8IQZDjS#pl^AK*Y3!xK z9VAaLsD|7QdJ9FnoiD6wvsp^YNO18K_ zG@zqwQazYbvgtsy3>zq?GYj!+ES%34LJXKtbW3)wdO`uvwac^Se2AYY-~6+hsrh|~ za@IOYzB2g-JQn0d?eD&u$v7U{!S1flY{`Y$+NF<$3T9CK<(|ytZma!6RDQWPQ{HT! zPo;Bk;R3e%?}x@X{8k*hOAv_Ho@aidJYx+lL zO|e7bkJ^6Sl7kClt!2U>_p6up-_i{5U2SO=@oy_?LM%$7Ek&211=aq)+_O$Y5H+x||lkY4Pk6M|q^IoSnj#;g8BPh)2A{ z6Wn>pBf@iDVn%u=c*Ofk{*n3ytPOuZt*oEV%hD&yv=@BwM#|2Gr#s&6yoqDH_^RxH z+CL2C-SL4Q@M0$HgWLXlA9%@m1$*)z#seO|dUVNyyx);-dg|Z{g>_@z@5-~jSUl^+ z|DEnDpZB^DBT~8HABASFVgUXRFpT@WL;_uw+1xw8&@~u}aC!*P6IeGmN@crV&Z=aMpZ*kG- z^{w`wNa*Z_GzOK(B)}yQQ8+bWM4^iifn}_X(+yk>YDv_TpcNC!6AmTl$tuE*l`(ZS z)nHaQ1dI z&BYR@p=@=rStL&NwQg1;#U#3p-pd~#n^cyqew_@6zeX~@dLR)0xO`(NwS`X92>vPS zzF(wlQ9%&5%QFUz=2NROp!Ho}wMQv@(Y6-*-QTG`r@iJLZ8hsw3W64Ca^VUs0r5iQ zL5Wv0mTZNOPQ!WqKxPfvZwpj4$9kMTtNfAS5pkj_j0Dq1Tls3gZ<^4T`s+jBrMoYk zTWBIUJF}fNT_H@MQHHEz5TB-5m$|{zPg}@f4eoT2xkJ@_ibb^~WDRE1EaN#AbRrC= zXW~2`|84Fk#rv}YTrPBfUX`8*-MLl{apuBWf$<93#m>|v$y_)#B4dq@!|?@CLbuxO zN^+u@A!0^?7jFzFu14IcE#&#?-6ZfXoRN zfa!VhFKSqPl5YwY9JkK_ zW{Rt}^gUDcR_s`+iFUNuk7>?_ec&x^AK0Yq1MLgU%~oTXRa??nHM(Zu(ot_gT`AW6 zx{6DEXViN8nyk5Hv(33>R-I(%pMnaO{-o7cO2&k@ESqt*cg3@b`l+I+iR#>==+t(; zLZf=}G*K*^TfelfY^-`d@Ug3|9*Wk;PC}>B2el?LIa;(XDB+?dDqOTwg^Pw1Vf{L! zV6qsT>;Gl69U_BZhZ8-Dlft5hCqO`jDln0f#hS%QA!>h$tVY&Dq1`@(*JLCJ-es(r z(Eo{!93(LQPWJizM&;(L!i&eHWvDhFWc`qEO~?dPZbXF00Tt2HHC@i)Q$P3wRKZk+ zM+Q!|+C5lm?xm26h02eG(nr$NTrYo!O!1PoeFe1pn#Z!}6iA;syJ|H4hqkt*BiJ)n zmD-=*>OhqQGD^nyjPXE5OkwAn4W&yXZf~;~!CLII$SxeMp~RYMHn1_^H5)?7hBBFe zWJ3iVaU~ln<%e1(8>SsyYs@yivebTg3(aRWj$OP=+SkR=-&j-P@(ndJ$bI4I(Ckm~ zr19j(GfsxTnG>BF9Sumx? zYV(j)ZM*Kh;u#p)l2J;Eu_?wb-G^5lo7SbG&3HiNskGSMF7=Js_R8Q815CqN2XV%VQ&X#5Z z=CV*xcJCaYk0NX%vhiCaYOGm*Zj;%1Zq#~6YRQ)E?xHkSR9(FO+*Y!5F8zjN30Ujp z8bz~yW{;7LZNus%wm^^7TsuALN!EJv-XI9Pxy+3Ex6p|C_G5`pj7=Y$TPqLgX@i&4 zW-{+(?r_JZ7;{V9TZ~Ie-p<+$`yKv2q)b`NL#uU@GH+Seg>D8ku#Yw3*4D)9t@7Fw zs<*C@&Y6#dAO9lpf}<8B|7v`zpGeI)~g#M*{`aomgCJ#l6-V zKfv8d99R!n+f=oG)vx*vgC90l^@loF{lr+^U%~n2)ZdBVId_CC>*?LvOMRD(yy<&+1b8sINfpxRn^hkYV%)?2b3O7~ zYpCfo%s$5xYtRz?b1CrZ!Od*ybH)!alZ=C=xz~K_v~b|~Hfd%j&1{fnPIp>C3V9j& z)oD4cJnEZ;;|aOgghS*+RL!>Wp-Uq;<5=#;VFL0m{K04;c1sbf@?HI*%Pyt&m#ux= zdnt>A1rKwX=LG;^*Rs&EKlg_g@p~Dwxrlk}O;<}5|6!rTH)U*KeG@P+ySRL3OBCMT z^BYA>1lQBh=kXOrzM;8ZiyhB)@gHRlJIY6J_ieV~0c%asX{_+^tZ+dr)Ob`s0Hn*x zHf6mzq=D&f1!?933et4DZoY{IYt3b0`o&b9dC_^u^&>Yfc$m8dvUc5HKcqV8>>g-{ zHk0N1$Z$S1F|%nkx$Uo#%d$#&v^JFRmk3@&l|rjx-SEEH^r~}X-l_q2Y+7yA_WpGF zzVUKhx^{D~2nD(FBdcX!2ZcAc!6l3%sWLWWOg@7hqz~6`Q)KrW{3$s$n{ZE|>P_~Ix{&df>#CXkQ`li$HKB=<(*$ZjmFtm(|9CG~)=c;sNdKI^ z*V?zdm&q`%6TZzz-^6q+evW?Fr_qR*JM^o_2FH@pi6m1mF>1I-@{yVVssk1lym{#@qyk?Bdi7MLNU zXoL|XGuR$}J7`OV`DR)?=2WbsNbtwW%C0Q3*GaR4v0lM%`OcIaHZHY6C5w#RZ?iz2 zwk2+7=Gw_a|6~HtCX_lJOHIQusk>oi2T!5~d+}Gpd}7K)sq3_bBjI^(^>M?-1WlLuFLsAwgu!41;ZnC#P_I@*Kp(A>jI*DBVPXq{4P?mZ1F z2@`2<8-4FYNWRzV^IBK<^9ZRg#BtoJ2^m*;GQMGh{u&PouXBCn3ahOV#(x;z-bT&M z3~f%;Q~hsLJrzGLF(=OqH@39Mj(;XzZ;#$bd*s^cPgHz|qOO8?J;PcOuzw_9Hk(rk zHZY&PQ~sl$eu7%^lXZH>I0j4Zhb8xU%`31vYkqGT$8BbI{d{r5oN5`Bhi5N6ujIXk z{#w%*4-ySb8Fn%kOP!M5GA~Y?A@OzMoB_5>DqQ|Nmv8u6GD50A;9AEc;;HWpm+u=R zzF!?k^q-fhR)deNlvVkqEV}*Dy_f3q$){Hp!!5} z@AT(l#|~#!AFV>bRl)D+Ec8;;FQ?{fNlbr?J&R$Pa3m?jzXKy#Is;i3`9%Cw}dP-f@qy4`TayrQh!JGkEHKse{09TpS+(<;k4A!sAnhiw!9EgZiJ=6bM)id#r zCL(!cFT(}CxmiI)U%h?jJ+#KwkV)>X{?V8772L5}1P`*D4`Vx1g!kIm^kmp02%j!6;~wk&k>4=zX%Lrs=U)v?)?~^v(IPV} zLp0vYk6jjxwk3>jznjSj%?`-FK_n^aQ#73EZOSZn+i(4v?qF0>#OZ8ye&VuqTa)Q? z+kfVhjD?YtRGf7CWd_aCko~ZHnFhKZMS|L#KN8X}y~^6sL|t(fPhfNKmC}y`R#N^m z)&rq>UaY%9jr=gZWt=V`Ul9FjNhzqSx8Uc{!7JRE_>oEZB-OUq^J$jRMUVz1WA){Y z{acf>z4F}=8QMDRSvr&Xp82{`5r$M=;~tIQA2;8c{svCx6lrP6+#PDA9+`$Wf5kMQ zbWnZj20rc|3N`VG|AMg|A(^x(3G9m5Ny+=qSrTXtOJoB%@5%%+hC zSghTAv+j@aXc^1-t86Q&?=dCjs8xo0Pt|L_VVSF>psTEqQ}9O=>=GXLT^{I9`K!i2 zy(?apo}G|8mZ(pVwi2W*W(}6(!r``8NEwB(mG&2OF+5ls`+qCduB^8g=(K&2y1dv7 zmFBk3(`jH>5QZ;fO_h?w$t=F>y1*7dl5Vp+W1o-Uv%>koSFWQU4m{o^=YuJsN|3CvCgAejfbR>po*8TW zF5lHjp37y%rhW%L-m@*{@Zoe{zZ4QU3*o! zuKAwthTXjE3zxWkLwk5qLPFea|8bL02fx`SGByw@Gjtj|<@B)Cd=zcBgAgt&?zh$i z*rENFAe?wuuKj-?Z8d5A$ssjI3`J5HdB8Azb)1(}pW$|a zc6-S$6wahSMl*{9WQu6B81sR5mA`K)qxY=xP`mVG5q{nM((~$hjlS!ufOrLnL$ar4 zwH4#^_*P!*IBE)e8^z(ct>{{JzIAu#c7}9~yL@(aQ)p5DUs8BIx{SB-;}z1P`K0)( zZ!3EwT}&~<&esJmwJ7<3DtW2tzHoNc-}`r`q#olfcl?Zr1&>n&-MZj-In#ZE6#Gkh zx$%}KUOBPM$E-H5vBzV!dGY#71pK%fwz!=JP51B2*5A3M@GcCTs*@7S$Nq&L3uC3p zSvzR4AY9bk!oEc4e=?bZxJVus#&-ili)NuL=IQLIf6{Mp*jfzj70egNd-LZGefC{L z$cc-!>&+Wd;}v2hJ{)Edz7vfV@0}a9Hm!}q4XSu(&t#4gtWXbQb$4R)|RM7 zYNeDOgw#K9(0BF8PA^Ndy^J4~J|`=Qx$UEMG9q9vgsS#!B#Do!7i?l+^DK?ngZhWj ztxbD`7Q`P+?{diE;8r1+il2;I?V(@HZ2;UWVp)oZlwNitXDexLbaJ{GTM!I1WZb8? z@4)ejuD}k5Hmd2adUn;P`%bLdwxT?JEMcxLkvhJZ{nS^y%3ZZhJn6@)oThbIWH_}k zAu^l@if$z^9FC`#CTx zSdfLg&~kf1o#HMip={tdSb%A02QM(5RGKBBswal`edQ=~F9$@up$_XKNm<3sN97$O&@Kvg$(kE4=wF#q@_DRL^{X90Kr(&OI77S~We`G=a zwZHmPwd9V83-b|uMYyDpFladyunR^;W{F#%+d=!{2_>L>38Pmv)8$HRSf-vj^C^#V zMd49arbmkJrK4Ke=WoQi&|8IT{$Wr@vfdI?#TIg4P5B71MI_zXue*L*DZlA6eI;VZ zq@pf2XR$o-2BA1p@esJw*ZSn8zP86N^|e1r{Y+K2>?Ai0yK1F{?R`nyFa1kqG`B@F zEy658uO5sjf|pPld&6CtzPuVc%DgMImNhW8E>j}QSJW5vjD5zjq2it>pqzRf@m=6s zOl>c8x)tEwLt2hLWXpZ-=G0^a5JeUfZ`%Yv+AgSMFB=+i<+}I z$(dhxew8WAYVcQWHn;d9f3Q{sP^x?z-?XN6fT75J?d%4B>Q1I<8os<0D9UA4BMaat z#*RlO4tU0uZda_#xU$FX}u*3N6Uilv&}nQ$JzTWKLFD6jwHfN_cU= z)B8Mg0txT1%(gYhE0Y41TIJjLL}D=G1at6;m>6^x4G2gbTIAL{>xhUEffjBabc5K z10l|eFc;ty-fo}8XY8fEjS;oJ5p=$yh4?$uQI$~)5(jkF!^xUAfMp^8fmOt+OQ10E z)juP@8=F0*AJe}Aw^|K#jvMoY)n(y4W;B2x?Fo`s!R{|G-{smcfzgC-#~tV**Y|4)U=cF8=iJSS2x%Gtq5A{s6I*Ex2LXWAn7JBTr z*558HL7Z-6v%ow6Jd~YQ=gu1uNPrSm;{N>?`Xhz3ei{&_wNHXPWivpfM+1&=fNBHz z{OR%IhSA+CKH}ZW{A_D?d;box#%Z>@{_pUVt}U88cm7MI2xr(z$^rGpW=a?Q)OmCB zh94B=;H`3`R_9avdqbtjfw;SQ`-gff=FR5qpT;(KH4=|+-NnEOiT&y%-K5Yc*%bT< zNimg#ExWMkCvM~6d}GwZxovLWx&TFFOsB1Oj|&J{+M-d1;; zn#bLglX%!y(^tr6OJ*h^>q$m6Gw$nYU5!UgQPcG@K3H|<4*ucH++D9Gt?7E1B}vDp zi!_c4~%Xb8{xUtwm3BSot&D_&7*5uBHa^S{vFGf1Ej^9eUI^@{tJ)L)=L^p{h7!1$%$sZw%f7A}0BPP` zucc(^L87AaOuB-I7`9Hl_h1)2G2#BbG+z+mbX+#7yWC%;^$}JZTr6}+^)D1~PzkYs z?!F{0D`knmZ>4~_gCg{iY+Z5y9!f8--EOhD;s{dczCm@+FCIUS(Z#wi?(@tX$KVdQXUWDJL(N(Oky)ltCNHuAuObR!s|hG?f&mW)U|d(UGz#t zf-E6bMS@_)%!nyg;%(e!bdiHw*>j|ex_=Cd=fcv%QWlJd@^2PVb zBP~kHX4GSe*~4uemU&TRQ>QQP3A44~I2spI!%0{XVrPm|empFg=(oQE5@A(Em3ep_ z!u6>-_3E+Izpp1%4qPGcq5I7eb~|;?s4SCp}F+c+G8{-E^$R%5N9tkjlF*rp^GZ z2gXn1MGRA?;3p#WQTxe%h$D81Ox!jejcp>$2Sr{>{fc)5U+@YQ5@NOYR&{$~VJpA6 zc>xWO&2PM&KfpN$iv+Prm4HAygRQu;o97ZvJLegC8=^7BPYHZpQ-6{S2l+OMuV5>_ zO(3gm_I{Nb69YIN5n%Xx=0qABSj4GYz44jsaP@PRNP9h)7J`fit;V8sHoeJ z?vV7^n#-20Vmh)zuencZw$N;uo!}ThEv0=ixJeaI3~wkZoH^vrw1{;`$YzKL$G5(% z{6~$MbadrmzyhSRdn65wk>k<2M*&e$Ekn|yW$I8(akwA1gs>KgfqHYmoiOIYu9Y;4 zK_g$y{6GIGGcPYpz0C_w_4yt4pLk-8#CVP+S7^KarSGyXN%GLIG)aD!oViY<=479%DZpN%FhaSVD1h@lOk<-VEDS6UI=_wy2k5bs{Ip!=&4OCrJ6IA8M z0o*G(AoN%WbJWX1`Vw`mR!b|N-JnJ0#d5T5W+uC(BbW#jo$kjWfK8kOoC3L(ep^Gv z$^58uK@=zn}2$S^iH=MU*ScpkdVq?G%b>ndM;)^4P)QsUb|u{Z6N~~TZ8FO@|lc9d`$ltFDCG= zwhY0=c$lf>-2NRn%i!A-4V@M(PEWPIDw8ih0uouzU}gOf-~^qPWaNKnY-wgWKAMFf z#MV;Bu24Bf8MwA0IJ(;twfBFU&OhuJ{Yu0T#)3u=db=VeT6hvArqlX4V5f2^jm8i8 zLSBND(6=Y^HUlrE>hM92?EAm|=1-J|gf+E!No^YzLa$T5IGR+`)&4?jD!X5}jq))h zGdI0qFte84#~&MhRY{d?_C@rJPYy)k8>#?-b&eWaYajv_rJlm#FgYGfdQZo(x-p0T z`3!{HMkFqyNJJ*S_$98dOU_0rY!B&UDn=7V5HA6Ba#r_9!YE^`81h_w0+|A_R8<(j z!?LA>4oS5o*$_rfPB?T?0TN1ip_*R%IpBekS)38ae8U5zE1WqV2@MFZ+8mqR+}vV} zR7UM{H!#^?%&F>=hfk-dPjJVN@d=h1!M=i9DHQogm6wx3F=@ajB!ELO)_5iBG9oG! zCSJv{`4bRVregWSfL3{JIo%it6h1e&M_~!KD@^rWJ;G{myTS$h2$uLxbbW!xEBPmE z5aGhLiGL6BkAQsk%n*I{`My3vogTXm=9s>M8IYIrdFk$IkJ`oHNxC;Tr`O&~>*=WE zgxZK;(4L;@ytSt}?Zqn+i0t2sD^W=L`-vIc`XzuaXmf(os(G~_1lc?6SFja~$24kg z!Qhm4CSgNBsynrvJX(4ZI>KrUPPs{VV3(4tI|qNyoU(Ye;z8q1i5tOq4YFhDPOU$v zhApa0wuTK(4N8Jcbxx>3_CejLdeUT>vQFg?cma#=XR85n&l*c5?2|e|y zH4LcN93gyT!krEY;~NMHn#;E1zd6oI1p=*hAluImMpga@^z*H$rWP!QJu0wrHJg37J5LQhq8I{+U?8slYfXs)y@+YYpLgW zMv1$`0>Dw9YmD7M))1~7zs>&$ zev?o+M8{Lv8xRq)kR$y``6F=`PuFp{6@EvL3E37~yQ51jcl0{vmvn=x&@B%GIlnS} zICvt0f!jL2BJ1T#+p^; zdU%$$+oenZ9;xPz(;|+J%?VXtMWhU*Fm7kHP`F15BE_7x*R;8S2sC)QV&CtJgHn;- z`ZPs-B`<&1ZVjy zPYPF!QO=7l&Kn`D>c=@yn>|LHLtMb|usDTJ3@^bxYt=7*OiL-dh-U%{KN(LnU&a6YW)v z4(ufEtSMCD+t^&p(Vc`Fg(+TtHM7%Izn;H-@6|G`>-8x7O;^KsyPDSXSLTs-q(0vH zEAPtqxI^5^)GM)X?=R=^Xtp3(zSW=QAM(ZdMaEHBU zMt{o?W7ebp2YQ(^`n^;Tn>6|k#a8Bed>iMga}}=$sPoQ~bumGVL)bVy2t`_o$uVWy z32oL9gFtK4PQ4_ys7ez->`y$JSOL~w4Iv(r#_l2iLGsbhpw$P}eEr0lZy2w6NNOId z@f7OUQk>i;=|;i#1EB$HL9sapS}|~;HD}!NUNu%JyQOTY78641XSh-OM824Fiu9hV zd1b)5fbSQ`%;;&EqoxH0)+M)40(ioby+YXT6Ut{k^R4~w-6vHU2YkK@cNl*;0N3#@ zOnrf+$?n_Gx1e!l%oSUbSlSA^YqOu%2a#3JDehaW5^g8K+?KeKV-GVciaYE}{*uXX z+Cp@J{aqLs`m0Kfk$Q0_k8SpUQDD5U)|JjGj&--aJu#;>nwW!@RHSM^=g|f*r&r@@ z4}{ilyu);Iq@+I%an<&<^B93~RSaLCbCDGi7fa6<*e6N59b!U(9$zQdVEZ<9coG#~ z7wAzrcKumEgDrRD346p62`;Af5P#|)Iup#d$o?PlExd>WvPwiM+|s2IZzWMA%FHTJ zWCC{gi@Io%vuASx3huy zD}_dhMW9D`L_2D3bHxhBu=vU+&uz*O@aLK}^LxdwdZ{Ql&6C4`WY_Qns> zZ)&+Su7M-Xht~2Txn7AHV_D=)Wf6s^l+|awea@4nU2~?TYJn3Q;k`ZTlRH6Yt!h(laF^Da$zf0yRO859+h&SjSY{c(BSFbR! zhny?cUYQdAhN3Kx;jzD3sc>bn;EX->8d^}>GVe%Rd!a#COj1uVNjIiaBsq6cmgu<= znVayB%|z&8^Fpowb;64hHD%VOI|!qki4MON!8(>FW`vU5WZ_1t{~)0NXDmOMffsmugMN`2$Rl{m-)$yn<6^~joF&>pOq9q=+)y0Fvqqb)Lo{dMXka*OJ|64q&48{Cy z`sni$g97_bNNUPA^gq01Lr<44XHneN^5xd*7>AxTt$MO=Dy{^Ls~%y*a%Hg9A~uIk zSWHh9WzVE6fv3J-ZD-yZ$iJr$PF1~qS@IccyhJX+jn5aFJdu0r_|WGq)^2NwsMn{)hM5lOO&fN=o=+1UQ`#v#?8L&bs46~3@( zIk|0=n%>RGhvLo@RyD3c=`V?1i^kM>{XetY|4L?)`3;cJ8d>u$=LliTT+@ zrN-70In_4DHpYtDF1n&)QU|V`6qDL>sF+kOvuCXu%UBnqo`OycY7LKiIH#pQMR=jm zB8a~*;U4=OHi6(0Iu11eHQrYrIxa{6urXdt;!xR=5{Qi2y%0CqAIi-zBmIK;+ThD# z3S*TP4}s10OY0Z|8*%L&w)LuNV>n}NkV@mz)vno+y!4L-pAcSm(rf0bR;j=V2rbe9 zq1&t@B_4DKvQ3oxccvc3uNZPm)j#4v8#)aYbLGz(1Kfmx&I9Y|Xwi5WXi+u{w8y@X zPBGd6Oo* zQt$(oy|7_fvSG2x*n~k86@{i%V5uC$aG1GcJgRimY*<7L=>dBiyFocKn^^@*2$)x| zWkwffHq|nFwlyeO-Z5!^!vX#R;25X~BmISntlLHy={6lkn!RwgixQ?Gc{8dU`7QOz z{j&JD6h?N2)eU96dQJ9gAd8 z#8DX^037Ukq3vfW0}A>+s^}wf+p41ztE$I4aLZL{TmWmE{qEo7+)muRW#cKlEM14} za@Js`keQ8tBoR4a99X@QIKrK%D;nbiDIQ(M%Lm5myMX#cV(0qjgvcCv>#U-iErsxJT&V)b!7C z`*M^DbA+;tcMmG)?6Q<9GmPvx0ZmSnY7{%ixnohPO*%@ow5l>*($S%U-g@kT0TuMt zBw?z3JX?{b$ziGwiP)ngcXR(}=0p)=VpEnM)jx&EG>8BGW{sjp4*&ItW19I(vD}*viq+n# z2pI#OOnjx)7A4A)km{!K0NOVQpgoQNS~DEYhEzA%mrTAV7o0OJi3|Q1bl9xSy`NsT5*1w6l&OpA@gWY1O}MP!a%Y^&$#hU)@P1aCA-dwnQ&-u(+Ou-Qg1KGH zqb6X}9Y})KRA~HoM6e3tg#b%P@Wq{k0vrDbvq(l|*!( zO#la>z-jL3J*!itUk1UR{HN;NbeA?2u;Akdj2Q}1LOC63s zS1>s|ws$m~-8*8yO+@L$du%T?7+Z^czruNupu-;f?JF2FZPFIIJA@hL;{~Oe(k#dG zxFFp0KJmy8QJBSyRL7(JJ1t|zx-8*>er3eTQw=4ntng0UtqjMV8lq(Gdu-BsFYkJ! zStkx`nVC%`gRqK5$$S4xYiZ2b&0igt`(vg*CoWea{&V7TRY9K=)S*pmq^hvorio#> zP^sLo+#dUnUxCI#mNFHU_70vYMp$mt3CnG=AJA$0BJFvxV}wet?6L2VGV<^}{gEcI z?BVVU-4;5Tc~M2^UMQGrNXM8F`lG(3mQqGx-{33#wG5VF5HWsN=vH2=q0r6zkiCa3 zpye!CjkW3HYe|8HlUjxWzJmPnUYc@a!V>_*CI=GGaoLlpg0;0TcGbFS6|MjPZ>K@R;frt2$Ml+!v5|H4CkiM9dz(CflY27c= zZ%xxN$zlLt85XfHy#V*J$#J*f^)kLKXPIKs35J*rzU@`Pw@n`^_*PMmStyC`6MP$` zoz}7MSZohxmmr=Ez7PofdA2#^5ml^2=`E~FuL}<~z@Tc-( zrPe&=nh4Tw?O(GZ0bWav~QwT>;5x&w5+pNJ2Wdj%dst6?ypuaUKT%AdZ+XEN`6CfTJEy+ z(GZOOr^dfV_$q3%HmT<5+s6G?BLnH{5g<4Yb>BF$Vl9@B;dIQ}@u(MBt~= z-+~SAt)$K~s|S|Fyr@7IJan~h7}qUVqiO6z&}*J$%gH2(IyVRM_^5?Cbhd10Vv&7d zg-T9FI>BhyzDP@+avJTh|I7!oCS*UWGjKM@%H4VKUGY7n zCL*z@{X;2fbC@4B;@M75+cM0-VU&UmS1$hgKVD{cC#Wt>S~ zts7)#4oN;I)j8{GTpl-GS?utTSZFoIgGK`3rs>+W)nG2Pk>iiTfU+1Z@M1Rp`ju=W zQr*r}h%AZKF*@5}Kh~!vizVHVzD(&+5HqSpuPZ~XboVv8HQdO?mLCr>{m#UHuLIEL@)W=T1ExiBIpvXEs`L@)`6>~cqG z*LdGzI7wu>8)|2gW;*UX-YVqmW!+8f_QXIx-7b;MBH{q zYNXDjo~?R`A07)BraV4Db_w;%JrAW!E3zut@w%Wf*_(zNkgFZ#m6MFeTH;l1U6A@^ zM|vS(Lff=2!)fMU7fYn#eP;mw%Y`&)7#XuPsQZruKX({g7m3Q|Xb?RP@GT21w~WhH z!Sgs-4ITGV9v!Pz>UhA5Ej>Izh-6(#?bM@TQ!WXDe8=R%wVWb>lntiBly7`XS+gd5 zoRNBrk1X}#?}N?u@zj^Sy84KmO45s)RR%dBZU2`U5rV$iAZ)wRY*=i5d6~6o34IhK zevZ$$TNNr`n5gad%6BviYEcL@!!U%pIw0SE2>35-5LcL(+0e+(YO|qfF(mIQjDb7d7z5kv zt0-bNET@#P5&;Qy@?4w!kAE?LqEk&_355g^C?_)JT-dfsCzQG5L%a52V-_ zlQ}lNwbYqwdMyW1Zl+uwVffo61fS_(M0gfEU8|sL`uHZ<4)&{m&ZlGL6aFnoo~6hy zH_AvlWLsL!Z-T>o!dTr(pnh&AX2{tYD2z@G&n|Pu9sgYVnA{H!jeUS?jE_MbQjxls zaVVLb2C3=$`bSOV?zhJ#@@8_;tF_Xr(S6b@JwuEx+>;EqGS6Ja*2!Pj;O(KC$R>tJ zO2{L|6Uc_h-0pP9Skvi>mqr%{r6I(GZ10cNove*lq-U`4RNVK>D%p4{TKNRkOTD>W z&vI3!Chf!X=kUR8QlIgxE4^iKtH9MboEfiVed)OgI1!G~jpC^Nlcj2G`@)~+RN$x( zFvOhc_6D6SI#@M{*cN)}I_}$??_+yl$dnGzDrPy<1JRmX`?luAr|%a#GvCIJwN1&U z8r1o)T7%o2xA*-6ZUkjtxE(hfeF>3oXq3_r`*}fM$qq>Rlkk{?@i_2=dwXJo3uNb9 z<`vwY*)^{yJp&7~KZXzVIuO@%A| z6>GyaeRM59=Wu1TmY&m4k*~$o>ZzB@GqT%fZBp50aJ-+a$)Zd%t5NKnr2Nbmp*!J5 zmIr(}`32OeF_aGX^aV0vsq?&0EJtfb*)IcjI7%61(+qP~;bH;nlRidWag|)8nk<9k$Qj~>MXX~)T=h)Q&l6l9MA_Q%K;hAZna}6 z5uM{b!~;7hy}o}lkCyc)i?7dypP+NpIy#5SzZdds8Ip67&Y`~U(sd=NKg0@+N$Q-ZM-O6>rtIYRpsoyi#J*Ft-{een#IJB{~jBi)E+a!RjhNMT)@S+NfB zOzKs|pCg_-ne{aiuX^kp_)}b}ehGI0As3DA@pHxt4#G$Hek~TD$TG8id3r`Nvg{*_ zGZ|UFogX1AiZ!>{L)2+1ml!&_uRg>@i|7o(D3R%jgaD%KaR@I5x@Q9LnFS-3~GZz<%Z`j_7@h^+XE$ zSFdzMumyvO2x797jv#h4Lgk?Zu~B&H`v_vWJB`Gi@t;6*(C&oin-8-lM_C&d3nh@) z%Owi_(=CagiyS}#=fBY%Yxp`|&0!gcHLp~z$X-*Xd=|0!5s{5QQ%g`*@*K(RR5zxJ zI0gl69C7pzF-b&0oFEqSPUvqB@8Y&rl2|V-^Tg)(>z`9opL$%HT`!GS*=Eu==UrL2 zAg6Kn3dX4qvksW*SFMz9Sk0dF{@aJUT-*)_7ZueDRyRLJ7Gq$wD>l!HEXx~#TaU;E z2@S?>KiU)(HXP&4%9jZ`BdhTF+Wa6Pg8guQxu|vXO7n6S;8lcZD5B( zEglj?QDV($sUL$nZEZ+0l`=>D_FYt|<|KZE@a}C3vA%X;CIQ6HR)X?);ZssW03fZf z509G$!tjnhgjyOc;rUS!i^nquQXOPw0LyJ3%7IuaV%^5-ex*!~XPc2K%hp-q)ahGS zP9N1NX7sR3^>Qk`RE!=0m$|g}eO7u6@cV|{WKP%WZXmi#Pkiexqf+r*kkYz_t(3^E z;qLJo#7>>t^=YYFsWUevFLf;`iH>^atxO+Z9izO37))=>hFa^+&CIdML9;!ii|)Ce zcZbMb|9N^`PkNTfoeAEw*=I@$Wbqr*D9P_frAB84K@A#2wngMf74c{qQj4xuE2GkY zs%Ot4wSvh=zZ9WQ*YI<40qoX{^g{d|2nq9b#k`khAB6EcCL2YJv|qn1dHUzXYS@@T(mM_Ik+q#3#(*I{e;h`b~5bzSabr?);UP4!gt&4 zlUeBxg|vq`17`XU(LPeH;22jnsK8Pb@U-wP%^^Q?G0U3NJo2KcLZkh|oXYtdu{?|U zS(Wwfst@7YtaV_)68EmxUf|>@eH_&bMM-r$)d*)+edN{B+N^Cx+;HU}k`fdSw@ga; ztDM8K0IQGt2AtC(63Y|`$dzaPY0i79C8UKr2Z>^s<=faE6axcGAh{NfzR(^>$`z}E z&)wp%7ouO@JHm;Y8=N?f(kdERBR4H=lv_re?JmdSTM1QeQly>uT8Y9awc;fSubeZ< zA%l6pQ|tG9`dm0InnicrJ0ec=;9ZAlN>1(BQEM;*ZnlQ%`~Sej?_q$XTT}^|TUSI! zm{iA0*BmaJ^uCg(Q0;!}kU^&p*(gK_cZ+jKMXBiyC2DjR{%*b{5K`X_ZD6mc>`mWA zUjJSle0>o|QztpUS@;{wYoZWt2>s2G2-|r=$Y!NG#UDl5D7? zpG-_U7RJK%N~;~4<|Jbf3EP<+?0u(7K#?*oGLH*EAug)@j9SJKrY;f#;;9~d8E+9f z!U*PLk^aisq?9{O9Dm!5m_MTHeGM}@@z*jFzTjvkc(y)YFlZx zZGDqe41`gwa0&Nl>}Wc=!!eiIU5>dFw>;gmbP-Lq$9yFiGP|ss^>+^X(Q*cTPJbku z737jTIvIq~o2{o96k-odPPNG0?ZBSOem~dFlKGbW9yQYYuf-hUoSk?r`^Y3tP~Z{oX-eD}h+u92Uh~D1a4O#ZC2_)~WnmoHwb=tVw9wXudY` zv~7c?k>7t9Wqu2g^IVwEGa)r}r?AbQB66kF(seQWw(7jd!L8DF zZCw#RB8hoH3E4MiUS52)tk`t>!tSha@rCxxiY7)@jB59oFg|X)@TX5P-(!&(582!8 zlT>azrK0vs9#s}}=731Wa-dORGdqnd*biA?k1!}IsCrLbjNWd)+@;AS5N8*%ycvNRbf19e{Hq}^DjK^&< ztYrT5$uM1jm%l`>XbXIoaXlF|=~h%B9NQH}=g?}HLluHSL`FU6yFwMbfcv!v{n)R! z%glT7>*A+@2*aLUO|k6iLQ2GCk0KkBjQ@K;*A4l03qN z^ePHMz-ibXlBNV{@j4k;DVP{<Lv41tv;cGV#T_^{#r(8rtwh z)mkS?B-Tk0`Q*EI7=X?dE0FH~-N@;34yXiU-Yy$E|dmThaxv2{G<+iCWe6f&Z;~JLax> zES_Kfy!o7>LmGaZ*bLq*u%$@TSViBgrQUvX!ZOY1t3H9B|6)C%7mcoCK?@uh=rG-Ja;WKJUzEPRSo zc5FE7xCXTRq#||00t^PIvm~#D#g}%&rf4+5(g5Z?zLGyO^~3+Qx1(R-Of4u(n;$WF2uXelc$!bH9>UJzN#JOGroCy6U~QN4T8~0 z-~6`{t-r*fBF((`LFvsar?3rTDuk{&FtDfU@4lg5QCZc$d_zCuclZ(C(0}vW-kcp4 zeP=EC%Z+*b?ZtK`XW!=X3-G5_%aUr8U1IG_80`QJ6GoT0Y%j>)-jKgN-q5AwWd;y4H;)o5{+NiO9IK9o{RgDcT_|%sm_2(A z^9^rj@>M6DC10jc@}9iI-TArtj{WVZqV(vquC;I>OI}A!qZax-`kJV`mkz$?&i8#C zk0lPup1#@ltIeiS)8TRr?Tm3(mu2+xJmu$15z!<;pe|}RP)E8-f)lw~j<1ixu*OdLSufhyYd5kagQe24akKn0~ z(L_Ce;dw%DjqkJrr8sXqcA->&PtKtiv@Z)6CGP{Eu_ZO1F#tEl(ttj7jD|FKw{gey-Nu%G9*YA|?tlI7ynnz2@ z!0G$J{>{)3c-h$`8BgTNt1I6(q+t39&po`Y_5I)+^|O8^KT?bom2Yh}U%vUz{m-8C zu4${~b1|x{qKb+QQ3(=@$OX6D7A`S*>VjLZTyTpE*=)bu!akdjfK7-XlP|lK$o1@* z7|^j~Z4F)EoVi-D=B}>P)?8!pypIDgV(e=lp}GiYh|b7{o$Svea?FrP;c9axg#*Hv4y+?ghdPP2 z4t+J^^$o4nSvm4f~QnotIF+6Xcf36P#Nn zq5V)XW!vF)$zpDq^z(ZHIcTibi5g&rD(5jn2*j1ve3@Jw+aFQW4fy2_04-R-j1mYF)!VN(QIQB`>;l}s4FruBYFE+mE2rm0m(+{BdMjBIa%mvv4Z{e7QTrF9 zbaf_-pFv@8Wa)rBa3HAhU0wlU^2klX{Y;3+csZ9&YiULlB5prFi9Pd&08{DDbo*ke zRK4*Hi=CO9&4LJ=_^so$Yd^h!&N3A;M39Avf}m@`3>HK?u9QUZL#;gcHE-u#n;tyz zk6G$I+lb-6!iu-2V@7JKBei{){xuFo0@osCt9yCpB2ZxxX`yqQ_`knC2*HS)=2?{43^J7jHfyA!-=XigtA?e zpSqaL#<-{IzoRZj8zlZ9T9R`hl|LD&lFS;62MWr%`Ls6dLW+zwrd?u#4^UHK>Z zG+miSy)-P_J-bPSt^}YfjxSC>3|=`5LQcGVg(;&(rEcVjlOWBMLc2>e()a_Ql79xc z&g_|^1@p_S_6nszb-5uAoe1Dvvbu6QzglV-;h5|XBp~7mkuL<9ilHr7E==!@Qnl&5 znlKZ;#>0A>2C%+YR!3@t2q^FeExqJy0wcY?b?j$l@NH_0oy1sVGO&xq;gvGIGelw0 z&LXqOmKDxFG^aNnLFSl@kvXLeJopd#p1LiP<{j1Utx6|=6WE}sUUzFY_E z{RD{sKeB#^q*ao1yd=S-M^_e-bekl3a z7zaETa<1IJX@;`8nN{!8OrO(CafnvjOjBV)rDco{bR5x?sm{@@9G{;hHTId5AH%e% zO^3v^)_3mp2}C=X<{!hff7Uz3wzcO8!kyWu5bm5T!hMwYafIV^gF_*l)1&O3|1zv- z2xt2u#|lrUh{ipOmK@ysF^S+_5x7@@A3}jhAEL>WXUehq`)_%Und0}(wYBQ^Ht%dK ziJt)o6bj0jT#+*E;1;+Kvp+U=-x)Jj`UK}Hd}nN3IfXeS#~CwLh{-|mV|jgeh5o?T zzKLpOe?zu_|oMEEDEY5q43>)ZIA*+PKxh zvk%q@u@ufWpApQH5vcC7in;ZU!>)G9&|D%1Qj9h3{FS(%&K>K@)O1jKRAzTe6Pkm> zkFhqW9LjZMoPk{U&b`vfF|h7f5^RgwCsDAwi<3&>(+H%y1(@Qbn+hBsCK9ERO}ES*gJWSVZ5oap*|bsw3QSB)LzasGTOJ&EXfyR~nNK83eIQHvPvgf3q96l*txT&ti-%L-$r)P{H;eJ7Wz-&jD2<{10hUYNN@hboSQbVRsi$v z0ukm|x~3vN7IJ={+;YTaM<^d2>lia2%;;(Ia1%=5iC_^N#akV5S}sD7utSd)^o~@Rp%?grG2>u8(pcK?A#g>@ zeT_=)TV3Jc$tjadPQsH4yQnq?PY&nh{WM6C=r=r8t;D*%dS0yGsh81J93-hU$jyDH z!;)5&$g60OPw~C)I3AwmAu9=bMH0lYtt7$6A`_4+^*=1WsESB}xe6o|&P8dQ?~Pr> z^2}$=xQA>$bnE@azR~|o>dWej6}IO*jkJ!8=je-Sauwc=cX=FdwZ1r6x_ju{G2FE7 z{2D0r@1?r{-h4n2(%m41yDm-``ylJ!=i)50d6{?6giIeG-HDBz-9b5hZr_}bfSGqN zuNpIFQ7_{V>23pw%^WEQhY^YBzl2394zmxf?jmj>Q=CM2dEgh;91-FQ=ExfcKrpFm zj%aU4>4l=6T>w^fVU+AdmMirBVs`ZfMOnr{(OPp(W%V2p&*rF!jDQ=(RonV*sd@`u zvVwie7W*a5`rsvRficDTDWdqqkiF%@V~cSPJn`T1lnRE8rKM8w6GwnI-kUS2m8ZL3q^nm{5%gCsMD23D=Sc zCWx#hnD9+us$-bY$~Arp6XYTX!GsEh31z$qAi!TW&rDBu5FkC6g?^7#7}10La2&{<|1jB&IOEYiFwTLf z>5$py&%*T|k>xySM6SpZqD#{!SSfR|j4z$sl#rvjyq|^Bc(Qg9u2AqG$*qhjb$zkX zzj_Lp$Jo)$lZy(PO@J0;vWg{@_PVMuh!MG_Tgpw6YZkwAFJ9?ma*Y>&L9RKUg2_m! z%XP+_u8S8->zTOQ{-jhFsSxQsADb^=^g{t7L)DAG^9H7)aag*KY7Zgsj9W)4?7vh} zVOF~QJui+ll=UZy3sK9rY9*W-!644qVmsLjO4Q{&q)WXAgnPg0fH;Z( zdOCB=9-ZjvSS*3d|A)PEkE^Om|NlO4R8$l&GBm@3qM@mwl3|)25bu{zNx@+R6%7*= z71PQTMI8w#Ei1=vYD}3mW!KTt#JiR?mR+o|(sByY8k@?z6~Fhh_p>>hEj9Cf{a)YS zU%&HW@!6ket@W(6o^{!4t$pK?>jMUhn#f}B3nU#DP-(vATpqKu<3AwVY4Y@2iypjp zba+Ysc>imXj`exz(bBd0OJMorQbd0qjM+7_k!=u`D7Uqg0N$|Ca`urh=@OcNb{MqF z1>j6(YEfZ}j#AZpV=tZM{~UAqpM7)1ZPsa!4%O45A|?o{ z79F7Jl*re3q++T!i>38f@4&GOEhb1ERmyr`bVW0X*Pq)%c%tBunz5U}qQo%!BM z-b>$<#xkrf9f4LV{j{VBz=4nQbZe|{1}_+&^N!k%V_=u|9e2y6S$+9Ee_4%gCQr>i4qN=1!#Lgr36Q1YNPB zP-4pKNGeXyM5X7BqT$nXf0CXn8(sb2gw&~!jN!F(S~*Lbb0IqGnSPBtvjO7 zN-11kIbMD;qSX6fFxxE={_JG=3i|1!9yf)|syF5P0-o3WQo>trryL$fL*kk6m$eSYUzO7 z(z(1g1bGI#?%|`)^0H`rQo7JvCq%6AK-hZAR_TIou>*z8rl>mrw$x zb>XBYGrM#-10|g;?thlNa*69~(avx_U|F;;r0BeF_I)K@c@vy;#n454$xB?1$>xPN zBP^wYSD6zhzPmRzY)MyhEyODg{l3E74O!B@p9s}cONNg&>)48dFogtpk`vzc# z0iT8QIx`x^eTv`+J5epT@!~kgIk6o4$c0;RbXorYfY^N2*Zg83R)!CW?HLxwH?Y&i zeU3X47PptH*?bmMF6%}Fm9AWjepr*RIPN!07k7vNyw`gS=8RywEPUm1D+sr5rvDws z+E8=mF33cSC=n^$~CVw>_ov{&0#pKut$PZl9D}S0g~V zv~50_Tu<+5v(CM-Bq4Vh4`9VGgx->B=01%$RI_=UX&#ex>mG7>UPjyy+l{JVN0Uw= zs~4|2GtlUfH;<}lZ&f(*Je(c5plXh{>Uut^T-CK|w%2-{RKC3XES{<_un*>%2cs(9 zH}5vGuA@t?7oJ*92hf=j570tr=)69~I^bP9=0Sw)Fh`7vV>6SQ2rp^ZNm>uJKUP`~ z?-}5nVNS^y0S0iQlz?)BOIoa4O_PUWWPK#B0F&8aHYjK9Trj-yEGZCu03bNTFi!WL z!u+M6q;AQ)xP0z%=IcbixxGpZbCQI*eAIO)X*vZ-yr<$*uRJF{_ftN|3g&wbed-rM z?x+4JAls~uL@L?uGb<(iQ-2)tRK_*A3pLT&q2=Lct{MJ@%Pw6UyNOJvjzR->OH}+M z#@!drc{9Igx1Xzm@pK0H_T3U52IE<#l9af7+9+kkm~!%#cSrbus zebS!7JO9J{4%n1k1yu#%`4yY4t*Yu%#C@-9<20T56*oYcpQiaU;uV$qNX4E((GF8P zAK=5K;WC8s>Y=&T2>N|ekD(zmBQP5?5AH*&;4`ONIk1M*!yg}*!aRi6&l#Ua#(#W3 zFfCrb)d+;!JPN)qCEjGI)U2dux5_!G{a4*x88_N&kEzQRJd|@%aX~{)B((jh!9x>5)&5aD#nw ztSCc%haP>gBGVB*iu&mhy#Q_9pXiu{Ff6+7Hz?}E1n@tuc0_h>P&d)PoXe5_!4B%3l7-*eK@I2)Ove7h4r)ks zR;qVUqa=yI4yqZ#wbH142lev~)(&a~y_jiJI7|&Y<>c!K~m?HlVrqTv;TQ%``$TxN*WO zEvRFZ+!<0bIJ9JBBpa&o#^~oc^@pd_W6Lx{_C#g=Uf0}5jqqMkbZ4aOp|XL>CMwZF zSo-qST_Sj8e^kN;5nf!1$BQTH+Rz?-WS6v$)zJF5c1fqxu9h5iYU^ST@Y^vYu? zeCWhX(0p@G-IH78(kolqytm5Qo69u6qmvxUof0jI&V|hWy#HnvB0L_G5ERz0UqbhN zvl>s{7mguH)9NzTp4{#8v z1uvST5^jo$HE)WFlbfQFI}|7JdCl!~6Za6Nqm15D%5ZP4gL%AZSfp&(%@4oW`PXZ?ov~PTo-XD~oT5+F9xCU&BxGj&dl;y}gkG%AgW2(oq$Y zB)M(*q|EzN+FbmZ;2bispJfw#Wb6#r2M^|g~Me^MPG%$Ve z1}~Vy>k4Zmu5m+)7s(5et)w5e;|h6XBI?#Kzz z`X?_TVM|UiNs;$$mvz8`H&?8=K$fj~!mO(hT4Qwq??Js4H8N1mvu~IfVy#o`v6}Zn zV`y`{AVf(Y77)vMADE8eiAeTw$ADB2rZbRlH+Au<JIyq}sOC;6iFI+fW!gvA}BQ%^KHS5@}GkH*4QQACH$VR0Ymh$2P!o>VX@W~FWJqRKPe>~h_Eu6>{UgU29jyF2qN=Sp6?N1CX!*3f%3r$JV1f3c~g z$5I)Q=J8Dwv%)xLjARx2W|B07bKcqaOMgRKLwKBk zySiy|v9qE}hRPIP*6Tibl#NiuJ3CYNyDFQ|S7a(GUA{ZsT-=(nh5 zH)RdvuRQZ&-HnTbkK0E_r_7C0JZK17J)|i;HBzzE)f4=(BnLDJzAAUK8Gv5Ms z3;4JMwQt^X(tbb78ydivrfkLXX!X18xZAwFENs~hkXy4eSg)H;(o$M^k`}%3CH_!y zWWF@HWMH`Y{6?s?p-7I3ckcKiyd9U7DV1ErjW13O4Q6a0U2kc^J=`4>8Mb_m)h2ot zb04F2zUtkW>N3dUlt-wf458eM@hxR|;Y~q@4H4Z5Vuv zO^WIzNL9p+%6Vaxw01>}C<;`YU7*=aBbHfv;w ztuCMJ1N7wl;YUS^RnF=&3*Fj08B4u|Q|6NkHcLsb=||bxAL7h;ZK>SZH74{=a1olH zNB2I&0hK1t7)Tk-U6P?j(H?Iy6PNBg=e1X zDoQqr&cxT5%r``hpl8YE&zJ@{`pB0&X@88e9HdM%$T2%nK#z;?Zt7>oqVvhFbj?pg zg_cGx^6cndIqykHt~jLlwDaqHzmcN@ozF=7%9b46hVGlBhwR|-403RU9Ul6+%u&tE zCCk2(wyTUm45?!0F|6ZFA+{eQH?x}g6k@ig{obVC&GAmp-tOPcTWaPwzIcCzqs0U+ zN}1nQWyLR}OPtkWyw#hRsDJp?E6M&Jc&?mLaCj)qB*tte;oha>KM5nt^|R0ibttXJ zzDaUPk~EAX>>8p##)Whl;%itmDb%xvud(q3vZb_c@4Uw)`Qo?|FYf|3^JzBe3@VtI zY$)nw>xFyB4f+;#Hjyu4Cd)h=e|ogH^18cN=gJ10ePbSbiK8lZ%wr-}G>kK65ueGe ztM`PYkICh9)O%EgK_s(3?W){CJG?ahLNk6ZSNwhQQUH&Uk*;dC5Xw@sqH+s*WH_nO za2Pp~aY=pUgNO7Iz9OSC$t2Y=z=9 zl5w=OsE^6gdF$Z?OOA$&J=o_6IEHJRyE)!%H8pO-N#|8*RaQW(mhpi|z+{@68G{cB zq#q26LqUd|8aW4L&}q^?2EW4ni{pQS*z6&)`vT3<8R zF@n=7GgwonA7;|jyVM(Dnbo_r)H~cU_Kdk zl1gq4>HhA#*Q{ZuuQbxWR8NOPvKD9Xl5l-PdEoB(zB251WRrw0%8V)`Z28ZYUF9i( z4y?xI(N60|$V*0(S#xDH$@bM8O|<7<61uM$x=XF0Ym5+`%kB3Z$bOSn=gabSZF#J4 zs{oZL!(66JTq@<|1$b=2gBtNuTDJR{P{|vI84nXni~3`2&Of-!9i{hw#A8yNP1qdc zngG`U%-!esijSI^7a-Ul=88u=|A)kK%+t?BH&sWMw>qFapL7~aQ;kjINFK64$Wt-b zR|m{wi#d>zGnsrVnYoT+IF@8aSI5?5=AwKUy@qr$B^|3|*`-BU)gga7oe|Ku2}UbYzReoLFDQ4QR4zQ!HrSq^@>6g0hFDg;EoI z4h}XXHk)Jf`eO!bAG3zI29`m)mD##X_K@}SAzB<;XcpI+!^fGDWe$lLH%Bg=$z0wF zlnAkB{WNkImy^u2Q#l?In@d23aaskfN7EGEkA48C#I<|mE)^=c*B*rjEX3YSQ+wGfiC4oZujs18gg$S%F# zPP4S=Ez2i_u1Dh`jV8pGZ#UC?_^(5xzvAUcdHPQ-3=xJ4U;L-5(wPsH7VR>1WH5$U zPg@*f$0kRdsfMSUi5qCa)}`hl44U^JwjwgU(p99b+ia?jz zr4{d7X65t|RY215ORj8bXtid*vo`o&qD=sJF<_o>U?c8t^xJyui_uv)?Hn(+#Eq1vnOYiNJ%9YA}K0`r0b+v9Y3EaCDJS;zI51{@#hhLp2q(J@h@#0rUGf=qCf@!O95HYrcp?py1GAmDF1z@ zU+US%j>){R<-;!qIz5{vj!u8+8t5*r1u_5QOyyid{no7KCNp$it)XkXF3RJ^9=7Xh zeGa?m8islIWriA8nI;d53iSo?l&E#zJQtrMONJ#({>T*e8&bTuK?vUcd(4lT`E^NA z1-Y^|__;WM`K#~aa^4|W9?qU?fnGg*38L3E>^)rW5M=I{Q<56OyOG+ICRZ)lIXimc zdBbNI3z{;A%qhAfGYP zGI(o!MK=<+8VQYwek^M)o}TI-!hVZv0OpkNRyiiEOzq>)XLLU_yME;Z98@7N%XT;2 z;qWnD3({fUNuKnbVfLT)drI#g)yNut+3ulp>O1c%_Jr99hS2imI%LUXRzMGnX!A&3 z9bvwZXYup7g+WGfCHaW!e8$}B^qi@`_uX@Ymb|y%bn(m2=NdsqUo)4H@PTWlI_ zKuGaTA#bqH&8Ld^40|yj*#;Mc7WYq;Z-Dw!d4Te7xkj*2(Ve_t9Y!zmcoX?CGufYW zWc&W9Vlk@-mNW&UT+=_5ckyGd&fKlTyGhx}2(1vj4bPyR{TL-Fb0{t0!AwOoP7 z_4?)8%&8TJ8}rTZISC8*dXHiM-J$9+s^T>~`l77I^r)HT)KrJ=?82IF#~NL6hq-IY zqdG*6vYxxUW-y$KlF1z}pOj?=lm<|a zF77I9kKF1D+B9hbB!Pz0rQW}itX#72WA-7mv&7P#D~k7)-k)DzhSKm>T!N;L43JQj zk4eze#pkKh0Vn^6FX!R#5$u<+DIlv~*#Ti^BvaP^@8jNPEy?uJdN}qO? zG`mn5uNfYi;CYkUPA^#`V=Ma&+1N6*B=Q9g(~BdQUc-m=7B4jl8R2+^bTRiOFO`5? zViAzLW|n^^v&FtcED7(|XPnABe75s}4cz8g{O^T3&B%RIxf^11>ZlPF9j(If&7Mu> zG_zF37J2D#_~KF-g(P9@>1B@~B4tVXzLX_q)^%6l@1=d^Hn1Gt3sljYrr!BX!XIQlg`T%GlnuAW}q$O1cLW@-69huxpUDPqF>4Mdq~vW zw_pBBI$M_gO8Tbp5wd&u&TSQMrJ`z$(rr2A9>aRib1#)ll;w2KErn|UQ)Jw5d5-#B~7MDswrdZTa32EKmo&O4Jgu|BA7n=!U z-V}4Kw%AOKB63G&_#e4ECzChs+RUUVV|SI(FU(Sl@rI_T@mIYj<26NnlXy2XHJ1A% zEe4tS8DO;Iwn>utH=3l6!XgB zL&1GhO(oui^nR2NnRk*YNhZ-Ud(777%lhu6Pi7*{rdVl=ciCafACRRe-zUU6TpUU* z=*+0aXtfqD&uC^=%;g+MW$?v1Yf)Itz_Wv|ND?5Z@_J$y?Ve|zf300K^BO|w!urfy zWa=8ksO7CZR+rZz5rH>JEZdr=Us{4YrbaDTQVr5>Nr{RmZW-18c0ko zPO2mjCh`(V)FqM@mq=o(j$1j9gi9ArwsN8Q^E>i4xJLde|49wmU&Y{RUk3~tZ+>Gn zO5Sg1l%&h60uo}YQh8TdsJv0^Fr0g{na;*@E1_N*tvYVrThF4F_>okUTuW)2`-IXNpYiir_sr-Jeyn6V|HYz_g!i$N*+$(>4xxX?#tA*R0nX7l%x`#9EmzAF}txP^?(pQ51u3ZoJF8%hEwLkCO)N6C}l9R(Xf7iP#UkZ9!?9K0E zE1v(}a#OSaUcZS(xT4tE{3)L9WzmrGQTIN3OPy_>^e*Xfv}oC@*MZP(@JN;GE#~!N-E5y_w!M-Xq?ob<*nG zSLaY2!+50Xm8y5DzOHI!bTI}SInG3CJw8?Sebt$&3srTD21b}+-n>_9I9qtJ>KB=D zRe6jc7GpB&`u*SS#!O=zZoDzs$TNIKt}(%wZA>=`$lEmYXjUx>{(Jkl?d9euFLWl* zA;>Uf(sDEII3Af4p$zv6ZV2|f2X`2Ed0lQ0#3d(<9GTTG{RZE_X%nW;oH=nqfloxf z+a}JOHF^3pUtE_?3BJj*d^0D`m_2#s#GH0U<>SvW##i3;JZCswl89Tc8A!E|HYsC> zImY?d{JGdK;}rR>n#;)fNrYjl;!9sQ#)$)c_HN9nAvf>t^L&ndwJh#bh z;OsAD<4&+W(+RYG`8#Ahv%(GNBjE-`8^*8}lG_a?uJ<2ZHYS-#{>qqn!AH<#jWN})K_s$ zag%U!-dNBZw|&i@n{7uIWhwW|SS-2gBO1>S(`g3H9^;RF*g4KHx}Bq5vatv_2A78`$M5be z!&t%b`}jG32Pb1ooWzTpP&3|qWUG-$yse<~w`L~oZiW?a=Il%3y@^cXEuU%_hw=?$ z?{vd>ZVG)j51rEtBa-7dWcF|GXNK_}F6VQ~b_}~*Ag||tySxcU8A)*7Pw8K{cL=*k z!@og%w|xqalNa1$Ul_($Uy={>x5fRIJ=g!!F#3H%I|84>?I!Hp-wE%dtUhRC&5$nqsPAilSe!r(&XFf?~X42gNwW zSWwdQDQ}@XN-Mel0mtCX)$EL5DQI9IViF<&tcly;q~n4_4jm0Kkr3tr)L#^R6MPCO0h!mgyJ#9-HPRkTNO7eKBBlQ6sdpQ`ce z;eU}#N&4S`lD{uK0sqgKkG+kMsCD+MKc8S{7}vv( z1*1`UC&GyStC62}$Xg(%jW!AfB_BT$w+Hy%_s05zYH!iE2S?j(bc;hihIP_dci(3ggMgO)?&gWzW!{CdK5CR~gw{$9fNME>HxXm4s? ziN6wV=UezdTsOk?RJ_9Be}eVh{_g_e*TEOUk7@fXfZvIe@{M%J`ym^u@~z*}t~C9d zSud>n)~csFkw-b?<5b=Qd7?ueqw+e);~es@Pg#0)pEA~0{36w8tP}mu!Us6Qt%3U; zd>Pzs7ZUys_#w5Y@ZoT0`kgeL2v@wfP8#bsJJNj=?#$oflUDw6kUPscK;=Hn3rGM+J4*!<$vyS+Kz6|Jl>I?Sp;C6b#+q(Q4!R_?KKNxPO zC;X=`0_h3g0k_i=-V|=@f8e-be0w~gPxzN`Tc7X`;CB9n{|#>I6aGBhwukULaUSeB zUGXOT3w69pMmA98T@~XL!w6@e_k)ntSNRXeEPbCI3#79P*$41tYFFX?;dZ`T!Iym= zXy^It`JDMIz(4!UFj~M3ZNI14---J)z{BA=M+1DwQQ9}oL%A+~9LUFmA2Yt=ME(pI z!+5e3l=DF^#W=+uj~HclbIfm@?<4kz;4kZWQut>_0_O?gM~+x}-zD5`N4O`D%lUMi zVjIP;h$Hd`!S3L_9~ot-k~2-OQ02EH&vwXfRe3rn=jCo6)y&7$$PDT58++lg$~{xp6TExKM3fX|2}(^@OkPl{8qSKpTgV0?RF--E_|*d{EzPk z^o#%3@B)YbZn%Bk7XOWKr~ZfF`Ht|T2qX2n`mlv}f>OT&4r4!##jd;GV{Qk3MC(U* zm&1W{gm=*RZ3y?QBivQUrJjCxuV(lY#1VVH>ykg_;4KKV5XTy!Q9qZwBm9JpFA}aH zPTJ$I?^<~D-9UO2EDE0E} zoAeWorCx3TAA=XFy@fx3llWjj0Xs?jWL$gXmpSAQa*d!7 za?$(!fk1eX?}6LxOZea5N3~vrKL-!d_9lEOE)I3KgQ9P!Lti|yB$bCb@Z{j}6z0WA~i$?4s{7?AX`4WCB&P)%CA^odCsqf~BKkqflI&duUXYS>u zLU@kaPx$e@fpQD~2Tt_8Pq+d{xC6+gUA+W~eT!Z3?m#Z~AMTJRAZzO27xr+ya8Dq; zQuyCo{CRk;mS6m*;v_#m@3!z$P|EqtZli1`$5O5o@KJb*=2!SCoWv_dp5~A*K`!~f zd3PWm5`F+q^54=SZ?}ua& z!S99V;UvG`?y&GM7(@JZJB+f;97{QG0PlzUv^>J^#(9WWggo9MzZ1EXbMTHpx)Qz@ zPRbeSkbBCx4vCX;jw=s@7x@6V-QI-vhQF`n6y6eU+d+5}iSO|Le!FFd6N)>x2lPLQ zY?*_PgD2pmJfq&Qusaw-zUsbVltpvQZ{4D8hH-YAQ5K@*6@GeKK(Fv`aH8i+!bLg4 z{R6q!-hrAwgvCBuV1@bBJ-^7Vs);i>qx6tm8_e4Gu`LSO_y|GT@tySI-`4NZw zZ(9O-CEQc+P%WqMRq$gM0(ut0?Q)BM22Sjm;?R?XEY`t;;s4ld*`tZ#Pk*!Qvg>a~ z*(V%JJ~P2r;0Lw*!k@*79sh#-s6+lJ^6sQN^>2avOZaS@*yVbMJmyu}KXPdwzj7=% z`qe-@@$Uh*%O$)s-0n|id}Q5-S4Z*UD;EFgm4Ke-klp9tbKpa8lK;?`t^B_BlEohd zrCm&a$tYXIv6R0qI1XN>!B}NBK#2C9#4cnj}!Z>S1c5N-G|CS*3ZG4!7psG z%2lCwSaG-F3yODdGRjt~o)lt-)~o4{5)?^{xmd;e_7{ zx7R7cC&2A>hwz(lqQ|dT2Y*}N-e0U7Z4@9en{xJGu{)_)9^VFHcpT3*HF# zgQBk~DE+Xm`hWEoYn=E8DC5B{Q1tv2jDgQl-dQn3v9OHxrsa(;<8c}IY6l+x-{jza z_yz~RPPp1Z;&&`#ek3W=-h~_Z+v98gdc*kF6M^wU_z}20j~Bk%<-Zwj*R%MqfZO#Y zd@y{4=0|vYSNup<_z%_x@+;vt!|m})__OdypyXfp1{W`L@wG1gh>PD3cj_;3`FDid z?I@h|{<_wxw{@V@{~c=s@f$x%8&Lm-$Zfld{QToJ!yS3t((|}t6VQ*o`i~oBt91O4 zcvX+p9Um9w_bWO;Gf122;S7T=6%!^sgmcZ^Er6+#yFk zmLZq(+!R-O87@A6aM6TI1f~DhRepxN3E%T*&HTQET=MytOFqNJv*1p9rV{UR^d-2$ zwUcm;eE;%@RsLn5q?4-HNwE~T97aa3jC-T#4toZkVG5F5~TY#~kqK)p-&2p4aNKD!-=|AjvX z@-O_SKU?kKTTt@(Pf+w82BjUmLwINWU4O2b-s^;ua=%14yB&z$jmWzrf6$d)F>*;~ z0Vw@qlFL6+{Rg__NpO2S7yT^>C-qv_74E|7K)p)1cZFXNa~kU&gztpg{XqCuoaCoW z@ivEl%0tXgU3@g*;z)N8a@#Hvt~+wETLN<14kB;8I$$s15gNa~ruQ?lfyAo-n}hFx zvEZ9v8}K<$^gaaM04{x~roOy~YUX1&;iNtWBX{Pbz3OS>kiYjJ^K3_a;h`F?o-16% zgMoA;+!qf9@*(^vPSX8|aCW*Pe+no0Uk!Ex+kz2bL&Dkhkg|&T&I2|10eI>~YK?jm z|CixI9Q=s~jIwa4IrU$NzdbLI@Z(+n_3$6yi2vg%V||K)pIlW_-%@l)yO<2dklzuY zl&>!F?0kQ|lIsNbTl17sP{QA)IQagW;d&vL@vegn1f-MNQU-9Al*x&s{ zifOEq^!@~Qra!>NI}%U&MMIa|fZOAp=y?t&`F&8aWMx3_q`Usn zEb(W$cpmy!>v>K5GvT&9gpWdxw6kHVuO}$u*!8Y>9b9_lbwpCXEeL0~U(r_wc@N~L z@2#26ar_dI?{~@HKrVKD#wA~gTi4EHXBPJ&E4>$i+@SaV&P(yFB2(ri6Vx7k`RyQm<=}+x0Kuijj+b79+Ro zP2{td2ilSFER8=Jypiz9pp3g+K^b@3fHLl01e7u0at#>T4$KbXdgs;|c z_q)OsxWY}lJ5UaZmy45pPjJO+f?UR{pNp+^+CEV769!5>BrOZ%_b*FXBQ3N1-~W@< zUot@nKS=RPuo>}!z({cC(wgyCqC?8L5V>8>4NDB;kI1Dy(m<&nKPc&UQ1pRU!oMY~ z#IIOVGk*CJqimUuC!+5)_;Lq-8JU#-c~`vE$eSWx21bE%!M5N~!rArr#$v`Ytc(zj#?DZFMK&p#+h5d6!6nU z0sj|?SGLIVzXufm8^KQC4+{hRR>FM+x7TOF-@Pk4feCOvIhR?;r zHQxS0%idc-(enZ*c3f3hGyYWdAE*Aq@$aeQh3FpuKYhWF-#X!S;P!Pp;omH<^dAF7 z|G@>^@8F2P9d6#tsPSKf+xrC)|9*{Mtnp{#Z||3ie=a=Hq3;i___-#F zPd;`dx7(@6H_s2WGvUv}?RF;oX_x;K=#l!m%O#)ViZ_FBQtmugdTFkF4I`Y?!|A&$ zJfT<)b|?N{!6xA3yNvY@(r3iplCN=h8D-geJ{8^xf4lyLw^lt-V0+@9pI6iWG+`v& zk6rTZ$Yoq#?~*?Rx6gN?f2_lQ7(7k&b#%yg-O2rrcLv(C@PEPW_ALA`+|IY~131ao zv!K-1Q~2BUm5pq{oq_riemmT*FX6LrV)tpTcteq2joj~uek;wTu6Hd(HTjF8;>t+<$a2px-|?kdN58+zW^kyEIZX z6wl1D>bDXU|IZcQm=oCd3qiJ4<*O7sE5<5bPI#jZ8fae<{-@h4eV-^ERJ`xDfZo%y zdA3XCd5U8dPv9r{`AG3e#Z`)bQp`}iK`|7RcK=g>75}*60mTi9s}z@jL8LQJd7fgn z;%LR5py<6;c_YQ2XIbg|b5@|89z|A;6TJ_p|J{o7RbHSxM{%U$b&8+PwDuo&fzs|C z2E`r^f}(es^1BqLff6obW}qHMAWOl?y0sf9@j8K`r?p}vDDkRh1mc~Y!8is#r0wZw zzG3_Yp8bo}zJ$M!AMk%~I_o{S?JqoedcYnD@V`w9@O9G+V;=m+3zoiA_*k6eD+Kul zO?TB){Nd@EuJE%{jrFMxei}Z&!7Jc?2Y(Ch)VB+s=)T4*u6Xc2Kq7i~q_z?)P)}cZ1vdUYo*L0k`!DkA~a&geOi3*zqd( zOSf8fyF>A2#X*X_!1mN{2gT-!6_c62%fkWm*H0#MA309!zaNx#veMyyH?li%(vGI8 ze|Lv}7i6(Gi65r;Q?A9o$;G}}pPwW9K;^p~^6ki8Rrz1Q1k!l`{|*{&IkNfcf2(39 z{`R`RFS229`@A5$r-qADZ0-mjjEo0~s>2IEe~Xph4;5ckT&$R{c#Gl~#R0b%>pz)f zwc~$+DK74J@t!W8=;8@39`E99T)eL4V=rk-y}hEiMsfb6fSnFa~w2%db{Bgg0~fhr0ZC5JvPo4qi=tb(|RJXJ>QpK;91d363S)UvdKeUrykeU6qef z9H8h|?5Y??yiW+vZ=J-8b#b4Iw{Y<&7msxDa2F4C@emiUnou)8tH_hseGqcHJ;sX+ zZqG}89?yOePVDfy;;Uc~e1q~w74K8b2jj3`uJV!NxlW?}bqKOv>K_jVp(hR${Z}bA zQ9M1)(o+G7|KV`~JqM9(Q~6&NH{hSC^TK|}9#;9ipo}B)l+RS0s5sK0SK=k8JRG?_ z&VQE8Gv099PQw3=^N_FgiVrE?k{t-=Lw2LeLlu9{vf`aod{J?|;zNq<6h9lw`AO~f zkFl(`9DEOaD^BeFoJ0N;vIlUIzY{@E)rQ)rZsQ*9F+d}HYj?Z z0egbe{}4F;boc}NYB&iu5R`CNf|8$qj0vQFU<~6pPVDg(DE?&`0X_F*peH@R2c^?S z;dcHnjAlJPI>29oZ^Vh7TakxoIVT{y2`BTe1oe+l3?^K-uICSp;{Jb}kj&|_Z;1v$O34YAM zA01)sA5H=LlFmTN?)5}=Kdve9 z=Ym&&BVGPq-(>BV90Hr+zu=}o{NTZ~F_qT`Bk?~n%(CZwL#_RbXNFkm-V2KVR4^J$ zQn?S5^kWCt)cdbNhT%v4`5?-#?I;}CAzU})+rT#9)5;%EEC#PcK34h7py)|;>4_U; zlodMkwt_Eq@D{}DO}r~y@eJfq$WITn_MP?&4CH&(0IoYC7yCa5N;xNkQts&ukD%oD2E|xV@*4$;9q#EHi1#9jZzis!(*u-r-r}E> zds#{#Txc@yq(CnE??P_ZqsV8Z1ne(77jD~M_;{CpCVC{DZZ3JeD_(m7Nq*b7(hGOx zs{!F8zZd#g^>F}{@!?6Z3AhN9dbvX}qYvjv>Ym>^$=~okMp>+$hlRJ$a1n&F=M^Fk zMlNSK~CUSH&5 z-$Y0J>yVxF2hv~Ki*>GxuO$=GZ(1NfqT{B7`?a@GcIa2jKEf+qyxhf~aq*Jgf&CeY zKgQ*s;^J@js+rFVHwO5I8yN>&{B~p>>^M^KGR31kt?RBkdIsVrAp4Vx=OZ)YgE5q! zx9^$uYpS@HMkVdHtVcjk3bKcA(r@l`$=mcW%HoL2Z{2E6gjc{LwLJ?DbA%U8qpdy< z3I7l$>FonaqR}%q1kx4%M{v@)}gKBIQa_yatr=g@MvO&L@&DjwPQ< zQTQF)tN9fEWnv&*;k$7JZ}hZFz6Lpejc#?wZ$Z%C!GFA-^}UP#{rZ6ZJMdrQ;wA7C z7p(k=|45va=TJAJtlVyo6l4odJqLkN@f%$fv~r0w?8v#UU@hmik0aH)s@te6^NSHf1Rh~BOBr1E#apa9K{~5fYR?5DIW-my?^XzVRpwrI%}`tK0oA`u2ECuc78>E zs)J>p_d6KtPiekIUW}Y6Mx!anH>=%5el2pQ42|j{-{g?5#81*+21oWU8-|nk&D!w1C&!Yn`(hEm*J{3m z2RY&iKaP|Ba~PEPPsaxIivJq*pXrcKM>fL2`@pZnNja`;&Hk3Qk2hO!{i8K@(eYCF zMVy5D9F%fB)ygP)gJa2WbMVjbxtd?$D{vCN82K`Xd_HpN57QMVfRg_-P{xTatpeo` zJ#BDO&L3g|@(Lg8E;7*-d8SLA2DkgKgiC_k?MHYb-0ttfFN4QwxrKY+J_kR7OzQ6~ z#hr?e_yYO41KC&y?+g#YN%_~e4D6d`M)R&f7oQ5Z#~1P65KX*1dBTYfj;6XW=m#uQ~E;hr9`Lv9~NP#NJJ1K~CjR?;a2#axbMf z_w~q{JNUO(upYsQ{pL5da2zOhPXZ-=SH+f!jT9>{H_G~PEcMdra-O4tUldKs4__Wg zSNz|^$++>n;!-dXJ{y$urx8C?>rcW@!ga?#4V3!3&ZVy{a*6*-WFVcrk?gBE_#F5s z2miJSb2_*^P6}`A;zuv5=|2~4+fTyh!|i$zJ{W%Sf)txqwZIeLc0CKf25yg6!W+Wv z@lE)zP0;U%e?Yj-N4|*Qy*7jq`z&$E?}FRuNVo#HeI6E`?BcO-`@Agvzed!IKOJu8 zv$7%e6&}c!@O-$_KOwB9|K;$rS`G>Cf!pZ`Ki0Tr_}z_-vO?|m;=j`6KMn3oFB|UE zH_*is;GdyK>Ou6yyLbl|k8|-@7x%e%3m1=a@kkdBh1>Nb`3Z9Qf8NNNAKu-lX1F-y z{b|=;y3@4a@^Yekbytguey(1|3gCJ_>H%ZzR08hPxWMJ?|8GFx(zT zguj84_V!K#3ts?Z(7zJw1};_{*T5)ykYlm;k@~FD;HR|z3%?pa>F-y9(vL$y>BryJ z543LycMK=}_$7yY8L~VF?+$N;lYBK%tO~LAo4*J#%I0Z*c?{V;_*@6y;qrf-Frw!r z#l>Jt(w_t}q%=xbKA3p-aV+Jl2PQf62v2mRBfJYPh4_&!d1F_+BlWEEzNYwWy?{L) zLv{~N>^#vSAB!vnC;9HK*h#UqVw7Sd#URC>>ss+nDIQn+Q1LCrZHmt;u2Z~Uaf#v_ z#mS0)P#mh*Td}iZTwUHLQ^&I7YB1KteJpiWqkNYP@q4EzISjkKCE-dpGEmnLfi2OT}7WosNn)2)6_IXvp zT?@C*3;zagpO1vU3AfMV!neWg^SJOwang@hC{7oD9hb%+BahXb zA}AU3p@m_3yeWzOEInR4k7p8Vs+aXVN9A`U@XE=^a9mfwijvsX#mpP7~aU5@V93OBT|J`x?iR1XV zTmbsS&jIKI+x+}d&6!Ev16IPUH^PIVmTIF9E! zj_+|CKkYc);W$3(IR2;O_#4OZX~*#y$1!7Ibs4GHLZ@Tf&irM`S1|Z6Mpkm_(5&Q% z;|ltW%gY--Zo-ri6K72ZI|(W+TOztX2 zv--*Z*|vZhoz}(3nm}isj`1c;r#Dc+x=kTu}mqO)aO{F=_&B~cLt6=7I0@oBzo;F#+*9b~( zrnqIZr_~}IM{E5J#imy+%1t?WEVwpQ4ls}`mSMhVjGm0UcuuOf5 zT@?o@<|y8-xJpsxDZe?8|Nn8m#^6o2tl6D8HU7UI=c#Uk_Sn^KVbaikr#)UDPs8mT z-?eKO+NIBz-5_uB`0?Xr&B~fIx6`ErUUriog&K3k7`7eRjhbC$l-{X7QctwmqnWGpi- zFF`yn)ff4*CGROl;wO7Zs@mjCDQo<)vQ!_7Ir+KsmwMKcuTUlO41!aK+vL^yIgRXPx<5*uH}_y@}1?id;|Z{$KXGC z+AS(_>r1_8d2~^KpGOrdck4FKX<0J5XHA+d=l!gdq_iXr3@lT9Y(ts9bo7b+Qu}+% zxvs1+oj*$-H9WO;eJfo0vQ=NY>SH^v#_v-5)y$vnF&N=mk@@J8!))|Ts-ejuSy?$U z`>OuA%ezTu@P*dil2KU4(zk*^KTwO+(XrQr>722r*%G%tyPsy;0YGQ_t64kJe#g(6 z64Nr5ps@T<9^%+52ycEF2flB76XW+9{WC|IP20JGVdm zOR%x>$D6i4eP%Q+IB4S!!Jdt*1veUv3g1J=s-HN%*=xKU1V0vh`9_aXI6~z6oA}Rd zXl0x$uUmfO+Qx-9mIr|iz`Fh|$Aj;DQNpZkYn*G!7Q}}^#!88MJh3z{M$U?@zI7aCORnG#}(98(Ff}qX|28f zZQ01O;X~jtA^vSa;k}}LzE*_m6zxOKUS(RVQ2(|8K3}wFMQpTxcT6;E%CuJDmQ47q z@R&&dwm8Bx;#l$=HlL(b7bK1B2FA9yDF2q+=Ej!n#>7Lv;fssj zAz_>Nx5c1a@|q;PDIDFxcLZ6wZ958LhY?~A>QAvv z^^QE-#$vbJ#)g@uue??CxF*K7R-~82vFIA#)YwK@eko|>-%=FRI=X$x_~^;_B{iEI z9pfJ#J#J|OSt748IL;oywikY$kXxf;qSSw^v2EFW&q&Fi$J096(<~-BhA`D}NIQGH zvCW-tyIhB>s;+R>YgHfP-5-O!wArAIUxpc5mObh3zJcv2pNDcv`)kN6)Wfjtyr7X4 z_#dN91yT0>^^Jr^V11+Ty}O(Hx0JN?Z#hMt8249{Q$7#%eNW8W&4+m!HwZdmT-(WO zB;3>1fA0Ca{D+H1`M1R;wvG;(J9+DbxR~fA4gA}RqFP74b9q?F?yLOU_Fm=h_?HlG z%dg|wL{D0Mb;~`?jE;M*O6vG^T-)f0tFCUj_i|6iy;mi}ZU3N-i}Bmj%)f2Is@Rr$ z8wGb<(!}2}IiyYWc=QC#ZMQWU8~wG85wYFVaLPE^f#f~8JUco$Bs+STF*o}2I>wA= zXb+3Y+oF30cgzWijV|=Hihi40%8F=Ht;t^zc1>xqei+k($R%U^+X{VcqS;O~{l%}W z#b1WKQ^y$I>DQ|3ME~MZ#Bp9T1@?c!Rd3P{v$3btk%Y73!z1nbH4OTUY@O0qd|r^g;tK)kAHGnK ze&GuTX`jAG+6;XtF3Q;QTWv?i3K>V7V?%|Eb8Q%h+W5D4D&?)-HOD4rpQ+eg-rPdyVajNd-E%Gjg>LsV%N*2BvnXTJJHst-URJq!|ZUje4K#Kldf|E%`jI=M(1Ek!$a0 zSsi{ob!IPUfhn?pSHOAJrT55F+mK_un0`Wus&NVkmq zTq4Vrh<7$z$u0_Vl z=#sds>n@SeX)lq5)gq%`ToR6H=_Rt-`erO2n@+?H!DZtLaLaIy;5OlQdr48Eiqi?%fbEE?L*Rtlu@NCX)E$bUI21p-f zK9gPFnsa!V&ln*w(H=Q>ugEda-}EDMzEPW>Q9drZ0doTBYX;}nadnx;5ZA}~Yg{Xi zkHpM3=K)dUqGj$7B6_ddFiiSDLwGxxPjDWSJoq@z%+i2$4k=f{$^Z$6}TCJj5 zMF%qnOpa?Eom8hm%a{^x? zk;Sy5yefvC(}K+e8PoSUW6R!sF*BjFh1d^AkHvn?%aR zT!wwLNYOREK6Bsjp42IGS>n_od~4<;cKE*}qJ!|#hYiw+A)M$oOufG7I_QgSv2K{8 zn=E1Q3&wAJBKpkm+@*-!g7KGdeVKc(-iZvB{MBzwI-R0};$o}A77}k_RC~%58@+_` zNSqF4+LWW6hNK*YP&=C{Dy1~{>9!%Y&D>@gG?_}m# zF}U${>vuFjnFFsCWG+=GIL6raZPy)nwavS|^x-Jn09+nU`gl360vAPp&3EtMRmT?qSWxI>lbE zu|{99nE6SZuH*hU)^7i4j?cWQ);azL=IqSxnU@s4SHc{BIdlAPnG-x0bD;S&=Jnh= zkYLQcb?d?g13PwNUVm>F|GDoX8kg*DF)3Uq{eskMz?ZjMX5cB5GJ)u)7>lHW8Vmz2|@&9iA ze6Y6pGx?SA(LFb_$Mraau`b4#v7a@lk2$oA2eNJ)3=g9J2m4rWFz$|LOf!7e+O+;v z#_-;Z_d6Ny?RBoKGg~p&9>91xPL7#xOIv8oIl%909qliRiGG&wGIrYQ-WZK9;bP2i zG0|)sn0ox=!JHG~C-ZCD73$hl<~;U1y?3~=g>8*U?oX3>+7^R#fRA;6!8*Vv>txme zKJvyU2x|b4bwC{JfDqOJVjDR>*!l%Uw>2L#x_s@hX$ZE8Gxhub*VY;#vE<96Z8F|} zF6%Di=|M@xO4e8ra?U7XUi^FcwQXY8uRV7>o3;Jv^S5k&`mAH`=2%0kKg-_D<@Fc~ z;Lfoi9-#Gjy)zAA+621mDil-#YP+H#y_?W3@TZO#aeE8ysD}%<&*t+;0*=LF6ydKehU^|O9p7Phuk2f#n9Jw%PNJkIztcC2M zEE;Rs_gU;Mdnk+8XAyhft=#a(L>Ep9?5hNy|GzsgY;oDw+*iQ{((iIP-^DTB$X;JA zWj8Y9>$AqHRdtOB@q3&-i9YnlZ1F?>Tj!Dg7yB^MHYD#0uxB>;m;8Cy6R^vX>nca? zFw0-cVuz9P$oTSm?eX{b794xu4Fh}M9|W`a4R?;8=H7R-?tL#>)U#anz85X*S=;#O z%ukK+ZT$cKUUv-VeMz4+mpLxH#aI@{_^=$5u}l2yG3%s`VZr7Y);c;5F5`oYT@BUG z4!c~>EO)*EKqJ*=b1ImvgkNTV!4#=Y6LQ++$m9b7AHgm+X;WLw=_;=KM~ca+`pW z?CUnQ_HVMA`?obmC1*^D(nczf1mtOLrK+t9Yn{pe(4o4@`w(LUCCHROXt9(SpH z*#B&7lli!PE_SY`FB$We*4(4^h1MMNeqtX-_Na5&qizavUR|-Qsej8utvoANck!$& zj`nZiJlutSwrveqpM|jgZiuZzSszK*p&}=qf!{T)-)!E{)3Cuw9zA(0s6oSZW2S74 zDX09@=^&{m)>zC{BeGZ@Kk;i-+QJ3{IvTM?%VgGKJ|n1QVL!%r+lC>z=9+pTYj+Q8 z!$Ri9F$3C1OB<-zxq6M9%Xk1U+V3@5lKQrVjHQd%KN#Pv73=2?rrZeWXzdy3+TP43 zYiB8+l*`WVGmiXvsK<30O?8o$IaNnhz`@o^#Jy=1sD1{TSmb?dN~ICmllDjEl6|=Ko>u-Q%OK?|lEy zcM{+`lK@Ewx1ckVpmqYr3PlT^woC$AE@~C2KlJR83E1w0hzhGNpqNQePhijzrR}cl zLxQ&5fW2C6&7R~3=$;qVx|0^ug`bB$u~oS+HUt;e&>&Q z%*^-lxxVkWkKo<^1m(|sc`i2JbjGguXz%zk;2^(UB#(CS|9=PCC0R^18LOw@M-z>y zf__Q9l8zzysS3K{|KG}f%Qq%^w&(+BjK9{R87}+6o}MB{NuN;v*gLFj)UQ|2hvUwx zk&Q|;r8f8NB3HP{)9=^eJpTMawBU69s(xJ-T-ge)NSBpeMn0?k;GVD7UOvLiDnzes zWiCDFdSjpk@&UCVzg@g99=(gtJ>YOAeok~1bG;8wr@pDCE?{vn!GGbff zvw9Ew{~GeuU6amfa3SF9q+Dg~#)Gu6|OmX7SDDU@Je(ODZt-zcp67MM?J9>6w)WwhN+l0-ebaWT6DjRGyHe^$B^U%)# z=f0h}*u^$na_1?}Ikfd~sJ2Ev)>`byRWmNb&U<<6&_r{hm}@VL_Nr?hLw$a<*%^XwRTZm4-+XC9ab_T-_SLB7#5HMX40i@m(?vY2F|eH&(V^?+-K7^g{8qVu6E zIaN{Q>NfH69^|Yio=s#YIg^TF1(O@IzdpHi9X>PJ`SuosUU>80?Z4r}@YmsN7C|}5 z!d!uaAGLt^6VDg+4lp+n*h&L)lYqItf{A-#z#Mt9OLcu;9=sI+KSy)$=EIWUt{^A0L?bLh5y(!upsjbmI36_e0rDEbEJeV8m`PI>u zkxBb}<&4^x-l?joi`_M*u4W~_@8aVW&Wjm$eynFy!Rnrm)vn$L9G-jT?#nGK0+$E! zV6oV3$id=J{WU~;fN4`AI6U=!k-M6A1%IdBTX^Q4%U?h9JC~0Y4weZ1^bCFF@StVn zyqZmA^J_+c4@t)TcD8PHOE?+RJnjiyTvHX97kirDYtMZ2@_GEeCvr*6kNE%E=u2YT zDn46t6xe)(wH@(PRrCvITpas4u&H(Y`p+)v`b7*_9$y>NdQ^^_ACo+q9GQ&O^4;Uq z#y-c`rm-gb`JXbi7OrJ)YX4cHi~PpYeZcqr0DOnn$F35L3%>UO-`k(9&Q5kF7hPL~ z9j;(fW6@hDm#()kKJg>Lxajpz^mUMIwm2Z0VWW5WFWF2qd^nx*rIF%YvZr~mQ|-iD z4f_94W32u}dZ+Xs#Sf&Q%aWsG}%X;n$UVUF+J9JevbY~>ArWt)j^vBl$ z^etHN?a=@D5oN?Udf`wpO~Cwsze6%&ozo^6QS#xwfP6Rx`Orars@jKK&AhY7de;9@ zMa;xC4P8@bPmQLzUc)+XV&ATSCv1D>{A`Hd+lbjI+;C%874|Z0`{k=n_e|^5SX}~p z;fC|NWW(3Hht{9hM3uX^O^DNnXuGT6qF!LIzs)BpZS#Q}D3UpAJ%9+T{>e)rB7t3Qno=8Ze< zf4#u8UG0RQdxh97*}m+#E{w$&N97 zefmk+Togz6MnPoHjZSgo+t@KKFBpy8<;E__SF&Mjh5vXql#SzZuoocgz)8Pg%u?O2r`-|pj=^xQ@%|}cJqY7A z)-}(Ln861kzOVuqKTNEvU|e{2C?{XYMz#-rD!bV!cCsPj0*A7R>D{A4z{syTn$U-( zd>T3&ZTd|vmai4rKIWJii?JVUhHuRsVP?n{oq;d-KI=yQJBnFB530ldQi~l%HcZ); zWsj7fIS!8#AD6viC;NkApuE}g^4ztNrc<^Be-E`^`UL%K!Le#w4d9!6vzOA3KfmP3 zlNaQ-(F4xE$-1;M{xp3z^KRyz+L{*pOa6QL?*wIld^?dJgI`ZOb16}sW&W)ii*XyL z@q5Kg)3&nCv|V3j+P+Zb`4I%et&stnxIaIA2m4|iA9@XZ{QRi+?DMqv9mxk;i6QAA zhQxjAZ;z$#G`kNKcswXoX8L-kG(48R)7c$dcfU`y5!4SipYgTJ(`P(#`6A+oiijx^ z|4xrI-Syan8X~3}7_o9<26;wn4NW3e06A20SuRGc7`;OAUAiwFQ1V(W|4lCLEW)!% z^a;&Z`tfu=ZY6ROYaUiRdG8wOL&Uc0`S=hqXNQ1Ae~#4ID6gz0whKD1STn_3Wr!`3 zpQHf$mg26iCzdOT?fvXY=SFwQ=0#l5N}gYdp0bH=y}Pm{RFmWzEpFIYqF6CDCFrT- z-{5b?*>B7CX`)?6U@Jxpc@{XAjpw)IZ!|#n;|;eSPl4N-Uz%s0c-XYb=O$lUk$i_d zlg!;0oaf8lZn4+ybgdt}GIB<;vuH%-Aoki>#q={8x?gia(T;DGCj0gh5AiMRSL9ff z?sOtnuJGf}v+&7$-O9>u^sjL#zF2X@<+SZ=FHj(X%P ze=cF(=>=~a#u)gf+aH6T-;bx)0BgB5I_j;_L1MjSm*}J2Y++$-4RPr;K7I20*v@(p z?_Ii=^?H;!(>L<^MGrOIn0{Pb(}`U1SG(pFl!WHUhdhE9id4c{nL2WhsLkp zKEDgT|KlOv7fvM6XE?d5JEiXt*n#yO#@5TX-=5Z>1^LIer+stTH}+1l&ApWeDlT(I zM}AyjX2r>Y7|nS10z1>8-?BBAFy67mQGx zqN#?+x|*rrZ5+I<0*6VSi?y4QTwK+tSRC9gVf|zye#IHx_$cyF9Dn?1{tGUY3(-5O zwPLOlYOF^N8fxEy{NDUA`Aj*v`TyoK4USv+zxhn&^!ZGo;u`T9@nHYs^WqQG`}1;{ z;=|-JEx;Ziyc2y5<}xY1^$pFQ+n2=xAy#6mwpoa!$oCu{B^4(1-4vvf}E=i^Py&nMT`{D@fE>$jZ~`$ef!^NY!K z{6CAFCfyHle_r5z0r%$z?ibe7@$S#7v9D~aiv1!yqUNqK*go+M)fN`S9Prx1^P_w)tdQ(Ntj-VV+#lwuue7fUbUOSk6 zXy37pe?j(pD08d>Ui^__WUBu4V`MZh_}~fjRco_T4uV^P-NjkiF9*ujmncN8C^m!h zgYsnQq1N*hJ?)EwZ4vObq3w6XE6a|s895`19^u;&?yG#Z;ymJ&??YzFgn;{HtGf)g zJ8OSjR@0S1|Iz<%!xx=p*cqf7EjzDkQCM*uMU4(}fZA_I`=>rm2VWXnFMV14hW3s^XXm27w7|!1z*c&% z<3_}bR-ZgM%XP`wg9pap3FjJ{-${So|I_u!z4H9~!FlJyeEzei8~;!|poiQM!Le}S zg5h#Wz!CI=GWovqVSGQhvkTnW4ux0o(mbh>0ll5hyKc(GF@eM(oP%MMB zpUq5mwnogX);#=Z=UNayWJhZ~FWK1v-az2Cy^B86%g3!u_BqfU>z5->RBoU^CXT*VZV&eK< zA9d$r?#9TD%7*m&CSGr2cC_xTb<$hQtsbcMj?n)?bTaNOA#U*C;exPYHfC;~#+a%O zR8%{W$Yx-F*M{4=Y)svT3uS9A-3*>9KTdoibI<(M@kA=BarkYkJ=y)J3EQT4yG`g} z{qc6pcW}I&d0{B}L_E(Rd>33jI+R?+dH6%OK5zOJ5PY`nHoV726ZGjkI?2mn4 zVH{80>0RgimCjaRJq^saVrNgEC*GDROS4~1_-ftL(3V$Oll=RV+x}Z%t=xo{tKy!Y z(_T$y8^R#fX zb2)iQ``On7ZGV;ZolTzkxLxyhdpJlw2{_nNU}!?PCBMlee9?1XQrowalhRb+HZ}sg ze!u^42K{PW^t(#GpMp=GQF37hd{CQ$!%PGl<)~!eL}&bB&zA1xt*DL7kqr4f;pdLg?#wrm)w)KWdr{RcGHTW47}FUezkcy#Y}#gGk&kDs$bFj` zt87J2KGs@+eQK8GDms&4zIIH^(O;|QSgSKwtKD}dAEv1|6p^>-ZqDr@#S zV_FU!sRZs1Gu~ANq3jmNJTZ6ur;gip&-B_oMB7?3PkzeHk8u>hbC&O)eRR^MEfs&m z`t4->inM-=v$xF5+FX_FoJJo1@-vZxT(h)@eTU`^%4ZD|tFyS&EPWln*(YgF>!cbm zs_&w;z(zTvi2E-+70*5qGK+Oz*L%hSAB;hJGyUHJCUWpOy%-vp8^gXL#sEyX#r-gG zPE;BBPnnTp>PL>9lxytSt9 zhQe9B2kb1?J6m|Cc7GyUgEqUw##xUIYw z8*A1~!=LyDJZI7Vdn>lm&epJb;oe^?U-#n;)7LdJ#wKWcRor zd8H{fE^_;#qD31+<1}`SFE^()x73>x^Lg^XhUzC1@QN66&9lIh<`tW2*2pi@-_BpE zodR!MbJ_PbXELrcXEyrds$^VuFF*d{4b{gz-8_;TSCL(ZCqk1Jzx34X>=Xav==H8H56L(rG_Re!&c>kvT9mDxITTh%k`8;{ETlt(p-mJd;k8^XMxK^9ZSak(0tWK?79TwXjO1V6SD0BMUY38oo$LR^b)OdY;8Vzdrl%-2Q+crFCNG9c z_J0?DjFbCs-_6CN*?DYC-Y;LA=9L;_`jW-w@s3bQS$YY4`R9MTq;*_oZAZwBygLrM z`}4moYxw2PwMRnX$OVo&&9hM?`wnQ|!@wdi=)9kL5&YP;lGq07H*EVl-=bqbmk%Af zoC=xFII?3e?e&_H3*Gf+mi3M^orepe*yzh~ReH4JmXSGm;KqW^-igo)WP+Ye`#+Ux z+4QNNP5bz6`P8k*5b5*KVR-keCi%%DA+MguLTE-}qdO&CWh`v@>y?5BdHdw~^X<6D zQ-cL~wPX7`M{*UVz&-g&IP1Z-X=_J*>EOBFATP?Vvju%CvhJ4So00ccojmzO%qefI zgzrM*tdGTc+sg%Qr(TWT({=1Ro#WQcQhr%~A9f4@-AQA)&hD`{Mwe%O`Cs3XbCz;e47ynY*0^|enkE5O}cyJLlX_l^UIQ|=8 zE6Z3^i^gy-3!dtCJM-529&PgcHNm#E*2DQJHBOBwe@@@GzA+CEQ}lxkpuAmh0K8~k z!S=j&u|+V>q~}vB&)4mJUsP~hl5_ociMwV$l6}%YBgj?ErL_F+( z9ohwlV^gq)QR zLF@XIyC8ie#P!NaD~NCAzghoK1AFg=pLx*1KM?G{;B0W7{qg6LGOh$GJY6+5!LRI#U)qX?Dj7OyBRQHIRNbIcI@MeDcfYEqoNj zWEB~7FH=5+Z}8dbo>sq^?D0Rr-(lqHSCPN%{uQ8yBTuJt@adX8OLm3aX+Cd13@rXU z7vD!w-cE4M)5BO}9|jGz4@T3c*3aR$#tXmhi><%txL^0cj)8Yl0sG|XWY!=}2;#nE zY#;Zhf&1|1$Khq2%ZU+aXT5!VpYGW+?3n!V7@qfk$Dk4ZUJF;|nv%7msp0iw%Uq{q zW&*j|y}}S<6kRLZLKykQb)w$&^JQyt_T=N3U_`R1#yfX@a?K(1ij~;O7T{aa-W-8; zivGCBiL=mucZMR3`_Si7e^<*|BA0~*Xsv8Qw;&Vgn)Iw7-O&5Jf4j3{mSZj$4?l9T zuN|PyWA6Qay-YO5Z@WnQbK|^j^K#{NN%Rf|%*UGnki=!xd))tZ#OBLrV? z0C&$u+6&C?M#iuHyt^ZD0l2>gnKBe!T)w|L3vP>sTRPp5(=#8*?Y6jf+wo`M!5*$nuy#BvlYwjPdAQaNp85FI!9BD7 z!sD?!%~Pw1&EH$s50`r;ekyromBH?ge%T|s z0e@SJtfK4e$-z;j=oQqZa!NiP4Q)H$)|4tNnM-{t>R}NB_!c#sj!}o{%1C9j^pa3) z*0ib7t0HId|D5ROmnCcdIWjAH^%b=>OX^JQ*a8!K?7FhpxXVYxUb}Q;?7u7;6)U;K z#70HSV;@@=?;1tiO9}nGmN=jMuG!I>9b&u+P1|d;&yS8OHQjXwre_jvoTl~IYp>G;?uE}szl#JZ1n zy@@^KBtFr_@b|IcjQW3b|9uZux?{82H{neQ>ojWG$D(htMsMZT=dF!JQ%2b}IzM|P z2M^P!-4R@$eEjfj3c``zsf!;=RhiwK!^nDi`cKftt@U-V-KvfJc7$gs^w11+-$j3O ziIo!{%YPrZr7HSh9QAp4Fpg4rcA~XEC?m!{SJy{<`0@Utp`yP(z)ww%-NE*e1Nn0i zWQ={GmC@;wQqiz#4?t7>{5s`y_Hge2bv5PZ8H@a}Abbita=y>%T z((gNrDS=J@^&jr6sJdp=`;`fD$M7vT_s7ytXndOeJ`7E5mp-pGL^pmFdxB(9e=Ri@ z@qzrD#(bLz&#_061%_U0)8*+np?w$jW!OfJAGVg(i=e=4T23S3Xz;ZAXH zjb7!Rm|N;zR$i8-o}6*Km|(%)cIly?F>T|?$x!}F8vVe|&#sqeYtj0ozcH^`wh-kT z_&IbbU4%=H;)G`?Y!1?#A#G zCl0R-@2_yr@c2n^{j_$sV3YYr?JnJ|PA{TNnV{h0quX-W#G{i;>Hy)opYL6YK z+6Os<#(zS;70h`JsYz4=HXTlc9JIcl4KflCNiFJ8nKnY51qx- z(d!ITQ@WZp37+Yr0i@Ic*E=iSKaV~?-s|)EGyQuv*?XIcKO37*+Zx*=#q;^?jq_=K*Ckd)w=&kB zR4UFr+&H!GN{SHa~K;~n)RIP z6g0LYH!H@)t|v9zYz}H}J<+|i)=P?uy|rA;JX)xYrabd`#E@zIR?z2-tk-Jhw&|Ki zZ%vmq+HtqI^J1H>xiNR`#=y1JUGsDAYkqo8JhzcK$=9SfGVx^1p^{jkI_i4MSGJrO zvsz?IU!DxYSWr&!bu8ZwCVtsFP54sYSOtFlW*YbqfVo!am&w_rsaMd~OFe~Nbgne^ zsYx5lr~D)OyPj_yX|6m{u<^0V%Mw|^y`JIB*;$fbw{Y($GU=+?X<56L_!R_~9mpPn z*KaPW&b|T6Lz8FiTHo09E82S#{`(g2iA;HA=#nHkv0mQ))5s_*SdXXfHt&C&T%oPu zNY?YiQJ+k9GTJ!G`@iJM6%V;#<|X<#z$DY z6muHRkCVTieh$$B7hO4IE?96!!Bz4%y7=YrS$A$i7S&vPu<2+$6pxpgV7g@!_HnzF zH5wn_ulTO}6w~GVZ-cZ@_-=qrONaQ50iU8@hV|98ovv9UTczYL>Ix#y#$MWqAH4q> zwk=)L{Sol`c78Liep;k4`uVFtdX!J+{8*^qyZLb=w_+0vwvoQt?B?7F>Q=_Ae3J?} z{l9h1moK1aGt>72e>oeK2b^|bAIra&Pm_ai;VCXSZ{leb+zBDwZ4P>3Pzl5-ny7=9vlHreYzeTvve8x zKsBb+<;cCji;Mk4{V3+$U@KCLxN2ORNr@2aQ}HK3by z5WgY()EaiEU1*nZa5uk&zx7;C(1z^N31pd9ZLF(c!m~m5>v7dX=yBl8A>!?X8`=lo z-!YUf7sN?^+k8swcNS-~7g`&A9QnwA7YDi<2R01x?YgdK^7l)76l^ygqTPZk9>bU3 zC;YPG8UlCy`chWrV}GoT4}Rv`_!O@sUd!GcEUu}mexsnouT62{+fMP~W@kmTjvPA4 zC6&l?PwPLw>kI3s$CS#+6Y;LCPEpZ)8)M_{tG&F)V5|G-r%TGVI%5}C*WOq3lZ_?g z>JFTjmHkSwUd6;$z=JHURo`jZ>==`8vn$Hk9Y>#ASN%)Iqj?+nvF_jJj9$E-F+9v? zUP-MT*F450nde)K;oFR%&b>Z*aS7-BaBXVw#j$TQrj?9!CA<{BPxmIS%ci6;ek(A> zpI{%Wl-%Uci+Es-yE3kGVIqywk)cLo57O9ud1$Cqk9 zSM-+MkCMg3jK8|}OGR(8CpT>zJEiWx$Gv^YvnwjETjN&!NQa!G1bC?0EP~As3!kcY z_F-Uis5M-igGI}NN_lndU=wk9qd?IBf(;2F|cUm+5|DQs#k-|O`h&_#=HVs zA?wjrNsc47yg|0(EU@@Nd-f+RfB7crzIZucmFvz^4j6UwiyDstYv1B`6EyZ) z`?q4B9i7#Cg0~RsUP~>F8s_kDVEntW%YF+xt?by=ex0+|`{Va$8nhZ3IN}TF1Dw%w z1lf*w0%AKu(B=YcZSJ0S(Z?a^qW6AI{vqe7>ETJ833p-{QH=*zpMby+=HOXPSOL zqPpq+Nu5oX7h4%~I9}-c=2kp>NlogW`J72X-uOkPdu0`H3oO-NNbW80T1Wnn;9GMH zL;sTT23zk=vWMlx@F~Siiyx&f0#9crzzyn*^G=*+8=1Qwx08N&S!V}wXzIcN`DitA zZ2iN@-PA8QYJdr|D<=z7)$VoF*|4*ZX=-GCyX>IO4*U0xDB)jGF{y#>Z z)m$^a+zLOpbEVJxYxy$kVE*Zyo%p~^WG33vUP+(Imyt}|baM|}IIo}fsqVOc-JCVd z{oHlssObH{eS0!GJl^K(NY+*V((6*wOm}eHdN*#o{qBFBYuf+df9X5!xzJW@2-xDw zh!Za3{IRm3`W|ZE-^js*g(2i1yQaiND-NU|wsP0&L$&>NAoAPyLY!sgT=%~NuinSW*9IR!;?B2j9!)H664nAFc$f53!<<%BP*rK0Z3z>*sv(=|VrQ1>j9M>G0p@ z9dYqJ@H`_Lxb?Hg9dN{6V?Bq`6@1>3s>`#bgbZ;K@~!1;DTfDaDWWIohfSyapiST4 z1D$NTGqgKhHL==-7B($1otD?(6KZb#@{*qqoO`&0bwqdYYzDp`(c&0$uzV5Tr0<|^ zKNNqMtK19dQvDv&Df^RtU;hKsxt!lwey{j8WBNN|V>MU6$LyH--;}$*@-cq@zux_o z{(b)2F)+URALMj5eyc9MuKR1u_)nqPsR~QW?X&5Ns5u0UixB5)Yl1<;QkNL)4A?S| zc_PR>3z2hHU8ou%ZewCUF*{DANOkc2K8BjJzi$3B8plv$alsK{Yx?U5rzQ;80#odx z+R=XJx2HbcC*|w(;w;0oBR#5;kF8k(FG^CIQ|-fFPAnM7UXsgpl=XJN0bNJ_KVg;; zw+5Yd!2|c3=&W6`s@8Tc|{(H0dyd~ns@Z9evG^6Y+mqUpt- zocQ7cKXyhJ?Hy6nnEl?p74q3BW-ZM91ox}CkK9<~esg5CoqGrEZgi&f7MwA&I?&!U zo?poGi+Eo9Hn$d9M2uk=UCF(5VUe@uOv_8_g>$r#K;L`H${^HpU_BQCU+y=PoeQ}B zw11s7vDcYPn(No=`s1uUzwKCyxNEL>&g{Gj}DXM*TSG=a9vRf~gcX?DWh`XG2-06F=+@|GW<2wGU@Q zzL$fe55$Wh;7l;Jr%*IXlJLWEZ2K>6+-lJJ2@4WHoRYL0>E?*${mf-NCjqgRzw1 z``b8iN;9=({WdLqpxp-AJ3wq0@te^qJ~mb}!THGYnIsm+RFkR~x#Oh>XPd;+(ceDx zm?L*|l_~&R0WhWYH9sIb?;Ce{5kPNN5hsq(H$Q<7dK@Ws~ z%=LH#JKVVF%s$ne;F@#=fBw(hIeCZH>d2~wb-yES;m;naE>g}IftB8vSzm8m&X}@m zh;`X~d+aK`Uk$&jWL=139dd27CflFhZr7yYW65>TypNxRn2||Oo3{6`xxP|n*6hN@ zaOyfyv-;_Qbvj)v=1_2(W=~_pFf>DBg795mW;%nkv7YBdAI011_t;p*e!2N{FyAlZ zegpWB-5EUb<)ZqT^o@V{Vfqfri5lmv;EC^_@NJdq|1kR~o~b_lbJx|5|E%}hlSeIV z=4g98{RaCG&J71Y)&>uc^Xv3E@>kH`P_BItyF&mVs^=apy<&VfW$>#ulZjqA3+ zd__wm)S(tV6@PLLOv{R{dUN(_Kd)oHse3r11zEsB7N|srQyuHnBI$SjUV7jDdc4lr z-9zoiP%!SpvDUzc@%;VQo@>wQfp6i%D#i06C#2v*nv>Q`^LF8fN2rf6ot#8$Yxl20 zw`$n<%#OL_!yZBI=X-`J-SVK`Nl}B={S~Y zq3i&9CjxI=0M8eHR9>mf%Kh1jr`NCU`Z*uJ?}80AI-`tQ9N3MC0k3vS zi~O;8we%PZ_D8#B^wor)K=p3IjLX6#*Mj3y4clqwo3_P#6oXOMc4y7XFW*^nH+9?Y z(i)T(*t+h9IEK2CkgZ!U|9@R>o#u_G?fQZ~JU_p@(AI4?iR+1%;QYAaf#<7<7my6A zoMPmk6CwDhcnarForuR*Mr*s^hdN&uKfGN>#;kE>p)o0wJW*X@UU;2#tNhQ)u_5Mk zeQdSF8k8=l-OE^u6zyuB)4)Bg-=jI1XgYJdl#k|GfA5_&Et(^9(>~wL=ZlR0Kk}($ zyv>CpV*3k6SsSZlQ^sv=^ok*sZ_&zFYKJlT<0B?xNj0@Tc=m-V##fh%4bVFI<6F%5 zKFqv_qrEm~Xq<9}B*zrMw+GHlP)rx`xP2(~+pZ;tybpYq-?&wB4)LR#TaukEW1p!I z&G6#|Te%kAXr?^z6!q|bVWxnyOaALPGwUxylNU>`4bm4sPSLh!Z7I+kWSbOu{fFj} z4|XSU?3+R}g_B9-cjXAJX6<#a^%7_Uv>;4v!z$voY#Zpiwtd>jZ!>e^|K{)y{DXN5ct`$i1_pR z$ydqtt6Ixdn_4U86ok;ZCYSmA(axWmYyJHyADn(WBIJWJmYnWyb<*~CPG0c2+#2<- z&Dvz=`vsHtB#1Hb$CTM%-?KSCt-1R??s107!Rn^u1Fw^7ihaW7^XvW1oX_{k_3J($ zr+@Zw+lO-c>Elpnh@5`<6TB|p|EZ&YLr(uB@^fN-PX9Tza}sZk`lMi>b?gE3ljH;1 zr`HSCKDLNGT707JMZ0%7_?a7CbhK(l_CbHo#6!ROtCO=1Yk%)wP+frR+Yej7fCpO> zbFejW%Fcpt;~8&HoU#jf_KV~@rGYU+ez(Tg%bXSG@MLHX53$cNrSe)THyt5t*M56-XNfUNf+@~dr5^-SaC zR8Po!+U8W3Wj=i?wxwI{#CF5kwM}b@WiA@9)Aak>nO86PXPl|grhAaJ?lax)iPVct zTTJKc%;O08*jvcQhK7`>&JQ}hmyg|IY|f}JV;&Hm;Kvu7S-N<2v22O`xzXYo|K|DE zj0Jn)DSO)k55=|$4l-u^9(E4kj_jz|82@$it{Fpr-Zi-NZ<2RSKZVehcIKnK(7b|j zq>qczgDrm{{@mQ<=`^9ONt-u@OhPTkf>=`Zq?6cdsX^Y4Bgl<8d}WWlKDcLnDgJeR z`~ADf3bJ3yPU+ipy|$1?SpTFaN88v9e{70h6K%+Yq0{Dlk0bB9DO7%RsJw5x*8#gJ zzB=+0i8qHwh#wt=M=IwqOdSAXnjcTGhJOAxIy!k{xx7N2SN@^iSM31B2xtw{KO_I! zF&FAR{rA5WBjeA(-vj=atA4!3= z4ZnixC7#VFxSpQAJrEz%A^2xa1S9Q?+sjiveXfAo@XxBUdQqSa`DQh5$yYWOEVmBL zQ6rn5^<8-~is#ey*Zw)y9y`-P>_p^x-+H`_Tl;s+HF01w9Q-I$$P-q(k<}WMex~$T%UG!ayRy#=gu`n&&A1GZYGy` zp~G**#qi(8hVw1o2zK0u!kJ8`JNlUim8+1mzac9n`=*}Fz5x@5u)(SC6ma9u(U1M~ z&$$~mkD2uxl*i2a2lJTM^Iy5lipg}DgX|H?VeUa*(SF)IW_XMCQTNmE7(W)%&t>ii z5K3?AQk9E+?q{;O=Y2zvo6G;Qn;{ zeMjd$w01FP-7bIz$hMGHKJzWPeC7q{Ho<)61+p6rpU)h`>5jVOm9cBgnt9mdT8Hza zhLYXo*kd%VL@2XXIJpa3U2B!OGWH8+&5F_FX|FxAEOqszk^{DTHl_ew$XzdH9k7jI zCpgtt>%XJ-6}OlPA)DmMZv!}w-=4Pe?+0_y#eeeYLj66-?hJE@Q+G3tUat4?*L3Tw zjdo=9;Q#0FsRsXF%71@;YQvX>^gDHr>HaUs67^dG?UO$le7~JMh~WR{u+IelUn)CO zZk0f1kEftj#0(W)8dbP5V3b9oSRfFWG?HNX@N@-=*$U3I5od`DFP0Q9r%Zu^R59 zHrJ_LK4;PPNBz7-+rxeGY3FaL{h)IhgL&_gN5nTaqvs^9ba#aRI@!4!zeY3bBtKJx zb&TVC+Qd4|XT2-k!dN|anilp#XSf(_B8o=}gE#I%XdSuon_Hmou<3hpLF`8*X z@aRjj`#og;2zymjQWGW4W83ZUoMO&Smz~nu0Sm%Khpt`KC3~~}=kE!2Q-E~~5g z0`G2$(8l(v*oq`)h?~L~xhCDs%10?Pw!U>U?T8M2Q*BHmhHwt>gH6$%Q+}T5Zk>s2 zNdF!1RTG~cy$qOnaHi?Bu^RDnZQazJ{6B-Su=kw{up_01uoc<0btW%%D_^!XTkSK* z7s^HU?Ma?L!L!M?o|EkBMMi7B5dSqk=q%%Fo+drYvps2Ctrwb3t-sEhRGrqj*gszV z&B>)FL*tL8c;2u3=kK}BRt=u3dOBZK3w!U?QucC$WsA83+A|dV3GPl(3ogjt+yQ#` zuTV$MgV|oIt9W(fK9Bz3&uciFLoeeEjKj*WqPvH=?%VX`x4F8=JboA(zt&c=vv6AV z)((K{+P4Eg8B@)*em#4VezV8&WA=iplFNehAnu~;72t0OWjZIUOB~nz-jI>b(%;6* z;NrVa%ckPT|0^HJ`>k3pzP-=C_R6)qzm0Pbx1D2ojrgZOf5o;OsA8V0vyPQkp zOddE#Rk3>e6c4iPi})kRbH^s`^OX2x;)8UbbASHnVBv0uF;-H8?$z7n=Tc6V2ao%( zaqPq9>i3zyu0a`LE9)DatA$zCH2)ph2%5APIhvGzU%KsIaz7vDa(Zw z-2YXdNp`RLjOkvqzdye6MCGJaHh-bM&UD9l&X+TYW9hC66Q41Jtfn>f?G>No{)$VI z-7AO%dbKFo-3b4z=j^C3w7YV0Wwibr(_K$H;-~p>pZRB2-N3laB;sSV&%l!@nHeoo zZWL!zrI3$C6H}voLPk1~)R|VfXWMhAi1ylA+!RFly9gy978nXN|yc-`qDSH9-^*!)&!QAy_*KF7&eHy)2w4ieI zl&tGaS}eK|%H^3hg(f$qEZz3t;qO+M&W=_12B>-G!;SyF3%$nwE&I0F=+Ui?>#*;V z<8wp@1v6As2CyDNb{(duZjmNiRg=U6qJ9C#CYzu>7CJ0l~ zwes())?xlxIi8Je;5>KDH7Wb*Y|a~lKA2A>(Kq>?@7Y)$LyonHN zro6zw7_eua3PZvh!Lsbg=&$A4L!WlI@TGm_=m*s9m9IxQlLS|Me&b>*^FP8Bf-g>4 z$9>?+cCPzvO3$0cnzcTR%uG$7!1!!ig6USCUqD~efi1n~pP?u@*@IP2HjR%pvrLs2 z51q4TZ~sSpNAlTF^TqmXGA;MmI=GpZyFc~iC4*%#`LmPAbFz^oh%rA!o*QExPa@AH z3NoGLIe9KYy+8K7pMHw2eweZDqkgx1z(>HT%@32yhHa<8WY#uNBY1O*>0Ad*i_JA_ z6~peLcg1R#+4wBCspP8gTvN8Ws;pXm2Y2$mgB=B#W37u!-_8e3=U1;beP6l6*f@Eg zZ)rU7Ijf@a|1LU{^T`9N(y!3zmhPng{B;K(%BIo&iy`;I*85ot;##a;hK@81+PL!} z&nG2X=J%6%5S{9N>%X{8x)kuUwD~Io|8amU)$X8hoZfr3zqic z^Vqv-ApUMR`WxIUf874}PXGuhn%g0c3WvT!&w`xGc)~Xp=YX~Tg3SK{u!TN<-@~A z;SnE3&a`q4`hx6Vim^G)-U%;shK%ITK>x$aIGdTfN23Sp#vRykZbPnd!Tr}I*W~eA z(dP}k>(8}E&*nWN+Pj8le3~lVUu*mB2#==r=4fh9j;8iNQ-A#tX)1iWpQcV6hNg;6 zo&;8gqKS2q({eQN2sE)&;|o}hTTHFK7wT5D4iUa5cUk=;Y08htv7;EJq zJv*oSKTCW7{rLNnKkgtbM%3SFbmtE%n+_*i2o{5DD*fj`$l6Zrxe?g&tX?!5ed0L! zgs(RtPtJTDJ>v7!MSpaO=a0X>Y}p0}-6%OaoRtm)UFp1t_`=%J73e|XJUwVqV+z>y z=~(Au=s}UXq4l6=lb!qFnf^Kq(uE%ObfG7JabNE6znh>hwZPDkf{HzwllIQfO;8QS z;Jg#eub25X$^Qc1)Seh*o+Rz^+;;u<D!3;eo%haYP=7t@ z7n-YFI?+X+=5TNLzL4R9&&uzOtlFpg3aul04|Y8A-;cTG$z!p&SMy741;-$sBb%~d zqA6n)mzpmD=(5 zT>E5mAvNC|Khqu$mB*JqR*g~TDkXFKlY-7?_-$!Du>iu06n$&_YSUjo8xP9fC@1T= zTwHDBES+lktBtGjG2DDj(qtZXB;CE{b-mh$R-zmIbmz8dN%TcMh9P+ zpLZVIoBld1K0j0bA#;?Cc=LG~o708BymSj|B020RbLQ;2J=9-ZBiXu{e4UnylUXYlMyL=pZ4nJOS-IV9{D84jPm|QJ*#|Cvm-^93_C?s!c z1pVrNn9oSAeT>ifhT5@D#iOc=RvNiES~b>vDG3uw7_36Lw z#|^R{isgTUH6Q9;xlzpkjm4AS`kztl7F}acW!L&JG($L0zooExI9yynoa{pK(;CQ6 z3*#3{5RX<(PFZ4dYf%~;Uf4eN%Ib{;SIyl$dQB^|xR;zsU6(ArynXDNEnExk|B

    mToN2G;_Q9zfoOf#Lji2km|0>|0bK?8Z)3&9s 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

    +8|`Fe}5he^`<0O^nL5??QI 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 - - PreserveNewest - - - PreserveNewest - From 1351bfdd1fb2aeac020f6f032125db47bda0e3c0 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Fri, 9 Aug 2019 19:21:21 +0930 Subject: [PATCH 0608/2815] Add iOS FFmpeg references --- osu.iOS.props | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.iOS.props b/osu.iOS.props index 82c60d0aed..58c2a55b74 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -97,6 +97,11 @@ False True + + Static + False + True + From f68e41f7b9fef17f238cf322d655f9c7b8164074 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Fri, 9 Aug 2019 19:30:31 +0930 Subject: [PATCH 0609/2815] Remove BASS references from osu.Game.Tests.iOS --- osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj | 8 -------- 1 file changed, 8 deletions(-) diff --git a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj index ea5ab699f3..5c0713b895 100644 --- a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj +++ b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj @@ -13,14 +13,6 @@ - - libbass.a - PreserveNewest - - - libbass_fx.a - PreserveNewest - Linker.xml From 90b1fe81f3ffa99167bfa20af7b87ba3c60c8ca9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2019 19:12:29 +0900 Subject: [PATCH 0610/2815] Update cached usage in line with framework changes --- osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- osu.Game.Tournament/Screens/Ladder/LadderScreen.cs | 2 +- osu.Game/Graphics/Backgrounds/Triangles.cs | 4 ++-- osu.Game/Graphics/UserInterface/LineGraph.cs | 2 +- osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs | 2 +- osu.Game/Screens/Play/SquareGraph.cs | 2 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 ++-- osu.Game/Storyboards/CommandTimeline.cs | 4 ++-- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs index 8102718edf..a92e56d3c3 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces } } - private Cached subtractionCache = new Cached(); + private readonly Cached subtractionCache = new Cached(); public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) { diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index a4638c31f2..d3279652c7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Objects public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; public double Duration => EndTime - StartTime; - private Cached endPositionCache; + private readonly Cached endPositionCache = new Cached(); public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1); diff --git a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs index 67531ce5d3..83a41a662f 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs @@ -88,7 +88,7 @@ namespace osu.Game.Tournament.Screens.Ladder }; } - private Cached layout = new Cached(); + private readonly Cached layout = new Cached(); protected override void Update() { diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 2b68e8530d..dffa0c4fd5 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -191,7 +191,7 @@ namespace osu.Game.Graphics.Backgrounds private readonly List parts = new List(); private Vector2 size; - private TriangleBatch vertexBatch; + private QuadBatch vertexBatch; public TrianglesDrawNode(Triangles source) : base(source) @@ -217,7 +217,7 @@ namespace osu.Game.Graphics.Backgrounds if (Source.AimCount > 0 && (vertexBatch == null || vertexBatch.Size != Source.AimCount)) { vertexBatch?.Dispose(); - vertexBatch = new TriangleBatch(Source.AimCount, 1); + vertexBatch = new QuadBatch(Source.AimCount, 1); } shader.Bind(); diff --git a/osu.Game/Graphics/UserInterface/LineGraph.cs b/osu.Game/Graphics/UserInterface/LineGraph.cs index 714e953816..6d65b77cbf 100644 --- a/osu.Game/Graphics/UserInterface/LineGraph.cs +++ b/osu.Game/Graphics/UserInterface/LineGraph.cs @@ -93,7 +93,7 @@ namespace osu.Game.Graphics.UserInterface return base.Invalidate(invalidation, source, shallPropagate); } - private Cached pathCached = new Cached(); + private readonly Cached pathCached = new Cached(); protected override void Update() { diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 069e2d1a0b..19247d8a37 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.UI.Scrolling [Resolved] private IScrollingInfo scrollingInfo { get; set; } - private Cached initialStateCache = new Cached(); + private readonly Cached initialStateCache = new Cached(); public ScrollingHitObjectContainer() { diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index 5b7a9574b6..9c56725c4e 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -75,7 +75,7 @@ namespace osu.Game.Screens.Play return base.Invalidate(invalidation, source, shallPropagate); } - private Cached layout = new Cached(); + private readonly Cached layout = new Cached(); private ScheduledDelegate scheduledCreate; protected override void Update() diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index cf88b697d9..7366fa8c17 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -93,8 +93,8 @@ namespace osu.Game.Screens.Select } private readonly List yPositions = new List(); - private Cached itemsCache = new Cached(); - private Cached scrollPositionCache = new Cached(); + private readonly Cached itemsCache = new Cached(); + private readonly Cached scrollPositionCache = new Cached(); private readonly Container scrollableContent; diff --git a/osu.Game/Storyboards/CommandTimeline.cs b/osu.Game/Storyboards/CommandTimeline.cs index aa1f137cf3..bcf642b4ea 100644 --- a/osu.Game/Storyboards/CommandTimeline.cs +++ b/osu.Game/Storyboards/CommandTimeline.cs @@ -15,10 +15,10 @@ namespace osu.Game.Storyboards public IEnumerable Commands => commands.OrderBy(c => c.StartTime); public bool HasCommands => commands.Count > 0; - private Cached startTimeBacking; + private readonly Cached startTimeBacking = new Cached(); public double StartTime => startTimeBacking.IsValid ? startTimeBacking : startTimeBacking.Value = HasCommands ? commands.Min(c => c.StartTime) : double.MinValue; - private Cached endTimeBacking; + private readonly Cached endTimeBacking = new Cached(); public double EndTime => endTimeBacking.IsValid ? endTimeBacking : endTimeBacking.Value = HasCommands ? commands.Max(c => c.EndTime) : double.MaxValue; public T StartValue => HasCommands ? commands.OrderBy(c => c.StartTime).First().StartValue : default; From cb0cd7ed589ef56f1b9a3cedae662a709397a022 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2019 20:05:28 +0900 Subject: [PATCH 0611/2815] Add triangles intro --- .../Visual/Menus/TestSceneIntroTriangles.cs | 15 + osu.Game/Configuration/IntroSequence.cs | 11 + osu.Game/Configuration/OsuConfigManager.cs | 5 +- .../Sections/Audio/MainMenuSettings.cs | 11 +- osu.Game/Screens/BackgroundScreen.cs | 16 +- .../Backgrounds/BackgroundScreenDefault.cs | 5 + osu.Game/Screens/Loader.cs | 19 +- osu.Game/Screens/Menu/IntroTriangles.cs | 413 ++++++++++++++++++ 8 files changed, 486 insertions(+), 9 deletions(-) create mode 100644 osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs create mode 100644 osu.Game/Configuration/IntroSequence.cs create mode 100644 osu.Game/Screens/Menu/IntroTriangles.cs diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs new file mode 100644 index 0000000000..df79584167 --- /dev/null +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs @@ -0,0 +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 NUnit.Framework; +using osu.Framework.Screens; +using osu.Game.Screens.Menu; + +namespace osu.Game.Tests.Visual.Menus +{ + [TestFixture] + public class TestSceneIntroTriangles : IntroTestScene + { + protected override IScreen CreateScreen() => new IntroTriangles(); + } +} diff --git a/osu.Game/Configuration/IntroSequence.cs b/osu.Game/Configuration/IntroSequence.cs new file mode 100644 index 0000000000..1eb953be36 --- /dev/null +++ b/osu.Game/Configuration/IntroSequence.cs @@ -0,0 +1,11 @@ +// 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.Configuration +{ + public enum IntroSequence + { + Circles, + Triangles + } +} diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 1da7c7ec1d..19f46c1d6a 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -105,6 +105,8 @@ namespace osu.Game.Configuration Set(OsuSetting.ScalingPositionY, 0.5f, 0f, 1f); Set(OsuSetting.UIScale, 1f, 0.8f, 1.6f, 0.01f); + + Set(OsuSetting.IntroSequence, IntroSequence.Triangles); } public OsuConfigManager(Storage storage) @@ -167,6 +169,7 @@ namespace osu.Game.Configuration ScalingPositionY, ScalingSizeX, ScalingSizeY, - UIScale + UIScale, + IntroSequence } } diff --git a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs index 4e43caff23..5ccdc952ba 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs @@ -1,7 +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.Linq; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Game.Configuration; namespace osu.Game.Overlays.Settings.Sections.Audio @@ -13,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - Children = new[] + Children = new Drawable[] { new SettingsCheckbox { @@ -25,6 +28,12 @@ namespace osu.Game.Overlays.Settings.Sections.Audio LabelText = "osu! music theme", Bindable = config.GetBindable(OsuSetting.MenuMusic) }, + new SettingsDropdown + { + LabelText = "Intro sequence", + Bindable = config.GetBindable(OsuSetting.IntroSequence), + Items = Enum.GetValues(typeof(IntroSequence)).Cast() + }, }; } } diff --git a/osu.Game/Screens/BackgroundScreen.cs b/osu.Game/Screens/BackgroundScreen.cs index bbe162cf7c..5dfaceccf5 100644 --- a/osu.Game/Screens/BackgroundScreen.cs +++ b/osu.Game/Screens/BackgroundScreen.cs @@ -11,8 +11,11 @@ namespace osu.Game.Screens { public abstract class BackgroundScreen : Screen, IEquatable { - protected BackgroundScreen() + private readonly bool animateOnEnter; + + protected BackgroundScreen(bool animateOnEnter = true) { + this.animateOnEnter = animateOnEnter; Anchor = Anchor.Centre; Origin = Anchor.Centre; } @@ -39,11 +42,14 @@ namespace osu.Game.Screens public override void OnEntering(IScreen last) { - this.FadeOut(); - this.MoveToX(x_movement_amount); + if (animateOnEnter) + { + this.FadeOut(); + this.MoveToX(x_movement_amount); - this.FadeIn(transition_length, Easing.InOutQuart); - this.MoveToX(0, transition_length, Easing.InOutQuart); + this.FadeIn(transition_length, Easing.InOutQuart); + this.MoveToX(0, transition_length, Easing.InOutQuart); + } base.OnEntering(last); } diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 55338ea01a..2d7fe6a6a3 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -25,6 +25,11 @@ namespace osu.Game.Screens.Backgrounds private Bindable user; private Bindable skin; + public BackgroundScreenDefault(bool animateOnEnter = true) + : base(animateOnEnter) + { + } + [BackgroundDependencyLoader] private void load(IAPIProvider api, SkinManager skinManager) { diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index de00ba2e9f..850349272e 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -9,6 +9,8 @@ using osu.Framework.Graphics.Shaders; using osu.Game.Screens.Menu; using osuTK; using osu.Framework.Screens; +using osu.Game.Configuration; +using IntroSequence = osu.Game.Configuration.IntroSequence; namespace osu.Game.Screens { @@ -45,6 +47,8 @@ namespace osu.Game.Screens private OsuScreen loadableScreen; private ShaderPrecompiler precompiler; + private IntroSequence introSequence; + protected virtual OsuScreen CreateLoadableScreen() { if (showDisclaimer) @@ -53,7 +57,17 @@ namespace osu.Game.Screens return getIntroSequence(); } - private IntroScreen getIntroSequence() => new IntroCircles(); + private IntroScreen getIntroSequence() + { + switch (introSequence) + { + case IntroSequence.Circles: + return new IntroCircles(); + + default: + return new IntroTriangles(); + } + } protected virtual ShaderPrecompiler CreateShaderPrecompiler() => new ShaderPrecompiler(); @@ -79,9 +93,10 @@ namespace osu.Game.Screens } [BackgroundDependencyLoader] - private void load(OsuGameBase game) + private void load(OsuGameBase game, OsuConfigManager config) { showDisclaimer = game.IsDeployedBuild; + introSequence = config.Get(OsuSetting.IntroSequence); } ///

    public int MaxCatchUpFrames { get; set; } = 5; + /// + /// Whether to enable frame-stable playback. + /// + internal bool FrameStablePlayback = true; + [Cached] public GameplayClock GameplayClock { get; } @@ -113,6 +118,9 @@ namespace osu.Game.Rulesets.UI try { + if (!FrameStablePlayback) + manualClock.CurrentTime = newProposedTime; + if (firstConsumption) { // On the first update, frame-stability seeking would result in unexpected/unwanted behaviour. From 0f4bada21e05264e6376f2227aa17524e145c474 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Aug 2019 18:27:45 +0900 Subject: [PATCH 0715/2815] Fix right click absolute scrolling interfering with context menus --- osu.Game/Screens/Select/BeatmapCarousel.cs | 33 +++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 6fda81e47d..7df27de55f 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -115,7 +115,7 @@ namespace osu.Game.Screens.Select InternalChild = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, - Child = scroll = new OsuScrollContainer + Child = scroll = new CarouselScrollContainer { Masking = false, RelativeSizeAxes = Axes.Both, @@ -694,5 +694,36 @@ namespace osu.Game.Screens.Select base.PerformSelection(); } } + + private class CarouselScrollContainer : OsuScrollContainer + { + private bool rightMouseScrollBlocked; + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (e.Button == MouseButton.Right) + { + // we need to block right click absolute scrolling when hovering a carousel item so context menus can display. + // this can be reconsidered when we have an alternative to right click scrolling. + if (GetContainingInputManager().HoveredDrawables.OfType().Any()) + { + rightMouseScrollBlocked = true; + return false; + } + + rightMouseScrollBlocked = false; + } + + return base.OnMouseDown(e); + } + + protected override bool OnDragStart(DragStartEvent e) + { + if (rightMouseScrollBlocked) + return false; + + return base.OnDragStart(e); + } + } } } From b57298406ff7f6a40b1dc661ac2769a363d49d3f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Aug 2019 19:25:33 +0900 Subject: [PATCH 0716/2815] Fix right click blocking not resetting correctly --- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 7df27de55f..23c581c6f9 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -710,10 +710,9 @@ namespace osu.Game.Screens.Select rightMouseScrollBlocked = true; return false; } - - rightMouseScrollBlocked = false; } + rightMouseScrollBlocked = false; return base.OnMouseDown(e); } From 386d78881394f344ab87054b926bbe3eb6963a81 Mon Sep 17 00:00:00 2001 From: Desconocidosmh Date: Thu, 15 Aug 2019 18:32:45 +0200 Subject: [PATCH 0717/2815] Change if-else to ternary if --- osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index a46ecab6f0..6326c3a044 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -113,11 +113,7 @@ namespace osu.Game.Rulesets.Taiko.Replays else throw new InvalidOperationException("Unknown hit object type."); - TaikoHitObject nextHitObject; - if (i < Beatmap.HitObjects.Count - 1) - nextHitObject = Beatmap.HitObjects[i + 1]; - else - nextHitObject = null; + TaikoHitObject nextHitObject = i < Beatmap.HitObjects.Count - 1 ? Beatmap.HitObjects[i + 1] : null; bool canDelayKeyUp = nextHitObject != null && nextHitObject.StartTime > endTime + KEY_UP_DELAY; From aa3651f65eb98777c6254ae29e2512f2db098ba4 Mon Sep 17 00:00:00 2001 From: Desconocidosmh Date: Thu, 15 Aug 2019 19:45:10 +0200 Subject: [PATCH 0718/2815] Change the logic so the last button gets unpressed instead of staying pressed forever --- osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index 6326c3a044..1d35393de0 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -115,7 +115,7 @@ namespace osu.Game.Rulesets.Taiko.Replays TaikoHitObject nextHitObject = i < Beatmap.HitObjects.Count - 1 ? Beatmap.HitObjects[i + 1] : null; - bool canDelayKeyUp = nextHitObject != null && nextHitObject.StartTime > endTime + KEY_UP_DELAY; + bool canDelayKeyUp = nextHitObject == null || nextHitObject.StartTime > endTime + KEY_UP_DELAY; if (canDelayKeyUp) Frames.Add(new TaikoReplayFrame(endTime + KEY_UP_DELAY)); From ff601eefe6afaf6683af631f71361e05d644278a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Aug 2019 13:21:28 +0900 Subject: [PATCH 0719/2815] Fix failing test --- osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs index d42b61ea55..0c5ead10cf 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs @@ -17,6 +17,8 @@ namespace osu.Game.Tests.Visual.Gameplay { private bool exitAction; + protected override double TimePerAction => 100; // required for the early exit test, since hold-to-confirm delay is 200ms + [BackgroundDependencyLoader] private void load() { From d732d276b1186ce2108c2a5c88a6c2ec45907284 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Aug 2019 13:21:41 +0900 Subject: [PATCH 0720/2815] Make activation delay customisable --- .../Graphics/Containers/HoldToConfirmContainer.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs b/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs index 18a4241a79..773265d19b 100644 --- a/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs +++ b/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs @@ -12,9 +12,11 @@ namespace osu.Game.Graphics.Containers { public Action Action; - private const int activate_delay = 200; + private const int default_activation_delay = 200; private const int fadeout_delay = 200; + private readonly double activationDelay; + private bool fired; private bool confirming; @@ -25,13 +27,22 @@ namespace osu.Game.Graphics.Containers public Bindable Progress = new BindableDouble(); + /// + /// Create a new instance. + /// + /// The time requried before an action is confirmed. + protected HoldToConfirmContainer(double activationDelay = default_activation_delay) + { + this.activationDelay = activationDelay; + } + protected void BeginConfirm() { if (confirming || (!AllowMultipleFires && fired)) return; confirming = true; - this.TransformBindableTo(Progress, 1, activate_delay * (1 - Progress.Value), Easing.Out).OnComplete(_ => Confirm()); + this.TransformBindableTo(Progress, 1, activationDelay * (1 - Progress.Value), Easing.Out).OnComplete(_ => Confirm()); } protected virtual void Confirm() From 3949d46383b6adece23f0e43445ee92b3ff31585 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Aug 2019 13:46:08 +0900 Subject: [PATCH 0721/2815] Add test fix to other test --- .../Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs index a017418e3a..f787754aa4 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs @@ -13,6 +13,8 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestSceneHoldToConfirmOverlay : OsuTestScene { + protected override double TimePerAction => 100; // required for the early exit test, since hold-to-confirm delay is 200ms + public override IReadOnlyList RequiredTypes => new[] { typeof(ExitConfirmOverlay), From b2e05252d78c32b0e2711354bd9aac50797a2c95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Aug 2019 14:16:00 +0900 Subject: [PATCH 0722/2815] Update fastlane and dependencies --- Gemfile.lock | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 17c0db12e7..56e640599f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,7 +6,7 @@ GEM public_suffix (>= 2.0.2, < 4.0) atomos (0.1.3) babosa (1.0.2) - claide (1.0.2) + claide (1.0.3) colored (1.2) colored2 (3.1.2) commander-fastlane (4.4.6) @@ -14,11 +14,11 @@ GEM declarative (0.0.10) declarative-option (0.1.0) digest-crc (0.4.1) - domain_name (0.5.20180417) + domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) - dotenv (2.7.1) + dotenv (2.7.5) emoji_regex (1.0.1) - excon (0.62.0) + excon (0.66.0) faraday (0.15.4) multipart-post (>= 1.2, < 3) faraday-cookie_jar (0.0.6) @@ -27,7 +27,7 @@ GEM faraday_middleware (0.13.1) faraday (>= 0.7.4, < 1.0) fastimage (2.1.5) - fastlane (2.117.0) + fastlane (2.129.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.3, < 3.0.0) babosa (>= 1.0.2, < 2.0.0) @@ -46,8 +46,8 @@ GEM google-cloud-storage (>= 1.15.0, < 2.0.0) highline (>= 1.7.2, < 2.0.0) json (< 3.0.0) - mini_magick (~> 4.5.1) - multi_json + jwt (~> 2.1.0) + mini_magick (>= 4.9.4, < 5.0.0) multi_xml (~> 0.5) multipart-post (~> 2.0.0) plist (>= 3.1.0, < 4.0.0) @@ -56,12 +56,12 @@ GEM security (= 0.1.3) simctl (~> 1.6.3) slack-notifier (>= 2.0.0, < 3.0.0) - terminal-notifier (>= 1.6.2, < 2.0.0) + terminal-notifier (>= 2.0.0, < 3.0.0) terminal-table (>= 1.4.5, < 2.0.0) tty-screen (>= 0.6.3, < 1.0.0) tty-spinner (>= 0.8.0, < 1.0.0) word_wrap (~> 1.0.0) - xcodeproj (>= 1.6.0, < 2.0.0) + xcodeproj (>= 1.8.1, < 2.0.0) xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) fastlane-plugin-clean_testflight_testers (0.2.0) @@ -79,7 +79,7 @@ GEM signet (~> 0.9) google-cloud-core (1.3.0) google-cloud-env (~> 1.0) - google-cloud-env (1.0.5) + google-cloud-env (1.2.0) faraday (~> 0.11) google-cloud-storage (1.16.0) digest-crc (~> 0.4) @@ -102,8 +102,8 @@ GEM memoist (0.16.0) mime-types (3.2.2) mime-types-data (~> 3.2015) - mime-types-data (3.2018.0812) - mini_magick (4.5.1) + mime-types-data (3.2019.0331) + mini_magick (4.9.5) mini_portile2 (2.4.0) multi_json (1.13.1) multi_xml (0.6.0) @@ -112,7 +112,7 @@ GEM naturally (2.2.0) nokogiri (1.10.1) mini_portile2 (~> 2.4.0) - os (1.0.0) + os (1.0.1) plist (3.5.0) public_suffix (2.0.5) representable (3.0.4) @@ -121,7 +121,7 @@ GEM uber (< 0.2.0) retriable (3.1.2) rouge (2.0.7) - rubyzip (1.2.2) + rubyzip (1.2.3) security (0.1.3) signet (0.11.0) addressable (~> 2.3) @@ -136,20 +136,20 @@ GEM fastlane (>= 2.29.0) highline (~> 1.7) nokogiri (~> 1.7) - terminal-notifier (1.8.0) + terminal-notifier (2.0.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) - tty-cursor (0.6.1) - tty-screen (0.6.5) - tty-spinner (0.9.0) - tty-cursor (~> 0.6.0) + tty-cursor (0.7.0) + tty-screen (0.7.0) + tty-spinner (0.9.1) + tty-cursor (~> 0.7) uber (0.1.0) unf (0.1.4) unf_ext - unf_ext (0.0.7.5) - unicode-display_width (1.4.1) + unf_ext (0.0.7.6) + unicode-display_width (1.6.0) word_wrap (1.0.0) - xcodeproj (1.8.1) + xcodeproj (1.12.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) From 7843b9c532bea4e80934a4da675e37f18d68ab68 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Aug 2019 15:44:17 +0900 Subject: [PATCH 0723/2815] Update fastlane plugins --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 56e640599f..f7c19064b4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -64,7 +64,7 @@ GEM xcodeproj (>= 1.8.1, < 2.0.0) xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) - fastlane-plugin-clean_testflight_testers (0.2.0) + fastlane-plugin-clean_testflight_testers (0.3.0) fastlane-plugin-souyuz (0.8.1) souyuz (>= 0.8.1) fastlane-plugin-xamarin (0.6.3) @@ -110,7 +110,7 @@ GEM multipart-post (2.0.0) nanaimo (0.2.6) naturally (2.2.0) - nokogiri (1.10.1) + nokogiri (1.10.4) mini_portile2 (~> 2.4.0) os (1.0.1) plist (3.5.0) From 3c208b709bfdfd9432ab67a695da5503deee83a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Aug 2019 15:44:38 +0900 Subject: [PATCH 0724/2815] Reorder functions in Fastfile for readability --- fastlane/Fastfile | 29 +++++++++++++---------------- fastlane/README.md | 30 +++++++++++++++--------------- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 48c16caf0f..0b60e28b0f 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -1,22 +1,6 @@ update_fastlane -default_platform(:ios) - platform :ios do - lane :testflight_prune_dry do - clean_testflight_testers(days_of_inactivity:45, dry_run: true) - end - - # Specify a custom number for what's "inactive" - lane :testflight_prune do - clean_testflight_testers(days_of_inactivity: 45) # 120 days, so about 4 months - end - - lane :update_version do |options| - options[:plist_path] = '../osu.iOS/Info.plist' - app_version(options) - end - desc 'Deploy to testflight' lane :beta do |options| update_version(options) @@ -62,4 +46,17 @@ platform :ios do match(options) end + + lane :update_version do |options| + options[:plist_path] = '../osu.iOS/Info.plist' + app_version(options) + end + + lane :testflight_prune_dry do + clean_testflight_testers(days_of_inactivity:45, dry_run: true) + end + + lane :testflight_prune do + clean_testflight_testers(days_of_inactivity: 45) + end end diff --git a/fastlane/README.md b/fastlane/README.md index 53bbc62cae..fbccf1c8c0 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -16,21 +16,6 @@ or alternatively using `brew cask install fastlane` # Available Actions ## iOS -### ios testflight_prune_dry -``` -fastlane ios testflight_prune_dry -``` - -### ios testflight_prune -``` -fastlane ios testflight_prune -``` - -### ios update_version -``` -fastlane ios update_version -``` - ### ios beta ``` fastlane ios beta @@ -46,6 +31,21 @@ Compile the project fastlane ios provision ``` Install provisioning profiles using match +### ios update_version +``` +fastlane ios update_version +``` + +### ios testflight_prune_dry +``` +fastlane ios testflight_prune_dry +``` + +### ios testflight_prune +``` +fastlane ios testflight_prune +``` + ---- From a41356cf0efbae9a44e8c32a2544468d379ed7b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Aug 2019 16:30:46 +0900 Subject: [PATCH 0725/2815] Add android build automation via fastlane --- fastlane/Fastfile | 34 ++++++++++++++++++++++++++++++++++ fastlane/README.md | 19 +++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 0b60e28b0f..9cc5e4aa74 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -1,5 +1,39 @@ update_fastlane +platform :android do +desc 'Deploy to play store' + lane :beta do |options| + # update csproj version + update_version(options) + + build( + build_configuration: 'Release', + ) + + client = HTTPClient.new + changelog = client.get_content 'https://gist.githubusercontent.com/peppy/ab89c29dcc0dce95f39eb218e8fad197/raw' + changelog.gsub!('$BUILD_ID', options[:build]) + end + + desc 'Compile the project' + lane :build do + nuget_restore( + project_path: 'osu.Android.sln' + ) + + souyuz( + solution_path: 'osu.Android.sln', + platform: "android", + ) + end + + lane :update_version do |options| + options[:plist_path] = '../osu.iOS/Info.plist' + app_version(options) + end + +end + platform :ios do desc 'Deploy to testflight' lane :beta do |options| diff --git a/fastlane/README.md b/fastlane/README.md index fbccf1c8c0..6145620870 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -15,6 +15,25 @@ Install _fastlane_ using or alternatively using `brew cask install fastlane` # Available Actions +## Android +### android beta +``` +fastlane android beta +``` +Deploy to play store +### android build +``` +fastlane android build +``` +Compile the project +### android update_version +``` +fastlane android update_version +``` + + +---- + ## iOS ### ios beta ``` From 536e7d875736de01a5b574259fcdd8085c37aa60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Aug 2019 19:32:17 +0900 Subject: [PATCH 0726/2815] 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 7bc60ef884..bb283dc0c5 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -61,6 +61,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f5e4d4b1fb..758c4dda4c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -15,7 +15,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 63fa354418..d6ad35b663 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,8 +118,8 @@ - - + + From 24d2b504dd4ce36f2e128a28075444f69860de73 Mon Sep 17 00:00:00 2001 From: Desconocidosmh Date: Fri, 16 Aug 2019 12:39:54 +0200 Subject: [PATCH 0727/2815] Stop autoplay from missing on very dense notes when playing mania --- .../Replays/ManiaAutoGenerator.cs | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index e5669816fa..49bb47cc2b 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Replays; using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Replays; @@ -77,10 +78,35 @@ namespace osu.Game.Rulesets.Mania.Replays private IEnumerable generateActionPoints() { - foreach (var obj in Beatmap.HitObjects) + for (int i = 0; i < Beatmap.HitObjects.Count; i++) { - yield return new HitPoint { Time = obj.StartTime, Column = obj.Column }; - yield return new ReleasePoint { Time = ((obj as IHasEndTime)?.EndTime ?? obj.StartTime) + RELEASE_DELAY, Column = obj.Column }; + var currentObject = Beatmap.HitObjects[i]; + + double endTime = (currentObject as IHasEndTime)?.EndTime ?? currentObject.StartTime; + + var nextObjectInTheSameColumn = getNextObjectInTheSameColumn(i); + + bool canDelayKeyUp = nextObjectInTheSameColumn == null || + nextObjectInTheSameColumn.StartTime > endTime + KEY_UP_DELAY; + + double releaseDelay = canDelayKeyUp ? RELEASE_DELAY : nextObjectInTheSameColumn.StartTime - endTime - 1; + + yield return new HitPoint { Time = currentObject.StartTime, Column = currentObject.Column }; + + yield return new ReleasePoint { Time = endTime + releaseDelay, Column = currentObject.Column }; + } + + ManiaHitObject getNextObjectInTheSameColumn(int currentIndex) + { + int desiredColumn = Beatmap.HitObjects[currentIndex++].Column; + + for (; currentIndex < Beatmap.HitObjects.Count; currentIndex++) + { + if (Beatmap.HitObjects[currentIndex].Column == desiredColumn) + return Beatmap.HitObjects[currentIndex]; + } + + return null; } } From 132d51a2cc7ff16b0f35227fa03cb97efee60088 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Aug 2019 19:47:35 +0900 Subject: [PATCH 0728/2815] Update tooltip implementation --- .../Profile/Header/Components/RankGraph.cs | 47 +++++++++++++++---- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index 9cb9d48de7..de760eedfd 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -196,17 +196,30 @@ namespace osu.Game.Overlays.Profile.Header.Components } } - public string TooltipText => Statistics.Value?.Ranks.Global == null ? "" : $"#{ranks[dayIndex].Value:#,##0}|{ranked_days - ranks[dayIndex].Key + 1}"; + public object TooltipContent + { + get + { + if (Statistics.Value?.Ranks.Global == null) + return null; + + var days = ranked_days - ranks[dayIndex].Key + 1; + + return new TooltipDisplayContent + { + Rank = $"#{ranks[dayIndex].Value:#,##0}", + Time = days == 0 ? "now" : $"{days} days ago" + }; + } + } public ITooltip GetCustomTooltip() => new RankGraphTooltip(); - public class RankGraphTooltip : VisibilityContainer, ITooltip + private class RankGraphTooltip : VisibilityContainer, ITooltip { private readonly OsuSpriteText globalRankingText, timeText; private readonly Box background; - public string TooltipText { get; set; } - public RankGraphTooltip() { AutoSizeAxes = Axes.Both; @@ -260,11 +273,14 @@ namespace osu.Game.Overlays.Profile.Header.Components background.Colour = colours.GreySeafoamDark; } - public void Refresh() + public bool SetContent(object content) { - var info = TooltipText.Split('|'); - globalRankingText.Text = info[0]; - timeText.Text = info[1] == "0" ? "now" : $"{info[1]} days ago"; + if (!(content is TooltipDisplayContent info)) + return false; + + globalRankingText.Text = info.Rank; + timeText.Text = info.Time; + return true; } private bool instantMove = true; @@ -280,9 +296,24 @@ namespace osu.Game.Overlays.Profile.Header.Components this.MoveTo(pos, 200, Easing.OutQuint); } + public void Refresh() + { + } + + public string TooltipText + { + set => throw new InvalidOperationException(); + } + protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); } + + private class TooltipDisplayContent + { + public string Rank; + public string Time; + } } } From 7de1757aae1bae0baf006a010cd3be4eb172da62 Mon Sep 17 00:00:00 2001 From: Desconocidosmh Date: Fri, 16 Aug 2019 12:50:48 +0200 Subject: [PATCH 0729/2815] Small improvements --- osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 49bb47cc2b..4fea834748 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -81,19 +81,18 @@ namespace osu.Game.Rulesets.Mania.Replays for (int i = 0; i < Beatmap.HitObjects.Count; i++) { var currentObject = Beatmap.HitObjects[i]; + var nextObjectInTheSameColumn = getNextObjectInTheSameColumn(i); double endTime = (currentObject as IHasEndTime)?.EndTime ?? currentObject.StartTime; - var nextObjectInTheSameColumn = getNextObjectInTheSameColumn(i); - bool canDelayKeyUp = nextObjectInTheSameColumn == null || nextObjectInTheSameColumn.StartTime > endTime + KEY_UP_DELAY; - double releaseDelay = canDelayKeyUp ? RELEASE_DELAY : nextObjectInTheSameColumn.StartTime - endTime - 1; + double calculatedDelay = canDelayKeyUp ? RELEASE_DELAY : (nextObjectInTheSameColumn.StartTime - endTime) * 0.9; yield return new HitPoint { Time = currentObject.StartTime, Column = currentObject.Column }; - yield return new ReleasePoint { Time = endTime + releaseDelay, Column = currentObject.Column }; + yield return new ReleasePoint { Time = endTime + calculatedDelay, Column = currentObject.Column }; } ManiaHitObject getNextObjectInTheSameColumn(int currentIndex) From 58d2268b9e6da29b4208ab7d4bf8e0e9c96ab0a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Aug 2019 19:52:35 +0900 Subject: [PATCH 0730/2815] Combine conditionals that provide the same behaviour --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 7c143aa158..66ba6f7e64 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -118,9 +118,6 @@ namespace osu.Game.Rulesets.UI try { - if (!FrameStablePlayback) - manualClock.CurrentTime = newProposedTime; - if (firstConsumption) { // On the first update, frame-stability seeking would result in unexpected/unwanted behaviour. @@ -141,7 +138,7 @@ namespace osu.Game.Rulesets.UI : Math.Max(newProposedTime, manualClock.CurrentTime - sixty_frame_time); } - if (!isAttached) + if (!isAttached || !FrameStablePlayback) { manualClock.CurrentTime = newProposedTime; } From d11b896148508b3e1555982c64ee44ed20b4ff29 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Aug 2019 20:21:00 +0900 Subject: [PATCH 0731/2815] Move FrameStablePlayback handling to early return --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 66ba6f7e64..25fe5cdd96 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -118,7 +118,14 @@ namespace osu.Game.Rulesets.UI try { - if (firstConsumption) + if (!FrameStablePlayback) + { + manualClock.CurrentTime = newProposedTime; + requireMoreUpdateLoops = false; + return; + } + + else if (firstConsumption) { // On the first update, frame-stability seeking would result in unexpected/unwanted behaviour. // Instead we perform an initial seek to the proposed time. @@ -138,7 +145,7 @@ namespace osu.Game.Rulesets.UI : Math.Max(newProposedTime, manualClock.CurrentTime - sixty_frame_time); } - if (!isAttached || !FrameStablePlayback) + if (!isAttached) { manualClock.CurrentTime = newProposedTime; } From 152df9f3d570ee36d05a088c2bbe6d88c3e4a033 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Aug 2019 20:23:09 +0900 Subject: [PATCH 0732/2815] Remove accidental blank line --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 25fe5cdd96..05d3c02381 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -124,7 +124,6 @@ namespace osu.Game.Rulesets.UI requireMoreUpdateLoops = false; return; } - else if (firstConsumption) { // On the first update, frame-stability seeking would result in unexpected/unwanted behaviour. From 0f9706e7985428f1273fd23a9b0e3925dbf1f357 Mon Sep 17 00:00:00 2001 From: Desconocidosmh Date: Sat, 17 Aug 2019 00:18:25 +0200 Subject: [PATCH 0733/2815] Fix using invalid constant --- osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 4fea834748..43150958d0 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Mania.Replays double endTime = (currentObject as IHasEndTime)?.EndTime ?? currentObject.StartTime; bool canDelayKeyUp = nextObjectInTheSameColumn == null || - nextObjectInTheSameColumn.StartTime > endTime + KEY_UP_DELAY; + nextObjectInTheSameColumn.StartTime > endTime + RELEASE_DELAY; double calculatedDelay = canDelayKeyUp ? RELEASE_DELAY : (nextObjectInTheSameColumn.StartTime - endTime) * 0.9; From 4fa9abeecec9540da8b24faee553fd482006379d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Aug 2019 15:16:24 +0900 Subject: [PATCH 0734/2815] Replace DifficultyColouredContainer with a more scalable solution --- osu.Game/Beatmaps/BeatmapInfo.cs | 16 ++++ osu.Game/Beatmaps/DifficultyRating.cs | 15 ++++ .../Drawables/DifficultyColouredContainer.cs | 85 ------------------- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 11 ++- osu.Game/Graphics/OsuColour.cs | 26 ++++++ osu.Game/Screens/Select/BeatmapInfoWedge.cs | 14 +-- 6 files changed, 73 insertions(+), 94 deletions(-) create mode 100644 osu.Game/Beatmaps/DifficultyRating.cs delete mode 100644 osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 8042f6b4b9..700f981088 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -129,6 +129,22 @@ namespace osu.Game.Beatmaps ///
    diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs new file mode 100644 index 0000000000..87d6012205 --- /dev/null +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -0,0 +1,413 @@ +// 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.IO; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Audio.Track; +using osu.Framework.Bindables; +using osu.Framework.Screens; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Textures; +using osu.Framework.Graphics.Video; +using osu.Framework.MathUtils; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.IO.Archives; +using osu.Game.Rulesets; +using osu.Game.Screens.Backgrounds; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Menu +{ + public class IntroTriangles : IntroScreen + { + private const string menu_music_beatmap_hash = "a1556d0801b3a6b175dda32ef546f0ec812b400499f575c44fccbe9c67f9b1e5"; + + private SampleChannel welcome; + + protected override BackgroundScreen CreateBackground() => background = new BackgroundScreenDefault(false) + { + Alpha = 0, + }; + + [Resolved] + private AudioManager audio { get; set; } + + private Bindable menuMusic; + private Track track; + private WorkingBeatmap introBeatmap; + + private BackgroundScreenDefault background; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game) + { + menuMusic = config.GetBindable(OsuSetting.MenuMusic); + + BeatmapSetInfo setInfo = null; + + if (!menuMusic.Value) + { + var sets = beatmaps.GetAllUsableBeatmapSets(); + if (sets.Count > 0) + setInfo = beatmaps.QueryBeatmapSet(s => s.ID == sets[RNG.Next(0, sets.Count - 1)].ID); + } + + if (setInfo == null) + { + setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == menu_music_beatmap_hash); + + if (setInfo == null) + { + // we need to import the default menu background beatmap + setInfo = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream(@"Tracks/triangles.osz"), "triangles.osz")).Result; + + setInfo.Protected = true; + beatmaps.Update(setInfo); + } + } + + introBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); + + track = introBeatmap.Track; + track.Reset(); + + if (config.Get(OsuSetting.MenuVoice) && !menuMusic.Value) + // triangles has welcome sound included in the track. only play this if the user doesn't want menu music. + welcome = audio.Samples.Get(@"welcome"); + } + + protected override void LogoArriving(OsuLogo logo, bool resuming) + { + base.LogoArriving(logo, resuming); + + logo.Triangles = true; + + if (!resuming) + { + Beatmap.Value = introBeatmap; + introBeatmap = null; + + PrepareMenuLoad(); + + LoadComponentAsync(new TrianglesIntroSequence(logo, background) + { + RelativeSizeAxes = Axes.Both, + Clock = new FramedClock(track), + LoadMenu = LoadMenu + }, t => + { + AddInternal(t); + welcome?.Play(); + + // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Manu. + if (menuMusic.Value) + track.Start(); + }); + } + } + + public override void OnResuming(IScreen last) + { + base.OnResuming(last); + background.FadeOut(100); + } + + public override void OnSuspending(IScreen next) + { + track = null; + base.OnSuspending(next); + } + + private class TrianglesIntroSequence : CompositeDrawable + { + private readonly OsuLogo logo; + private readonly BackgroundScreenDefault background; + private OsuSpriteText welcomeText; + + private RulesetFlow rulesets; + private Container rulesetsScale; + private Drawable logoContainerSecondary; + private Drawable logoContainer; + + private GlitchingTriangles triangles; + + public Action LoadMenu; + + public TrianglesIntroSequence(OsuLogo logo, BackgroundScreenDefault background) + { + this.logo = logo; + this.background = background; + } + + private OsuGameBase game; + + [BackgroundDependencyLoader] + private void load(TextureStore textures, OsuGameBase game) + { + this.game = game; + + InternalChildren = new[] + { + triangles = new GlitchingTriangles + { + Alpha = 0, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(0.4f, 0.16f) + }, + welcomeText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Padding = new MarginPadding { Bottom = 10 }, + Font = OsuFont.GetFont(weight: FontWeight.Light, size: 42), + Alpha = 1, + Spacing = new Vector2(5), + }, + rulesetsScale = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + rulesets = new RulesetFlow() + } + }, + logoContainerSecondary = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Child = logoContainer = new LazerLogo(textures.GetStream("Menu/logo-triangles.mp4")) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }, + }; + } + + private const double text_1 = 200; + private const double text_2 = 400; + private const double text_3 = 700; + private const double text_4 = 900; + private const double text_glitch = 1060; + + private const double rulesets_1 = 1450; + private const double rulesets_2 = 1650; + private const double rulesets_3 = 1850; + + private const double logo_scale_duration = 920; + private const double logo_1 = 2080; + private const double logo_2 = logo_1 + logo_scale_duration; + + protected override void LoadComplete() + { + base.LoadComplete(); + + const float scale_start = 1.2f; + const float scale_adjust = 0.8f; + + rulesets.Hide(); + logoContainer.Hide(); + background.Hide(); + + using (BeginAbsoluteSequence(0, true)) + { + using (BeginDelayedSequence(text_1, true)) + welcomeText.FadeIn().OnComplete(t => t.Text = "wel"); + + using (BeginDelayedSequence(text_2, true)) + welcomeText.FadeIn().OnComplete(t => t.Text = "welcome"); + + using (BeginDelayedSequence(text_3, true)) + welcomeText.FadeIn().OnComplete(t => t.Text = "welcome to"); + + using (BeginDelayedSequence(text_4, true)) + { + welcomeText.FadeIn().OnComplete(t => t.Text = "welcome to osu!"); + welcomeText.TransformTo(nameof(welcomeText.Spacing), new Vector2(50, 0), 5000); + } + + using (BeginDelayedSequence(text_glitch, true)) + triangles.FadeIn(); + + using (BeginDelayedSequence(rulesets_1, true)) + { + rulesetsScale.ScaleTo(0.8f, 1000); + rulesets.FadeIn().ScaleTo(1).TransformSpacingTo(new Vector2(200, 0)); + welcomeText.FadeOut(); + triangles.FadeOut(); + } + + using (BeginDelayedSequence(rulesets_2, true)) + { + rulesets.ScaleTo(2).TransformSpacingTo(new Vector2(30, 0)); + } + + using (BeginDelayedSequence(rulesets_3, true)) + { + rulesets.ScaleTo(4).TransformSpacingTo(new Vector2(10, 0)); + rulesetsScale.ScaleTo(1.3f, 1000); + } + + using (BeginDelayedSequence(logo_1, true)) + { + rulesets.FadeOut(); + + // matching flyte curve y = 0.25x^2 + (max(0, x - 0.7) / 0.3) ^ 5 + logoContainer.FadeIn().ScaleTo(scale_start).Then().Delay(logo_scale_duration * 0.7f).ScaleTo(scale_start - scale_adjust, logo_scale_duration * 0.3f, Easing.InQuint); + logoContainerSecondary.ScaleTo(scale_start).Then().ScaleTo(scale_start - scale_adjust * 0.25f, logo_scale_duration, Easing.InQuad); + } + + using (BeginDelayedSequence(logo_2, true)) + { + logoContainer.FadeOut().OnComplete(_ => + { + logo.FadeIn(); + background.FadeIn(); + + game.Add(new GameWideFlash()); + + LoadMenu(); + }); + } + } + } + + private class GameWideFlash : Box + { + private const double flash_length = 1000; + + public GameWideFlash() + { + Colour = Color4.White; + RelativeSizeAxes = Axes.Both; + Blending = BlendingMode.Additive; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + this.FadeOutFromOne(flash_length, Easing.Out); + } + } + + private class LazerLogo : CompositeDrawable + { + public LazerLogo(Stream videoStream) + { + Size = new Vector2(960); + + InternalChild = new VideoSprite(videoStream) + { + RelativeSizeAxes = Axes.Both, + Clock = new FramedOffsetClock(Clock) { Offset = -logo_1 } + }; + } + } + + private class RulesetFlow : FillFlowContainer + { + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + var modes = new List(); + + foreach (var ruleset in rulesets.AvailableRulesets) + { + var icon = new ConstrainedIconContainer + { + Icon = ruleset.CreateInstance().CreateIcon(), + Size = new Vector2(30), + }; + + modes.Add(icon); + } + + AutoSizeAxes = Axes.Both; + Children = modes; + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + } + + private class GlitchingTriangles : CompositeDrawable + { + public GlitchingTriangles() + { + RelativeSizeAxes = Axes.Both; + } + + private double? lastGenTime; + + private const double time_between_triangles = 22; + + protected override void Update() + { + base.Update(); + + if (lastGenTime == null || Time.Current - lastGenTime > time_between_triangles) + { + lastGenTime = (lastGenTime ?? Time.Current) + time_between_triangles; + + Drawable triangle = new OutlineTriangle(RNG.NextBool(), (RNG.NextSingle() + 0.2f) * 80) + { + RelativePositionAxes = Axes.Both, + Position = new Vector2(RNG.NextSingle(), RNG.NextSingle()), + }; + + AddInternal(triangle); + + triangle.FadeOutFromOne(120); + } + } + + /// + /// Represents a sprite that is drawn in a triangle shape, instead of a rectangle shape. + /// + public class OutlineTriangle : BufferedContainer + { + public OutlineTriangle(bool outlineOnly, float size) + { + Size = new Vector2(size); + + InternalChildren = new Drawable[] + { + new Triangle { RelativeSizeAxes = Axes.Both }, + }; + + if (outlineOnly) + { + AddInternal(new Triangle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = Color4.Black, + Size = new Vector2(size - 5), + Blending = BlendingMode.None, + }); + } + + Blending = BlendingMode.Additive; + CacheDrawnFrameBuffer = true; + } + } + } + } + } +} From 8e6a3162ab3bfead6842379973fd671f927e39c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2019 20:05:36 +0900 Subject: [PATCH 0612/2815] 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 40c15a1162..4ec67ab30d 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -62,7 +62,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8ee325c2ac..fb9269e5f0 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -14,7 +14,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index b46438f766..1532e2d71b 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -104,7 +104,7 @@ - + From 6264a6a1c95b9b3c924e2ace2b4f8ff0594862f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2019 20:18:02 +0900 Subject: [PATCH 0613/2815] Adjust slider snaking and hitcircle fading to match stable --- .../Objects/Drawables/Pieces/SnakingSliderBody.cs | 2 +- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs index 0590ca1d96..70a1bad4a3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces var spanProgress = slider.ProgressAt(completionProgress); double start = 0; - double end = SnakingIn.Value ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadeIn, 0, 1) : 1; + double end = SnakingIn.Value ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / (slider.TimePreempt / 3), 0, 1) : 1; if (span >= slider.SpanCount() - 1) { diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index d1221fd2d3..b52bfcd181 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Objects base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450); - TimeFadeIn = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1200, 800, 300); + TimeFadeIn = 400; // as per osu-stable Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2; } From 7dd62ae6f350211cb09bd7b58afc4adf2f83e80b Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Fri, 9 Aug 2019 20:48:08 +0930 Subject: [PATCH 0614/2815] Remove Android BASS libraries --- osu.Android.props | 4 +--- osu.Android/lib/arm64-v8a/libbass.so | Bin 308904 -> 0 bytes osu.Android/lib/arm64-v8a/libbass_fx.so | Bin 154408 -> 0 bytes osu.Android/lib/armeabi-v7a/libbass.so | Bin 222620 -> 0 bytes osu.Android/lib/armeabi-v7a/libbass_fx.so | Bin 92176 -> 0 bytes osu.Android/lib/x86/libbass.so | Bin 316716 -> 0 bytes osu.Android/lib/x86/libbass_fx.so | Bin 153532 -> 0 bytes 7 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 osu.Android/lib/arm64-v8a/libbass.so delete mode 100644 osu.Android/lib/arm64-v8a/libbass_fx.so delete mode 100644 osu.Android/lib/armeabi-v7a/libbass.so delete mode 100644 osu.Android/lib/armeabi-v7a/libbass_fx.so delete mode 100644 osu.Android/lib/x86/libbass.so delete mode 100644 osu.Android/lib/x86/libbass_fx.so diff --git a/osu.Android.props b/osu.Android.props index 40c15a1162..82d8696ca5 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -49,9 +49,7 @@ osu.licenseheader - - Always - + diff --git a/osu.Android/lib/arm64-v8a/libbass.so b/osu.Android/lib/arm64-v8a/libbass.so deleted file mode 100644 index d5c24b3e4b2918a33153cf6a0561366996be16f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 308904 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

    ^m>m@_QI`Gyr3D`HGSXQzB#7TNkp#fsh! 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%0A0ic&Bx^ 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 T?MJo0F6IAiepbA(NM`E) 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 z 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 EAM_!vo&W#< diff --git a/osu.Android/lib/armeabi-v7a/libbass_fx.so b/osu.Android/lib/armeabi-v7a/libbass_fx.so deleted file mode 100644 index 006e2feb30bb005eec727f829cc3965fb136f16c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 92176 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 V>7s+MhuN$Sd><=0>Ypgz{{j9{WcmOA diff --git a/osu.Android/lib/x86/libbass.so b/osu.Android/lib/x86/libbass.so deleted file mode 100644 index b0f758a42b7397b3289557f51de6b55df7b3b4ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 316716 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 z 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 kkiFcJ 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 E0opfv^#A|> From caf13ae52ddbf06fbf11fe3c711e1fb304f8915f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2019 23:29:32 +0900 Subject: [PATCH 0615/2815] 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 40c15a1162..3b2e6574ac 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -63,6 +63,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8ee325c2ac..1dd19ac7ed 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -15,7 +15,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index b46438f766..1c3faeed39 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From fc4c0cc2e72d9108881cba86f1854110ee17549e Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 10 Aug 2019 10:52:37 +0200 Subject: [PATCH 0616/2815] Fix visual discrepancies with master --- osu.Game/Overlays/OSD/Toast.cs | 1 + osu.Game/Overlays/OSD/TrackedSettingToast.cs | 13 +++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/OSD/Toast.cs b/osu.Game/Overlays/OSD/Toast.cs index 67c9b46c77..db5e6e4a6a 100644 --- a/osu.Game/Overlays/OSD/Toast.cs +++ b/osu.Game/Overlays/OSD/Toast.cs @@ -65,6 +65,7 @@ namespace osu.Game.Overlays.OSD Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Name = "Shortcut", + Alpha = 0.3f, Margin = new MarginPadding { Bottom = 15 }, Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), Text = string.IsNullOrEmpty(keybinding) ? "NO KEY BOUND" : keybinding.ToUpperInvariant() diff --git a/osu.Game/Overlays/OSD/TrackedSettingToast.cs b/osu.Game/Overlays/OSD/TrackedSettingToast.cs index 9812dcd797..0f4bd34779 100644 --- a/osu.Game/Overlays/OSD/TrackedSettingToast.cs +++ b/osu.Game/Overlays/OSD/TrackedSettingToast.cs @@ -17,6 +17,8 @@ namespace osu.Game.Overlays.OSD { public class TrackedSettingToast : Toast { + private const int lights_bottom_margin = 40; + public TrackedSettingToast(SettingDescription description) : base(description.Name, description.Value, description.Shortcut) { @@ -24,17 +26,16 @@ namespace osu.Game.Overlays.OSD Children = new Drawable[] { - new FillFlowContainer + new Container { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Direction = FillDirection.Vertical, - Margin = new MarginPadding { Top = 70 }, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Margin = new MarginPadding { Bottom = lights_bottom_margin }, Children = new Drawable[] { optionLights = new FillFlowContainer { - Padding = new MarginPadding { Bottom = 5 }, + Margin = new MarginPadding { Bottom = 5 }, Spacing = new Vector2(5, 0), Direction = FillDirection.Horizontal, Anchor = Anchor.TopCentre, From b0a71779877c55e6b181e8e50207de80ff79e96a Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 10 Aug 2019 13:26:19 +0200 Subject: [PATCH 0617/2815] Add ScreenTitleIcon class, used for displaying custom specific icons on overlays. --- .../Graphics/UserInterface/ScreenTitle.cs | 2 +- .../Graphics/UserInterface/ScreenTitleIcon.cs | 61 +++++++++++++++++++ .../Overlays/Changelog/ChangelogHeader.cs | 45 +------------- 3 files changed, 63 insertions(+), 45 deletions(-) create mode 100644 osu.Game/Graphics/UserInterface/ScreenTitleIcon.cs diff --git a/osu.Game/Graphics/UserInterface/ScreenTitle.cs b/osu.Game/Graphics/UserInterface/ScreenTitle.cs index 7b39238e5e..10fc312d8b 100644 --- a/osu.Game/Graphics/UserInterface/ScreenTitle.cs +++ b/osu.Game/Graphics/UserInterface/ScreenTitle.cs @@ -15,7 +15,7 @@ namespace osu.Game.Graphics.UserInterface { public const float ICON_WIDTH = ICON_SIZE + icon_spacing; - protected const float ICON_SIZE = 25; + public const float ICON_SIZE = 25; private SpriteIcon iconSprite; private readonly OsuSpriteText titleText, pageText; diff --git a/osu.Game/Graphics/UserInterface/ScreenTitleIcon.cs b/osu.Game/Graphics/UserInterface/ScreenTitleIcon.cs new file mode 100644 index 0000000000..12e0617e35 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/ScreenTitleIcon.cs @@ -0,0 +1,61 @@ +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osuTK; + +namespace osu.Game.Graphics.UserInterface +{ + ///

    + /// A custom icon class for use with + /// + public class ScreenTitleIcon : CompositeDrawable + { + private const float circle_allowance = 0.8f; + + private string icon; + + public ScreenTitleIcon(string icon) + { + this.icon = icon; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures, OsuColour colours) + { + Size = new Vector2(ScreenTitle.ICON_SIZE / circle_allowance); + + InternalChildren = new Drawable[] + { + new CircularContainer + { + Masking = true, + BorderColour = colours.Violet, + BorderThickness = 3, + MaskingSmoothness = 1, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Sprite + { + RelativeSizeAxes = Axes.Both, + Texture = textures.Get(icon), + Size = new Vector2(circle_allowance), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Violet, + Alpha = 0, + AlwaysPresent = true, + }, + } + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Changelog/ChangelogHeader.cs b/osu.Game/Overlays/Changelog/ChangelogHeader.cs index fca62fbb44..8b78216136 100644 --- a/osu.Game/Overlays/Changelog/ChangelogHeader.cs +++ b/osu.Game/Overlays/Changelog/ChangelogHeader.cs @@ -7,13 +7,11 @@ 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.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; -using osuTK; namespace osu.Game.Overlays.Changelog { @@ -123,48 +121,7 @@ namespace osu.Game.Overlays.Changelog AccentColour = colours.Violet; } - protected override Drawable CreateIcon() => new ChangelogIcon(); - - internal class ChangelogIcon : CompositeDrawable - { - private const float circle_allowance = 0.8f; - - [BackgroundDependencyLoader] - private void load(TextureStore textures, OsuColour colours) - { - Size = new Vector2(ICON_SIZE / circle_allowance); - - InternalChildren = new Drawable[] - { - new CircularContainer - { - Masking = true, - BorderColour = colours.Violet, - BorderThickness = 3, - MaskingSmoothness = 1, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Sprite - { - RelativeSizeAxes = Axes.Both, - Texture = textures.Get(@"Icons/changelog"), - Size = new Vector2(circle_allowance), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Violet, - Alpha = 0, - AlwaysPresent = true, - }, - } - }, - }; - } - } + protected override Drawable CreateIcon() => new ScreenTitleIcon(@"Icons/changelog"); } } } From 8eb3409a648bb03ff702d2f66807e6c0e0c02c86 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 10 Aug 2019 14:03:44 +0200 Subject: [PATCH 0618/2815] Add missing licence header --- osu.Game/Graphics/UserInterface/ScreenTitleIcon.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/ScreenTitleIcon.cs b/osu.Game/Graphics/UserInterface/ScreenTitleIcon.cs index 12e0617e35..8a42645b37 100644 --- a/osu.Game/Graphics/UserInterface/ScreenTitleIcon.cs +++ b/osu.Game/Graphics/UserInterface/ScreenTitleIcon.cs @@ -1,4 +1,7 @@ -using osu.Framework.Allocation; +// 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; From d55f9404dab5cd639d8f3524df9e97c31bf909f3 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 10 Aug 2019 14:23:37 +0200 Subject: [PATCH 0619/2815] Fix CI issues --- osu.Game/Graphics/UserInterface/ScreenTitleIcon.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/ScreenTitleIcon.cs b/osu.Game/Graphics/UserInterface/ScreenTitleIcon.cs index 8a42645b37..4ce554009a 100644 --- a/osu.Game/Graphics/UserInterface/ScreenTitleIcon.cs +++ b/osu.Game/Graphics/UserInterface/ScreenTitleIcon.cs @@ -18,7 +18,7 @@ namespace osu.Game.Graphics.UserInterface { private const float circle_allowance = 0.8f; - private string icon; + private readonly string icon; public ScreenTitleIcon(string icon) { From e3d52d8d7171076a28ddb67feaf130cf62a7ea3a Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 10 Aug 2019 12:22:45 +0200 Subject: [PATCH 0620/2815] Add NewsOverlay class --- osu.Game/Overlays/NewsOverlay.cs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 osu.Game/Overlays/NewsOverlay.cs diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs new file mode 100644 index 0000000000..76040a6086 --- /dev/null +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -0,0 +1,23 @@ +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; + +namespace osu.Game.Overlays +{ + public class NewsOverlay : FullscreenOverlay + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.PurpleLightAlternative + } + }; + } + } +} From 4b0ac381b7372a0ba2f6238e2890509c158debb2 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 10 Aug 2019 12:26:54 +0200 Subject: [PATCH 0621/2815] Add visual tests. --- .../Visual/Online/TestSceneNewsOverlay.cs | 17 +++++++++++++++++ osu.Game/Overlays/NewsOverlay.cs | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs new file mode 100644 index 0000000000..3362d4700f --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs @@ -0,0 +1,17 @@ +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneNewsOverlay : OsuTestScene + { + private NewsOverlay news; + + protected override void LoadComplete() + { + base.LoadComplete(); + Add(news = new NewsOverlay()); + AddStep(@"Show", news.Show); + AddStep(@"Hide", news.Hide); + } + } +} diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index 76040a6086..76b917b65d 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -15,7 +15,7 @@ namespace osu.Game.Overlays new Box { RelativeSizeAxes = Axes.Both, - Colour = colours.PurpleLightAlternative + Colour = colours.PurpleDarkAlternative } }; } From b19c378fc8a7e5b571ed0e288b79c6e2ac628941 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 10 Aug 2019 12:53:34 +0200 Subject: [PATCH 0622/2815] Add NewsHeader class --- osu.Game/Overlays/News/NewsHeader.cs | 71 ++++++++++++++++++++++++++++ osu.Game/Overlays/NewsOverlay.cs | 21 +++++++- 2 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Overlays/News/NewsHeader.cs diff --git a/osu.Game/Overlays/News/NewsHeader.cs b/osu.Game/Overlays/News/NewsHeader.cs new file mode 100644 index 0000000000..43a514cdc6 --- /dev/null +++ b/osu.Game/Overlays/News/NewsHeader.cs @@ -0,0 +1,71 @@ +using osu.Framework.Allocation; +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.UserInterface; + +namespace osu.Game.Overlays.News +{ + public class NewsHeader : OverlayHeader + { + private const string front_page_string = "Front Page"; + + private NewsHeaderTitle title; + + public NewsHeader() + { + TabControl.AddItem(front_page_string); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colour) + { + TabControl.AccentColour = colour.Violet; + } + + protected override Drawable CreateBackground() => new NewsHeaderBackground(); + + protected override Drawable CreateContent() => new Container(); + + protected override ScreenTitle CreateTitle() => title = new NewsHeaderTitle(); + + private class NewsHeaderBackground : Sprite + { + public NewsHeaderBackground() + { + RelativeSizeAxes = Axes.Both; + FillMode = FillMode.Fill; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + Texture = textures.Get(@"Headers/changelog"); //using changelog bg until corresponding osu-resources pr is merged. + } + } + + private class NewsHeaderTitle : ScreenTitle + { + private const string article_string = "Article"; + + public bool IsReadingArticle + { + set => Section = value ? article_string : front_page_string; + } + + public NewsHeaderTitle() + { + Title = "News"; + IsReadingArticle = false; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.Violet; + } + } + } +} diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index 76b917b65d..80088a25bc 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -1,12 +1,17 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Overlays.News; namespace osu.Game.Overlays { public class NewsOverlay : FullscreenOverlay { + private NewsHeader header; + [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -16,7 +21,21 @@ namespace osu.Game.Overlays { RelativeSizeAxes = Axes.Both, Colour = colours.PurpleDarkAlternative - } + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + header = new NewsHeader() + }, + }, + }, }; } } From 0e5561c783344856d751338f32e876cb373cb4a0 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 10 Aug 2019 16:12:42 +0200 Subject: [PATCH 0623/2815] Use News overlay resources --- osu.Game/Overlays/News/NewsHeader.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/News/NewsHeader.cs b/osu.Game/Overlays/News/NewsHeader.cs index 43a514cdc6..fb02f7f105 100644 --- a/osu.Game/Overlays/News/NewsHeader.cs +++ b/osu.Game/Overlays/News/NewsHeader.cs @@ -42,7 +42,7 @@ namespace osu.Game.Overlays.News [BackgroundDependencyLoader] private void load(TextureStore textures) { - Texture = textures.Get(@"Headers/changelog"); //using changelog bg until corresponding osu-resources pr is merged. + Texture = textures.Get(@"Headers/news"); } } @@ -61,6 +61,8 @@ namespace osu.Game.Overlays.News IsReadingArticle = false; } + protected override Drawable CreateIcon() => new ScreenTitleIcon(@"Icons/news"); + [BackgroundDependencyLoader] private void load(OsuColour colours) { From 7825923cb63f157e7e9fcd7307ae05b9d3d0b51d Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 10 Aug 2019 16:33:16 +0200 Subject: [PATCH 0624/2815] Use media keys as default bindings for jukebox. --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 6 +++--- osu.Game/Overlays/MusicController.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 8c927c2bc6..809ec9a09e 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -45,9 +45,9 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.Enter, GlobalAction.Select), new KeyBinding(InputKey.KeypadEnter, GlobalAction.Select), - new KeyBinding(InputKey.F1, GlobalAction.MusicPrev), - new KeyBinding(InputKey.F5, GlobalAction.MusicNext), - new KeyBinding(InputKey.F3, GlobalAction.MusicPlay), + new KeyBinding(InputKey.TrackPrevious, GlobalAction.MusicPrev), + new KeyBinding(InputKey.TrackNext, GlobalAction.MusicNext), + new KeyBinding(InputKey.PlayPause, GlobalAction.MusicPlay), }; public IEnumerable InGameKeyBindings => new[] diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 724be21957..ed51a80924 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -565,7 +565,7 @@ namespace osu.Game.Overlays { if (beatmap.Disabled) return false; - play(); + togglePause(); return true; } From b92e331730f2971c5fbe7b5afe0031e781c2c494 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 10 Aug 2019 17:06:52 +0200 Subject: [PATCH 0625/2815] Add tabcontrol logic to news overlay --- .../Visual/Online/TestSceneNewsOverlay.cs | 3 ++ osu.Game/Overlays/News/NewsHeader.cs | 35 ++++++++++++++++++- osu.Game/Overlays/NewsOverlay.cs | 13 +++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs index 3362d4700f..d5273801d8 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs @@ -12,6 +12,9 @@ namespace osu.Game.Tests.Visual.Online Add(news = new NewsOverlay()); AddStep(@"Show", news.Show); AddStep(@"Hide", news.Hide); + + AddStep(@"Show front page", () => news.ShowFrontPage()); + AddStep(@"Custom article", () => news.Current.Value = "Test Article 101"); } } } diff --git a/osu.Game/Overlays/News/NewsHeader.cs b/osu.Game/Overlays/News/NewsHeader.cs index fb02f7f105..e887d48456 100644 --- a/osu.Game/Overlays/News/NewsHeader.cs +++ b/osu.Game/Overlays/News/NewsHeader.cs @@ -1,10 +1,12 @@ 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.Graphics; using osu.Game.Graphics.UserInterface; +using System; namespace osu.Game.Overlays.News { @@ -14,9 +16,21 @@ namespace osu.Game.Overlays.News private NewsHeaderTitle title; + public readonly Bindable Current = new Bindable(null); + + public Action ShowFrontPage; + public NewsHeader() { TabControl.AddItem(front_page_string); + + TabControl.Current.ValueChanged += e => + { + if (e.NewValue == front_page_string) + ShowFrontPage?.Invoke(); + }; + + Current.ValueChanged += showArticle; } [BackgroundDependencyLoader] @@ -25,6 +39,25 @@ namespace osu.Game.Overlays.News TabControl.AccentColour = colour.Violet; } + private void showArticle(ValueChangedEvent e) + { + if (e.OldValue != null) + TabControl.RemoveItem(e.OldValue); + + if (e.NewValue != null) + { + TabControl.AddItem(e.NewValue); + TabControl.Current.Value = e.NewValue; + + title.IsReadingArticle = true; + } + else + { + TabControl.Current.Value = front_page_string; + title.IsReadingArticle = false; + } + } + protected override Drawable CreateBackground() => new NewsHeaderBackground(); protected override Drawable CreateContent() => new Container(); @@ -52,7 +85,7 @@ namespace osu.Game.Overlays.News public bool IsReadingArticle { - set => Section = value ? article_string : front_page_string; + set => Section = value ? article_string : front_page_string; } public NewsHeaderTitle() diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index 80088a25bc..1506cbb288 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -1,4 +1,5 @@ using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -12,6 +13,8 @@ namespace osu.Game.Overlays { private NewsHeader header; + public readonly Bindable Current = new Bindable(null); + [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -37,6 +40,16 @@ namespace osu.Game.Overlays }, }, }; + + header.Current.BindTo(Current); + header.ShowFrontPage = ShowFrontPage; + Current.TriggerChange(); + } + + public void ShowFrontPage() + { + Current.Value = null; + Show(); } } } From 684c37bf05e8bc3367d5c48d4997a7e1bab75157 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 11 Aug 2019 16:15:44 +0900 Subject: [PATCH 0626/2815] Rename class to better match usage --- ...{ScreenTitleIcon.cs => ScreenTitleTextureIcon.cs} | 12 ++++++------ osu.Game/Overlays/Changelog/ChangelogHeader.cs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) rename osu.Game/Graphics/UserInterface/{ScreenTitleIcon.cs => ScreenTitleTextureIcon.cs} (85%) diff --git a/osu.Game/Graphics/UserInterface/ScreenTitleIcon.cs b/osu.Game/Graphics/UserInterface/ScreenTitleTextureIcon.cs similarity index 85% rename from osu.Game/Graphics/UserInterface/ScreenTitleIcon.cs rename to osu.Game/Graphics/UserInterface/ScreenTitleTextureIcon.cs index 4ce554009a..f590e7e357 100644 --- a/osu.Game/Graphics/UserInterface/ScreenTitleIcon.cs +++ b/osu.Game/Graphics/UserInterface/ScreenTitleTextureIcon.cs @@ -12,17 +12,17 @@ using osuTK; namespace osu.Game.Graphics.UserInterface { /// - /// A custom icon class for use with + /// A custom icon class for use with based off a texture resource. /// - public class ScreenTitleIcon : CompositeDrawable + public class ScreenTitleTextureIcon : CompositeDrawable { private const float circle_allowance = 0.8f; - private readonly string icon; + private readonly string textureName; - public ScreenTitleIcon(string icon) + public ScreenTitleTextureIcon(string textureName) { - this.icon = icon; + this.textureName = textureName; } [BackgroundDependencyLoader] @@ -44,7 +44,7 @@ namespace osu.Game.Graphics.UserInterface new Sprite { RelativeSizeAxes = Axes.Both, - Texture = textures.Get(icon), + Texture = textures.Get(textureName), Size = new Vector2(circle_allowance), Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Overlays/Changelog/ChangelogHeader.cs b/osu.Game/Overlays/Changelog/ChangelogHeader.cs index 8b78216136..b2e9be24b3 100644 --- a/osu.Game/Overlays/Changelog/ChangelogHeader.cs +++ b/osu.Game/Overlays/Changelog/ChangelogHeader.cs @@ -121,7 +121,7 @@ namespace osu.Game.Overlays.Changelog AccentColour = colours.Violet; } - protected override Drawable CreateIcon() => new ScreenTitleIcon(@"Icons/changelog"); + protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/changelog"); } } } From fd334e0319729576afb81d600b71128f1fff2970 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 11 Aug 2019 14:57:21 +0300 Subject: [PATCH 0627/2815] Implement basic layout for AccuracyBar --- osu.Game/Screens/Play/HUD/AccuracyBar.cs | 19 +++++++++++++++++++ osu.Game/Screens/Play/HUDOverlay.cs | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 osu.Game/Screens/Play/HUD/AccuracyBar.cs diff --git a/osu.Game/Screens/Play/HUD/AccuracyBar.cs b/osu.Game/Screens/Play/HUD/AccuracyBar.cs new file mode 100644 index 0000000000..8b85014b2f --- /dev/null +++ b/osu.Game/Screens/Play/HUD/AccuracyBar.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.Framework.Graphics.Containers; +using osu.Game.Rulesets.Judgements; + +namespace osu.Game.Screens.Play.HUD +{ + public class AccuracyBar : Container + { + public AccuracyBar(bool mirrored) + { + } + + public void OnNewJudgement(JudgementResult judgement) + { + } + } +} diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 43b9491750..88ad57c175 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -35,6 +35,8 @@ namespace osu.Game.Screens.Play public readonly ModDisplay ModDisplay; public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; + public readonly AccuracyBar LeftAccuracyBar; + public readonly AccuracyBar RightAccuracyBar; public Bindable ShowHealthbar = new Bindable(true); @@ -84,6 +86,8 @@ namespace osu.Game.Screens.Play HealthDisplay = CreateHealthDisplay(), Progress = CreateProgress(), ModDisplay = CreateModsContainer(), + LeftAccuracyBar = CreateAccuracyBar(false), + RightAccuracyBar = CreateAccuracyBar(), } }, PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), @@ -256,6 +260,15 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20, Right = 10 }, }; + protected virtual AccuracyBar CreateAccuracyBar(bool mirrored = true) => new AccuracyBar(mirrored) + { + Anchor = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, + AutoSizeAxes = Axes.X, + Height = 300, + Margin = new MarginPadding { Horizontal = 20 } + }; + protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay(); protected virtual void BindProcessor(ScoreProcessor processor) @@ -265,6 +278,12 @@ namespace osu.Game.Screens.Play ComboCounter?.Current.BindTo(processor.Combo); HealthDisplay?.Current.BindTo(processor.Health); + if (LeftAccuracyBar != null) + processor.NewJudgement += LeftAccuracyBar.OnNewJudgement; + + if (RightAccuracyBar != null) + processor.NewJudgement += RightAccuracyBar.OnNewJudgement; + if (HealthDisplay is StandardHealthDisplay shd) processor.NewJudgement += shd.Flash; } From ed409d113b2e02bc49c252c4074468393bc6a8ae Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 11 Aug 2019 15:53:15 +0300 Subject: [PATCH 0628/2815] Add judgement lines generator --- osu.Game/Screens/Play/HUD/AccuracyBar.cs | 42 +++++++++++++++++++++++- osu.Game/Screens/Play/HUDOverlay.cs | 4 +-- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/AccuracyBar.cs b/osu.Game/Screens/Play/HUD/AccuracyBar.cs index 8b85014b2f..8b14bc47eb 100644 --- a/osu.Game/Screens/Play/HUD/AccuracyBar.cs +++ b/osu.Game/Screens/Play/HUD/AccuracyBar.cs @@ -1,19 +1,59 @@ // 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.Containers; +using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Judgements; +using osuTK.Graphics; +using osuTK; namespace osu.Game.Screens.Play.HUD { public class AccuracyBar : Container { - public AccuracyBar(bool mirrored) + private const int bar_width = 5; + private const int bar_height = 250; + private const int spacing = 3; + + private readonly bool mirrored; + + public AccuracyBar(bool mirrored = false) { + this.mirrored = mirrored; + + Size = new Vector2(bar_width, bar_height); + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }; } public void OnNewJudgement(JudgementResult judgement) { + Container judgementLine; + + Add(judgementLine = CreateJudgementLine(judgement.TimeOffset)); + + judgementLine.FadeOut(5000, Easing.OutQuint); + judgementLine.Expire(); } + + protected virtual Container CreateJudgementLine(double offset) => new CircularContainer + { + Anchor = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = mirrored ? Anchor.CentreLeft : Anchor.CentreRight, + Masking = true, + Size = new Vector2(10, 2), + RelativePositionAxes = Axes.Y, + Y = (float)offset / bar_height, + X = mirrored ? spacing : -spacing, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + } + }; } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 88ad57c175..3fafc21ea8 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -264,9 +264,7 @@ namespace osu.Game.Screens.Play { Anchor = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, Origin = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, - AutoSizeAxes = Axes.X, - Height = 300, - Margin = new MarginPadding { Horizontal = 20 } + Margin = new MarginPadding { Horizontal = 30 } }; protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay(); From 0a255fe4d1750440e02fc429f40021f7df9b55c8 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 11 Aug 2019 16:38:03 +0300 Subject: [PATCH 0629/2815] Add moving arrow --- osu.Game/Screens/Play/HUD/AccuracyBar.cs | 46 ++++++++++++++++++++---- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/AccuracyBar.cs b/osu.Game/Screens/Play/HUD/AccuracyBar.cs index 8b14bc47eb..3c13111b22 100644 --- a/osu.Game/Screens/Play/HUD/AccuracyBar.cs +++ b/osu.Game/Screens/Play/HUD/AccuracyBar.cs @@ -7,6 +7,8 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Judgements; using osuTK.Graphics; using osuTK; +using osu.Framework.Graphics.Sprites; +using System.Collections.Generic; namespace osu.Game.Screens.Play.HUD { @@ -17,16 +19,31 @@ namespace osu.Game.Screens.Play.HUD private const int spacing = 3; private readonly bool mirrored; + private readonly SpriteIcon arrow; + private readonly List judgementOffsets = new List(); public AccuracyBar(bool mirrored = false) { this.mirrored = mirrored; Size = new Vector2(bar_width, bar_height); - Child = new Box + + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }, + arrow = new SpriteIcon + { + Anchor = mirrored ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, + X = mirrored ? -spacing : spacing, + RelativePositionAxes = Axes.Y, + Icon = mirrored ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, + Size = new Vector2(10), + } }; } @@ -34,20 +51,22 @@ namespace osu.Game.Screens.Play.HUD { Container judgementLine; - Add(judgementLine = CreateJudgementLine(judgement.TimeOffset)); + Add(judgementLine = CreateJudgementLine(judgement)); judgementLine.FadeOut(5000, Easing.OutQuint); judgementLine.Expire(); + + arrow.MoveToY(calculateArrowPosition(judgement) / bar_height, 500, Easing.OutQuint); } - protected virtual Container CreateJudgementLine(double offset) => new CircularContainer + protected virtual Container CreateJudgementLine(JudgementResult judgement) => new CircularContainer { Anchor = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, Origin = mirrored ? Anchor.CentreLeft : Anchor.CentreRight, Masking = true, Size = new Vector2(10, 2), RelativePositionAxes = Axes.Y, - Y = (float)offset / bar_height, + Y = (float)judgement.TimeOffset / bar_height, X = mirrored ? spacing : -spacing, Child = new Box { @@ -55,5 +74,20 @@ namespace osu.Game.Screens.Play.HUD Colour = Color4.White, } }; + + private float calculateArrowPosition(JudgementResult judgement) + { + if (judgementOffsets.Count > 5) + judgementOffsets.RemoveAt(0); + + judgementOffsets.Add(judgement.TimeOffset); + + double offsets = 0; + + foreach (var offset in judgementOffsets) + offsets += offset; + + return (float)offsets / judgementOffsets.Count; + } } } From cc4ee2df0591301b420675255a28e0612862654f Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 11 Aug 2019 16:41:56 +0200 Subject: [PATCH 0630/2815] add ToString() override to Beatmap class --- osu.Game/Beatmaps/Beatmap.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 4ebeee40bf..6079a112e6 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -60,5 +60,7 @@ namespace osu.Game.Beatmaps public class Beatmap : Beatmap { public new Beatmap Clone() => (Beatmap)base.Clone(); + + public override string ToString() => BeatmapInfo?.ToString(); } } From 2a35c3c3e24708ef174f5482250d4de773ccb6d9 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 11 Aug 2019 18:04:54 +0300 Subject: [PATCH 0631/2815] Calculate real position for judgement lines --- osu.Game/Screens/Play/HUD/AccuracyBar.cs | 25 ++++++++++++++++++++---- osu.Game/Screens/Play/HUDOverlay.cs | 6 ++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/AccuracyBar.cs b/osu.Game/Screens/Play/HUD/AccuracyBar.cs index 3c13111b22..590e9ca4d9 100644 --- a/osu.Game/Screens/Play/HUD/AccuracyBar.cs +++ b/osu.Game/Screens/Play/HUD/AccuracyBar.cs @@ -9,6 +9,10 @@ using osuTK.Graphics; using osuTK; using osu.Framework.Graphics.Sprites; using System.Collections.Generic; +using osu.Game.Rulesets.Objects; +using osu.Game.Beatmaps; +using osu.Framework.Bindables; +using osu.Framework.Allocation; namespace osu.Game.Screens.Play.HUD { @@ -18,6 +22,11 @@ namespace osu.Game.Screens.Play.HUD private const int bar_height = 250; private const int spacing = 3; + public HitWindows HitWindows { get; set; } + + [Resolved] + private Bindable beatmap { get; set; } + private readonly bool mirrored; private readonly SpriteIcon arrow; private readonly List judgementOffsets = new List(); @@ -47,6 +56,12 @@ namespace osu.Game.Screens.Play.HUD }; } + protected override void LoadComplete() + { + base.LoadComplete(); + HitWindows.SetDifficulty(beatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty); + } + public void OnNewJudgement(JudgementResult judgement) { Container judgementLine; @@ -56,7 +71,7 @@ namespace osu.Game.Screens.Play.HUD judgementLine.FadeOut(5000, Easing.OutQuint); judgementLine.Expire(); - arrow.MoveToY(calculateArrowPosition(judgement) / bar_height, 500, Easing.OutQuint); + arrow.MoveToY(getRelativeJudgementPosition(calculateArrowPosition(judgement)), 500, Easing.OutQuint); } protected virtual Container CreateJudgementLine(JudgementResult judgement) => new CircularContainer @@ -66,7 +81,7 @@ namespace osu.Game.Screens.Play.HUD Masking = true, Size = new Vector2(10, 2), RelativePositionAxes = Axes.Y, - Y = (float)judgement.TimeOffset / bar_height, + Y = getRelativeJudgementPosition(judgement.TimeOffset), X = mirrored ? spacing : -spacing, Child = new Box { @@ -75,7 +90,9 @@ namespace osu.Game.Screens.Play.HUD } }; - private float calculateArrowPosition(JudgementResult judgement) + private float getRelativeJudgementPosition(double value) => (float)(value / HitWindows.Miss); + + private double calculateArrowPosition(JudgementResult judgement) { if (judgementOffsets.Count > 5) judgementOffsets.RemoveAt(0); @@ -87,7 +104,7 @@ namespace osu.Game.Screens.Play.HUD foreach (var offset in judgementOffsets) offsets += offset; - return (float)offsets / judgementOffsets.Count; + return offsets / judgementOffsets.Count; } } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 3fafc21ea8..d30a32343a 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -277,10 +277,16 @@ namespace osu.Game.Screens.Play HealthDisplay?.Current.BindTo(processor.Health); if (LeftAccuracyBar != null) + { processor.NewJudgement += LeftAccuracyBar.OnNewJudgement; + LeftAccuracyBar.HitWindows = processor.CreateHitWindows(); + } if (RightAccuracyBar != null) + { processor.NewJudgement += RightAccuracyBar.OnNewJudgement; + RightAccuracyBar.HitWindows = processor.CreateHitWindows(); + } if (HealthDisplay is StandardHealthDisplay shd) processor.NewJudgement += shd.Flash; From 177a317a48b34d73643b946a90db01fe245e24c9 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 11 Aug 2019 18:11:49 +0300 Subject: [PATCH 0632/2815] rename AccuracyBar to HitErrorDisplay --- .../{AccuracyBar.cs => HitErrorDisplay.cs} | 4 ++-- osu.Game/Screens/Play/HUDOverlay.cs | 22 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) rename osu.Game/Screens/Play/HUD/{AccuracyBar.cs => HitErrorDisplay.cs} (97%) diff --git a/osu.Game/Screens/Play/HUD/AccuracyBar.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs similarity index 97% rename from osu.Game/Screens/Play/HUD/AccuracyBar.cs rename to osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 590e9ca4d9..2d33cb08a0 100644 --- a/osu.Game/Screens/Play/HUD/AccuracyBar.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -16,7 +16,7 @@ using osu.Framework.Allocation; namespace osu.Game.Screens.Play.HUD { - public class AccuracyBar : Container + public class HitErrorDisplay : Container { private const int bar_width = 5; private const int bar_height = 250; @@ -31,7 +31,7 @@ namespace osu.Game.Screens.Play.HUD private readonly SpriteIcon arrow; private readonly List judgementOffsets = new List(); - public AccuracyBar(bool mirrored = false) + public HitErrorDisplay(bool mirrored = false) { this.mirrored = mirrored; diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index d30a32343a..a9a469486e 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -35,8 +35,8 @@ namespace osu.Game.Screens.Play public readonly ModDisplay ModDisplay; public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; - public readonly AccuracyBar LeftAccuracyBar; - public readonly AccuracyBar RightAccuracyBar; + public readonly HitErrorDisplay LeftHitErrorDisplay; + public readonly HitErrorDisplay RightHitErrorDisplay; public Bindable ShowHealthbar = new Bindable(true); @@ -86,8 +86,8 @@ namespace osu.Game.Screens.Play HealthDisplay = CreateHealthDisplay(), Progress = CreateProgress(), ModDisplay = CreateModsContainer(), - LeftAccuracyBar = CreateAccuracyBar(false), - RightAccuracyBar = CreateAccuracyBar(), + LeftHitErrorDisplay = CreateAccuracyBar(false), + RightHitErrorDisplay = CreateAccuracyBar(), } }, PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), @@ -260,7 +260,7 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20, Right = 10 }, }; - protected virtual AccuracyBar CreateAccuracyBar(bool mirrored = true) => new AccuracyBar(mirrored) + protected virtual HitErrorDisplay CreateAccuracyBar(bool mirrored = true) => new HitErrorDisplay(mirrored) { Anchor = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, Origin = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, @@ -276,16 +276,16 @@ namespace osu.Game.Screens.Play ComboCounter?.Current.BindTo(processor.Combo); HealthDisplay?.Current.BindTo(processor.Health); - if (LeftAccuracyBar != null) + if (LeftHitErrorDisplay != null) { - processor.NewJudgement += LeftAccuracyBar.OnNewJudgement; - LeftAccuracyBar.HitWindows = processor.CreateHitWindows(); + processor.NewJudgement += LeftHitErrorDisplay.OnNewJudgement; + LeftHitErrorDisplay.HitWindows = processor.CreateHitWindows(); } - if (RightAccuracyBar != null) + if (RightHitErrorDisplay != null) { - processor.NewJudgement += RightAccuracyBar.OnNewJudgement; - RightAccuracyBar.HitWindows = processor.CreateHitWindows(); + processor.NewJudgement += RightHitErrorDisplay.OnNewJudgement; + RightHitErrorDisplay.HitWindows = processor.CreateHitWindows(); } if (HealthDisplay is StandardHealthDisplay shd) From 5e0ac28ca88cecd41586c162d5d3b6862b11cfdd Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 11 Aug 2019 18:30:03 +0300 Subject: [PATCH 0633/2815] Add basic colours --- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 41 ++++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 2d33cb08a0..dba32d4ef8 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -13,12 +13,14 @@ using osu.Game.Rulesets.Objects; using osu.Game.Beatmaps; using osu.Framework.Bindables; using osu.Framework.Allocation; +using osu.Framework.Graphics.Colour; +using osu.Framework.Extensions.Color4Extensions; namespace osu.Game.Screens.Play.HUD { public class HitErrorDisplay : Container { - private const int bar_width = 5; + private const int bar_width = 4; private const int bar_height = 250; private const int spacing = 3; @@ -39,10 +41,43 @@ namespace osu.Game.Screens.Play.HUD Children = new Drawable[] { - new Box + new FillFlowContainer { RelativeSizeAxes = Axes.Both, - Colour = Color4.White, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Color4.Orange), + Height = 0.3f + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Green, + Height = 0.15f + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Blue, + Height = 0.1f + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Green, + Height = 0.15f + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(Color4.Orange, Color4.Black.Opacity(0)), + Height = 0.3f + } + } }, arrow = new SpriteIcon { From 3136d46c7f366c169413f6d3f64f135dbdb98ff2 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 11 Aug 2019 19:04:56 +0300 Subject: [PATCH 0634/2815] Do not generate new judgement line on miss --- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index dba32d4ef8..874a2cc088 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -99,6 +99,9 @@ namespace osu.Game.Screens.Play.HUD public void OnNewJudgement(JudgementResult judgement) { + if (!judgement.IsHit) + return; + Container judgementLine; Add(judgementLine = CreateJudgementLine(judgement)); From e7964c165f3c2c206dc404d23c7f3e6bcb4f5acd Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 11 Aug 2019 19:09:50 +0300 Subject: [PATCH 0635/2815] Make judgement lines alive for a bit longer --- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 874a2cc088..4cac73c975 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -106,7 +106,7 @@ namespace osu.Game.Screens.Play.HUD Add(judgementLine = CreateJudgementLine(judgement)); - judgementLine.FadeOut(5000, Easing.OutQuint); + judgementLine.FadeOut(10000, Easing.OutQuint); judgementLine.Expire(); arrow.MoveToY(getRelativeJudgementPosition(calculateArrowPosition(judgement)), 500, Easing.OutQuint); From ce62f6b56e76e13f7ca17891d37453514e3eb3e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Aug 2019 01:40:11 +0900 Subject: [PATCH 0636/2815] ToString should never return null --- osu.Game/Beatmaps/Beatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 6079a112e6..a09a1bb9cb 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -61,6 +61,6 @@ namespace osu.Game.Beatmaps { public new Beatmap Clone() => (Beatmap)base.Clone(); - public override string ToString() => BeatmapInfo?.ToString(); + public override string ToString() => BeatmapInfo?.ToString() ?? base.ToString(); } } From ff4b271f64c508227f8d70c4301046afad16f2e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Aug 2019 01:42:05 +0900 Subject: [PATCH 0637/2815] Add extra quotations around output --- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index d6d6804d16..9a8197ad82 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -36,7 +36,7 @@ namespace osu.Game.Beatmaps.Formats { if (!Enum.TryParse(line.Substring(1, line.Length - 2), out section)) { - Logger.Log($"Unknown section \"{line}\" in {output}"); + Logger.Log($"Unknown section \"{line}\" in \"{output}\""); section = Section.None; } @@ -49,7 +49,7 @@ namespace osu.Game.Beatmaps.Formats } catch (Exception e) { - Logger.Log($"Failed to process line \"{line}\" into {output}: {e.Message}", LoggingTarget.Runtime, LogLevel.Important); + Logger.Log($"Failed to process line \"{line}\" into \"{output}\": {e.Message}", LoggingTarget.Runtime, LogLevel.Important); } } } From 359261d4a47353221ac5ca15f86b71f7253a8087 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Aug 2019 02:04:06 +0900 Subject: [PATCH 0638/2815] Fix game not starting if intro music is disabled --- osu.Game/Screens/Menu/IntroTriangles.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 87d6012205..ba0d624959 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.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; @@ -104,14 +104,14 @@ namespace osu.Game.Screens.Menu LoadComponentAsync(new TrianglesIntroSequence(logo, background) { RelativeSizeAxes = Axes.Both, - Clock = new FramedClock(track), + Clock = new FramedClock(menuMusic.Value ? track : null), LoadMenu = LoadMenu }, t => { AddInternal(t); welcome?.Play(); - // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Manu. + // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu. if (menuMusic.Value) track.Start(); }); From e4eed83d85bec2274d40c14d4965cde6f3743def Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 11 Aug 2019 19:14:49 +0200 Subject: [PATCH 0639/2815] Add dual bindings for Jukebox hotkeys --- .../Input/Bindings/GlobalActionContainer.cs | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 809ec9a09e..b2cbb77087 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -46,8 +46,11 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.KeypadEnter, GlobalAction.Select), new KeyBinding(InputKey.TrackPrevious, GlobalAction.MusicPrev), + new KeyBinding(InputKey.F1, GlobalAction.MusicPrev), new KeyBinding(InputKey.TrackNext, GlobalAction.MusicNext), + new KeyBinding(InputKey.F5, GlobalAction.MusicNext), new KeyBinding(InputKey.PlayPause, GlobalAction.MusicPlay), + new KeyBinding(InputKey.F3, GlobalAction.MusicPlay) }; public IEnumerable InGameKeyBindings => new[] @@ -92,16 +95,6 @@ namespace osu.Game.Input.Bindings [Description("Toggle mute")] ToggleMute, - // Game-wide beatmap jukebox keybindings - [Description("Jukebox next track")] - MusicNext, - - [Description("Jukebox previous track")] - MusicPrev, - - [Description("Jukebox play / pause current track")] - MusicPlay, - // In-Game Keybindings [Description("Skip cutscene")] SkipCutscene, @@ -129,5 +122,15 @@ namespace osu.Game.Input.Bindings [Description("Quick exit (Hold)")] QuickExit, + + // Game-wide beatmap jukebox keybindings + [Description("Jukebox next track")] + MusicNext, + + [Description("Jukebox previous track")] + MusicPrev, + + [Description("Jukebox play / pause current track")] + MusicPlay, } } From bc32726f3caee2c95c3111095f4b089e0326fc64 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 11 Aug 2019 23:08:14 +0300 Subject: [PATCH 0640/2815] Apply renaming suggestions Co-Authored-By: Dean Herbert --- osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index bffee7f1f7..813ca904ca 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -149,7 +149,7 @@ namespace osu.Game.Tests.Visual.Background } /// - /// Check if the is properly accepting user-defined visual changes in background at all. + /// Ensure is properly accepting user-defined visual changes for a background. /// [Test] public void DisableUserDimBackgroundTest() @@ -166,7 +166,7 @@ namespace osu.Game.Tests.Visual.Background } /// - /// Check if the is properly accepting user-defined visual changes in storyboard at all. + /// Ensure is properly accepting user-defined visual changes for a storyboard. /// [Test] public void DisableUserDimStoryboardTest() From fe20e1924352fcfea0918e14f55b0c43b6038ffd Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 11 Aug 2019 23:19:22 +0300 Subject: [PATCH 0641/2815] Rename toggling steps --- .../Visual/Background/TestSceneUserDimContainer.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index 813ca904ca..3061a3a542 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -119,14 +119,14 @@ namespace osu.Game.Tests.Visual.Background { performFullSetup(); createFakeStoryboard(); - AddStep("Storyboard Enabled", () => + AddStep("Enable Storyboard", () => { player.ReplacesBackground.Value = true; player.StoryboardEnabled.Value = true; }); waitForDim(); AddAssert("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible); - AddStep("Storyboard Disabled", () => + AddStep("Disable Storyboard", () => { player.ReplacesBackground.Value = false; player.StoryboardEnabled.Value = false; @@ -157,10 +157,10 @@ namespace osu.Game.Tests.Visual.Background performFullSetup(); waitForDim(); AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); - AddStep("EnableUserDim disabled", () => songSelect.DimEnabled.Value = false); + AddStep("Enable user dim", () => songSelect.DimEnabled.Value = false); waitForDim(); AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled()); - AddStep("EnableUserDim enabled", () => songSelect.DimEnabled.Value = true); + AddStep("Disable user dim", () => songSelect.DimEnabled.Value = true); waitForDim(); AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); } @@ -173,16 +173,16 @@ namespace osu.Game.Tests.Visual.Background { performFullSetup(); createFakeStoryboard(); - AddStep("Storyboard Enabled", () => + AddStep("Enable Storyboard", () => { player.ReplacesBackground.Value = true; player.StoryboardEnabled.Value = true; }); - AddStep("EnableUserDim enabled", () => player.DimmableStoryboard.EnableUserDim.Value = true); + AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true); AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f); waitForDim(); AddAssert("Storyboard is invisible", () => !player.IsStoryboardVisible); - AddStep("EnableUserDim disabled", () => player.DimmableStoryboard.EnableUserDim.Value = false); + AddStep("Disable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = false); waitForDim(); AddAssert("Storyboard is visible", () => player.IsStoryboardVisible); } From 45b4fc9201849483d0d08ec770c6c5b89d413d6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Aug 2019 15:00:32 +0900 Subject: [PATCH 0642/2815] Add xmldoc --- osu.Game.Rulesets.Osu/OsuInputManager.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index 58e275ba26..cdea7276f3 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -18,6 +18,10 @@ namespace osu.Game.Rulesets.Osu set => ((OsuKeyBindingContainer)KeyBindingContainer).AllowUserPresses = value; } + /// + /// Whether the user's cursor movement events should be accepted. + /// Can be used to block only movement while still accepting button input. + /// public bool AllowUserCursorMovement { get; set; } = true; protected override RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) From 707911acac196e5c97ce6d6f5b95a0dbeb3dcdbc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Aug 2019 15:05:27 +0900 Subject: [PATCH 0643/2815] Tidy up code formatting / variable naming --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 1853b0228f..ca72f18e9c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -29,19 +29,21 @@ namespace osu.Game.Rulesets.Osu.Mods private OsuInputManager inputManager; private List replayFrames; - private int frameIndex; + + private int currentFrame; public void Update(Playfield playfield) { - // If we are on the last replay frame, no need to do anything - if (frameIndex == replayFrames.Count - 1) return; + if (currentFrame == replayFrames.Count - 1) return; - // Check if we are closer to the next replay frame then the current one - if (Math.Abs(replayFrames[frameIndex].Time - playfield.Time.Current) >= Math.Abs(replayFrames[frameIndex + 1].Time - playfield.Time.Current)) + double time = playfield.Time.Current; + + // Very naive implementation of autopilot based on proximity to replay frames. + // TODO: this needs to be based on user interactions to better match stable (pausing until judgement is registered). + if (Math.Abs(replayFrames[currentFrame + 1].Time - time) <= Math.Abs(replayFrames[currentFrame].Time - time)) { - // If we are, move to the next frame, and update the mouse position - frameIndex++; - new MousePositionAbsoluteInput { Position = playfield.ToScreenSpace(replayFrames[frameIndex].Position) }.Apply(inputManager.CurrentState, inputManager); + currentFrame++; + new MousePositionAbsoluteInput { Position = playfield.ToScreenSpace(replayFrames[currentFrame].Position) }.Apply(inputManager.CurrentState, inputManager); } // TODO: Implement the functionality to automatically spin spinners From 520924b440c0a642597d25d58d24efbcd705ff3a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 12 Aug 2019 09:45:21 +0300 Subject: [PATCH 0644/2815] Don't create a new request if onlineId is null --- .../Screens/Multi/Match/Components/MatchBeatmapPanel.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs b/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs index f73059d069..397f158f2b 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs @@ -40,15 +40,15 @@ namespace osu.Game.Screens.Multi.Match.Components panel = null; } - var onlineId = item.NewValue?.Beatmap.OnlineBeatmapID ?? 0; + var onlineId = item.NewValue?.Beatmap.OnlineBeatmapID; - if (onlineId != 0) + if (onlineId.HasValue) { - request = new GetBeatmapSetRequest(onlineId, BeatmapSetLookupType.BeatmapId); + request = new GetBeatmapSetRequest(onlineId.Value, BeatmapSetLookupType.BeatmapId); request.Success += beatmap => { panel = new DirectGridPanel(beatmap.ToBeatmapSet(rulesets)); - LoadComponentAsync(panel, p => { AddInternal(panel); }); + LoadComponentAsync(panel, p => { AddInternal(p); }); }; api.Queue(request); } From 5b68a2f34a2acc1abe91bbb65197e8dcde93ff0d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 12 Aug 2019 09:50:49 +0300 Subject: [PATCH 0645/2815] Rename trackManager to previewTrackManager --- .../Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs | 4 ++-- osu.Game/Screens/Multi/Match/MatchSubScreen.cs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs index db2b61cdd9..f148170847 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }; [Resolved] - private PreviewTrackManager trackManager { get; set; } + private PreviewTrackManager previewTrackManager { get; set; } public TestSceneMatchBeatmapPanel() { @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("Select random beatmap", () => { Room.CurrentItem.Value = Room.Playlist[RNG.Next(Room.Playlist.Count)]; - trackManager.StopAnyPlaying(this); + previewTrackManager.StopAnyPlaying(this); }); } diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index c89c32759d..8d79b21b16 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Multi.Match private BeatmapManager beatmapManager { get; set; } [Resolved] - private PreviewTrackManager trackManager { get; set; } + private PreviewTrackManager previewTrackManager { get; set; } [Resolved(CanBeNull = true)] private OsuGame game { get; set; } @@ -188,7 +188,7 @@ namespace osu.Game.Screens.Multi.Match Mods.Value = Array.Empty(); - trackManager.StopAnyPlaying(this); + previewTrackManager.StopAnyPlaying(this); return base.OnExiting(next); } @@ -207,7 +207,7 @@ namespace osu.Game.Screens.Multi.Match if (e.NewValue?.Ruleset != null) Ruleset.Value = e.NewValue.Ruleset; - trackManager.StopAnyPlaying(this); + previewTrackManager.StopAnyPlaying(this); } /// @@ -233,7 +233,7 @@ namespace osu.Game.Screens.Multi.Match private void onStart() { - trackManager.StopAnyPlaying(this); + previewTrackManager.StopAnyPlaying(this); switch (type.Value) { From cb910a651881819595c639c542af9c1f8686c6c2 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 12 Aug 2019 10:02:45 +0300 Subject: [PATCH 0646/2815] Replace if/else statement --- .../Screens/Multi/Match/MatchSubScreen.cs | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index 8d79b21b16..dafea70092 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -150,20 +150,12 @@ namespace osu.Game.Screens.Multi.Match { const float fade_duration = 500; - if (tab.NewValue is SettingsMatchPage) - { - header.ShowBeatmapPanel.Value = false; - settings.Show(); - info.FadeOut(fade_duration, Easing.OutQuint); - bottomRow.FadeOut(fade_duration, Easing.OutQuint); - } - else - { - header.ShowBeatmapPanel.Value = true; - settings.Hide(); - info.FadeIn(fade_duration, Easing.OutQuint); - bottomRow.FadeIn(fade_duration, Easing.OutQuint); - } + var settingsDisplayed = tab.NewValue is SettingsMatchPage; + + header.ShowBeatmapPanel.Value = !settingsDisplayed; + settings.State.Value = settingsDisplayed ? Visibility.Visible : Visibility.Hidden; + info.FadeTo(settingsDisplayed ? 0 : 1, fade_duration, Easing.OutQuint); + bottomRow.FadeTo(settingsDisplayed ? 0 : 1, fade_duration, Easing.OutQuint); }, true); chat.Exit += () => From 75cb0d093b59cf74b50a328f7331158be5f6feca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Aug 2019 16:10:25 +0900 Subject: [PATCH 0647/2815] Use description correctly Required for localisation --- osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs | 2 +- .../Select/Leaderboards/BeatmapLeaderboardScope.cs | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs index c74c7cbc2b..7eb9b1829c 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs @@ -69,7 +69,7 @@ namespace osu.Game.Overlays.BeatmapSet Margin = new MarginPadding { Bottom = 8 }, Origin = Anchor.BottomCentre, Anchor = Anchor.BottomCentre, - Text = value.GetDescription() + " Ranking", + Text = value.GetDescription(), Font = OsuFont.GetFont(weight: FontWeight.Regular), }, box = new Box diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboardScope.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboardScope.cs index 9e480b61c6..dc4c2ba4e2 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboardScope.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboardScope.cs @@ -1,13 +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 System.ComponentModel; + namespace osu.Game.Screens.Select.Leaderboards { public enum BeatmapLeaderboardScope { + [Description("Local Ranking")] Local, + + [Description("Country Ranking")] Country, + + [Description("Global Ranking")] Global, + + [Description("Friend Ranking")] Friend, } } From 982066dfdfe2bf2356ff1d7acfc5c0827d0894cd Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 12 Aug 2019 10:38:34 +0300 Subject: [PATCH 0648/2815] Convert to method group --- osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs b/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs index 397f158f2b..7939b18e97 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Multi.Match.Components request.Success += beatmap => { panel = new DirectGridPanel(beatmap.ToBeatmapSet(rulesets)); - LoadComponentAsync(panel, p => { AddInternal(p); }); + LoadComponentAsync(panel, AddInternal); }; api.Queue(request); } From 1bbd0ca54e0bcb4ab90b40cbaa0b533223bf043f Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2019 08:30:26 +0000 Subject: [PATCH 0649/2815] Bump ppy.osu.Game.Resources from 2019.731.1 to 2019.809.0 Bumps [ppy.osu.Game.Resources](https://github.com/ppy/osu-resources) from 2019.731.1 to 2019.809.0. - [Release notes](https://github.com/ppy/osu-resources/releases) - [Commits](https://github.com/ppy/osu-resources/compare/2019.731.1...2019.809.0) Signed-off-by: dependabot-preview[bot] --- 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 3b2e6574ac..721d341c08 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -62,7 +62,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 1dd19ac7ed..b5266fd75d 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -14,7 +14,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 1c3faeed39..103d89cadc 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -104,7 +104,7 @@ - + From 9c15024014d9e196e97a9dee1e817c30993080af Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 12 Aug 2019 10:53:06 +0200 Subject: [PATCH 0650/2815] Reword and add missing xmldoc to OnScreenDisplay --- osu.Game/Overlays/OnScreenDisplay.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs index a30ce5c56f..a92320945e 100644 --- a/osu.Game/Overlays/OnScreenDisplay.cs +++ b/osu.Game/Overlays/OnScreenDisplay.cs @@ -16,6 +16,10 @@ using osu.Game.Overlays.OSD; namespace osu.Game.Overlays { + /// + /// An on-screen display which automatically tracks and displays toast notifications for . + /// Can also display custom content via + /// public class OnScreenDisplay : Container { private readonly Container box; @@ -97,7 +101,7 @@ namespace osu.Game.Overlays } /// - /// Displays the given as parameter on the OSD + /// Displays the provided temporarily. /// /// public void Display(Toast toast) From 144d41f143fb429a848591ecb77bce3d548b64c9 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 12 Aug 2019 12:33:01 +0300 Subject: [PATCH 0651/2815] Add ability to not add all the items if enum --- osu.Game/Graphics/UserInterface/OsuTabControl.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index 11f41b1a48..1bb37560b2 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -31,6 +31,8 @@ namespace osu.Game.Graphics.UserInterface protected virtual float StripWidth() => TabContainer.Children.Sum(c => c.IsPresent ? c.DrawWidth + TabContainer.Spacing.X : 0) - TabContainer.Spacing.X; protected virtual float StripHeight() => 1; + protected virtual bool AddAllItemsIfEnum => true; + private static bool isEnumType => typeof(T).IsEnum; public OsuTabControl() @@ -45,7 +47,7 @@ namespace osu.Game.Graphics.UserInterface Colour = Color4.White.Opacity(0), }); - if (isEnumType) + if (isEnumType && AddAllItemsIfEnum) foreach (var val in (T[])Enum.GetValues(typeof(T))) AddItem(val); } From fc521ac93b3727bc69c3dbebdb94ee53085be1eb Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 12 Aug 2019 13:08:15 +0300 Subject: [PATCH 0652/2815] Expose BoxColour property --- osu.Game/Graphics/UserInterface/PageTabControl.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/PageTabControl.cs b/osu.Game/Graphics/UserInterface/PageTabControl.cs index 156a556b5e..f8d1c7502a 100644 --- a/osu.Game/Graphics/UserInterface/PageTabControl.cs +++ b/osu.Game/Graphics/UserInterface/PageTabControl.cs @@ -32,6 +32,12 @@ namespace osu.Game.Graphics.UserInterface protected readonly SpriteText Text; + protected Color4 BoxColour + { + get => box.Colour; + set => box.Colour = value; + } + public PageTabItem(T value) : base(value) { @@ -66,7 +72,7 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(OsuColour colours) { - box.Colour = colours.Yellow; + BoxColour = colours.Yellow; } protected override bool OnHover(HoverEvent e) From 6533f114d42762d30e98a06770033fdfae247b8b Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 12 Aug 2019 12:09:09 +0200 Subject: [PATCH 0653/2815] Apply review suggestions --- .../UserInterface/TestSceneOnScreenDisplay.cs | 14 ++++++++++++++ osu.Game/Overlays/OSD/Toast.cs | 14 +++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs index 4decfc7dd6..558c027e1d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs @@ -24,6 +24,11 @@ namespace osu.Game.Tests.Visual.UserInterface Add(osd); AddStep("Display empty osd toast", () => osd.Display(new EmptyToast())); + AddAssert("Toast width is 240", () => osd.Child.Width == 240); + + AddStep("Display toast with lengthy text", () => osd.Display(new LengthyToast())); + AddAssert("Toast width is greater than 240", () => osd.Child.Width > 240); + AddRepeatStep("Change toggle (no bind)", () => config.ToggleSetting(TestConfigSetting.ToggleSettingNoKeybind), 2); AddRepeatStep("Change toggle (with bind)", () => config.ToggleSetting(TestConfigSetting.ToggleSettingWithKeybind), 2); AddRepeatStep("Change enum (no bind)", () => config.IncrementEnumSetting(TestConfigSetting.EnumSettingNoKeybind), 3); @@ -96,6 +101,15 @@ namespace osu.Game.Tests.Visual.UserInterface } } + private class LengthyToast : Toast + { + public LengthyToast() + : base("Toast with a very very very long text", "A very very very very very very long text also", "A very very very very very long shortcut") + { + + } + } + private class TestOnScreenDisplay : OnScreenDisplay { protected override void DisplayTemporarily(Drawable toDisplay) => toDisplay.FadeIn().ResizeHeightTo(110); diff --git a/osu.Game/Overlays/OSD/Toast.cs b/osu.Game/Overlays/OSD/Toast.cs index db5e6e4a6a..46c53ec409 100644 --- a/osu.Game/Overlays/OSD/Toast.cs +++ b/osu.Game/Overlays/OSD/Toast.cs @@ -13,22 +13,30 @@ namespace osu.Game.Overlays.OSD { public abstract class Toast : Container { + private const int toast_minimum_width = 240; + private readonly Container content; protected override Container Content => content; protected readonly OsuSpriteText ValueText; - protected Toast(string description, string value, string keybinding) + protected Toast(string description, string value, string shortcut) { Anchor = Anchor.Centre; Origin = Anchor.Centre; - Width = 240; // A toast's height is decided (and transformed) by the containing OnScreenDisplay. RelativeSizeAxes = Axes.Y; + AutoSizeAxes = Axes.X; InternalChildren = new Drawable[] { + new Container //this container exists just to set a minimum width for the toast + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = toast_minimum_width + }, new Box { RelativeSizeAxes = Axes.Both, @@ -68,7 +76,7 @@ namespace osu.Game.Overlays.OSD Alpha = 0.3f, Margin = new MarginPadding { Bottom = 15 }, Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), - Text = string.IsNullOrEmpty(keybinding) ? "NO KEY BOUND" : keybinding.ToUpperInvariant() + Text = string.IsNullOrEmpty(shortcut) ? "NO KEY BOUND" : shortcut.ToUpperInvariant() }, }; } From 5e49d0fb28e704a5c53f34ce1358a20173269cdb Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 12 Aug 2019 12:11:01 +0200 Subject: [PATCH 0654/2815] Fix CI issues --- osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs index 558c027e1d..45720548c8 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs @@ -106,7 +106,6 @@ namespace osu.Game.Tests.Visual.UserInterface public LengthyToast() : base("Toast with a very very very long text", "A very very very very very very long text also", "A very very very very very long shortcut") { - } } From ba49a4c2da7d0791c53ac49955a41114acfa3a88 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 12 Aug 2019 13:16:57 +0300 Subject: [PATCH 0655/2815] Use existing PageTabControl for layout --- .../BeatmapSet/LeaderboardScopeSelector.cs | 63 ++++--------------- 1 file changed, 12 insertions(+), 51 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs index 7eb9b1829c..f54509ff77 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs @@ -8,25 +8,24 @@ using osu.Framework.Graphics.Containers; using osuTK; using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.Sprites; -using osu.Framework.Extensions; using osu.Game.Graphics; using osu.Framework.Allocation; using osuTK.Graphics; -using osu.Framework.Input.Events; using osu.Framework.Graphics.Colour; +using osu.Framework.Input.Events; namespace osu.Game.Overlays.BeatmapSet { - public class LeaderboardScopeSelector : TabControl + public class LeaderboardScopeSelector : PageTabControl { + protected override bool AddAllItemsIfEnum => false; + protected override Dropdown CreateDropdown() => null; protected override TabItem CreateTabItem(BeatmapLeaderboardScope value) => new ScopeSelectorTabItem(value); public LeaderboardScopeSelector() { - AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; AddItem(BeatmapLeaderboardScope.Global); @@ -42,57 +41,31 @@ namespace osu.Game.Overlays.BeatmapSet protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, Direction = FillDirection.Horizontal, Spacing = new Vector2(20, 0), }; - private class ScopeSelectorTabItem : TabItem + private class ScopeSelectorTabItem : PageTabItem { - private const float transition_duration = 100; - - private readonly Box box; - - protected readonly OsuSpriteText Text; - public ScopeSelectorTabItem(BeatmapLeaderboardScope value) : base(value) { - AutoSizeAxes = Axes.Both; - - Children = new Drawable[] - { - Text = new OsuSpriteText - { - Margin = new MarginPadding { Bottom = 8 }, - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - Text = value.GetDescription(), - Font = OsuFont.GetFont(weight: FontWeight.Regular), - }, - box = new Box - { - RelativeSizeAxes = Axes.X, - Height = 5, - Scale = new Vector2(1f, 0f), - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - }, - new HoverClickSounds() - }; + Text.Font = OsuFont.GetFont(size: 16); } [BackgroundDependencyLoader] private void load(OsuColour colours) { - box.Colour = colours.Blue; + BoxColour = colours.Blue; } protected override bool OnHover(HoverEvent e) { - Text.FadeColour(Color4.LightSkyBlue); + Text.FadeColour(BoxColour); return base.OnHover(e); } @@ -103,18 +76,6 @@ namespace osu.Game.Overlays.BeatmapSet Text.FadeColour(Color4.White); } - - protected override void OnActivated() - { - box.ScaleTo(new Vector2(1f), transition_duration); - Text.Font = Text.Font.With(weight: FontWeight.Black); - } - - protected override void OnDeactivated() - { - box.ScaleTo(new Vector2(1f, 0f), transition_duration); - Text.Font = Text.Font.With(weight: FontWeight.Regular); - } } private class Line : GridContainer From 9c36cb4af4f60a8675640471a99adf65968ea7dd Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 12 Aug 2019 14:33:30 +0300 Subject: [PATCH 0656/2815] Use existing AccentColour logic instead of weird BoxColour --- .../Graphics/UserInterface/PageTabControl.cs | 26 ++++++++++++------- .../BeatmapSet/LeaderboardScopeSelector.cs | 14 +++++----- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageTabControl.cs b/osu.Game/Graphics/UserInterface/PageTabControl.cs index f8d1c7502a..a0d3745180 100644 --- a/osu.Game/Graphics/UserInterface/PageTabControl.cs +++ b/osu.Game/Graphics/UserInterface/PageTabControl.cs @@ -24,7 +24,13 @@ namespace osu.Game.Graphics.UserInterface Height = 30; } - public class PageTabItem : TabItem + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.Yellow; + } + + public class PageTabItem : TabItem, IHasAccentColour { private const float transition_duration = 100; @@ -32,10 +38,16 @@ namespace osu.Game.Graphics.UserInterface protected readonly SpriteText Text; - protected Color4 BoxColour + private Color4 accentColour; + + public Color4 AccentColour { - get => box.Colour; - set => box.Colour = value; + get => accentColour; + set + { + accentColour = value; + box.Colour = accentColour; + } } public PageTabItem(T value) @@ -69,12 +81,6 @@ namespace osu.Game.Graphics.UserInterface Active.BindValueChanged(active => Text.Font = Text.Font.With(Typeface.Exo, weight: active.NewValue ? FontWeight.Bold : FontWeight.Medium), true); } - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - BoxColour = colours.Yellow; - } - protected override bool OnHover(HoverEvent e) { if (!Active.Value) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs index f54509ff77..c867cc3780 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs @@ -39,6 +39,12 @@ namespace osu.Game.Overlays.BeatmapSet }); } + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.Blue; + } + protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer { Anchor = Anchor.BottomCentre, @@ -57,15 +63,9 @@ namespace osu.Game.Overlays.BeatmapSet Text.Font = OsuFont.GetFont(size: 16); } - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - BoxColour = colours.Blue; - } - protected override bool OnHover(HoverEvent e) { - Text.FadeColour(BoxColour); + Text.FadeColour(AccentColour); return base.OnHover(e); } From 0070f6b26072b391e3989eb880261bc632ce41fb Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 12 Aug 2019 14:49:08 +0300 Subject: [PATCH 0657/2815] Use CompositeDrawable as a parent class --- osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index 99c51813c5..399cd4a49a 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -19,7 +19,7 @@ using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.BeatmapSet { - public class LeaderboardModSelector : Container + public class LeaderboardModSelector : CompositeDrawable { public readonly Bindable> SelectedMods = new Bindable>(); public readonly Bindable Ruleset = new Bindable(); @@ -30,7 +30,7 @@ namespace osu.Game.Overlays.BeatmapSet { AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; - Child = modsContainer = new FillFlowContainer + InternalChild = modsContainer = new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, From 21af39032749fdb8da92fd773a5063f03ab955c6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 12 Aug 2019 14:57:16 +0300 Subject: [PATCH 0658/2815] Move binding to LoadComplete --- osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index 399cd4a49a..99c1b54467 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -38,8 +38,12 @@ namespace osu.Game.Overlays.BeatmapSet Direction = FillDirection.Full, Spacing = new Vector2(4), }; + } - Ruleset.BindValueChanged(onRulesetChanged); + protected override void LoadComplete() + { + base.LoadComplete(); + Ruleset.BindValueChanged(onRulesetChanged, true); } private void onRulesetChanged(ValueChangedEvent ruleset) From 62a91e4aaab46c458152233ef4208278dc635e4b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 12 Aug 2019 15:20:21 +0300 Subject: [PATCH 0659/2815] Add the ability to override Highlighted action to the ModIcon --- .../Overlays/BeatmapSet/LeaderboardModSelector.cs | 14 ++++++++++++-- osu.Game/Rulesets/UI/ModIcon.cs | 13 +++++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index 99c1b54467..03d2e6ce4b 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -92,11 +92,16 @@ namespace osu.Game.Overlays.BeatmapSet { Scale = new Vector2(mod_scale); Add(new HoverClickSounds()); + } + + protected override void LoadComplete() + { + base.LoadComplete(); Selected.BindValueChanged(selected => { updateState(); - OnSelectionChanged?.Invoke(mod, selected.NewValue); + OnSelectionChanged?.Invoke(Mod, selected.NewValue); }, true); } @@ -120,7 +125,12 @@ namespace osu.Game.Overlays.BeatmapSet private void updateState() { - this.FadeColour(IsHovered || Selected.Value ? Color4.White : Color4.Gray, duration, Easing.OutQuint); + Highlighted.Value = (IsHovered || Selected.Value) ? true : false; + } + + protected override void OnHighlightedChange(ValueChangedEvent highlighted) + { + this.FadeColour(highlighted.NewValue ? Color4.White : Color4.Gray, duration, Easing.OutQuint); } } diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 962263adba..e713216f35 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -34,9 +34,11 @@ namespace osu.Game.Rulesets.UI public virtual string TooltipText { get; } + protected Mod Mod { get; private set; } + public ModIcon(Mod mod) { - if (mod == null) throw new ArgumentNullException(nameof(mod)); + Mod = mod ?? throw new ArgumentNullException(nameof(mod)); type = mod.Type; @@ -106,12 +108,19 @@ namespace osu.Game.Rulesets.UI modIcon.Colour = colours.Yellow; break; } + + background.Colour = backgroundColour; } protected override void LoadComplete() { base.LoadComplete(); - Highlighted.BindValueChanged(highlighted => background.Colour = highlighted.NewValue ? highlightedColour : backgroundColour, true); + Highlighted.BindValueChanged(OnHighlightedChange, true); + } + + protected virtual void OnHighlightedChange(ValueChangedEvent highlighted) + { + background.Colour = highlighted.NewValue ? highlightedColour : backgroundColour; } } } From b71c776e65c291df39a45a175bc8301d6e3661d7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 12 Aug 2019 16:20:36 +0300 Subject: [PATCH 0660/2815] Add web-like hover behavior --- .../BeatmapSet/LeaderboardModSelector.cs | 39 +++++++++++++++++-- osu.Game/Rulesets/UI/ModIcon.cs | 2 +- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index 03d2e6ce4b..1e10c41478 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -57,12 +57,13 @@ namespace osu.Game.Overlays.BeatmapSet modsContainer.Add(new ModButton(new NoMod())); - foreach (var mod in ruleset.NewValue.CreateInstance().GetAllMods()) + ruleset.NewValue.CreateInstance().GetAllMods().ForEach(mod => + { if (mod.Ranked) modsContainer.Add(new ModButton(mod)); + }); - foreach (var mod in modsContainer) - mod.OnSelectionChanged += selectionChanged; + modsContainer.ForEach(button => button.OnSelectionChanged += selectionChanged); } private void selectionChanged(Mod mod, bool selected) @@ -74,11 +75,39 @@ namespace osu.Game.Overlays.BeatmapSet else mods.Remove(mod); + if (!mods.Any() && !IsHovered) + modsContainer.ForEach(button => button.Highlighted.Value = true); + SelectedMods.Value = mods; } + protected override bool OnHover(HoverEvent e) + { + if (!SelectedMods.Value.Any()) + dehighlightAll(); + + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + + if (!SelectedMods.Value.Any()) + modsContainer.ForEach(mod => mod.Highlighted.Value = true); + } + public void DeselectAll() => modsContainer.ForEach(mod => mod.Selected.Value = false); + private void dehighlightAll() + { + modsContainer.ForEach(button => + { + if (!button.IsHovered) + button.Highlighted.Value = false; + }); + } + private class ModButton : ModIcon { private const float mod_scale = 0.4f; @@ -98,11 +127,13 @@ namespace osu.Game.Overlays.BeatmapSet { base.LoadComplete(); + Highlighted.Value = true; + Selected.BindValueChanged(selected => { updateState(); OnSelectionChanged?.Invoke(Mod, selected.NewValue); - }, true); + }); } protected override bool OnClick(ClickEvent e) diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index e713216f35..1bcd2dc780 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -115,7 +115,7 @@ namespace osu.Game.Rulesets.UI protected override void LoadComplete() { base.LoadComplete(); - Highlighted.BindValueChanged(OnHighlightedChange, true); + Highlighted.BindValueChanged(OnHighlightedChange); } protected virtual void OnHighlightedChange(ValueChangedEvent highlighted) From 86c9d5251faea12d24347dc68ae1c94ea3c738db Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 12 Aug 2019 16:28:53 +0300 Subject: [PATCH 0661/2815] Remove unused function --- .../Overlays/BeatmapSet/LeaderboardModSelector.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index 1e10c41478..fffbc400b1 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -84,7 +84,11 @@ namespace osu.Game.Overlays.BeatmapSet protected override bool OnHover(HoverEvent e) { if (!SelectedMods.Value.Any()) - dehighlightAll(); + modsContainer.ForEach(button => + { + if (!button.IsHovered) + button.Highlighted.Value = false; + }); return base.OnHover(e); } @@ -99,15 +103,6 @@ namespace osu.Game.Overlays.BeatmapSet public void DeselectAll() => modsContainer.ForEach(mod => mod.Selected.Value = false); - private void dehighlightAll() - { - modsContainer.ForEach(button => - { - if (!button.IsHovered) - button.Highlighted.Value = false; - }); - } - private class ModButton : ModIcon { private const float mod_scale = 0.4f; From cf92d6b1b01174666b484580242ad99891a839e0 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 12 Aug 2019 16:32:04 +0300 Subject: [PATCH 0662/2815] Add highlightAll function to avoid duplication --- osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index fffbc400b1..1489907589 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -76,7 +76,7 @@ namespace osu.Game.Overlays.BeatmapSet mods.Remove(mod); if (!mods.Any() && !IsHovered) - modsContainer.ForEach(button => button.Highlighted.Value = true); + highlightAll(); SelectedMods.Value = mods; } @@ -98,11 +98,13 @@ namespace osu.Game.Overlays.BeatmapSet base.OnHoverLost(e); if (!SelectedMods.Value.Any()) - modsContainer.ForEach(mod => mod.Highlighted.Value = true); + highlightAll(); } public void DeselectAll() => modsContainer.ForEach(mod => mod.Selected.Value = false); + private void highlightAll() => modsContainer.ForEach(mod => mod.Highlighted.Value = true); + private class ModButton : ModIcon { private const float mod_scale = 0.4f; From 883102ee5d912b503db9ea9a9e7e162657023b40 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 12 Aug 2019 16:40:52 +0300 Subject: [PATCH 0663/2815] Move score multiplier logic inside score calculation --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index ba2375bec1..c8858233aa 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -398,7 +398,7 @@ namespace osu.Game.Rulesets.Scoring if (rollingMaxBaseScore != 0) Accuracy.Value = baseScore / rollingMaxBaseScore; - TotalScore.Value = getScore(Mode.Value) * scoreMultiplier; + TotalScore.Value = getScore(Mode.Value); } private double getScore(ScoringMode mode) @@ -407,11 +407,11 @@ namespace osu.Game.Rulesets.Scoring { default: case ScoringMode.Standardised: - return max_score * (base_portion * baseScore / maxBaseScore + combo_portion * HighestCombo.Value / maxHighestCombo) + bonusScore; + return (max_score * (base_portion * baseScore / maxBaseScore + combo_portion * HighestCombo.Value / maxHighestCombo) + bonusScore) * scoreMultiplier; case ScoringMode.Classic: // should emulate osu-stable's scoring as closely as we can (https://osu.ppy.sh/help/wiki/Score/ScoreV1) - return bonusScore + baseScore * (1 + Math.Max(0, HighestCombo.Value - 1) / 25); + return bonusScore + baseScore * ((1 + Math.Max(0, HighestCombo.Value - 1) * scoreMultiplier) / 25); } } From 1bfb87fcdd01536874269a16ffcf13d6e7ffcc7c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 12 Aug 2019 16:41:35 +0300 Subject: [PATCH 0664/2815] Remove redundant conditional ternary expression --- osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index 1489907589..66d78f927a 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -28,8 +28,7 @@ namespace osu.Game.Overlays.BeatmapSet public LeaderboardModSelector() { - AutoSizeAxes = Axes.Y; - RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Both; InternalChild = modsContainer = new FillFlowContainer { Anchor = Anchor.Centre, @@ -151,10 +150,7 @@ namespace osu.Game.Overlays.BeatmapSet updateState(); } - private void updateState() - { - Highlighted.Value = (IsHovered || Selected.Value) ? true : false; - } + private void updateState() => Highlighted.Value = IsHovered || Selected.Value; protected override void OnHighlightedChange(ValueChangedEvent highlighted) { From c0f0fbbaa93927fb5c520182e0ece74eaaff5e09 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Aug 2019 00:14:37 +0900 Subject: [PATCH 0665/2815] Rename variable and add xmldoc --- osu.Game/Graphics/UserInterface/OsuTabControl.cs | 7 +++++-- osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index 1bb37560b2..c55d14456b 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -31,7 +31,10 @@ namespace osu.Game.Graphics.UserInterface protected virtual float StripWidth() => TabContainer.Children.Sum(c => c.IsPresent ? c.DrawWidth + TabContainer.Spacing.X : 0) - TabContainer.Spacing.X; protected virtual float StripHeight() => 1; - protected virtual bool AddAllItemsIfEnum => true; + /// + /// Whether entries should be automatically populated if is an type. + /// + protected virtual bool AddEnumEntriesAutomatically => true; private static bool isEnumType => typeof(T).IsEnum; @@ -47,7 +50,7 @@ namespace osu.Game.Graphics.UserInterface Colour = Color4.White.Opacity(0), }); - if (isEnumType && AddAllItemsIfEnum) + if (isEnumType && AddEnumEntriesAutomatically) foreach (var val in (T[])Enum.GetValues(typeof(T))) AddItem(val); } diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs index c867cc3780..04713fd88c 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs @@ -18,7 +18,7 @@ namespace osu.Game.Overlays.BeatmapSet { public class LeaderboardScopeSelector : PageTabControl { - protected override bool AddAllItemsIfEnum => false; + protected override bool AddEnumEntriesAutomatically => false; protected override Dropdown CreateDropdown() => null; From 433b701df3478eb7038ed4002d7f0ba9d9489f86 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Aug 2019 00:21:47 +0900 Subject: [PATCH 0666/2815] Make line slightly thicker (to display better at low resolutions) Also tidies up code. --- .../Overlays/BeatmapSet/LeaderboardScopeSelector.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs index 04713fd88c..dcd58db427 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.BeatmapSet AddItem(BeatmapLeaderboardScope.Country); AddItem(BeatmapLeaderboardScope.Friend); - AddInternal(new Line + AddInternal(new GradientLine { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, @@ -78,19 +78,20 @@ namespace osu.Game.Overlays.BeatmapSet } } - private class Line : GridContainer + private class GradientLine : GridContainer { - public Line() + public GradientLine() { - Height = 1; RelativeSizeAxes = Axes.X; - Width = 0.8f; + Size = new Vector2(0.8f, 1.5f); + ColumnDimensions = new[] { new Dimension(), new Dimension(mode: GridSizeMode.Relative, size: 0.4f), new Dimension(), }; + Content = new[] { new Drawable[] From 87811afade5abfdbf6dc3a2503242f2be4be076b Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 12 Aug 2019 20:16:41 +0200 Subject: [PATCH 0667/2815] Add missing licence headers to added files. --- osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs | 5 ++++- osu.Game/Overlays/News/NewsHeader.cs | 5 ++++- osu.Game/Overlays/NewsOverlay.cs | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs index d5273801d8..546f6ac182 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs @@ -1,4 +1,7 @@ -using osu.Game.Overlays; +// 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.Overlays; namespace osu.Game.Tests.Visual.Online { diff --git a/osu.Game/Overlays/News/NewsHeader.cs b/osu.Game/Overlays/News/NewsHeader.cs index e887d48456..6a14828473 100644 --- a/osu.Game/Overlays/News/NewsHeader.cs +++ b/osu.Game/Overlays/News/NewsHeader.cs @@ -1,4 +1,7 @@ -using osu.Framework.Allocation; +// 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; diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index 1506cbb288..b509204c58 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -1,4 +1,7 @@ -using osu.Framework.Allocation; +// 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; From 5447e7cf222ea170409d18e0a8261185b090119a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Aug 2019 11:34:45 +0900 Subject: [PATCH 0668/2815] Fix file ordering --- osu.Game/Overlays/OSD/TrackedSettingToast.cs | 35 +++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/osu.Game/Overlays/OSD/TrackedSettingToast.cs b/osu.Game/Overlays/OSD/TrackedSettingToast.cs index 0f4bd34779..8e8a99a0a7 100644 --- a/osu.Game/Overlays/OSD/TrackedSettingToast.cs +++ b/osu.Game/Overlays/OSD/TrackedSettingToast.cs @@ -66,12 +66,7 @@ namespace osu.Game.Overlays.OSD ValueText.Origin = optionCount > 0 ? Anchor.BottomCentre : Anchor.Centre; for (int i = 0; i < optionCount; i++) - { - optionLights.Add(new OptionLight - { - Glowing = i == selectedOption - }); - } + optionLights.Add(new OptionLight { Glowing = i == selectedOption }); } private class OptionLight : Container @@ -109,20 +104,6 @@ namespace osu.Game.Overlays.OSD } } - private void updateGlow() - { - if (glowing) - { - fill.FadeColour(glowingColour, transition_speed, Easing.OutQuint); - FadeEdgeEffectTo(glow_strength, transition_speed, Easing.OutQuint); - } - else - { - FadeEdgeEffectTo(0, transition_speed, Easing.OutQuint); - fill.FadeColour(idleColour, transition_speed, Easing.OutQuint); - } - } - [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -147,6 +128,20 @@ namespace osu.Game.Overlays.OSD updateGlow(); FinishTransforms(true); } + + private void updateGlow() + { + if (glowing) + { + fill.FadeColour(glowingColour, transition_speed, Easing.OutQuint); + FadeEdgeEffectTo(glow_strength, transition_speed, Easing.OutQuint); + } + else + { + FadeEdgeEffectTo(0, transition_speed, Easing.OutQuint); + fill.FadeColour(idleColour, transition_speed, Easing.OutQuint); + } + } } } } From f3380c9372c14a2c87db03b832c27001845a1e55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Aug 2019 12:06:57 +0900 Subject: [PATCH 0669/2815] Remove "jukebox" terminology --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 6 +++--- osu.Game/OsuGame.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index b2cbb77087..c54e3f596e 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -124,13 +124,13 @@ namespace osu.Game.Input.Bindings QuickExit, // Game-wide beatmap jukebox keybindings - [Description("Jukebox next track")] + [Description("Next track")] MusicNext, - [Description("Jukebox previous track")] + [Description("Previous track")] MusicPrev, - [Description("Jukebox play / pause current track")] + [Description("Play / pause")] MusicPlay, } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index af77c8816a..1541d1fa29 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -301,7 +301,7 @@ namespace osu.Game }, $"watch {databasedScoreInfo}", bypassScreenAllowChecks: true); } - #region Beatmap jukebox progression + #region Beatmap progression private void beatmapChanged(ValueChangedEvent beatmap) { From aa6f8757eb955cc6179b928dc87038fcc3ff87cd Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 13 Aug 2019 12:26:06 +0900 Subject: [PATCH 0670/2815] remove string param, move menu check to method, add const padding --- .../Visual/Menus/TestSceneScreenNavigation.cs | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs index 663447d0b4..efb4f3e83a 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs @@ -26,12 +26,14 @@ namespace osu.Game.Tests.Visual.Menus { public class TestSceneScreenNavigation : ManualInputManagerTestScene { + private const float click_padding = 25; + private GameHost gameHost; private TestOsuGame osuGame; - private Vector2 backButtonPosition => osuGame.ToScreenSpace(new Vector2(25, osuGame.LayoutRectangle.Bottom - 25)); + private Vector2 backButtonPosition => osuGame.ToScreenSpace(new Vector2(click_padding, osuGame.LayoutRectangle.Bottom - click_padding)); - private Vector2 optionsButtonPosition => osuGame.ToScreenSpace(new Vector2(25, 25)); + private Vector2 optionsButtonPosition => osuGame.ToScreenSpace(new Vector2(click_padding, click_padding)); [BackgroundDependencyLoader] private void load(GameHost gameHost) @@ -63,7 +65,7 @@ namespace osu.Game.Tests.Visual.Menus }); AddUntilStep("Wait for load", () => osuGame.IsLoaded); AddUntilStep("Wait for intro", () => osuGame.ScreenStack.CurrentScreen is IntroScreen); - AddUntilStep("Wait for main menu", () => osuGame.ScreenStack.CurrentScreen is MainMenu menu && menu.IsLoaded); + confirmAtMainMenu(); } [Test] @@ -71,7 +73,7 @@ namespace osu.Game.Tests.Visual.Menus { TestSongSelect songSelect = null; - pushAndConfirm(() => songSelect = new TestSongSelect(), "song select"); + pushAndConfirm(() => songSelect = new TestSongSelect()); AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show()); AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible); AddStep("Press escape", () => pressAndRelease(Key.Escape)); @@ -84,7 +86,7 @@ namespace osu.Game.Tests.Visual.Menus { TestSongSelect songSelect = null; - pushAndConfirm(() => songSelect = new TestSongSelect(), "song select"); + pushAndConfirm(() => songSelect = new TestSongSelect()); AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show()); AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible); AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition)); @@ -100,14 +102,14 @@ namespace osu.Game.Tests.Visual.Menus [Test] public void TestExitMultiWithEscape() { - pushAndConfirm(() => new Screens.Multi.Multiplayer(), "multiplayer"); + pushAndConfirm(() => new Screens.Multi.Multiplayer()); exitViaEscapeAndConfirm(); } [Test] public void TestExitMultiWithBackButton() { - pushAndConfirm(() => new Screens.Multi.Multiplayer(), "multiplayer"); + pushAndConfirm(() => new Screens.Multi.Multiplayer()); exitViaBackButtonAndConfirm(); } @@ -123,26 +125,28 @@ namespace osu.Game.Tests.Visual.Menus AddAssert("Options overlay was closed", () => osuGame.Settings.State.Value == Visibility.Hidden); } - private void pushAndConfirm(Func newScreen, string screenName) + private void pushAndConfirm(Func newScreen) { Screen screen = null; - AddStep($"Push new {screenName}", () => osuGame.ScreenStack.Push(screen = newScreen())); - AddUntilStep($"Wait for new {screenName}", () => osuGame.ScreenStack.CurrentScreen == screen && screen.IsLoaded); + AddStep("Push new screen", () => osuGame.ScreenStack.Push(screen = newScreen())); + AddUntilStep("Wait for new screen", () => osuGame.ScreenStack.CurrentScreen == screen && screen.IsLoaded); } private void exitViaEscapeAndConfirm() { AddStep("Press escape", () => pressAndRelease(Key.Escape)); - AddUntilStep("Wait for main menu", () => osuGame.ScreenStack.CurrentScreen is MainMenu); + confirmAtMainMenu(); } private void exitViaBackButtonAndConfirm() { AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition)); AddStep("Click back button", () => InputManager.Click(MouseButton.Left)); - AddUntilStep("Wait for main menu", () => osuGame.ScreenStack.CurrentScreen is MainMenu); + confirmAtMainMenu(); } + private void confirmAtMainMenu() => AddUntilStep("Wait for main menu", () => osuGame.ScreenStack.CurrentScreen is MainMenu); + private void pressAndRelease(Key key) { InputManager.PressKey(key); From 0c0c4052168e9cba36bcd096c359c9c89c1e4a1a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Aug 2019 12:27:49 +0900 Subject: [PATCH 0671/2815] Add note to README about ffmpeg requirement on linux --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c4d676f4be..5dc4da12a4 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,9 @@ Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh ## Requirements - A desktop platform with the [.NET Core SDK 2.2](https://www.microsoft.com/net/learn/get-started) or higher installed. +- When running on linux, please have a system-wide ffmpeg installation available to support video decoding. +- When running on Windows 7 or 8.1, there are **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs. - When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio 2017+](https://visualstudio.microsoft.com/vs/), [Jetbrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/). -- Note that there are **[additional requirements for Windows 7 and Windows 8.1](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** which you may need to manually install if your operating system is not up-to-date. ## Running osu! From 33a119b7263501823c8f2c1f71a0d881bfd602fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Aug 2019 12:35:06 +0900 Subject: [PATCH 0672/2815] Fix double grammar Co-Authored-By: Dan Balasescu --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5dc4da12a4..56491a4be4 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh - A desktop platform with the [.NET Core SDK 2.2](https://www.microsoft.com/net/learn/get-started) or higher installed. - When running on linux, please have a system-wide ffmpeg installation available to support video decoding. -- When running on Windows 7 or 8.1, there are **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs. +- When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs. - When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio 2017+](https://visualstudio.microsoft.com/vs/), [Jetbrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/). ## Running osu! From 5dbde38a6b00586a7d7b9223e775ea7b4fddbf61 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Aug 2019 12:40:20 +0900 Subject: [PATCH 0673/2815] Group key bindings together --- .../Input/Bindings/GlobalActionContainer.cs | 30 +++++++++++-------- .../KeyBinding/GlobalKeyBindingsSection.cs | 12 ++++++++ 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index c54e3f596e..8073200c47 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -20,7 +20,7 @@ namespace osu.Game.Input.Bindings handler = game; } - public override IEnumerable DefaultKeyBindings => GlobalKeyBindings.Concat(InGameKeyBindings); + public override IEnumerable DefaultKeyBindings => GlobalKeyBindings.Concat(InGameKeyBindings).Concat(AudioControlKeyBindings); public IEnumerable GlobalKeyBindings => new[] { @@ -32,11 +32,6 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings), new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar), new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings), - new KeyBinding(InputKey.Up, GlobalAction.IncreaseVolume), - new KeyBinding(InputKey.MouseWheelUp, GlobalAction.IncreaseVolume), - new KeyBinding(InputKey.Down, GlobalAction.DecreaseVolume), - new KeyBinding(InputKey.MouseWheelDown, GlobalAction.DecreaseVolume), - new KeyBinding(InputKey.F4, GlobalAction.ToggleMute), new KeyBinding(InputKey.Escape, GlobalAction.Back), new KeyBinding(InputKey.ExtraMouseButton1, GlobalAction.Back), @@ -44,13 +39,6 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.Space, GlobalAction.Select), new KeyBinding(InputKey.Enter, GlobalAction.Select), new KeyBinding(InputKey.KeypadEnter, GlobalAction.Select), - - new KeyBinding(InputKey.TrackPrevious, GlobalAction.MusicPrev), - new KeyBinding(InputKey.F1, GlobalAction.MusicPrev), - new KeyBinding(InputKey.TrackNext, GlobalAction.MusicNext), - new KeyBinding(InputKey.F5, GlobalAction.MusicNext), - new KeyBinding(InputKey.PlayPause, GlobalAction.MusicPlay), - new KeyBinding(InputKey.F3, GlobalAction.MusicPlay) }; public IEnumerable InGameKeyBindings => new[] @@ -62,6 +50,22 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed), }; + public IEnumerable AudioControlKeyBindings => new[] + { + new KeyBinding(InputKey.Up, GlobalAction.IncreaseVolume), + new KeyBinding(InputKey.MouseWheelUp, GlobalAction.IncreaseVolume), + new KeyBinding(InputKey.Down, GlobalAction.DecreaseVolume), + new KeyBinding(InputKey.MouseWheelDown, GlobalAction.DecreaseVolume), + new KeyBinding(InputKey.F4, GlobalAction.ToggleMute), + + new KeyBinding(InputKey.TrackPrevious, GlobalAction.MusicPrev), + new KeyBinding(InputKey.F1, GlobalAction.MusicPrev), + new KeyBinding(InputKey.TrackNext, GlobalAction.MusicNext), + new KeyBinding(InputKey.F5, GlobalAction.MusicNext), + new KeyBinding(InputKey.PlayPause, GlobalAction.MusicPlay), + new KeyBinding(InputKey.F3, GlobalAction.MusicPlay) + }; + protected override IEnumerable KeyBindingInputQueue => handler == null ? base.KeyBindingInputQueue : base.KeyBindingInputQueue.Prepend(handler); } diff --git a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs index 7e33d7ba27..56e93b6a1e 100644 --- a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs +++ b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs @@ -15,6 +15,7 @@ namespace osu.Game.Overlays.KeyBinding public GlobalKeyBindingsSection(GlobalActionContainer manager) { Add(new DefaultBindingsSubsection(manager)); + Add(new AudioControlKeyBindingsSubsection(manager)); Add(new InGameKeyBindingsSubsection(manager)); } @@ -39,5 +40,16 @@ namespace osu.Game.Overlays.KeyBinding Defaults = manager.InGameKeyBindings; } } + + private class AudioControlKeyBindingsSubsection : KeyBindingsSubsection + { + protected override string Header => "Audio"; + + public AudioControlKeyBindingsSubsection(GlobalActionContainer manager) + : base(null) + { + Defaults = manager.AudioControlKeyBindings; + } + } } } From 5681d1097c107c4cbbe060c36997c08cc19ebe74 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 13 Aug 2019 14:07:40 +0900 Subject: [PATCH 0674/2815] Move into components namespace --- .../Visual/Online/TestScenePreviousUsernamesContainer.cs | 2 +- .../Header/{ => Components}/PreviousUsernamesContainer.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Overlays/Profile/Header/{ => Components}/PreviousUsernamesContainer.cs (99%) diff --git a/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs b/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs index b891fda0f4..43373f872a 100644 --- a/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs @@ -7,7 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Online.API; using osu.Game.Online.API.Requests; -using osu.Game.Overlays.Profile.Header; +using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Users; namespace osu.Game.Tests.Visual.Online diff --git a/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs b/osu.Game/Overlays/Profile/Header/Components/PreviousUsernamesContainer.cs similarity index 99% rename from osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs rename to osu.Game/Overlays/Profile/Header/Components/PreviousUsernamesContainer.cs index b53ac8eb80..ef9c01e12b 100644 --- a/osu.Game/Overlays/Profile/Header/PreviousUsernamesContainer.cs +++ b/osu.Game/Overlays/Profile/Header/Components/PreviousUsernamesContainer.cs @@ -1,6 +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.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -11,10 +13,8 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Users; using osuTK; -using System; -using System.Linq; -namespace osu.Game.Overlays.Profile.Header +namespace osu.Game.Overlays.Profile.Header.Components { public class PreviousUsernamesContainer : CompositeDrawable { From 8d3f2f76459d062cd2ee54afa747d96981794e9a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 13 Aug 2019 14:09:10 +0900 Subject: [PATCH 0675/2815] Drop container from name --- ....cs => TestSceneUserProfilePreviousUsernames.cs} | 13 ++++++++++--- ...usUsernamesContainer.cs => PreviousUsernames.cs} | 4 ++-- 2 files changed, 12 insertions(+), 5 deletions(-) rename osu.Game.Tests/Visual/Online/{TestScenePreviousUsernamesContainer.cs => TestSceneUserProfilePreviousUsernames.cs} (85%) rename osu.Game/Overlays/Profile/Header/Components/{PreviousUsernamesContainer.cs => PreviousUsernames.cs} (98%) diff --git a/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs similarity index 85% rename from osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs rename to osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs index 43373f872a..d09a50b12c 100644 --- a/osu.Game.Tests/Visual/Online/TestScenePreviousUsernamesContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs @@ -1,6 +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 NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -13,16 +15,21 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Online { [TestFixture] - public class TestScenePreviousUsernamesContainer : OsuTestScene + public class TestSceneUserProfilePreviousUsernames : OsuTestScene { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(PreviousUsernames) + }; + [Resolved] private IAPIProvider api { get; set; } private readonly Bindable user = new Bindable(); - public TestScenePreviousUsernamesContainer() + public TestSceneUserProfilePreviousUsernames() { - Child = new PreviousUsernamesContainer + Child = new PreviousUsernames { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Overlays/Profile/Header/Components/PreviousUsernamesContainer.cs b/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs similarity index 98% rename from osu.Game/Overlays/Profile/Header/Components/PreviousUsernamesContainer.cs rename to osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs index ef9c01e12b..f18f319e27 100644 --- a/osu.Game/Overlays/Profile/Header/Components/PreviousUsernamesContainer.cs +++ b/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs @@ -16,7 +16,7 @@ using osuTK; namespace osu.Game.Overlays.Profile.Header.Components { - public class PreviousUsernamesContainer : CompositeDrawable + public class PreviousUsernames : CompositeDrawable { private const int duration = 200; private const int margin = 10; @@ -29,7 +29,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private readonly Box background; private readonly SpriteText header; - public PreviousUsernamesContainer() + public PreviousUsernames() { HoverIconContainer hoverIcon; From ad24265730b98ac86b5b8c739afdf2da172782ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Aug 2019 14:29:58 +0900 Subject: [PATCH 0676/2815] Split visual component out of MusicController --- .../Visual/Gameplay/TestSceneStoryboard.cs | 40 +- .../TestSceneBeatSyncedContainer.cs | 29 +- ...oller.cs => TestSceneNowPlayingOverlay.cs} | 18 +- osu.Game/OsuGame.cs | 4 +- osu.Game/Overlays/MusicController.cs | 475 +++--------------- osu.Game/Overlays/NowPlayingOverlay.cs | 403 +++++++++++++++ .../Overlays/Toolbar/ToolbarMusicButton.cs | 2 +- 7 files changed, 532 insertions(+), 439 deletions(-) rename osu.Game.Tests/Visual/UserInterface/{TestSceneMusicController.cs => TestSceneNowPlayingOverlay.cs} (58%) create mode 100644 osu.Game/Overlays/NowPlayingOverlay.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs index ead7a4b7fc..ff8437311e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs @@ -21,32 +21,38 @@ namespace osu.Game.Tests.Visual.Gameplay private readonly Container storyboardContainer; private DrawableStoryboard storyboard; + [Cached] + private MusicController musicController = new MusicController(); + public TestSceneStoryboard() { Clock = new FramedClock(); - Add(new Container + AddRange(new Drawable[] { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + musicController, + new Container { - new Box + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - storyboardContainer = new Container - { - RelativeSizeAxes = Axes.Both, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + storyboardContainer = new Container + { + RelativeSizeAxes = Axes.Both, + }, }, }, - }); - - Add(new MusicController - { - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, - State = { Value = Visibility.Visible }, + new NowPlayingOverlay + { + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + State = { Value = Visibility.Visible }, + } }); AddStep("Restart", restart); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index 28f0cc027e..94228e22f0 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -3,6 +3,7 @@ using System; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -22,30 +23,36 @@ namespace osu.Game.Tests.Visual.UserInterface [TestFixture] public class TestSceneBeatSyncedContainer : OsuTestScene { - private readonly MusicController mc; + private readonly NowPlayingOverlay np; + + [Cached] + private MusicController musicController = new MusicController(); public TestSceneBeatSyncedContainer() { Clock = new FramedClock(); Clock.ProcessFrame(); - Add(new BeatContainer + AddRange(new Drawable[] { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - }); - - Add(mc = new MusicController - { - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, + musicController, + new BeatContainer + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + }, + np = new NowPlayingOverlay + { + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + } }); } protected override void LoadComplete() { base.LoadComplete(); - mc.ToggleVisibility(); + np.ToggleVisibility(); } private class BeatContainer : BeatSyncedContainer diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMusicController.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs similarity index 58% rename from osu.Game.Tests/Visual/UserInterface/TestSceneMusicController.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs index ab2ca47100..e3daa9c279 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneMusicController.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Timing; using osu.Game.Overlays; @@ -9,22 +10,27 @@ using osu.Game.Overlays; namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestSceneMusicController : OsuTestScene + public class TestSceneNowPlayingOverlay : OsuTestScene { - public TestSceneMusicController() + [Cached] + private MusicController musicController = new MusicController(); + + public TestSceneNowPlayingOverlay() { Clock = new FramedClock(); - var mc = new MusicController + var np = new NowPlayingOverlay { Origin = Anchor.Centre, Anchor = Anchor.Centre }; - Add(mc); - AddStep(@"show", () => mc.Show()); + Add(musicController); + Add(np); + + AddStep(@"show", () => np.Show()); AddToggleStep(@"toggle beatmap lock", state => Beatmap.Disabled = state); - AddStep(@"show", () => mc.Hide()); + AddStep(@"show", () => np.Hide()); } } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b9e2b79b05..edf3424e0e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -469,6 +469,8 @@ namespace osu.Game loadComponentSingleFile(volume = new VolumeOverlay(), leftFloatingOverlayContent.Add); loadComponentSingleFile(new OnScreenDisplay(), Add, true); + loadComponentSingleFile(musicController = new MusicController(), Add, true); + loadComponentSingleFile(notifications = new NotificationOverlay { GetToolbarHeight = () => ToolbarOffset, @@ -495,7 +497,7 @@ namespace osu.Game Origin = Anchor.TopRight, }, rightFloatingOverlayContent.Add, true); - loadComponentSingleFile(musicController = new MusicController + loadComponentSingleFile(new NowPlayingOverlay { GetToolbarHeight = () => ToolbarOffset, Anchor = Anchor.TopRight, diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index abbcec5094..d1086d589d 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -4,281 +4,87 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; 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.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Framework.Input.Events; -using osu.Framework.Localisation; using osu.Framework.Threading; using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Music; using osu.Game.Rulesets.Mods; -using osuTK; -using osuTK.Graphics; namespace osu.Game.Overlays { - public class MusicController : OsuFocusedOverlayContainer + /// + /// Handles playback of the global music track. + /// + public class MusicController : Component { - private const float player_height = 130; - private const float transition_length = 800; - private const float progress_height = 10; - private const float bottom_black_area_height = 55; - - private Drawable background; - private ProgressBar progressBar; - - private IconButton prevButton; - private IconButton playButton; - private IconButton nextButton; - private IconButton playlistButton; - - private SpriteText title, artist; - - private PlaylistOverlay playlist; - - private BeatmapManager beatmaps; + [Resolved] + private BeatmapManager beatmaps { get; set; } private List beatmapSets; - private Container dragContainer; - private Container playerContainer; - public bool IsUserPaused { get; private set; } + /// + /// Fired when the global has changed. + /// Includes direction information for display purposes. + /// + public event Action TrackChanged; + [Resolved] - private Bindable beatmap { get; set; } + private IBindable beatmap { get; set; } [Resolved] private IBindable> mods { get; set; } - /// - /// Provide a source for the toolbar height. - /// - public Func GetToolbarHeight; - - public MusicController() - { - Width = 400; - Margin = new MarginPadding(10); - } - [BackgroundDependencyLoader] - private void load(Bindable beatmap, BeatmapManager beatmaps, OsuColour colours) + private void load() { - this.beatmaps = beatmaps; - - Children = new Drawable[] - { - dragContainer = new DragContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - playlist = new PlaylistOverlay - { - RelativeSizeAxes = Axes.X, - Y = player_height + 10, - OrderChanged = playlistOrderChanged - }, - playerContainer = new Container - { - RelativeSizeAxes = Axes.X, - Height = player_height, - Masking = true, - CornerRadius = 5, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(40), - Radius = 5, - }, - Children = new[] - { - background = new Background(), - title = new OsuSpriteText - { - Origin = Anchor.BottomCentre, - Anchor = Anchor.TopCentre, - Position = new Vector2(0, 40), - Font = OsuFont.GetFont(size: 25, italics: true), - Colour = Color4.White, - Text = @"Nothing to play", - }, - artist = new OsuSpriteText - { - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Position = new Vector2(0, 45), - Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold, italics: true), - Colour = Color4.White, - Text = @"Nothing to play", - }, - new Container - { - Padding = new MarginPadding { Bottom = progress_height }, - Height = bottom_black_area_height, - RelativeSizeAxes = Axes.X, - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5), - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Children = new[] - { - prevButton = new MusicIconButton - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Action = prev, - Icon = FontAwesome.Solid.StepBackward, - }, - playButton = new MusicIconButton - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(1.4f), - IconScale = new Vector2(1.4f), - Action = togglePause, - Icon = FontAwesome.Regular.PlayCircle, - }, - nextButton = new MusicIconButton - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Action = () => next(), - Icon = FontAwesome.Solid.StepForward, - }, - } - }, - playlistButton = new MusicIconButton - { - Origin = Anchor.Centre, - Anchor = Anchor.CentreRight, - Position = new Vector2(-bottom_black_area_height / 2, 0), - Icon = FontAwesome.Solid.Bars, - Action = () => playlist.ToggleVisibility(), - }, - } - }, - progressBar = new ProgressBar - { - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - Height = progress_height, - FillColour = colours.Yellow, - OnSeek = attemptSeek - } - }, - }, - } - } - }; - beatmapSets = beatmaps.GetAllUsableBeatmapSets(); beatmaps.ItemAdded += handleBeatmapAdded; beatmaps.ItemRemoved += handleBeatmapRemoved; - - playlist.State.ValueChanged += s => playlistButton.FadeColour(s.NewValue == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint); } - private ScheduledDelegate seekDelegate; - - private void attemptSeek(double progress) + protected override void LoadComplete() { - seekDelegate?.Cancel(); - seekDelegate = Schedule(() => - { - if (!beatmap.Disabled) - current?.Track.Seek(progress); - }); + beatmap.BindValueChanged(beatmapChanged, true); + mods.BindValueChanged(_ => updateAudioAdjustments(), true); + base.LoadComplete(); } - private void playlistOrderChanged(BeatmapSetInfo beatmapSetInfo, int index) + /// + /// Change the position of a in the current playlist. + /// + /// The beatmap to move. + /// The new position. + public void ChangeBeatmapSetPosition(BeatmapSetInfo beatmapSetInfo, int index) { beatmapSets.Remove(beatmapSetInfo); beatmapSets.Insert(index, beatmapSetInfo); } - private void handleBeatmapAdded(BeatmapSetInfo set) => Schedule(() => beatmapSets.Add(set)); + private void handleBeatmapAdded(BeatmapSetInfo set) => + Schedule(() => beatmapSets.Add(set)); - private void handleBeatmapRemoved(BeatmapSetInfo set) => Schedule(() => beatmapSets.RemoveAll(s => s.ID == set.ID)); + private void handleBeatmapRemoved(BeatmapSetInfo set) => + Schedule(() => beatmapSets.RemoveAll(s => s.ID == set.ID)); - protected override void LoadComplete() + private ScheduledDelegate seekDelegate; + + public void SeekTo(double position) { - beatmap.BindValueChanged(beatmapChanged, true); - beatmap.BindDisabledChanged(beatmapDisabledChanged, true); - mods.BindValueChanged(_ => updateAudioAdjustments(), true); - base.LoadComplete(); - } - - private void beatmapDisabledChanged(bool disabled) - { - if (disabled) - playlist.Hide(); - - playButton.Enabled.Value = !disabled; - prevButton.Enabled.Value = !disabled; - nextButton.Enabled.Value = !disabled; - playlistButton.Enabled.Value = !disabled; - } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - Height = dragContainer.Height; - - dragContainer.Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; - } - - protected override void Update() - { - base.Update(); - - if (pendingBeatmapSwitch != null) + seekDelegate?.Cancel(); + seekDelegate = Schedule(() => { - pendingBeatmapSwitch(); - pendingBeatmapSwitch = null; - } - - var track = current?.TrackLoaded ?? false ? current.Track : null; - - if (track?.IsDummyDevice == false) - { - progressBar.EndTime = track.Length; - progressBar.CurrentTime = track.CurrentTime; - - playButton.Icon = track.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle; - } - else - { - progressBar.CurrentTime = 0; - progressBar.EndTime = 1; - playButton.Icon = FontAwesome.Regular.PlayCircle; - } + if (!beatmap.Disabled) + current?.Track.Seek(position); + }); } - private void togglePause() + /// + /// Toggle pause / play. + /// + public void TogglePause() { var track = current?.Track; @@ -301,46 +107,57 @@ namespace osu.Game.Overlays } } - private void prev() + /// + /// Play the previous track. + /// + public void PrevTrack() { - queuedDirection = TransformDirection.Prev; + queuedDirection = TrackChangeDirection.Prev; var playable = beatmapSets.TakeWhile(i => i.ID != current.BeatmapSetInfo.ID).LastOrDefault() ?? beatmapSets.LastOrDefault(); if (playable != null) { - beatmap.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); + if (beatmap is Bindable working) + working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); beatmap.Value.Track.Restart(); } } + /// + /// Play the next random or playlist track. + /// + public void NextTrack() => next(); + private void next(bool instant = false) { if (!instant) - queuedDirection = TransformDirection.Next; + queuedDirection = TrackChangeDirection.Next; var playable = beatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).Skip(1).FirstOrDefault() ?? beatmapSets.FirstOrDefault(); if (playable != null) { - beatmap.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); + if (beatmap is Bindable working) + working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); beatmap.Value.Track.Restart(); } } private WorkingBeatmap current; - private TransformDirection? queuedDirection; + + private TrackChangeDirection? queuedDirection; private void beatmapChanged(ValueChangedEvent beatmap) { - TransformDirection direction = TransformDirection.None; + TrackChangeDirection direction = TrackChangeDirection.None; if (current != null) { bool audioEquals = beatmap.NewValue?.BeatmapInfo?.AudioEquals(current.BeatmapInfo) ?? false; if (audioEquals) - direction = TransformDirection.None; + direction = TrackChangeDirection.None; else if (queuedDirection.HasValue) { direction = queuedDirection.Value; @@ -352,13 +169,13 @@ namespace osu.Game.Overlays var last = beatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count(); var next = beatmap.NewValue == null ? -1 : beatmapSets.TakeWhile(b => b.ID != beatmap.NewValue.BeatmapSetInfo?.ID).Count(); - direction = last > next ? TransformDirection.Prev : TransformDirection.Next; + direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next; } } - progressBar.CurrentTime = 0; + current = beatmap.NewValue; + TrackChanged?.Invoke(current, direction); - updateDisplay(current = beatmap.NewValue, direction); updateAudioAdjustments(); queuedDirection = null; @@ -376,167 +193,19 @@ namespace osu.Game.Overlays mod.ApplyToClock(track); } - private Action pendingBeatmapSwitch; - - private void updateDisplay(WorkingBeatmap beatmap, TransformDirection direction) + protected override void Dispose(bool isDisposing) { - // avoid using scheduler as our scheduler may not be run for a long time, holding references to beatmaps. - pendingBeatmapSwitch = delegate - { - // todo: this can likely be replaced with WorkingBeatmap.GetBeatmapAsync() - Task.Run(() => - { - if (beatmap?.Beatmap == null) //this is not needed if a placeholder exists - { - title.Text = @"Nothing to play"; - artist.Text = @"Nothing to play"; - } - else - { - BeatmapMetadata metadata = beatmap.Metadata; - title.Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)); - artist.Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist)); - } - }); + base.Dispose(isDisposing); - LoadComponentAsync(new Background(beatmap) { Depth = float.MaxValue }, newBackground => - { - switch (direction) - { - case TransformDirection.Next: - newBackground.Position = new Vector2(400, 0); - newBackground.MoveToX(0, 500, Easing.OutCubic); - background.MoveToX(-400, 500, Easing.OutCubic); - break; - - case TransformDirection.Prev: - newBackground.Position = new Vector2(-400, 0); - newBackground.MoveToX(0, 500, Easing.OutCubic); - background.MoveToX(400, 500, Easing.OutCubic); - break; - } - - background.Expire(); - background = newBackground; - - playerContainer.Add(newBackground); - }); - }; + beatmaps.ItemAdded -= handleBeatmapAdded; + beatmaps.ItemRemoved -= handleBeatmapRemoved; } + } - protected override void PopIn() - { - base.PopIn(); - - this.FadeIn(transition_length, Easing.OutQuint); - dragContainer.ScaleTo(1, transition_length, Easing.OutElastic); - } - - protected override void PopOut() - { - base.PopOut(); - - this.FadeOut(transition_length, Easing.OutQuint); - dragContainer.ScaleTo(0.9f, transition_length, Easing.OutQuint); - } - - private enum TransformDirection - { - None, - Next, - Prev - } - - private class MusicIconButton : IconButton - { - public MusicIconButton() - { - AutoSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - HoverColour = colours.YellowDark.Opacity(0.6f); - FlashColour = colours.Yellow; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - // works with AutoSizeAxes above to make buttons autosize with the scale animation. - Content.AutoSizeAxes = Axes.None; - Content.Size = new Vector2(DEFAULT_BUTTON_SIZE); - } - } - - private class Background : BufferedContainer - { - private readonly Sprite sprite; - private readonly WorkingBeatmap beatmap; - - public Background(WorkingBeatmap beatmap = null) - { - this.beatmap = beatmap; - CacheDrawnFrameBuffer = true; - Depth = float.MaxValue; - RelativeSizeAxes = Axes.Both; - - Children = new Drawable[] - { - sprite = new Sprite - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(150), - FillMode = FillMode.Fill, - }, - new Box - { - RelativeSizeAxes = Axes.X, - Height = bottom_black_area_height, - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - Colour = Color4.Black.Opacity(0.5f) - } - }; - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg4"); - } - } - - private class DragContainer : Container - { - protected override bool OnDragStart(DragStartEvent e) - { - return true; - } - - protected override bool OnDrag(DragEvent e) - { - Vector2 change = e.MousePosition - e.MouseDownPosition; - - // Diminish the drag distance as we go further to simulate "rubber band" feeling. - change *= change.Length <= 0 ? 0 : (float)Math.Pow(change.Length, 0.7f) / change.Length; - - this.MoveTo(change); - return true; - } - - protected override bool OnDragEnd(DragEndEvent e) - { - this.MoveTo(Vector2.Zero, 800, Easing.OutElastic); - return base.OnDragEnd(e); - } - } - - /// - /// Play the next random or playlist track. - /// - public void NextTrack() => next(); + public enum TrackChangeDirection + { + None, + Next, + Prev } } diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs new file mode 100644 index 0000000000..98bad5323d --- /dev/null +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -0,0 +1,403 @@ +// 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.Threading.Tasks; +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.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input.Events; +using osu.Framework.Localisation; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Music; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays +{ + public class NowPlayingOverlay : OsuFocusedOverlayContainer + { + private const float player_height = 130; + private const float transition_length = 800; + private const float progress_height = 10; + private const float bottom_black_area_height = 55; + + private Drawable background; + private ProgressBar progressBar; + + private IconButton prevButton; + private IconButton playButton; + private IconButton nextButton; + private IconButton playlistButton; + + private SpriteText title, artist; + + private PlaylistOverlay playlist; + + private Container dragContainer; + private Container playerContainer; + + /// + /// Provide a source for the toolbar height. + /// + public Func GetToolbarHeight; + + [Resolved] + private MusicController musicController { get; set; } + + [Resolved] + private Bindable beatmap { get; set; } + + public NowPlayingOverlay() + { + Width = 400; + Margin = new MarginPadding(10); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Children = new Drawable[] + { + dragContainer = new DragContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + playlist = new PlaylistOverlay + { + RelativeSizeAxes = Axes.X, + Y = player_height + 10, + OrderChanged = musicController.ChangeBeatmapSetPosition + }, + playerContainer = new Container + { + RelativeSizeAxes = Axes.X, + Height = player_height, + Masking = true, + CornerRadius = 5, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(40), + Radius = 5, + }, + Children = new[] + { + background = new Background(), + title = new OsuSpriteText + { + Origin = Anchor.BottomCentre, + Anchor = Anchor.TopCentre, + Position = new Vector2(0, 40), + Font = OsuFont.GetFont(size: 25, italics: true), + Colour = Color4.White, + Text = @"Nothing to play", + }, + artist = new OsuSpriteText + { + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Position = new Vector2(0, 45), + Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold, italics: true), + Colour = Color4.White, + Text = @"Nothing to play", + }, + new Container + { + Padding = new MarginPadding { Bottom = progress_height }, + Height = bottom_black_area_height, + RelativeSizeAxes = Axes.X, + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Children = new[] + { + prevButton = new MusicIconButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Action = musicController.PrevTrack, + Icon = FontAwesome.Solid.StepBackward, + }, + playButton = new MusicIconButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(1.4f), + IconScale = new Vector2(1.4f), + Action = musicController.TogglePause, + Icon = FontAwesome.Regular.PlayCircle, + }, + nextButton = new MusicIconButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Action = musicController.NextTrack, + Icon = FontAwesome.Solid.StepForward, + }, + } + }, + playlistButton = new MusicIconButton + { + Origin = Anchor.Centre, + Anchor = Anchor.CentreRight, + Position = new Vector2(-bottom_black_area_height / 2, 0), + Icon = FontAwesome.Solid.Bars, + Action = () => playlist.ToggleVisibility(), + }, + } + }, + progressBar = new ProgressBar + { + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + Height = progress_height, + FillColour = colours.Yellow, + OnSeek = musicController.SeekTo + } + }, + }, + } + } + }; + + playlist.State.ValueChanged += s => playlistButton.FadeColour(s.NewValue == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + beatmap.BindDisabledChanged(beatmapDisabledChanged, true); + + musicController.TrackChanged += trackChanged; + trackChanged(beatmap.Value); + } + + protected override void PopIn() + { + base.PopIn(); + + this.FadeIn(transition_length, Easing.OutQuint); + dragContainer.ScaleTo(1, transition_length, Easing.OutElastic); + } + + protected override void PopOut() + { + base.PopOut(); + + this.FadeOut(transition_length, Easing.OutQuint); + dragContainer.ScaleTo(0.9f, transition_length, Easing.OutQuint); + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + Height = dragContainer.Height; + dragContainer.Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; + } + + protected override void Update() + { + base.Update(); + + if (pendingBeatmapSwitch != null) + { + pendingBeatmapSwitch(); + pendingBeatmapSwitch = null; + } + + var track = beatmap.Value?.TrackLoaded ?? false ? beatmap.Value.Track : null; + + if (track?.IsDummyDevice == false) + { + progressBar.EndTime = track.Length; + progressBar.CurrentTime = track.CurrentTime; + + playButton.Icon = track.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle; + } + else + { + progressBar.CurrentTime = 0; + progressBar.EndTime = 1; + playButton.Icon = FontAwesome.Regular.PlayCircle; + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + musicController.TrackChanged -= trackChanged; + } + + private Action pendingBeatmapSwitch; + + private void trackChanged(WorkingBeatmap beatmap, TrackChangeDirection direction = TrackChangeDirection.None) + { + // avoid using scheduler as our scheduler may not be run for a long time, holding references to beatmaps. + pendingBeatmapSwitch = delegate + { + // todo: this can likely be replaced with WorkingBeatmap.GetBeatmapAsync() + Task.Run(() => + { + if (beatmap?.Beatmap == null) //this is not needed if a placeholder exists + { + title.Text = @"Nothing to play"; + artist.Text = @"Nothing to play"; + } + else + { + BeatmapMetadata metadata = beatmap.Metadata; + title.Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)); + artist.Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist)); + } + }); + + LoadComponentAsync(new Background(beatmap) { Depth = float.MaxValue }, newBackground => + { + switch (direction) + { + case TrackChangeDirection.Next: + newBackground.Position = new Vector2(400, 0); + newBackground.MoveToX(0, 500, Easing.OutCubic); + background.MoveToX(-400, 500, Easing.OutCubic); + break; + + case TrackChangeDirection.Prev: + newBackground.Position = new Vector2(-400, 0); + newBackground.MoveToX(0, 500, Easing.OutCubic); + background.MoveToX(400, 500, Easing.OutCubic); + break; + } + + background.Expire(); + background = newBackground; + + playerContainer.Add(newBackground); + }); + }; + } + + private void beatmapDisabledChanged(bool disabled) + { + if (disabled) + playlist.Hide(); + + playButton.Enabled.Value = !disabled; + prevButton.Enabled.Value = !disabled; + nextButton.Enabled.Value = !disabled; + playlistButton.Enabled.Value = !disabled; + } + + private class MusicIconButton : IconButton + { + public MusicIconButton() + { + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + HoverColour = colours.YellowDark.Opacity(0.6f); + FlashColour = colours.Yellow; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // works with AutoSizeAxes above to make buttons autosize with the scale animation. + Content.AutoSizeAxes = Axes.None; + Content.Size = new Vector2(DEFAULT_BUTTON_SIZE); + } + } + + private class Background : BufferedContainer + { + private readonly Sprite sprite; + private readonly WorkingBeatmap beatmap; + + public Background(WorkingBeatmap beatmap = null) + { + this.beatmap = beatmap; + CacheDrawnFrameBuffer = true; + Depth = float.MaxValue; + RelativeSizeAxes = Axes.Both; + + Children = new Drawable[] + { + sprite = new Sprite + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(150), + FillMode = FillMode.Fill, + }, + new Box + { + RelativeSizeAxes = Axes.X, + Height = bottom_black_area_height, + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + Colour = Color4.Black.Opacity(0.5f) + } + }; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg4"); + } + } + + private class DragContainer : Container + { + protected override bool OnDragStart(DragStartEvent e) + { + return true; + } + + protected override bool OnDrag(DragEvent e) + { + Vector2 change = e.MousePosition - e.MouseDownPosition; + + // Diminish the drag distance as we go further to simulate "rubber band" feeling. + change *= change.Length <= 0 ? 0 : (float)Math.Pow(change.Length, 0.7f) / change.Length; + + this.MoveTo(change); + return true; + } + + protected override bool OnDragEnd(DragEndEvent e) + { + this.MoveTo(Vector2.Zero, 800, Easing.OutElastic); + return base.OnDragEnd(e); + } + } + } +} diff --git a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs index f03df2ed93..b29aec5842 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs @@ -14,7 +14,7 @@ namespace osu.Game.Overlays.Toolbar } [BackgroundDependencyLoader(true)] - private void load(MusicController music) + private void load(NowPlayingOverlay music) { StateContainer = music; } From 9aac5efa4e5bf8ff6374731b815ec5e26a8a75d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Aug 2019 14:38:49 +0900 Subject: [PATCH 0677/2815] Move logic out of OsuGame --- osu.Game/OsuGame.cs | 25 +------ osu.Game/Overlays/MusicController.cs | 74 +++++++++++++++++-- osu.Game/Overlays/NowPlayingOverlay.cs | 6 +- osu.Game/Overlays/OSD/MusicControllerToast.cs | 13 ---- 4 files changed, 70 insertions(+), 48 deletions(-) delete mode 100644 osu.Game/Overlays/OSD/MusicControllerToast.cs diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ab7e5b19d1..0e804ecbaf 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -68,8 +68,6 @@ namespace osu.Game private BeatmapSetOverlay beatmapSetOverlay; - private OnScreenDisplay osd; - [Cached] private readonly ScreenshotManager screenshotManager = new ScreenshotManager(); @@ -469,7 +467,7 @@ namespace osu.Game }); loadComponentSingleFile(volume = new VolumeOverlay(), leftFloatingOverlayContent.Add); - loadComponentSingleFile(osd = new OnScreenDisplay(), Add, true); + loadComponentSingleFile(new OnScreenDisplay(), Add, true); loadComponentSingleFile(musicController = new MusicController(), Add, true); @@ -734,27 +732,6 @@ namespace osu.Game case GlobalAction.ToggleGameplayMouseButtons: LocalConfig.Set(OsuSetting.MouseDisableButtons, !LocalConfig.Get(OsuSetting.MouseDisableButtons)); return true; - - case GlobalAction.MusicPlay: - if (!musicController.IsLoaded) return true; - - if (musicController.PlayTrack()) - osd.Display(new Overlays.OSD.MusicControllerToast(musicController.IsPlaying ? "Play track" : "Pause track")); - return true; - - case GlobalAction.MusicNext: - if (!musicController.IsLoaded) return true; - - if (musicController.NextTrack()) - osd.Display(new Overlays.OSD.MusicControllerToast("Next track")); - return true; - - case GlobalAction.MusicPrev: - if (!musicController.IsLoaded) return true; - - if (musicController.PreviousTrack()) - osd.Display(new Overlays.OSD.MusicControllerToast("Previous track")); - return true; } return false; diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index d1086d589d..14f7b574da 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -7,8 +7,11 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Input.Bindings; using osu.Framework.Threading; using osu.Game.Beatmaps; +using osu.Game.Input.Bindings; +using osu.Game.Overlays.OSD; using osu.Game.Rulesets.Mods; namespace osu.Game.Overlays @@ -16,7 +19,7 @@ namespace osu.Game.Overlays /// /// Handles playback of the global music track. /// - public class MusicController : Component + public class MusicController : Component, IKeyBindingHandler { [Resolved] private BeatmapManager beatmaps { get; set; } @@ -37,6 +40,9 @@ namespace osu.Game.Overlays [Resolved] private IBindable> mods { get; set; } + [Resolved(canBeNull: true)] + private OnScreenDisplay onScreenDisplay { get; set; } + [BackgroundDependencyLoader] private void load() { @@ -58,11 +64,17 @@ namespace osu.Game.Overlays /// The beatmap to move. /// The new position. public void ChangeBeatmapSetPosition(BeatmapSetInfo beatmapSetInfo, int index) + { beatmapSets.Remove(beatmapSetInfo); beatmapSets.Insert(index, beatmapSetInfo); } + /// + /// Returns whether the current beatmap track is playing. + /// + public bool IsPlaying => beatmap.Value.Track.IsRunning; + private void handleBeatmapAdded(BeatmapSetInfo set) => Schedule(() => beatmapSets.Add(set)); @@ -84,15 +96,17 @@ namespace osu.Game.Overlays /// /// Toggle pause / play. /// - public void TogglePause() + public bool TogglePause() { var track = current?.Track; if (track == null) { - if (!beatmap.Disabled) - next(true); - return; + if (beatmap.Disabled) + return false; + + next(true); + return true; } if (track.IsRunning) @@ -105,12 +119,14 @@ namespace osu.Game.Overlays track.Start(); IsUserPaused = false; } + + return true; } /// /// Play the previous track. /// - public void PrevTrack() + public bool PrevTrack() { queuedDirection = TrackChangeDirection.Prev; @@ -121,15 +137,19 @@ namespace osu.Game.Overlays if (beatmap is Bindable working) working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); beatmap.Value.Track.Restart(); + + return true; } + + return false; } /// /// Play the next random or playlist track. /// - public void NextTrack() => next(); + public bool NextTrack() => next(); - private void next(bool instant = false) + private bool next(bool instant = false) { if (!instant) queuedDirection = TrackChangeDirection.Next; @@ -141,7 +161,10 @@ namespace osu.Game.Overlays if (beatmap is Bindable working) working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); beatmap.Value.Track.Restart(); + return true; } + + return false; } private WorkingBeatmap current; @@ -200,6 +223,41 @@ namespace osu.Game.Overlays beatmaps.ItemAdded -= handleBeatmapAdded; beatmaps.ItemRemoved -= handleBeatmapRemoved; } + + public bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.MusicPlay: + if (TogglePause()) + onScreenDisplay?.Display(new MusicControllerToast(IsPlaying ? "Play track" : "Pause track")); + return true; + + case GlobalAction.MusicNext: + if (NextTrack()) + onScreenDisplay?.Display(new MusicControllerToast("Next track")); + + return true; + + case GlobalAction.MusicPrev: + if (PrevTrack()) + onScreenDisplay?.Display(new MusicControllerToast("Previous track")); + + return true; + } + + return false; + } + + public bool OnReleased(GlobalAction action) => false; + + public class MusicControllerToast : Toast + { + public MusicControllerToast(string action) + : base("Music Playback", action, string.Empty) + { + } + } } public enum TrackChangeDirection diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 98bad5323d..f14adcb53d 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -138,7 +138,7 @@ namespace osu.Game.Overlays { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Action = musicController.PrevTrack, + Action = () => musicController.PrevTrack(), Icon = FontAwesome.Solid.StepBackward, }, playButton = new MusicIconButton @@ -147,14 +147,14 @@ namespace osu.Game.Overlays Origin = Anchor.Centre, Scale = new Vector2(1.4f), IconScale = new Vector2(1.4f), - Action = musicController.TogglePause, + Action = () => musicController.TogglePause(), Icon = FontAwesome.Regular.PlayCircle, }, nextButton = new MusicIconButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Action = musicController.NextTrack, + Action = () => musicController.NextTrack(), Icon = FontAwesome.Solid.StepForward, }, } diff --git a/osu.Game/Overlays/OSD/MusicControllerToast.cs b/osu.Game/Overlays/OSD/MusicControllerToast.cs deleted file mode 100644 index d9e0ad2c07..0000000000 --- a/osu.Game/Overlays/OSD/MusicControllerToast.cs +++ /dev/null @@ -1,13 +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.Overlays.OSD -{ - public class MusicControllerToast : Toast - { - public MusicControllerToast(string value) - : base("Music Playback", value, "") - { - } - } -} From de1ab56a2c28e408879d273fde84c509d91c31cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Aug 2019 14:45:27 +0900 Subject: [PATCH 0678/2815] Fix potential nullref on disposal --- osu.Game/Overlays/MusicController.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index d1086d589d..681e318c92 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -197,8 +197,11 @@ namespace osu.Game.Overlays { base.Dispose(isDisposing); - beatmaps.ItemAdded -= handleBeatmapAdded; - beatmaps.ItemRemoved -= handleBeatmapRemoved; + if (beatmaps != null) + { + beatmaps.ItemAdded -= handleBeatmapAdded; + beatmaps.ItemRemoved -= handleBeatmapRemoved; + } } } From 2cbdf8c01c3044a028a876e6fd9753996b4b34fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Aug 2019 14:46:57 +0900 Subject: [PATCH 0679/2815] Update public methods in line with future usage --- osu.Game/Overlays/MusicController.cs | 35 ++++++++++++++++++++------ osu.Game/Overlays/NowPlayingOverlay.cs | 6 ++--- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 681e318c92..da9c34238e 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.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; @@ -63,6 +63,11 @@ namespace osu.Game.Overlays beatmapSets.Insert(index, beatmapSetInfo); } + /// + /// Returns whether the current beatmap track is playing. + /// + public bool IsPlaying => beatmap.Value.Track.IsRunning; + private void handleBeatmapAdded(BeatmapSetInfo set) => Schedule(() => beatmapSets.Add(set)); @@ -84,15 +89,18 @@ namespace osu.Game.Overlays /// /// Toggle pause / play. /// - public void TogglePause() + /// Whether the operation was successful. + public bool TogglePause() { var track = current?.Track; if (track == null) { - if (!beatmap.Disabled) - next(true); - return; + if (beatmap.Disabled) + return false; + + next(true); + return true; } if (track.IsRunning) @@ -105,12 +113,15 @@ namespace osu.Game.Overlays track.Start(); IsUserPaused = false; } + + return true; } /// /// Play the previous track. /// - public void PrevTrack() + /// Whether the operation was successful. + public bool PrevTrack() { queuedDirection = TrackChangeDirection.Prev; @@ -121,15 +132,20 @@ namespace osu.Game.Overlays if (beatmap is Bindable working) working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); beatmap.Value.Track.Restart(); + + return true; } + + return false; } /// /// Play the next random or playlist track. /// - public void NextTrack() => next(); + /// Whether the operation was successful. + public bool NextTrack() => next(); - private void next(bool instant = false) + private bool next(bool instant = false) { if (!instant) queuedDirection = TrackChangeDirection.Next; @@ -141,7 +157,10 @@ namespace osu.Game.Overlays if (beatmap is Bindable working) working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); beatmap.Value.Track.Restart(); + return true; } + + return false; } private WorkingBeatmap current; diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 98bad5323d..f14adcb53d 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -138,7 +138,7 @@ namespace osu.Game.Overlays { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Action = musicController.PrevTrack, + Action = () => musicController.PrevTrack(), Icon = FontAwesome.Solid.StepBackward, }, playButton = new MusicIconButton @@ -147,14 +147,14 @@ namespace osu.Game.Overlays Origin = Anchor.Centre, Scale = new Vector2(1.4f), IconScale = new Vector2(1.4f), - Action = musicController.TogglePause, + Action = () => musicController.TogglePause(), Icon = FontAwesome.Regular.PlayCircle, }, nextButton = new MusicIconButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Action = musicController.NextTrack, + Action = () => musicController.NextTrack(), Icon = FontAwesome.Solid.StepForward, }, } From 81f8b5f325702b5d1ae227006b9c26b7d2dd2834 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Aug 2019 14:52:51 +0900 Subject: [PATCH 0680/2815] Fix merge issue --- osu.Game/Overlays/MusicController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index b055c7ef1b..91220907a0 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -64,7 +64,6 @@ namespace osu.Game.Overlays /// The beatmap to move. /// The new position. public void ChangeBeatmapSetPosition(BeatmapSetInfo beatmapSetInfo, int index) - { beatmapSets.Remove(beatmapSetInfo); beatmapSets.Insert(index, beatmapSetInfo); From b942192e004c95891e02048d007077836c51f9b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Aug 2019 15:04:04 +0900 Subject: [PATCH 0681/2815] Fix remaining nullref --- osu.Game/Overlays/NowPlayingOverlay.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index f14adcb53d..a3243a655e 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -247,13 +247,6 @@ namespace osu.Game.Overlays } } - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - musicController.TrackChanged -= trackChanged; - } - private Action pendingBeatmapSwitch; private void trackChanged(WorkingBeatmap beatmap, TrackChangeDirection direction = TrackChangeDirection.None) @@ -313,6 +306,14 @@ namespace osu.Game.Overlays playlistButton.Enabled.Value = !disabled; } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (musicController != null) + musicController.TrackChanged -= trackChanged; + } + private class MusicIconButton : IconButton { public MusicIconButton() From 8ef4b2a0f6a1467c0e8fd9dec63e35d63d1df942 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 13 Aug 2019 11:29:48 +0300 Subject: [PATCH 0682/2815] Hide NotificationOverlay --- osu.Desktop/Overlays/VersionManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs index 1f1d2cea5f..51e801c185 100644 --- a/osu.Desktop/Overlays/VersionManager.cs +++ b/osu.Desktop/Overlays/VersionManager.cs @@ -113,13 +113,14 @@ namespace osu.Desktop.Overlays } [BackgroundDependencyLoader] - private void load(OsuColour colours, ChangelogOverlay changelog) + private void load(OsuColour colours, ChangelogOverlay changelog, NotificationOverlay notificationOverlay) { Icon = FontAwesome.Solid.CheckSquare; IconBackgound.Colour = colours.BlueDark; Activated = delegate { + notificationOverlay.Hide(); changelog.ShowBuild(OsuGameBase.CLIENT_STREAM_NAME, version); return true; }; From 6b57c9801d8e9a97d5d520f4e0705530190bd216 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 13 Aug 2019 17:32:43 +0900 Subject: [PATCH 0683/2815] Cull some unnecessary whitespace --- osu.Game/Screens/Multi/Match/MatchSubScreen.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index dafea70092..ea7a713e0e 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -177,9 +177,7 @@ namespace osu.Game.Screens.Multi.Match public override bool OnExiting(IScreen next) { RoomManager?.PartRoom(); - Mods.Value = Array.Empty(); - previewTrackManager.StopAnyPlaying(this); return base.OnExiting(next); From 92c2dafa1281048437cf7651caf61643b5b302d7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 13 Aug 2019 17:34:16 +0900 Subject: [PATCH 0684/2815] Tighten accessibility --- osu.Game/Screens/Multi/Match/Components/Header.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Multi/Match/Components/Header.cs b/osu.Game/Screens/Multi/Match/Components/Header.cs index 7f2b1278b7..a52d43acf4 100644 --- a/osu.Game/Screens/Multi/Match/Components/Header.cs +++ b/osu.Game/Screens/Multi/Match/Components/Header.cs @@ -25,12 +25,12 @@ namespace osu.Game.Screens.Multi.Match.Components { public const float HEIGHT = 200; - public MatchTabControl Tabs; + public readonly BindableBool ShowBeatmapPanel = new BindableBool(); + + public MatchTabControl Tabs { get; private set; } public Action RequestBeatmapSelection; - public BindableBool ShowBeatmapPanel = new BindableBool(); - private MatchBeatmapPanel beatmapPanel; public Header() From 7c9c9f1ce1f062f667ec38e6d2c300332cbfe857 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 13 Aug 2019 17:38:21 +0900 Subject: [PATCH 0685/2815] Simplify caching --- .../Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs | 8 +------- osu.Game/Screens/Multi/Match/MatchSubScreen.cs | 8 +------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs index f148170847..68ad0b42b4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs @@ -13,6 +13,7 @@ using osu.Framework.Allocation; namespace osu.Game.Tests.Visual.Multiplayer { + [Cached(typeof(IPreviewTrackOwner))] public class TestSceneMatchBeatmapPanel : MultiplayerTestScene, IPreviewTrackOwner { public override IReadOnlyList RequiredTypes => new[] @@ -48,12 +49,5 @@ namespace osu.Game.Tests.Visual.Multiplayer previewTrackManager.StopAnyPlaying(this); }); } - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.CacheAs(this); - return dependencies; - } } } diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index ea7a713e0e..f3e10db444 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -19,6 +19,7 @@ using PlaylistItem = osu.Game.Online.Multiplayer.PlaylistItem; namespace osu.Game.Screens.Multi.Match { + [Cached(typeof(IPreviewTrackOwner))] public class MatchSubScreen : MultiplayerSubScreen, IPreviewTrackOwner { public override bool DisallowExternalBeatmapRulesetChanges => true; @@ -237,13 +238,6 @@ namespace osu.Game.Screens.Multi.Match } } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.CacheAs(this); - return dependencies; - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From f2e596de1640c4939ada0d6c18d2b71e89295679 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 13 Aug 2019 08:54:17 +0000 Subject: [PATCH 0686/2815] Bump ppy.osu.Framework from 2019.809.0 to 2019.813.0 Bumps [ppy.osu.Framework](https://github.com/ppy/osu-framework) from 2019.809.0 to 2019.813.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.809.0...2019.813.0) 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 b5266fd75d..e149c4338d 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -15,7 +15,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 103d89cadc..2bb95d0772 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,7 +105,7 @@ - + From e356cc8d9ef288056745f4b0b1899c0503284ee5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 13 Aug 2019 17:57:16 +0900 Subject: [PATCH 0687/2815] Refactor MatchBeatmapPanel for thread safety --- .../Match/Components/MatchBeatmapPanel.cs | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs b/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs index 7939b18e97..7c1fe91393 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.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.Threading; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Direct; @@ -18,6 +20,7 @@ namespace osu.Game.Screens.Multi.Match.Components [Resolved] private RulesetStore rulesets { get; set; } + private CancellationTokenSource loadCancellation; private GetBeatmapSetRequest request; private DirectGridPanel panel; @@ -29,30 +32,31 @@ namespace osu.Game.Screens.Multi.Match.Components [BackgroundDependencyLoader] private void load() { - CurrentItem.BindValueChanged(item => + CurrentItem.BindValueChanged(item => loadNewPanel(item.NewValue?.Beatmap), true); + } + + private void loadNewPanel(BeatmapInfo beatmap) + { + loadCancellation?.Cancel(); + request?.Cancel(); + + panel?.FadeOut(200); + panel?.Expire(); + panel = null; + + if (beatmap?.OnlineBeatmapID == null) + return; + + loadCancellation = new CancellationTokenSource(); + + request = new GetBeatmapSetRequest(beatmap.OnlineBeatmapID.Value, BeatmapSetLookupType.BeatmapId); + request.Success += res => Schedule(() => { - request?.Cancel(); + panel = new DirectGridPanel(res.ToBeatmapSet(rulesets)); + LoadComponentAsync(panel, AddInternal, loadCancellation.Token); + }); - if (panel != null) - { - panel.FadeOut(200); - panel.Expire(); - panel = null; - } - - var onlineId = item.NewValue?.Beatmap.OnlineBeatmapID; - - if (onlineId.HasValue) - { - request = new GetBeatmapSetRequest(onlineId.Value, BeatmapSetLookupType.BeatmapId); - request.Success += beatmap => - { - panel = new DirectGridPanel(beatmap.ToBeatmapSet(rulesets)); - LoadComponentAsync(panel, AddInternal); - }; - api.Queue(request); - } - }, true); + api.Queue(request); } } } From fe7bc824df6b2eda0b52e48517b37535363f4537 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 13 Aug 2019 09:00:20 +0000 Subject: [PATCH 0688/2815] Bump ppy.osu.Framework.NativeLibs from 2019.307.0 to 2019.813.0 Bumps [ppy.osu.Framework.NativeLibs](https://github.com/ppy/osu-framework) from 2019.307.0 to 2019.813.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.307.0...2019.813.0) Signed-off-by: dependabot-preview[bot] --- osu.iOS.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.iOS.props b/osu.iOS.props index 103d89cadc..09b9da142c 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -111,6 +111,6 @@ - + From 6b3eeb88fc9dac58b70f60991aceb147aab67895 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 13 Aug 2019 09:00:43 +0000 Subject: [PATCH 0689/2815] Bump ppy.osu.Framework.iOS from 2019.809.0 to 2019.813.0 Bumps [ppy.osu.Framework.iOS](https://github.com/ppy/osu-framework) from 2019.809.0 to 2019.813.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.809.0...2019.813.0) Signed-off-by: dependabot-preview[bot] --- osu.iOS.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.iOS.props b/osu.iOS.props index 103d89cadc..d444a5893f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -106,7 +106,7 @@ - + From 91068dc5709585c1c4175acd45fca05f581ff583 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 13 Aug 2019 09:01:00 +0000 Subject: [PATCH 0690/2815] Bump ppy.osu.Framework.Android from 2019.809.0 to 2019.813.0 Bumps [ppy.osu.Framework.Android](https://github.com/ppy/osu-framework) from 2019.809.0 to 2019.813.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.809.0...2019.813.0) Signed-off-by: dependabot-preview[bot] --- osu.Android.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Android.props b/osu.Android.props index 721d341c08..85741fcf84 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -63,6 +63,6 @@ - + From 5963f7d9147e7ead009611f4b90b27e67506433b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Aug 2019 19:52:40 +0900 Subject: [PATCH 0691/2815] Update comment --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 8073200c47..b70072a222 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -127,7 +127,7 @@ namespace osu.Game.Input.Bindings [Description("Quick exit (Hold)")] QuickExit, - // Game-wide beatmap jukebox keybindings + // Game-wide beatmap msi ccotolle keybindings [Description("Next track")] MusicNext, From 2cd0015735fa28996b2c374a3d632e76a019c503 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Aug 2019 23:27:27 +0900 Subject: [PATCH 0692/2815] Attempt to appease apple with some plist keys --- osu.iOS/Info.plist | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.iOS/Info.plist b/osu.iOS/Info.plist index 0775d1522d..a118b329aa 100644 --- a/osu.iOS/Info.plist +++ b/osu.iOS/Info.plist @@ -31,6 +31,10 @@ UIStatusBarHidden + NSCameraUsageDescription + We don't really use the camera. + NSMicrophoneUsageDescription + We don't really use the microphone. UISupportedInterfaceOrientations UIInterfaceOrientationPortrait From b6bc84af2c886f964c5aafada6cd82c2065a3bee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Aug 2019 10:52:26 +0900 Subject: [PATCH 0693/2815] Fix chat context menus displaying out-of-bounds --- osu.Game/Overlays/Chat/DrawableChannel.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 8d56e250fc..928201581a 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -32,16 +32,16 @@ namespace osu.Game.Overlays.Chat { Children = new Drawable[] { - scroll = new OsuScrollContainer + new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, - // Some chat lines have effects that slightly protrude to the bottom, - // which we do not want to mask away, hence the padding. - Padding = new MarginPadding { Bottom = 5 }, - Child = new OsuContextMenuContainer + Masking = true, + Child = scroll = new OsuScrollContainer { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.Both, + // Some chat lines have effects that slightly protrude to the bottom, + // which we do not want to mask away, hence the padding. + Padding = new MarginPadding { Bottom = 5 }, Child = ChatLineFlow = new ChatLineContainer { Padding = new MarginPadding { Left = 20, Right = 20 }, From 52f42ddadeed12febc7e23897bd243b42448d982 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Aug 2019 10:53:47 +0900 Subject: [PATCH 0694/2815] Use child instead of children --- osu.Game/Overlays/Chat/DrawableChannel.cs | 31 ++++++++++------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 928201581a..f831266b1b 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -30,27 +30,24 @@ namespace osu.Game.Overlays.Chat [BackgroundDependencyLoader] private void load() { - Children = new Drawable[] + Child = new OsuContextMenuContainer { - new OsuContextMenuContainer + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = scroll = new OsuScrollContainer { RelativeSizeAxes = Axes.Both, - Masking = true, - Child = scroll = new OsuScrollContainer + // Some chat lines have effects that slightly protrude to the bottom, + // which we do not want to mask away, hence the padding. + Padding = new MarginPadding { Bottom = 5 }, + Child = ChatLineFlow = new ChatLineContainer { - RelativeSizeAxes = Axes.Both, - // Some chat lines have effects that slightly protrude to the bottom, - // which we do not want to mask away, hence the padding. - Padding = new MarginPadding { Bottom = 5 }, - Child = ChatLineFlow = new ChatLineContainer - { - Padding = new MarginPadding { Left = 20, Right = 20 }, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - } - }, - } + Padding = new MarginPadding { Left = 20, Right = 20 }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + } + }, }; newMessagesArrived(Channel.Messages); From 480e489c44d28130edd5be8dca5d02763455cecb Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 14 Aug 2019 12:51:43 +0900 Subject: [PATCH 0695/2815] add back missing loaded check --- osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs index efb4f3e83a..515f4cdce6 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneScreenNavigation.cs @@ -145,7 +145,7 @@ namespace osu.Game.Tests.Visual.Menus confirmAtMainMenu(); } - private void confirmAtMainMenu() => AddUntilStep("Wait for main menu", () => osuGame.ScreenStack.CurrentScreen is MainMenu); + private void confirmAtMainMenu() => AddUntilStep("Wait for main menu", () => osuGame.ScreenStack.CurrentScreen is MainMenu menu && menu.IsLoaded); private void pressAndRelease(Key key) { From 3a79a4149a430e9f5dec5ab7a47bf425a9cd7cb0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Aug 2019 15:19:21 +0900 Subject: [PATCH 0696/2815] Disable music controls when beatmap is disabled --- osu.Game/Overlays/MusicController.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 91220907a0..f6208c46cb 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -231,6 +231,9 @@ namespace osu.Game.Overlays public bool OnPressed(GlobalAction action) { + if (beatmap.Disabled) + return false; + switch (action) { case GlobalAction.MusicPlay: From 840a142e98a592ef93d0d8ad1dfbf3796005a039 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 14 Aug 2019 12:16:32 +0000 Subject: [PATCH 0697/2815] Bump ppy.osu.Framework.iOS from 2019.813.0 to 2019.814.0 Bumps [ppy.osu.Framework.iOS](https://github.com/ppy/osu-framework) from 2019.813.0 to 2019.814.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.813.0...2019.814.0) Signed-off-by: dependabot-preview[bot] --- osu.iOS.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.iOS.props b/osu.iOS.props index 9ea5081658..d50f23c2c2 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -119,7 +119,7 @@ - + From e36e2a899ae1602dc303246eaae217abfd700421 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 14 Aug 2019 12:16:53 +0000 Subject: [PATCH 0698/2815] Bump ppy.osu.Framework.Android from 2019.813.0 to 2019.814.0 Bumps [ppy.osu.Framework.Android](https://github.com/ppy/osu-framework) from 2019.813.0 to 2019.814.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.813.0...2019.814.0) Signed-off-by: dependabot-preview[bot] --- osu.Android.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Android.props b/osu.Android.props index 065c66ebba..7bc60ef884 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -61,6 +61,6 @@ - + From 3379672a43a9f1f0536e85db3887b51653b12a22 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 14 Aug 2019 12:17:17 +0000 Subject: [PATCH 0699/2815] Bump ppy.osu.Framework from 2019.813.0 to 2019.814.0 Bumps [ppy.osu.Framework](https://github.com/ppy/osu-framework) from 2019.813.0 to 2019.814.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.813.0...2019.814.0) 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 e149c4338d..f5e4d4b1fb 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -15,7 +15,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 9ea5081658..e831fdc7cb 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,7 +118,7 @@ - + From f849b4ce54e4b8e75a8286192b4dfbae24f80e25 Mon Sep 17 00:00:00 2001 From: Desconocidosmh Date: Wed, 14 Aug 2019 19:49:32 +0200 Subject: [PATCH 0700/2815] Make autoplay unpress sooner, if needed --- .../Replays/TaikoAutoGenerator.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index 422ba748e3..60fd88882e 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -113,14 +113,21 @@ namespace osu.Game.Rulesets.Taiko.Replays else throw new InvalidOperationException("Unknown hit object type."); - Frames.Add(new TaikoReplayFrame(endTime + KEY_UP_DELAY)); - if (i < Beatmap.HitObjects.Count - 1) { - double waitTime = Beatmap.HitObjects[i + 1].StartTime - 1000; + var nextHitObject = Beatmap.HitObjects[i + 1]; + + if (!(nextHitObject.StartTime < endTime + KEY_UP_DELAY)) + Frames.Add(new TaikoReplayFrame(endTime + KEY_UP_DELAY)); + + double waitTime = nextHitObject.StartTime - 1000; if (waitTime > endTime) Frames.Add(new TaikoReplayFrame(waitTime)); } + else + { + Frames.Add(new TaikoReplayFrame(endTime + KEY_UP_DELAY)); + } hitButton = !hitButton; } From e567e81981d6f940a9de97d68a3bab496e6d29cc Mon Sep 17 00:00:00 2001 From: Desconocidosmh Date: Wed, 14 Aug 2019 20:10:52 +0200 Subject: [PATCH 0701/2815] Refactoring --- osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index 60fd88882e..014511d090 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Taiko.Replays { var nextHitObject = Beatmap.HitObjects[i + 1]; - if (!(nextHitObject.StartTime < endTime + KEY_UP_DELAY)) + if (nextHitObject.StartTime > endTime + KEY_UP_DELAY) Frames.Add(new TaikoReplayFrame(endTime + KEY_UP_DELAY)); double waitTime = nextHitObject.StartTime - 1000; From ba539abac77484c3dd5103333e84231a46d81cf2 Mon Sep 17 00:00:00 2001 From: Desconocidosmh Date: Wed, 14 Aug 2019 21:05:24 +0200 Subject: [PATCH 0702/2815] Refactoring --- .../Replays/TaikoAutoGenerator.cs | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index 014511d090..a46ecab6f0 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -113,21 +113,23 @@ namespace osu.Game.Rulesets.Taiko.Replays else throw new InvalidOperationException("Unknown hit object type."); + TaikoHitObject nextHitObject; if (i < Beatmap.HitObjects.Count - 1) + nextHitObject = Beatmap.HitObjects[i + 1]; + else + nextHitObject = null; + + bool canDelayKeyUp = nextHitObject != null && nextHitObject.StartTime > endTime + KEY_UP_DELAY; + + if (canDelayKeyUp) + Frames.Add(new TaikoReplayFrame(endTime + KEY_UP_DELAY)); + + if (nextHitObject != null) { - var nextHitObject = Beatmap.HitObjects[i + 1]; - - if (nextHitObject.StartTime > endTime + KEY_UP_DELAY) - Frames.Add(new TaikoReplayFrame(endTime + KEY_UP_DELAY)); - double waitTime = nextHitObject.StartTime - 1000; if (waitTime > endTime) Frames.Add(new TaikoReplayFrame(waitTime)); } - else - { - Frames.Add(new TaikoReplayFrame(endTime + KEY_UP_DELAY)); - } hitButton = !hitButton; } From 840d4741daf285b0c2541997f39b6fda309a492b Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 14 Aug 2019 20:24:36 +0200 Subject: [PATCH 0703/2815] Add NewsContent class and fix broken reference. --- osu.Game/Overlays/News/NewsContent.cs | 16 ++++++++++++++++ osu.Game/Overlays/News/NewsHeader.cs | 2 +- osu.Game/Overlays/NewsOverlay.cs | 10 +++++++++- 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Overlays/News/NewsContent.cs diff --git a/osu.Game/Overlays/News/NewsContent.cs b/osu.Game/Overlays/News/NewsContent.cs new file mode 100644 index 0000000000..f0763285eb --- /dev/null +++ b/osu.Game/Overlays/News/NewsContent.cs @@ -0,0 +1,16 @@ +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Overlays.News +{ + public abstract class NewsContent : FillFlowContainer + { + public NewsContent() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + Padding = new MarginPadding { Bottom = 100 }; + } + } +} diff --git a/osu.Game/Overlays/News/NewsHeader.cs b/osu.Game/Overlays/News/NewsHeader.cs index 6a14828473..27620ab523 100644 --- a/osu.Game/Overlays/News/NewsHeader.cs +++ b/osu.Game/Overlays/News/NewsHeader.cs @@ -97,7 +97,7 @@ namespace osu.Game.Overlays.News IsReadingArticle = false; } - protected override Drawable CreateIcon() => new ScreenTitleIcon(@"Icons/news"); + protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/news"); [BackgroundDependencyLoader] private void load(OsuColour colours) diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index b509204c58..b341321a46 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -15,6 +15,7 @@ namespace osu.Game.Overlays public class NewsOverlay : FullscreenOverlay { private NewsHeader header; + private Container content; public readonly Bindable Current = new Bindable(null); @@ -39,13 +40,20 @@ namespace osu.Game.Overlays Children = new Drawable[] { header = new NewsHeader() + { + ShowFrontPage = ShowFrontPage + }, + content = new Container() + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } }, }, }, }; header.Current.BindTo(Current); - header.ShowFrontPage = ShowFrontPage; Current.TriggerChange(); } From 46e71e9ead75227f63080ed4751e3f61228d1f40 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 14 Aug 2019 21:34:29 +0200 Subject: [PATCH 0704/2815] Add missing licence header --- osu.Game/Overlays/News/NewsContent.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/News/NewsContent.cs b/osu.Game/Overlays/News/NewsContent.cs index f0763285eb..26f92b3825 100644 --- a/osu.Game/Overlays/News/NewsContent.cs +++ b/osu.Game/Overlays/News/NewsContent.cs @@ -1,4 +1,7 @@ -using osu.Framework.Graphics; +// 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.Containers; namespace osu.Game.Overlays.News From 4d1b1a4022d530d5c7fff5ceec3d04ffca641543 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 14 Aug 2019 21:52:36 +0200 Subject: [PATCH 0705/2815] Fix CI inspections --- osu.Game/Overlays/News/NewsContent.cs | 2 +- osu.Game/Overlays/NewsOverlay.cs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/News/NewsContent.cs b/osu.Game/Overlays/News/NewsContent.cs index 26f92b3825..16a8ed84b8 100644 --- a/osu.Game/Overlays/News/NewsContent.cs +++ b/osu.Game/Overlays/News/NewsContent.cs @@ -8,7 +8,7 @@ namespace osu.Game.Overlays.News { public abstract class NewsContent : FillFlowContainer { - public NewsContent() + protected NewsContent() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index b341321a46..aadca8883e 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -15,6 +15,8 @@ namespace osu.Game.Overlays public class NewsOverlay : FullscreenOverlay { private NewsHeader header; + + //ReSharper disable NotAccessedField.Local private Container content; public readonly Bindable Current = new Bindable(null); @@ -39,11 +41,11 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - header = new NewsHeader() + header = new NewsHeader { ShowFrontPage = ShowFrontPage }, - content = new Container() + content = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, From 07e7e1187c980f9e2962c013203869d3b4c774bc Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 15 Aug 2019 05:30:35 +0300 Subject: [PATCH 0706/2815] Add adjustment function in SkinnableSound --- osu.Game/Skinning/SkinnableSound.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 8e2b5cec98..205f25bf9d 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -37,6 +37,8 @@ namespace osu.Game.Skinning public void Play() => channels?.ForEach(c => c.Play()); + public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => channels?.ForEach(c => c.AddAdjustment(type, adjustBindable)); + public override bool IsPresent => false; // We don't need to receive updates. protected override void SkinChanged(ISkinSource skin, bool allowFallback) From cfa569b226ab742f9527615852a64373a5fd7cb6 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 15 Aug 2019 05:35:47 +0300 Subject: [PATCH 0707/2815] Add looping field and fix build --- osu.Game/Skinning/SkinnableSound.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 205f25bf9d..f9dd39d04d 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Audio; @@ -35,6 +36,8 @@ namespace osu.Game.Skinning this.audio = audio; } + public bool Looping; + public void Play() => channels?.ForEach(c => c.Play()); public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => channels?.ForEach(c => c.AddAdjustment(type, adjustBindable)); @@ -60,6 +63,7 @@ namespace osu.Game.Skinning if (ch == null) continue; + ch.Looping = Looping; ch.Volume.Value = info.Volume / 100.0; return ch; } From f355cff8bcc9c048f793d2032ebb719be89a6358 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Aug 2019 12:11:54 +0900 Subject: [PATCH 0708/2815] Apply reviews --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 167 +++++++++--------- osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 2 +- osu.Game/Overlays/Direct/DirectPanel.cs | 2 +- .../Screens/Multi/Components/ModeTypeInfo.cs | 2 +- .../Carousel/DrawableCarouselBeatmap.cs | 2 +- .../Carousel/DrawableCarouselBeatmapSet.cs | 2 +- 6 files changed, 88 insertions(+), 89 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index f5c8d0d029..b29303ad1e 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -23,7 +23,7 @@ namespace osu.Game.Beatmaps.Drawables { private readonly RulesetInfo ruleset; - public DifficultyIcon(BeatmapInfo beatmap, RulesetInfo ruleset = null, Boolean shouldShowTooltip = false) + public DifficultyIcon(BeatmapInfo beatmap, RulesetInfo ruleset = null, bool shouldShowTooltip = true) : base(beatmap) { if (beatmap == null) @@ -39,89 +39,6 @@ namespace osu.Game.Beatmaps.Drawables public ITooltip GetCustomTooltip() => new DifficultyIconTooltip(AccentColour); - public class DifficultyIconTooltip : VisibilityContainer, ITooltip - { - private readonly OsuSpriteText difficultyName, starRating; - private readonly Box background; - - public string TooltipText { get; set; } - - public DifficultyIconTooltip(Color4 accentColour) - { - AutoSizeAxes = Axes.Both; - Masking = true; - CornerRadius = 5; - - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Padding = new MarginPadding(10), - Children = new Drawable[] - { - difficultyName = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 16, weight: FontWeight.Bold), - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - starRating = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular), - Colour = accentColour - }, - new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Margin = new MarginPadding { Left = 4 }, - Icon = FontAwesome.Solid.Star, - Size = new Vector2(12), - Colour = accentColour, - }, - } - } - } - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - background.Colour = colours.GreyCarmineDark; - } - - public void Refresh() - { - var info = TooltipText.Split('$'); - difficultyName.Text = info[0]; - starRating.Text = info[1]; - } - - public void Move(Vector2 pos) => Position = pos; - - protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); - - protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); - } - [BackgroundDependencyLoader] private void load() { @@ -156,5 +73,87 @@ namespace osu.Game.Beatmaps.Drawables } }; } + + private class DifficultyIconTooltip : VisibilityContainer, ITooltip + { + private readonly OsuSpriteText difficultyName, starRating; + private readonly Box background; + + public string TooltipText { get; set; } + + public DifficultyIconTooltip(Color4 difficultyColour) + { + AutoSizeAxes = Axes.Both; + Masking = true; + CornerRadius = 5; + + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Padding = new MarginPadding(10), + Children = new Drawable[] + { + difficultyName = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 16, weight: FontWeight.Bold), + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Horizontal, + Colour = difficultyColour, + Children = new Drawable[] + { + starRating = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular), + }, + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Left = 4 }, + Icon = FontAwesome.Solid.Star, + Size = new Vector2(12), + }, + } + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.GreyCarmineDark; + } + + public void Refresh() + { + var info = TooltipText.Split('$'); + difficultyName.Text = info[0]; + starRating.Text = info[1]; + } + + public void Move(Vector2 pos) => Position = pos; + + protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); + + protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); + } } } diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index baf702eebc..104315f1c2 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -234,7 +234,7 @@ namespace osu.Game.Overlays.BeatmapSet Colour = Color4.Black.Opacity(0.5f), }, }, - icon = new DifficultyIcon(beatmap) + icon = new DifficultyIcon(beatmap, shouldShowTooltip: false) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index 41c565090f..8199d80528 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -143,7 +143,7 @@ namespace osu.Game.Overlays.Direct var icons = new List(); foreach (var b in SetInfo.Beatmaps.OrderBy(beatmap => beatmap.StarDifficulty)) - icons.Add(new DifficultyIcon(b, null, true)); + icons.Add(new DifficultyIcon(b)); return icons; } diff --git a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs index 434c5c443b..6080458aec 100644 --- a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs +++ b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs @@ -56,7 +56,7 @@ namespace osu.Game.Screens.Multi.Components if (item?.Beatmap != null) { drawableRuleset.FadeIn(transition_duration); - drawableRuleset.Child = new DifficultyIcon(item.Beatmap, item.Ruleset, true) { Size = new Vector2(height) }; + drawableRuleset.Child = new DifficultyIcon(item.Beatmap, item.Ruleset) { Size = new Vector2(height) }; } else drawableRuleset.FadeOut(transition_duration); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 0a20f2aa6d..fba7a328c1 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -82,7 +82,7 @@ namespace osu.Game.Screens.Select.Carousel Origin = Anchor.CentreLeft, Children = new Drawable[] { - new DifficultyIcon(beatmap) + new DifficultyIcon(beatmap, shouldShowTooltip: false) { Scale = new Vector2(1.8f), }, diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 86ae6a2e58..4ceb82d4cc 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -197,7 +197,7 @@ namespace osu.Game.Screens.Select.Carousel private readonly BindableBool filtered = new BindableBool(); public FilterableDifficultyIcon(CarouselBeatmap item) - : base(item.Beatmap, shouldShowTooltip: true) + : base(item.Beatmap) { filtered.BindTo(item.Filtered); filtered.ValueChanged += isFiltered => Schedule(() => this.FadeTo(isFiltered.NewValue ? 0.1f : 1, 100)); From e73a9c2748ad4e0c8b7c5ef263f7ccad14f4268e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Aug 2019 14:00:12 +0900 Subject: [PATCH 0709/2815] Fix song select context menus displaying off-screen --- osu.Game/Screens/Select/BeatmapCarousel.cs | 26 +++++++++++++--------- osu.Game/Screens/Select/SongSelect.cs | 1 - 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 7366fa8c17..6fda81e47d 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -24,7 +24,7 @@ using osu.Game.Screens.Select.Carousel; namespace osu.Game.Screens.Select { - public class BeatmapCarousel : OsuScrollContainer + public class BeatmapCarousel : CompositeDrawable { private const float bleed_top = FilterControl.HEIGHT; private const float bleed_bottom = Footer.HEIGHT; @@ -61,6 +61,8 @@ namespace osu.Game.Screens.Select /// public bool BeatmapSetsLoaded { get; private set; } + private readonly OsuScrollContainer scroll; + private IEnumerable beatmapSets => root.Children.OfType(); public IEnumerable BeatmapSets @@ -110,13 +112,17 @@ namespace osu.Game.Screens.Select public BeatmapCarousel() { root = new CarouselRoot(this); - Child = new OsuContextMenuContainer + InternalChild = new OsuContextMenuContainer { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Child = scrollableContent = new Container + RelativeSizeAxes = Axes.Both, + Child = scroll = new OsuScrollContainer { - RelativeSizeAxes = Axes.X, + Masking = false, + RelativeSizeAxes = Axes.Both, + Child = scrollableContent = new Container + { + RelativeSizeAxes = Axes.X, + } } }; } @@ -127,7 +133,7 @@ namespace osu.Game.Screens.Select config.BindWith(OsuSetting.RandomSelectAlgorithm, RandomAlgorithm); config.BindWith(OsuSetting.SongSelectRightMouseScroll, RightClickScrollingEnabled); - RightClickScrollingEnabled.ValueChanged += enabled => RightMouseScrollbar = enabled.NewValue; + RightClickScrollingEnabled.ValueChanged += enabled => scroll.RightMouseScrollbar = enabled.NewValue; RightClickScrollingEnabled.TriggerChange(); loadBeatmapSets(beatmaps.GetAllUsableBeatmapSetsEnumerable()); @@ -351,12 +357,12 @@ namespace osu.Game.Screens.Select /// /// The position of the lower visible bound with respect to the current scroll position. /// - private float visibleBottomBound => Current + DrawHeight + bleed_bottom; + private float visibleBottomBound => scroll.Current + DrawHeight + bleed_bottom; /// /// The position of the upper visible bound with respect to the current scroll position. /// - private float visibleUpperBound => Current - bleed_top; + private float visibleUpperBound => scroll.Current - bleed_top; public void FlushPendingFilterOperations() { @@ -628,7 +634,7 @@ namespace osu.Game.Screens.Select private void updateScrollPosition() { - if (scrollTarget != null) ScrollTo(scrollTarget.Value); + if (scrollTarget != null) scroll.ScrollTo(scrollTarget.Value); scrollPositionCache.Validate(); } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 7dd934f91a..8340814db9 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -157,7 +157,6 @@ namespace osu.Game.Screens.Select }, Child = Carousel = new BeatmapCarousel { - Masking = false, RelativeSizeAxes = Axes.Both, Size = new Vector2(1 - wedged_container_size.X, 1), Anchor = Anchor.CentreRight, From f7a92487ef548c060b8af695efe9d49fe76a9b6b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Aug 2019 14:09:30 +0900 Subject: [PATCH 0710/2815] Fix checkbox sounds playing too often --- .../Graphics/UserInterface/OsuCheckbox.cs | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs index 47324ee646..6593531099 100644 --- a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs @@ -72,17 +72,11 @@ namespace osu.Game.Graphics.UserInterface Current.DisabledChanged += disabled => labelText.Alpha = Nub.Alpha = disabled ? 0.3f : 1; } - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load(AudioManager audio) { - base.LoadComplete(); - - Current.ValueChanged += enabled => - { - if (enabled.NewValue) - sampleChecked?.Play(); - else - sampleUnchecked?.Play(); - }; + sampleChecked = audio.Samples.Get(@"UI/check-on"); + sampleUnchecked = audio.Samples.Get(@"UI/check-off"); } protected override bool OnHover(HoverEvent e) @@ -99,11 +93,13 @@ namespace osu.Game.Graphics.UserInterface base.OnHoverLost(e); } - [BackgroundDependencyLoader] - private void load(AudioManager audio) + protected override void OnUserChange(bool value) { - sampleChecked = audio.Samples.Get(@"UI/check-on"); - sampleUnchecked = audio.Samples.Get(@"UI/check-off"); + base.OnUserChange(value); + if (value) + sampleChecked?.Play(); + else + sampleUnchecked?.Play(); } } } From 9e13a6aeae1a2a0259f926cf3c0d4db284d04315 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Aug 2019 14:32:31 +0900 Subject: [PATCH 0711/2815] Fix newlines being considered in user profile content --- osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs index e7f7c2f490..691e18e6bf 100644 --- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -138,6 +138,9 @@ namespace osu.Game.Overlays.Profile.Header private void tryAddInfo(IconUsage icon, string content, string link = null) { + // newlines could be contained in API returned user content. + content = content?.Replace("\n", " "); + if (string.IsNullOrEmpty(content)) return; bottomLinkContainer.AddIcon(icon, text => From 8f638879728bff8c8812253ba24476d7721e176d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Aug 2019 14:38:49 +0900 Subject: [PATCH 0712/2815] Make use of existing null check --- osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs index 691e18e6bf..158641d816 100644 --- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -138,11 +138,11 @@ namespace osu.Game.Overlays.Profile.Header private void tryAddInfo(IconUsage icon, string content, string link = null) { - // newlines could be contained in API returned user content. - content = content?.Replace("\n", " "); - if (string.IsNullOrEmpty(content)) return; + // newlines could be contained in API returned user content. + content = content.Replace("\n", " "); + bottomLinkContainer.AddIcon(icon, text => { text.Font = text.Font.With(size: 10); From ef5ed915e5b51781117bcb514ffe8a2751a3ac86 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Aug 2019 17:14:00 +0900 Subject: [PATCH 0713/2815] Reduce delay for hold-to-confirm controls --- .../Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs | 7 ++++++- osu.Game/Graphics/Containers/HoldToConfirmContainer.cs | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs index 7e6cf1285e..a017418e3a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using osu.Framework.Graphics; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Screens.Menu; @@ -12,7 +13,11 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestSceneHoldToConfirmOverlay : OsuTestScene { - public override IReadOnlyList RequiredTypes => new[] { typeof(ExitConfirmOverlay) }; + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ExitConfirmOverlay), + typeof(HoldToConfirmContainer), + }; public TestSceneHoldToConfirmOverlay() { diff --git a/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs b/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs index cda5e150de..18a4241a79 100644 --- a/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs +++ b/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs @@ -12,7 +12,7 @@ namespace osu.Game.Graphics.Containers { public Action Action; - private const int activate_delay = 400; + private const int activate_delay = 200; private const int fadeout_delay = 200; private bool fired; From 8c67f58e2d1179f5403e94933dea70df65d90e75 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 15 Aug 2019 18:25:31 +0900 Subject: [PATCH 0714/2815] Disable frame-stable playback in the editor --- osu.Game/Rulesets/Edit/DrawableEditRuleset.cs | 6 ++++++ osu.Game/Rulesets/UI/DrawableRuleset.cs | 9 +++++++++ osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 8 ++++++++ 3 files changed, 23 insertions(+) diff --git a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs index 2200caeb20..e85ebb5f3a 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; @@ -57,7 +58,12 @@ namespace osu.Game.Rulesets.Edit this.drawableRuleset = drawableRuleset; InternalChild = drawableRuleset; + } + [BackgroundDependencyLoader] + private void load() + { + drawableRuleset.FrameStablePlayback = false; Playfield.DisplayJudgements.Value = false; } diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index ac81fdc719..eb14bd1f24 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -62,6 +62,15 @@ namespace osu.Game.Rulesets.UI public override GameplayClock FrameStableClock => frameStabilityContainer.GameplayClock; + /// + /// Whether to enable frame-stable playback. + /// + internal bool FrameStablePlayback + { + get => frameStabilityContainer.FrameStablePlayback; + set => frameStabilityContainer.FrameStablePlayback = value; + } + /// /// Invoked when a has been applied by a . /// diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 1cc56fff8b..7c143aa158 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -24,6 +24,11 @@ namespace osu.Game.Rulesets.UI ///

public List Scores { get; set; } + public DifficultyRating DifficultyRating + { + get + { + var rating = StarDifficulty; + + if (rating < 2.0) return DifficultyRating.Easy; + if (rating < 2.7) return DifficultyRating.Normal; + if (rating < 4.0) return DifficultyRating.Hard; + if (rating < 5.3) return DifficultyRating.Insane; + if (rating < 6.5) return DifficultyRating.Expert; + + return DifficultyRating.ExpertPlus; + } + } + public override string ToString() => $"{Metadata} [{Version}]".Trim(); public bool Equals(BeatmapInfo other) diff --git a/osu.Game/Beatmaps/DifficultyRating.cs b/osu.Game/Beatmaps/DifficultyRating.cs new file mode 100644 index 0000000000..f0ee0ad705 --- /dev/null +++ b/osu.Game/Beatmaps/DifficultyRating.cs @@ -0,0 +1,15 @@ +// 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.Beatmaps +{ + public enum DifficultyRating + { + Easy, + Normal, + Hard, + Insane, + Expert, + ExpertPlus + } +} diff --git a/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs b/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs deleted file mode 100644 index 26ffcca1ec..0000000000 --- a/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs +++ /dev/null @@ -1,85 +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 System; -using osu.Framework.Allocation; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osuTK.Graphics; - -namespace osu.Game.Beatmaps.Drawables -{ - public abstract class DifficultyColouredContainer : Container, IHasAccentColour - { - public Color4 AccentColour { get; set; } - - private readonly BeatmapInfo beatmap; - private OsuColour palette; - - protected DifficultyColouredContainer(BeatmapInfo beatmap) - { - this.beatmap = beatmap; - } - - [BackgroundDependencyLoader] - private void load(OsuColour palette) - { - if (palette == null) - throw new ArgumentNullException(nameof(palette)); - - this.palette = palette; - AccentColour = getColour(beatmap); - } - - private enum DifficultyRating - { - Easy, - Normal, - Hard, - Insane, - Expert, - ExpertPlus - } - - private DifficultyRating getDifficultyRating(BeatmapInfo beatmap) - { - if (beatmap == null) - throw new ArgumentNullException(nameof(beatmap)); - - var rating = beatmap.StarDifficulty; - - if (rating < 2.0) return DifficultyRating.Easy; - if (rating < 2.7) return DifficultyRating.Normal; - if (rating < 4.0) return DifficultyRating.Hard; - if (rating < 5.3) return DifficultyRating.Insane; - if (rating < 6.5) return DifficultyRating.Expert; - - return DifficultyRating.ExpertPlus; - } - - private Color4 getColour(BeatmapInfo beatmap) - { - switch (getDifficultyRating(beatmap)) - { - case DifficultyRating.Easy: - return palette.Green; - - default: - case DifficultyRating.Normal: - return palette.Blue; - - case DifficultyRating.Hard: - return palette.Yellow; - - case DifficultyRating.Insane: - return palette.Pink; - - case DifficultyRating.Expert: - return palette.Purple; - - case DifficultyRating.ExpertPlus: - return palette.Gray0; - } - } - } -} diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 0a0ad28fdf..2b0a0a5ac1 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Rulesets; using osuTK; @@ -16,23 +17,25 @@ using osuTK.Graphics; namespace osu.Game.Beatmaps.Drawables { - public class DifficultyIcon : DifficultyColouredContainer + public class DifficultyIcon : Container { + private readonly BeatmapInfo beatmap; private readonly RulesetInfo ruleset; public DifficultyIcon(BeatmapInfo beatmap, RulesetInfo ruleset = null) - : base(beatmap) { if (beatmap == null) throw new ArgumentNullException(nameof(beatmap)); + this.beatmap = beatmap; + this.ruleset = ruleset ?? beatmap.Ruleset; Size = new Vector2(20); } [BackgroundDependencyLoader] - private void load() + private void load(OsuColour colours) { Children = new Drawable[] { @@ -52,7 +55,7 @@ namespace osu.Game.Beatmaps.Drawables Child = new Box { RelativeSizeAxes = Axes.Both, - Colour = AccentColour, + Colour = colours.ForDifficultyRating(beatmap.DifficultyRating), }, }, new ConstrainedIconContainer diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 63ec24f84f..af66f57f14 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Game.Beatmaps; using osuTK.Graphics; namespace osu.Game.Graphics @@ -37,6 +38,31 @@ namespace osu.Game.Graphics } } + public Color4 ForDifficultyRating(DifficultyRating difficulty) + { + switch (difficulty) + { + case DifficultyRating.Easy: + return Green; + + default: + case DifficultyRating.Normal: + return Blue; + + case DifficultyRating.Hard: + return Yellow; + + case DifficultyRating.Insane: + return Pink; + + case DifficultyRating.Expert: + return Purple; + + case DifficultyRating.ExpertPlus: + return Gray0; + } + } + // See https://github.com/ppy/osu-web/blob/master/resources/assets/less/colors.less public readonly Color4 PurpleLighter = FromHex(@"eeeeff"); public readonly Color4 PurpleLight = FromHex(@"aa88ff"); diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index a9e4eaa9b3..5f6307e3b4 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -402,31 +402,35 @@ namespace osu.Game.Screens.Select } } - private class DifficultyColourBar : DifficultyColouredContainer + private class DifficultyColourBar : Container { + private readonly BeatmapInfo beatmap; + public DifficultyColourBar(BeatmapInfo beatmap) - : base(beatmap) { + this.beatmap = beatmap; } [BackgroundDependencyLoader] - private void load() + private void load(OsuColour colours) { const float full_opacity_ratio = 0.7f; + var difficultyColour = colours.ForDifficultyRating(beatmap.DifficultyRating); + Children = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Colour = AccentColour, + Colour = difficultyColour, Width = full_opacity_ratio, }, new Box { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, - Colour = AccentColour, + Colour = difficultyColour, Alpha = 0.5f, X = full_opacity_ratio, Width = 1 - full_opacity_ratio, From 50046d5f69bd206f0480271758b0488b3c97f61a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Aug 2019 15:20:27 +0900 Subject: [PATCH 0735/2815] Use new tooltip style --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 9b265de45b..37fbc617cc 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -32,14 +32,17 @@ namespace osu.Game.Beatmaps.Drawables this.beatmap = beatmap; this.ruleset = ruleset ?? beatmap.Ruleset; - TooltipText = shouldShowTooltip ? $"{beatmap.Version}${beatmap.StarDifficulty:0.##}" : String.Empty; + if (shouldShowTooltip) + TooltipContent = beatmap; Size = new Vector2(20); } public string TooltipText { get; set; } - public ITooltip GetCustomTooltip() => new DifficultyIconTooltip(AccentColour); + public ITooltip GetCustomTooltip() => new DifficultyIconTooltip(); + + public object TooltipContent { get; set; } [BackgroundDependencyLoader] private void load(OsuColour colours) @@ -81,9 +84,14 @@ namespace osu.Game.Beatmaps.Drawables private readonly OsuSpriteText difficultyName, starRating; private readonly Box background; - public string TooltipText { get; set; } + private readonly FillFlowContainer difficultyFlow; - public DifficultyIconTooltip(Color4 difficultyColour) + public string TooltipText + { + set { } + } + + public DifficultyIconTooltip() { AutoSizeAxes = Axes.Both; Masking = true; @@ -108,13 +116,12 @@ namespace osu.Game.Beatmaps.Drawables Origin = Anchor.Centre, Font = OsuFont.GetFont(size: 16, weight: FontWeight.Bold), }, - new FillFlowContainer + difficultyFlow = new FillFlowContainer { AutoSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, Direction = FillDirection.Horizontal, - Colour = difficultyColour, Children = new Drawable[] { starRating = new OsuSpriteText @@ -138,17 +145,29 @@ namespace osu.Game.Beatmaps.Drawables }; } + private OsuColour colours; + [BackgroundDependencyLoader] private void load(OsuColour colours) { + this.colours = colours; background.Colour = colours.GreyCarmineDark; } + public bool SetContent(object content) + { + if (!(content is BeatmapInfo beatmap)) + return false; + + difficultyName.Text = beatmap.Version; + starRating.Text = $"{beatmap.StarDifficulty:0.##}"; + difficultyFlow.Colour = colours.ForDifficultyRating(beatmap.DifficultyRating); + + return true; + } + public void Refresh() { - var info = TooltipText.Split('$'); - difficultyName.Text = info[0]; - starRating.Text = info[1]; } public void Move(Vector2 pos) => Position = pos; From 097763bb1ca9d7215b9ad7ba9bbe14554f9f24df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Aug 2019 15:29:39 +0900 Subject: [PATCH 0736/2815] Add auto size duration --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 37fbc617cc..962cb33a83 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -106,6 +106,8 @@ namespace osu.Game.Beatmaps.Drawables new FillFlowContainer { AutoSizeAxes = Axes.Both, + AutoSizeDuration = 200, + AutoSizeEasing = Easing.OutQuint, Direction = FillDirection.Vertical, Padding = new MarginPadding(10), Children = new Drawable[] From 87dc6499faabccc71e3062a2365446d31945a281 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Aug 2019 15:34:02 +0900 Subject: [PATCH 0737/2815] Fix json decoding being a bit too eager to consume --- osu.Game/Beatmaps/BeatmapInfo.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 700f981088..198046df4f 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -129,6 +129,7 @@ namespace osu.Game.Beatmaps ///
public List Scores { get; set; } + [JsonIgnore] public DifficultyRating DifficultyRating { get From f7024b513efeabd0a19b97716725770ab86138bd Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 18 Aug 2019 01:43:43 +0300 Subject: [PATCH 0738/2815] Visual improvements --- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 83 +++++++++++--------- 1 file changed, 45 insertions(+), 38 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 4cac73c975..ad72063313 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -14,14 +14,15 @@ using osu.Game.Beatmaps; using osu.Framework.Bindables; using osu.Framework.Allocation; using osu.Framework.Graphics.Colour; +using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; namespace osu.Game.Screens.Play.HUD { public class HitErrorDisplay : Container { - private const int bar_width = 4; - private const int bar_height = 250; + private const int bar_width = 3; + private const int bar_height = 200; private const int spacing = 3; public HitWindows HitWindows { get; set; } @@ -31,6 +32,7 @@ namespace osu.Game.Screens.Play.HUD private readonly bool mirrored; private readonly SpriteIcon arrow; + private readonly FillFlowContainer bar; private readonly List judgementOffsets = new List(); public HitErrorDisplay(bool mirrored = false) @@ -41,43 +43,10 @@ namespace osu.Game.Screens.Play.HUD Children = new Drawable[] { - new FillFlowContainer + bar = new FillFlowContainer { RelativeSizeAxes = Axes.Both, Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Color4.Orange), - Height = 0.3f - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Green, - Height = 0.15f - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Blue, - Height = 0.1f - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Green, - Height = 0.15f - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4.Orange, Color4.Black.Opacity(0)), - Height = 0.3f - } - } }, arrow = new SpriteIcon { @@ -86,11 +55,49 @@ namespace osu.Game.Screens.Play.HUD X = mirrored ? -spacing : spacing, RelativePositionAxes = Axes.Y, Icon = mirrored ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, - Size = new Vector2(10), + Size = new Vector2(8), } }; } + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + bar.AddRange(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(colours.Yellow.Opacity(0), colours.Yellow), + Height = 0.3f + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Green, + Height = 0.15f + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.BlueLight, + Height = 0.1f + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Green, + Height = 0.15f + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(colours.Yellow, colours.Yellow.Opacity(0)), + Height = 0.3f + } + }); + } + protected override void LoadComplete() { base.LoadComplete(); @@ -117,7 +124,7 @@ namespace osu.Game.Screens.Play.HUD Anchor = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, Origin = mirrored ? Anchor.CentreLeft : Anchor.CentreRight, Masking = true, - Size = new Vector2(10, 2), + Size = new Vector2(8, 2), RelativePositionAxes = Axes.Y, Y = getRelativeJudgementPosition(judgement.TimeOffset), X = mirrored ? spacing : -spacing, From 906984ad952f5df2b3c6becf66c30339af2cef36 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 18 Aug 2019 02:49:07 +0300 Subject: [PATCH 0739/2815] Fix the math --- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 35 +++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index ad72063313..6ef102e575 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -16,6 +16,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Colour; using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; +using System.Linq; namespace osu.Game.Screens.Play.HUD { @@ -30,6 +31,9 @@ namespace osu.Game.Screens.Play.HUD [Resolved] private Bindable beatmap { get; set; } + [Resolved] + private OsuColour colours { get; set; } + private readonly bool mirrored; private readonly SpriteIcon arrow; private readonly FillFlowContainer bar; @@ -60,50 +64,46 @@ namespace osu.Game.Screens.Play.HUD }; } - [BackgroundDependencyLoader] - private void load(OsuColour colours) + protected override void LoadComplete() { + base.LoadComplete(); + HitWindows.SetDifficulty(beatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty); + bar.AddRange(new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, Colour = ColourInfo.GradientVertical(colours.Yellow.Opacity(0), colours.Yellow), - Height = 0.3f + Height = (float)((HitWindows.Meh - HitWindows.Good) / (HitWindows.Meh * 2)) }, new Box { RelativeSizeAxes = Axes.Both, Colour = colours.Green, - Height = 0.15f + Height = (float)((HitWindows.Good - HitWindows.Great) / (HitWindows.Meh * 2)) }, new Box { RelativeSizeAxes = Axes.Both, Colour = colours.BlueLight, - Height = 0.1f + Height = (float)(HitWindows.Great / HitWindows.Meh) }, new Box { RelativeSizeAxes = Axes.Both, Colour = colours.Green, - Height = 0.15f + Height = (float)((HitWindows.Good - HitWindows.Great) / (HitWindows.Meh * 2)) }, new Box { RelativeSizeAxes = Axes.Both, Colour = ColourInfo.GradientVertical(colours.Yellow, colours.Yellow.Opacity(0)), - Height = 0.3f + Height = (float)((HitWindows.Meh - HitWindows.Good) / (HitWindows.Meh * 2)) } }); } - protected override void LoadComplete() - { - base.LoadComplete(); - HitWindows.SetDifficulty(beatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty); - } - public void OnNewJudgement(JudgementResult judgement) { if (!judgement.IsHit) @@ -135,7 +135,7 @@ namespace osu.Game.Screens.Play.HUD } }; - private float getRelativeJudgementPosition(double value) => (float)(value / HitWindows.Miss); + private float getRelativeJudgementPosition(double value) => (float)(value / HitWindows.Meh); private double calculateArrowPosition(JudgementResult judgement) { @@ -144,12 +144,7 @@ namespace osu.Game.Screens.Play.HUD judgementOffsets.Add(judgement.TimeOffset); - double offsets = 0; - - foreach (var offset in judgementOffsets) - offsets += offset; - - return offsets / judgementOffsets.Count; + return judgementOffsets.Average(); } } } From 50133ba8636159d4b3d79eda20ced928a4fdbf68 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 18 Aug 2019 02:57:12 +0300 Subject: [PATCH 0740/2815] naming adjustments --- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 6ef102e575..1dd77469ca 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -22,6 +22,7 @@ namespace osu.Game.Screens.Play.HUD { public class HitErrorDisplay : Container { + private const int stored_judgements_amount = 5; private const int bar_width = 3; private const int bar_height = 200; private const int spacing = 3; @@ -104,19 +105,19 @@ namespace osu.Game.Screens.Play.HUD }); } - public void OnNewJudgement(JudgementResult judgement) + public void OnNewJudgement(JudgementResult newJudgement) { - if (!judgement.IsHit) + if (!newJudgement.IsHit) return; Container judgementLine; - Add(judgementLine = CreateJudgementLine(judgement)); + Add(judgementLine = CreateJudgementLine(newJudgement)); judgementLine.FadeOut(10000, Easing.OutQuint); judgementLine.Expire(); - arrow.MoveToY(getRelativeJudgementPosition(calculateArrowPosition(judgement)), 500, Easing.OutQuint); + arrow.MoveToY(getRelativeJudgementPosition(calculateArrowPosition(newJudgement)), 500, Easing.OutQuint); } protected virtual Container CreateJudgementLine(JudgementResult judgement) => new CircularContainer @@ -137,12 +138,12 @@ namespace osu.Game.Screens.Play.HUD private float getRelativeJudgementPosition(double value) => (float)(value / HitWindows.Meh); - private double calculateArrowPosition(JudgementResult judgement) + private double calculateArrowPosition(JudgementResult newJudgement) { - if (judgementOffsets.Count > 5) + if (judgementOffsets.Count > stored_judgements_amount) judgementOffsets.RemoveAt(0); - judgementOffsets.Add(judgement.TimeOffset); + judgementOffsets.Add(newJudgement.TimeOffset); return judgementOffsets.Average(); } From ee5568e5968489b8dbce42023f4db425f28fff2c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 18 Aug 2019 14:43:34 +0300 Subject: [PATCH 0741/2815] Use Queue instead of List for stored Judgements --- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 1dd77469ca..7bb1d30f22 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Play.HUD private readonly bool mirrored; private readonly SpriteIcon arrow; private readonly FillFlowContainer bar; - private readonly List judgementOffsets = new List(); + private readonly Queue judgementOffsets = new Queue(); public HitErrorDisplay(bool mirrored = false) { @@ -117,7 +117,7 @@ namespace osu.Game.Screens.Play.HUD judgementLine.FadeOut(10000, Easing.OutQuint); judgementLine.Expire(); - arrow.MoveToY(getRelativeJudgementPosition(calculateArrowPosition(newJudgement)), 500, Easing.OutQuint); + arrow.MoveToY(calculateArrowPosition(newJudgement), 500, Easing.OutQuint); } protected virtual Container CreateJudgementLine(JudgementResult judgement) => new CircularContainer @@ -138,14 +138,14 @@ namespace osu.Game.Screens.Play.HUD private float getRelativeJudgementPosition(double value) => (float)(value / HitWindows.Meh); - private double calculateArrowPosition(JudgementResult newJudgement) + private float calculateArrowPosition(JudgementResult newJudgement) { if (judgementOffsets.Count > stored_judgements_amount) - judgementOffsets.RemoveAt(0); + judgementOffsets.Dequeue(); - judgementOffsets.Add(newJudgement.TimeOffset); + judgementOffsets.Enqueue(newJudgement.TimeOffset); - return judgementOffsets.Average(); + return getRelativeJudgementPosition(judgementOffsets.Average()); } } } From a59a14c9e6f518ab3c99871c5ddae5617dfefc75 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 18 Aug 2019 15:01:04 +0300 Subject: [PATCH 0742/2815] Add setting to enable/disable hit error visibility --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ osu.Game/Configuration/ScoreMeterType.cs | 8 +++++-- .../Sections/Gameplay/GeneralSettings.cs | 5 ++++ osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 24 +++++++++++++++++++ 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 19f46c1d6a..bffbce2a52 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -79,6 +79,7 @@ namespace osu.Game.Configuration Set(OsuSetting.ShowInterface, true); Set(OsuSetting.ShowHealthDisplayWhenCantFail, true); Set(OsuSetting.KeyOverlay, false); + Set(OsuSetting.ScoreMeter, ScoreMeterType.HitError); Set(OsuSetting.FloatingComments, false); @@ -132,6 +133,7 @@ namespace osu.Game.Configuration BlurLevel, ShowStoryboard, KeyOverlay, + ScoreMeter, FloatingComments, ShowInterface, ShowHealthDisplayWhenCantFail, diff --git a/osu.Game/Configuration/ScoreMeterType.cs b/osu.Game/Configuration/ScoreMeterType.cs index 21a63fb3ed..e78220c9c9 100644 --- a/osu.Game/Configuration/ScoreMeterType.cs +++ b/osu.Game/Configuration/ScoreMeterType.cs @@ -1,12 +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.ComponentModel; + namespace osu.Game.Configuration { public enum ScoreMeterType { + [Description("None")] None, - Colour, - Error + + [Description("Hit Error")] + HitError } } diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 9142492610..520a8852b3 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -44,6 +44,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay LabelText = "Always show key overlay", Bindable = config.GetBindable(OsuSetting.KeyOverlay) }, + new SettingsEnumDropdown + { + LabelText = "Score meter type", + Bindable = config.GetBindable(OsuSetting.ScoreMeter) + }, new SettingsEnumDropdown { LabelText = "Score display mode", diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 7bb1d30f22..9a5198b7cc 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -17,6 +17,7 @@ using osu.Framework.Graphics.Colour; using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; using System.Linq; +using osu.Game.Configuration; namespace osu.Game.Screens.Play.HUD { @@ -40,6 +41,8 @@ namespace osu.Game.Screens.Play.HUD private readonly FillFlowContainer bar; private readonly Queue judgementOffsets = new Queue(); + private readonly Bindable type = new Bindable(); + public HitErrorDisplay(bool mirrored = false) { this.mirrored = mirrored; @@ -65,6 +68,12 @@ namespace osu.Game.Screens.Play.HUD }; } + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + config.BindWith(OsuSetting.ScoreMeter, type); + } + protected override void LoadComplete() { base.LoadComplete(); @@ -103,6 +112,21 @@ namespace osu.Game.Screens.Play.HUD Height = (float)((HitWindows.Meh - HitWindows.Good) / (HitWindows.Meh * 2)) } }); + + type.BindValueChanged(onTypeChanged, true); + } + + private void onTypeChanged(ValueChangedEvent type) + { + switch (type.NewValue) + { + case ScoreMeterType.None: + this.FadeOut(200, Easing.OutQuint); + break; + case ScoreMeterType.HitError: + this.FadeIn(200, Easing.OutQuint); + break; + } } public void OnNewJudgement(JudgementResult newJudgement) From 8740ebd13f2fcac9a46151cf0991031e3edbdf32 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 18 Aug 2019 15:45:18 +0300 Subject: [PATCH 0743/2815] Simplify layout --- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 72 +++++++++++++------- osu.Game/Screens/Play/HUDOverlay.cs | 12 ++-- 2 files changed, 53 insertions(+), 31 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 9a5198b7cc..1a4d2a45c5 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -21,10 +21,11 @@ using osu.Game.Configuration; namespace osu.Game.Screens.Play.HUD { - public class HitErrorDisplay : Container + public class HitErrorDisplay : CompositeDrawable { private const int stored_judgements_amount = 5; private const int bar_width = 3; + private const int judgement_line_width = 8; private const int bar_height = 200; private const int spacing = 3; @@ -36,36 +37,57 @@ namespace osu.Game.Screens.Play.HUD [Resolved] private OsuColour colours { get; set; } - private readonly bool mirrored; private readonly SpriteIcon arrow; private readonly FillFlowContainer bar; + private readonly Container judgementsContainer; private readonly Queue judgementOffsets = new Queue(); private readonly Bindable type = new Bindable(); - public HitErrorDisplay(bool mirrored = false) + public HitErrorDisplay(bool reversed = false) { - this.mirrored = mirrored; + AutoSizeAxes = Axes.Both; - Size = new Vector2(bar_width, bar_height); - - Children = new Drawable[] + AddInternal(new FillFlowContainer { - bar = new FillFlowContainer + AutoSizeAxes = Axes.X, + Height = bar_height, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(spacing, 0), + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - }, - arrow = new SpriteIcon - { - Anchor = mirrored ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, - X = mirrored ? -spacing : spacing, - RelativePositionAxes = Axes.Y, - Icon = mirrored ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, - Size = new Vector2(8), + judgementsContainer = new Container + { + Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Width = judgement_line_width, + RelativeSizeAxes = Axes.Y, + }, + bar = new FillFlowContainer + { + Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Width = bar_width, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + }, + new Container + { + Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Child = arrow = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.Y, + Icon = reversed ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, + Size = new Vector2(8), + } + }, } - }; + }); } [BackgroundDependencyLoader] @@ -136,7 +158,7 @@ namespace osu.Game.Screens.Play.HUD Container judgementLine; - Add(judgementLine = CreateJudgementLine(newJudgement)); + judgementsContainer.Add(judgementLine = CreateJudgementLine(newJudgement)); judgementLine.FadeOut(10000, Easing.OutQuint); judgementLine.Expire(); @@ -146,13 +168,13 @@ namespace osu.Game.Screens.Play.HUD protected virtual Container CreateJudgementLine(JudgementResult judgement) => new CircularContainer { - Anchor = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = mirrored ? Anchor.CentreLeft : Anchor.CentreRight, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Masking = true, - Size = new Vector2(8, 2), + RelativeSizeAxes = Axes.X, + Height = 2, RelativePositionAxes = Axes.Y, Y = getRelativeJudgementPosition(judgement.TimeOffset), - X = mirrored ? spacing : -spacing, Child = new Box { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index a9a469486e..50480c001c 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -86,8 +86,8 @@ namespace osu.Game.Screens.Play HealthDisplay = CreateHealthDisplay(), Progress = CreateProgress(), ModDisplay = CreateModsContainer(), - LeftHitErrorDisplay = CreateAccuracyBar(false), - RightHitErrorDisplay = CreateAccuracyBar(), + LeftHitErrorDisplay = CreateHitErrorDisplay(false), + RightHitErrorDisplay = CreateHitErrorDisplay(), } }, PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), @@ -260,11 +260,11 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20, Right = 10 }, }; - protected virtual HitErrorDisplay CreateAccuracyBar(bool mirrored = true) => new HitErrorDisplay(mirrored) + protected virtual HitErrorDisplay CreateHitErrorDisplay(bool reversed = true) => new HitErrorDisplay(reversed) { - Anchor = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, - Margin = new MarginPadding { Horizontal = 30 } + Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Margin = new MarginPadding { Horizontal = 20 } }; protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay(); From af2ffac03a5fe99a4d62e0508e3e0f1e215d2be3 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 18 Aug 2019 14:52:26 +0200 Subject: [PATCH 0744/2815] Add global Top and Horizontal padding to NewsContent --- osu.Game/Overlays/News/NewsContent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/News/NewsContent.cs b/osu.Game/Overlays/News/NewsContent.cs index 16a8ed84b8..5ff210f9f5 100644 --- a/osu.Game/Overlays/News/NewsContent.cs +++ b/osu.Game/Overlays/News/NewsContent.cs @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.News RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Direction = FillDirection.Vertical; - Padding = new MarginPadding { Bottom = 100 }; + Padding = new MarginPadding { Bottom = 100, Top = 20, Horizontal = 50 }; } } } From 55cd1cecdfb57c6ba8794ce66946b24be2d8fe4d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 18 Aug 2019 15:53:42 +0300 Subject: [PATCH 0745/2815] Add missing blank line --- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 1a4d2a45c5..2959a9aca7 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -145,6 +145,7 @@ namespace osu.Game.Screens.Play.HUD case ScoreMeterType.None: this.FadeOut(200, Easing.OutQuint); break; + case ScoreMeterType.HitError: this.FadeIn(200, Easing.OutQuint); break; From 6c60db550ff9c3e76abdc89add98d6f26e415b28 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 18 Aug 2019 16:24:13 +0300 Subject: [PATCH 0746/2815] Fix crash if ruleset has no Meh hit windows --- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 21 ++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 2959a9aca7..2ed06209de 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -107,37 +107,46 @@ namespace osu.Game.Screens.Play.HUD { RelativeSizeAxes = Axes.Both, Colour = ColourInfo.GradientVertical(colours.Yellow.Opacity(0), colours.Yellow), - Height = (float)((HitWindows.Meh - HitWindows.Good) / (HitWindows.Meh * 2)) + Height = (float)((getMehHitWindows() - HitWindows.Good) / (getMehHitWindows() * 2)) }, new Box { RelativeSizeAxes = Axes.Both, Colour = colours.Green, - Height = (float)((HitWindows.Good - HitWindows.Great) / (HitWindows.Meh * 2)) + Height = (float)((HitWindows.Good - HitWindows.Great) / (getMehHitWindows() * 2)) }, new Box { RelativeSizeAxes = Axes.Both, Colour = colours.BlueLight, - Height = (float)(HitWindows.Great / HitWindows.Meh) + Height = (float)(HitWindows.Great / getMehHitWindows()) }, new Box { RelativeSizeAxes = Axes.Both, Colour = colours.Green, - Height = (float)((HitWindows.Good - HitWindows.Great) / (HitWindows.Meh * 2)) + Height = (float)((HitWindows.Good - HitWindows.Great) / (getMehHitWindows() * 2)) }, new Box { RelativeSizeAxes = Axes.Both, Colour = ColourInfo.GradientVertical(colours.Yellow, colours.Yellow.Opacity(0)), - Height = (float)((HitWindows.Meh - HitWindows.Good) / (HitWindows.Meh * 2)) + Height = (float)((getMehHitWindows() - HitWindows.Good) / (getMehHitWindows() * 2)) } }); type.BindValueChanged(onTypeChanged, true); } + private double getMehHitWindows() + { + // In case if ruleset has no Meh hit windows (like Taiko) + if (HitWindows.Meh == 0) + return HitWindows.Good + 40; + + return HitWindows.Meh; + } + private void onTypeChanged(ValueChangedEvent type) { switch (type.NewValue) @@ -183,7 +192,7 @@ namespace osu.Game.Screens.Play.HUD } }; - private float getRelativeJudgementPosition(double value) => (float)(value / HitWindows.Meh); + private float getRelativeJudgementPosition(double value) => (float)(value / getMehHitWindows()); private float calculateArrowPosition(JudgementResult newJudgement) { From dd6351b8caa83e917152a74220ed1a04fe56f3f3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 18 Aug 2019 16:51:16 +0300 Subject: [PATCH 0747/2815] Apply suggested changes --- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 16 +++++++++------- osu.Game/Screens/Play/HUDOverlay.cs | 18 +++++++++--------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 2ed06209de..1a3682351b 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -27,6 +27,9 @@ namespace osu.Game.Screens.Play.HUD private const int bar_width = 3; private const int judgement_line_width = 8; private const int bar_height = 200; + private const int fade_duration = 200; + private const int arrow_move_duration = 500; + private const int judgement_life_time = 10000; private const int spacing = 3; public HitWindows HitWindows { get; set; } @@ -152,11 +155,11 @@ namespace osu.Game.Screens.Play.HUD switch (type.NewValue) { case ScoreMeterType.None: - this.FadeOut(200, Easing.OutQuint); + this.FadeOut(fade_duration, Easing.OutQuint); break; case ScoreMeterType.HitError: - this.FadeIn(200, Easing.OutQuint); + this.FadeIn(fade_duration, Easing.OutQuint); break; } } @@ -166,14 +169,13 @@ namespace osu.Game.Screens.Play.HUD if (!newJudgement.IsHit) return; - Container judgementLine; + var judgementLine = CreateJudgementLine(newJudgement); - judgementsContainer.Add(judgementLine = CreateJudgementLine(newJudgement)); + judgementsContainer.Add(judgementLine); - judgementLine.FadeOut(10000, Easing.OutQuint); - judgementLine.Expire(); + judgementLine.FadeOut(judgement_life_time, Easing.OutQuint).Expire(); - arrow.MoveToY(calculateArrowPosition(newJudgement), 500, Easing.OutQuint); + arrow.MoveToY(calculateArrowPosition(newJudgement), arrow_move_duration, Easing.OutQuint); } protected virtual Container CreateJudgementLine(JudgementResult judgement) => new CircularContainer diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 50480c001c..114dc86757 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.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.Graphics.Containers; using osu.Framework.Input.Events; @@ -276,17 +277,16 @@ namespace osu.Game.Screens.Play ComboCounter?.Current.BindTo(processor.Combo); HealthDisplay?.Current.BindTo(processor.Health); - if (LeftHitErrorDisplay != null) - { - processor.NewJudgement += LeftHitErrorDisplay.OnNewJudgement; - LeftHitErrorDisplay.HitWindows = processor.CreateHitWindows(); - } + var hitWindows = processor.CreateHitWindows(); - if (RightHitErrorDisplay != null) + visibilityContainer.ForEach(drawable => { - processor.NewJudgement += RightHitErrorDisplay.OnNewJudgement; - RightHitErrorDisplay.HitWindows = processor.CreateHitWindows(); - } + if (drawable is HitErrorDisplay) + { + processor.NewJudgement += (drawable as HitErrorDisplay).OnNewJudgement; + (drawable as HitErrorDisplay).HitWindows = hitWindows; + } + }); if (HealthDisplay is StandardHealthDisplay shd) processor.NewJudgement += shd.Flash; From 4c817b18b7f217ef9c95c01c867d8e4442c7ad18 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 18 Aug 2019 17:03:11 +0300 Subject: [PATCH 0748/2815] Use direct cast --- osu.Game/Screens/Play/HUDOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 114dc86757..be291003b5 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -283,8 +283,8 @@ namespace osu.Game.Screens.Play { if (drawable is HitErrorDisplay) { - processor.NewJudgement += (drawable as HitErrorDisplay).OnNewJudgement; - (drawable as HitErrorDisplay).HitWindows = hitWindows; + processor.NewJudgement += ((HitErrorDisplay)drawable).OnNewJudgement; + ((HitErrorDisplay)drawable).HitWindows = hitWindows; } }); From 3fcd786198a60b61cd6179f56294b0b12d209808 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 18 Aug 2019 19:32:56 +0200 Subject: [PATCH 0749/2815] Take lease on WorkingBeatmap during intro screens to prevent weird interactions with Playback control. --- osu.Game/Screens/Menu/IntroScreen.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 27f3c9a45b..a621e29cf8 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -28,11 +28,18 @@ namespace osu.Game.Screens.Menu private Bindable menuVoice; + private LeasedBindable beatmap; + + public new Bindable Beatmap { get => beatmap; } + protected override BackgroundScreen CreateBackground() => new BackgroundScreenBlack(); [BackgroundDependencyLoader] private void load(OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game) { + //we take a lease on the beatmap bindable to prevent music playback from changing / pausing music during intros, as it is causing weird interactions with certains intros + beatmap = base.Beatmap.BeginLease(false); + menuVoice = config.GetBindable(OsuSetting.MenuVoice); seeya = audio.Samples.Get(@"seeya"); } @@ -108,6 +115,8 @@ namespace osu.Game.Screens.Menu protected void LoadMenu() { DidLoadMenu = true; + beatmap.Return(); //we return the lease to the beatmap bindable as we're pushing the main menu. + this.Push(mainMenu); } } From 2393bbc69b15e1f3421f3d4b140393bbaa8462ac Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 18 Aug 2019 21:27:53 +0300 Subject: [PATCH 0750/2815] Expand APIKudosuHistory --- .../Requests/GetUserKudosuHistoryRequest.cs | 10 ++++ .../Requests/Responses/APIKudosuHistory.cs | 46 +++++++++++++++++-- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs b/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs index e90e297672..af37bd4b51 100644 --- a/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs @@ -18,4 +18,14 @@ namespace osu.Game.Online.API.Requests protected override string Target => $"users/{userId}/kudosu"; } + + public enum KudosuAction + { + Give, + VoteGive, + Reset, + VoteReset, + DenyKudosuReset, + Revoke, + } } diff --git a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs index 271dcc320e..d02f71c339 100644 --- a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs +++ b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs @@ -2,19 +2,59 @@ // See the LICENCE file in the repository root for full licence text. using System; +using Humanizer; using Newtonsoft.Json; namespace osu.Game.Online.API.Requests.Responses { public class APIKudosuHistory { - [JsonProperty("id")] - public int ID; - [JsonProperty("created_at")] public DateTimeOffset CreatedAt; [JsonProperty("amount")] + private int amount + { + set => Amount = Math.Abs(value); + } + public int Amount; + + [JsonProperty("post")] + public ModdingPost Post; + + public class ModdingPost + { + [JsonProperty("url")] + public string Url; + + [JsonProperty("title")] + public string Title; + } + + [JsonProperty("giver")] + public KudosuGiver Giver; + + public class KudosuGiver + { + [JsonProperty("url")] + public string Url; + + [JsonProperty("username")] + public string Username; + } + + [JsonProperty("action")] + private string action + { + set + { + string parsed = value.Contains(".") ? value.Split('.')[0].Pascalize() + value.Split('.')[1].Pascalize() : value.Pascalize(); + + Action = (KudosuAction)Enum.Parse(typeof(KudosuAction), parsed); + } + } + + public KudosuAction Action; } } From be97804180f00c93818ea91b9afd58037e27c7dd Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 18 Aug 2019 21:28:07 +0300 Subject: [PATCH 0751/2815] Implement text formatting --- .../Kudosu/DrawableKudosuHistoryItem.cs | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index 1671073242..9b68131515 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.API.Requests; using osu.Game.Online.Chat; namespace osu.Game.Overlays.Profile.Sections.Kudosu @@ -51,6 +52,45 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu AutoSizeAxes = Axes.X, }; - private MessageFormatter.MessageFormatterResult createMessage() => MessageFormatter.FormatText($@"{historyItem.Amount}"); + private MessageFormatter.MessageFormatterResult createMessage() + { + string postLinkTemplate() => $"[{historyItem.Post.Url} {historyItem.Post.Title}]"; + string userLinkTemplate() => $"[{historyItem.Giver?.Url} {historyItem.Giver?.Username}]"; + + string message; + + switch (historyItem.Action) + { + case KudosuAction.Give: + message = $"Received {historyItem.Amount} kudosu from {userLinkTemplate()} for a post at {postLinkTemplate()}"; + break; + + case KudosuAction.VoteGive: + message = $"Received {historyItem.Amount} kudosu from obtaining votes in modding post of {postLinkTemplate()}"; + break; + + case KudosuAction.Reset: + message = $"Kudosu reset by {userLinkTemplate()} for the post {postLinkTemplate()}"; + break; + + case KudosuAction.VoteReset: + message = $"Lost {historyItem.Amount} kudosu from losing votes in modding post of {postLinkTemplate()}"; + break; + + case KudosuAction.DenyKudosuReset: + message = $"Denied {historyItem.Amount} kudosu from modding post {postLinkTemplate()}"; + break; + + case KudosuAction.Revoke: + message = $"Denied kudosu by {userLinkTemplate()} for the post {postLinkTemplate()}"; + break; + + default: + message = string.Empty; + break; + } + + return MessageFormatter.FormatText(message); + } } } From 4c9b621f43d9a702be1bd3149758a9e030f781bf Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 18 Aug 2019 22:01:36 +0300 Subject: [PATCH 0752/2815] Fix some user links can't be opened inside the game --- osu.Game/Online/Chat/MessageFormatter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 4aaffdd161..db26945ef3 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -122,6 +122,7 @@ namespace osu.Game.Online.Chat return new LinkDetails(LinkAction.OpenBeatmapSet, args[3]); case "u": + case "users": return new LinkDetails(LinkAction.OpenUserProfile, args[3]); } } From 015406f4d2b7256ee0f7c0c9d2e0bb5445ab86ea Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 18 Aug 2019 22:02:59 +0300 Subject: [PATCH 0753/2815] Fix link parser --- osu.Game/Online/Chat/MessageFormatter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 4aaffdd161..db26945ef3 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -122,6 +122,7 @@ namespace osu.Game.Online.Chat return new LinkDetails(LinkAction.OpenBeatmapSet, args[3]); case "u": + case "users": return new LinkDetails(LinkAction.OpenUserProfile, args[3]); } } From a51fbfa31b52aefb18a8ec9d78f93f475b724de5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Aug 2019 10:46:28 +0900 Subject: [PATCH 0754/2815] Fix osu! default tooltips not displaying --- .../Graphics/Cursor/OsuTooltipContainer.cs | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs index cfcda892fd..57f39bb8c7 100644 --- a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs @@ -30,22 +30,24 @@ namespace osu.Game.Graphics.Cursor private readonly OsuSpriteText text; private bool instantMovement = true; - public override string TooltipText + public override bool SetContent(object content) { - set + if (!(content is string contentString)) + return false; + + if (contentString == text.Text) return true; + + text.Text = contentString; + + if (IsPresent) { - if (value == text.Text) return; - - text.Text = value; - - if (IsPresent) - { - AutoSizeDuration = 250; - background.FlashColour(OsuColour.Gray(0.4f), 1000, Easing.OutQuint); - } - else - AutoSizeDuration = 0; + AutoSizeDuration = 250; + background.FlashColour(OsuColour.Gray(0.4f), 1000, Easing.OutQuint); } + else + AutoSizeDuration = 0; + + return true; } public OsuTooltip() From 1f00793891e1488151e7817edd99cbd9a362dfd7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Aug 2019 11:30:04 +0900 Subject: [PATCH 0755/2815] Unpause music when changing selection at song select --- osu.Game/Overlays/MusicController.cs | 9 +++++++++ osu.Game/Screens/Select/SongSelect.cs | 13 +++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index f6208c46cb..6ad147735b 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -92,6 +92,15 @@ namespace osu.Game.Overlays }); } + /// + /// Start playing the current track (if not already playing). + /// + public void Play() + { + if (!IsPlaying) + TogglePause(); + } + /// /// Toggle pause / play. /// diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 8340814db9..edb0e6deb8 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -359,6 +359,7 @@ namespace osu.Game.Screens.Select return; beatmapNoDebounce = beatmap; + performUpdateSelected(); } @@ -586,10 +587,18 @@ namespace osu.Game.Screens.Select { Track track = Beatmap.Value.Track; - if ((!track.IsRunning || restart) && music?.IsUserPaused != true) + if (!track.IsRunning || restart) { track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; - track.Restart(); + + if (music != null) + { + // use the global music controller (when available) to cancel a potential local user paused state. + music.SeekTo(track.RestartPoint); + music.Play(); + } + else + track.Restart(); } } From 7143497441a95db5f98d81797ed2966058671cf4 Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Mon, 19 Aug 2019 10:32:01 +0700 Subject: [PATCH 0756/2815] Match up tooltip background color with OsuTooltipContainer --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 962cb33a83..3c3a7c056e 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -153,7 +153,7 @@ namespace osu.Game.Beatmaps.Drawables private void load(OsuColour colours) { this.colours = colours; - background.Colour = colours.GreyCarmineDark; + background.Colour = colours.Gray3; } public bool SetContent(object content) From be51fde2925a674d0fd31b6b24bb9e8aeca015ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Aug 2019 13:22:32 +0900 Subject: [PATCH 0757/2815] Centre beatmap panels in profile overlay --- .../Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index 1b6c1c99a6..8a6b52b7ee 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -46,8 +46,11 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps if (!s.OnlineBeatmapSetID.HasValue) continue; - var panel = new DirectGridPanel(s.ToBeatmapSet(Rulesets)); - ItemsContainer.Add(panel); + ItemsContainer.Add(new DirectGridPanel(s.ToBeatmapSet(Rulesets)) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }); } }); From 4356f2ef9fdf072fbacdc7e196edff4659bd9582 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Aug 2019 17:45:54 +0900 Subject: [PATCH 0758/2815] Add sample usage of looping skinnable --- osu.Game/Skinning/LegacySkin.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index b560fd4f84..a41812bad0 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.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; @@ -102,6 +102,10 @@ namespace osu.Game.Skinning return null; + case "Play/osu/sliderfollowcircle": + animatable = true; + break; + case "Play/Miss": componentName = "hit0"; animatable = true; @@ -149,8 +153,6 @@ namespace osu.Game.Skinning texture = GetTexture($"{componentName}-{i}"); } - // This comment can be removed once we have components which are looping - // ReSharper disable once ConditionIsAlwaysTrueOrFalse animation.Repeat = looping; return animation; From d224405bc611aa7f1c4be3df83b7b51136a74716 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Aug 2019 17:46:05 +0900 Subject: [PATCH 0759/2815] Rename const and make more available --- osu.Game/Skinning/LegacySkin.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index a41812bad0..2b9ed2aa8b 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.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; @@ -76,11 +76,12 @@ namespace osu.Game.Skinning Samples?.Dispose(); } + private const double default_frame_time = 1000 / 60d; + public override Drawable GetDrawableComponent(string componentName) { bool animatable = false; bool looping = true; - const double frametime = 1000 / 60d; switch (componentName) { @@ -145,7 +146,7 @@ namespace osu.Game.Skinning if (texture != null && animatable) { - var animation = new TextureAnimation { DefaultFrameLength = frametime }; + var animation = new TextureAnimation { DefaultFrameLength = default_frame_time }; for (int i = 1; texture != null; i++) { From d02b8d14f7e18ebc7292cf30e2243e4268d498e7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Aug 2019 17:47:59 +0900 Subject: [PATCH 0760/2815] Avoid unnecessary texture retrieval for non-animated sprites --- 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 2b9ed2aa8b..d47e100d0e 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -142,9 +142,9 @@ namespace osu.Game.Skinning }; } - var texture = GetTexture($"{componentName}-0"); + Texture texture; - if (texture != null && animatable) + if (animatable && (texture = GetTexture($"{componentName}-0")) != null) { var animation = new TextureAnimation { DefaultFrameLength = default_frame_time }; From 539a27a557738923c4805b5dadfc89bc2be4853f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Aug 2019 19:23:54 +0900 Subject: [PATCH 0761/2815] Refactor texture lookup code --- osu.Game/Skinning/LegacySkin.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index d47e100d0e..94421b1251 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.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; @@ -142,28 +142,31 @@ namespace osu.Game.Skinning }; } + return getAnimation(componentName, animatable, looping); + } + + private Drawable getAnimation(string componentName, bool animatable, bool looping, string animationSeparator = "-") + { Texture texture; - if (animatable && (texture = GetTexture($"{componentName}-0")) != null) + Texture getFrameTexture(int frame) => GetTexture($"{componentName}{animationSeparator}{frame}"); + + if (animatable && (texture = getFrameTexture(0)) != null) { var animation = new TextureAnimation { DefaultFrameLength = default_frame_time }; for (int i = 1; texture != null; i++) { animation.AddFrame(texture); - texture = GetTexture($"{componentName}-{i}"); + texture = getFrameTexture(i); } animation.Repeat = looping; return animation; } - else - { - texture = GetTexture(componentName); - return texture == null ? null : new Sprite { Texture = texture }; - } + return (texture = GetTexture(componentName)) == null ? null : new Sprite { Texture = texture }; } public class LegacySliderBall : Sprite From d0766fa1cdaa15c1a35a096e1364de4455dcefb5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Aug 2019 19:52:53 +0900 Subject: [PATCH 0762/2815] Add slider ball animation support --- .../Objects/Drawables/Pieces/SliderBall.cs | 12 +++++- osu.Game/Skinning/LegacySkin.cs | 42 +++++++++++++------ 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index 8b72b23ca3..332e25750f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -55,7 +55,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Child = new Container { RelativeSizeAxes = Axes.Both, - // TODO: support skin filename animation (sliderb0, sliderb1...) Child = new SkinnableDrawable("Play/osu/sliderball", _ => new DefaultSliderBall()), } } @@ -168,9 +167,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces return action == OsuAction.LeftButton || action == OsuAction.RightButton; } + private Vector2? lastPosition; + public void UpdateProgress(double completionProgress) { - Position = slider.CurvePositionAt(completionProgress); + var newPos = slider.CurvePositionAt(completionProgress); + + var diff = lastPosition.HasValue ? lastPosition.Value - newPos : newPos - slider.CurvePositionAt(completionProgress + 0.01f); + + Position = newPos; + Rotation = 90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI); + + lastPosition = newPos; } private class FollowCircleContainer : Container diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 94421b1251..883e0ce3fc 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.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; @@ -92,8 +92,20 @@ namespace osu.Game.Skinning return null; case "Play/osu/sliderball": - if (GetTexture("sliderb") != null) - return new LegacySliderBall(); + var sliderBallContent = getAnimation("sliderb", true, true, ""); + + if (sliderBallContent != null) + { + var size = sliderBallContent.Size; + + sliderBallContent.RelativeSizeAxes = Axes.Both; + sliderBallContent.Size = Vector2.One; + + return new LegacySliderBall(sliderBallContent) + { + Size = size + }; + } return null; @@ -169,16 +181,6 @@ namespace osu.Game.Skinning return (texture = GetTexture(componentName)) == null ? null : new Sprite { Texture = texture }; } - public class LegacySliderBall : Sprite - { - [BackgroundDependencyLoader] - private void load(ISkinSource skin) - { - Texture = skin.GetTexture("sliderb"); - Colour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? Color4.White; - } - } - public override Texture GetTexture(string componentName) { float ratio = 2; @@ -333,6 +335,20 @@ namespace osu.Game.Skinning } } + public class LegacySliderBall : CompositeDrawable + { + public LegacySliderBall(Drawable content) + { + InternalChild = content; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + Colour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? Color4.White; + } + } + public class LegacyMainCirclePiece : CompositeDrawable { public LegacyMainCirclePiece() From b3556403aac6af43614e429054a4b6672583b700 Mon Sep 17 00:00:00 2001 From: Desconocidosmh Date: Mon, 19 Aug 2019 16:18:25 +0200 Subject: [PATCH 0763/2815] Make GetNextObject() a virtual method --- .../Replays/ManiaAutoGenerator.cs | 24 ++++++++--------- .../Replays/TaikoAutoGenerator.cs | 26 +++++++++++++++---- osu.Game/Rulesets/Replays/AutoGenerator.cs | 9 +++++++ 3 files changed, 42 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 43150958d0..b9dd32208e 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Replays; using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Replays; @@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Mania.Replays for (int i = 0; i < Beatmap.HitObjects.Count; i++) { var currentObject = Beatmap.HitObjects[i]; - var nextObjectInTheSameColumn = getNextObjectInTheSameColumn(i); + var nextObjectInTheSameColumn = GetNextObject(i); double endTime = (currentObject as IHasEndTime)?.EndTime ?? currentObject.StartTime; @@ -94,19 +94,19 @@ namespace osu.Game.Rulesets.Mania.Replays yield return new ReleasePoint { Time = endTime + calculatedDelay, Column = currentObject.Column }; } + } - ManiaHitObject getNextObjectInTheSameColumn(int currentIndex) + protected override HitObject GetNextObject(int currentIndex) + { + int desiredColumn = Beatmap.HitObjects[currentIndex++].Column; + + for (; currentIndex < Beatmap.HitObjects.Count; currentIndex++) { - int desiredColumn = Beatmap.HitObjects[currentIndex++].Column; - - for (; currentIndex < Beatmap.HitObjects.Count; currentIndex++) - { - if (Beatmap.HitObjects[currentIndex].Column == desiredColumn) - return Beatmap.HitObjects[currentIndex]; - } - - return null; + if (Beatmap.HitObjects[currentIndex].Column == desiredColumn) + return Beatmap.HitObjects[currentIndex]; } + + return null; } private interface IActionPoint diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index 1d35393de0..67f93a0259 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -10,6 +10,7 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Taiko.Beatmaps; +using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Taiko.Replays { @@ -113,16 +114,17 @@ namespace osu.Game.Rulesets.Taiko.Replays else throw new InvalidOperationException("Unknown hit object type."); - TaikoHitObject nextHitObject = i < Beatmap.HitObjects.Count - 1 ? Beatmap.HitObjects[i + 1] : null; + var nextHitObject = GetNextObject(i); bool canDelayKeyUp = nextHitObject == null || nextHitObject.StartTime > endTime + KEY_UP_DELAY; - if (canDelayKeyUp) - Frames.Add(new TaikoReplayFrame(endTime + KEY_UP_DELAY)); + double calculatedDelay = canDelayKeyUp ? KEY_UP_DELAY : nextHitObject.StartTime - endTime; - if (nextHitObject != null) + Frames.Add(new TaikoReplayFrame(endTime + calculatedDelay)); + + if (i < Beatmap.HitObjects.Count - 1) { - double waitTime = nextHitObject.StartTime - 1000; + double waitTime = Beatmap.HitObjects[i + 1].StartTime - 1000; if (waitTime > endTime) Frames.Add(new TaikoReplayFrame(waitTime)); } @@ -132,5 +134,19 @@ namespace osu.Game.Rulesets.Taiko.Replays return Replay; } + + protected override HitObject GetNextObject(int currentIndex) + { + Type desiredType = Beatmap.HitObjects[currentIndex++].GetType(); + + for (; currentIndex < Beatmap.HitObjects.Count; currentIndex++) + { + var currentObj = Beatmap.HitObjects[currentIndex]; + if (currentObj.GetType().Equals(desiredType) || currentObj is DrumRoll) + return Beatmap.HitObjects[currentIndex]; + } + + return null; + } } } diff --git a/osu.Game/Rulesets/Replays/AutoGenerator.cs b/osu.Game/Rulesets/Replays/AutoGenerator.cs index 1d4cdbf04c..3319f30a6f 100644 --- a/osu.Game/Rulesets/Replays/AutoGenerator.cs +++ b/osu.Game/Rulesets/Replays/AutoGenerator.cs @@ -3,6 +3,7 @@ using osu.Game.Beatmaps; using osu.Game.Replays; +using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Replays { @@ -34,5 +35,13 @@ namespace osu.Game.Rulesets.Replays protected const double KEY_UP_DELAY = 50; #endregion + + protected virtual HitObject GetNextObject(int currentIndex) + { + if (currentIndex >= Beatmap.HitObjects.Count - 1) + return null; + + return Beatmap.HitObjects[currentIndex + 1]; + } } } From daeefc449c30785ba9db860ddaaf7f7fc7c92c18 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 19 Aug 2019 17:49:53 +0300 Subject: [PATCH 0764/2815] Use another link format to avoid representation issues --- .../Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index 9b68131515..ac5801f989 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -54,7 +54,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu private MessageFormatter.MessageFormatterResult createMessage() { - string postLinkTemplate() => $"[{historyItem.Post.Url} {historyItem.Post.Title}]"; + string postLinkTemplate() => $"({historyItem.Post.Title})[{historyItem.Post.Url}]"; string userLinkTemplate() => $"[{historyItem.Giver?.Url} {historyItem.Giver?.Username}]"; string message; From 6b8fbf0eb1ead2c25e04b8a62ca70aaaf8e42b2c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 19 Aug 2019 17:59:55 +0300 Subject: [PATCH 0765/2815] Change link format back Due to unavaliability to handle round brackets --- .../Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index ac5801f989..9b68131515 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -54,7 +54,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu private MessageFormatter.MessageFormatterResult createMessage() { - string postLinkTemplate() => $"({historyItem.Post.Title})[{historyItem.Post.Url}]"; + string postLinkTemplate() => $"[{historyItem.Post.Url} {historyItem.Post.Title}]"; string userLinkTemplate() => $"[{historyItem.Giver?.Url} {historyItem.Giver?.Username}]"; string message; From eb83e36e0f85dc3b0ca6f4f6ce1c138b6ecc938c Mon Sep 17 00:00:00 2001 From: Desconocidosmh Date: Mon, 19 Aug 2019 17:05:25 +0200 Subject: [PATCH 0766/2815] Make TaikoAutoGenerator unpress sooner --- osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index 67f93a0259..463bb84d07 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Taiko.Replays bool canDelayKeyUp = nextHitObject == null || nextHitObject.StartTime > endTime + KEY_UP_DELAY; - double calculatedDelay = canDelayKeyUp ? KEY_UP_DELAY : nextHitObject.StartTime - endTime; + double calculatedDelay = canDelayKeyUp ? KEY_UP_DELAY : (nextHitObject.StartTime - endTime) * 0.9; Frames.Add(new TaikoReplayFrame(endTime + calculatedDelay)); From 9732f5d6226f739c51e6cae81b706581d5c0291d Mon Sep 17 00:00:00 2001 From: Desconocidosmh Date: Mon, 19 Aug 2019 17:28:32 +0200 Subject: [PATCH 0767/2815] Add Swell to GetNextObject() check --- osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index 463bb84d07..fbc7b75854 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -142,7 +142,8 @@ namespace osu.Game.Rulesets.Taiko.Replays for (; currentIndex < Beatmap.HitObjects.Count; currentIndex++) { var currentObj = Beatmap.HitObjects[currentIndex]; - if (currentObj.GetType().Equals(desiredType) || currentObj is DrumRoll) + if (currentObj.GetType().Equals(desiredType) || + currentObj is DrumRoll || currentObj is Swell) // It's best to unpress any remaining keys before DrumRoll or Swell return Beatmap.HitObjects[currentIndex]; } From 70084b5553102e93fb5d592fb7d24d4672c070b4 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 19 Aug 2019 20:28:03 +0300 Subject: [PATCH 0768/2815] Move HitErrorDisplay outside of the HUD --- osu.Game/Screens/Play/HUDOverlay.cs | 22 ------ .../HitErrorDisplay.cs | 66 +++++------------- .../HitErrorDisplay/HitErrorDisplayOverlay.cs | 69 +++++++++++++++++++ osu.Game/Screens/Play/Player.cs | 4 ++ 4 files changed, 90 insertions(+), 71 deletions(-) rename osu.Game/Screens/Play/{HUD => HitErrorDisplay}/HitErrorDisplay.cs (77%) create mode 100644 osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index be291003b5..1124c8f5f0 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -36,8 +36,6 @@ namespace osu.Game.Screens.Play public readonly ModDisplay ModDisplay; public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; - public readonly HitErrorDisplay LeftHitErrorDisplay; - public readonly HitErrorDisplay RightHitErrorDisplay; public Bindable ShowHealthbar = new Bindable(true); @@ -87,8 +85,6 @@ namespace osu.Game.Screens.Play HealthDisplay = CreateHealthDisplay(), Progress = CreateProgress(), ModDisplay = CreateModsContainer(), - LeftHitErrorDisplay = CreateHitErrorDisplay(false), - RightHitErrorDisplay = CreateHitErrorDisplay(), } }, PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), @@ -261,13 +257,6 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20, Right = 10 }, }; - protected virtual HitErrorDisplay CreateHitErrorDisplay(bool reversed = true) => new HitErrorDisplay(reversed) - { - Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - Margin = new MarginPadding { Horizontal = 20 } - }; - protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay(); protected virtual void BindProcessor(ScoreProcessor processor) @@ -277,17 +266,6 @@ namespace osu.Game.Screens.Play ComboCounter?.Current.BindTo(processor.Combo); HealthDisplay?.Current.BindTo(processor.Health); - var hitWindows = processor.CreateHitWindows(); - - visibilityContainer.ForEach(drawable => - { - if (drawable is HitErrorDisplay) - { - processor.NewJudgement += ((HitErrorDisplay)drawable).OnNewJudgement; - ((HitErrorDisplay)drawable).HitWindows = hitWindows; - } - }); - if (HealthDisplay is StandardHealthDisplay shd) processor.NewJudgement += shd.Flash; } diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs similarity index 77% rename from osu.Game/Screens/Play/HUD/HitErrorDisplay.cs rename to osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs index 1a3682351b..1a776bc78d 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs @@ -10,16 +10,13 @@ using osuTK; using osu.Framework.Graphics.Sprites; using System.Collections.Generic; using osu.Game.Rulesets.Objects; -using osu.Game.Beatmaps; -using osu.Framework.Bindables; using osu.Framework.Allocation; using osu.Framework.Graphics.Colour; using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; using System.Linq; -using osu.Game.Configuration; -namespace osu.Game.Screens.Play.HUD +namespace osu.Game.Screens.Play.HitErrorDisplay { public class HitErrorDisplay : CompositeDrawable { @@ -27,29 +24,24 @@ namespace osu.Game.Screens.Play.HUD private const int bar_width = 3; private const int judgement_line_width = 8; private const int bar_height = 200; - private const int fade_duration = 200; private const int arrow_move_duration = 500; private const int judgement_life_time = 10000; private const int spacing = 3; - public HitWindows HitWindows { get; set; } - - [Resolved] - private Bindable beatmap { get; set; } - - [Resolved] - private OsuColour colours { get; set; } - + private readonly HitWindows hitWindows; private readonly SpriteIcon arrow; private readonly FillFlowContainer bar; private readonly Container judgementsContainer; private readonly Queue judgementOffsets = new Queue(); - private readonly Bindable type = new Bindable(); - - public HitErrorDisplay(bool reversed = false) + public HitErrorDisplay(float overallDifficulty, HitWindows hitWindows, bool reversed = false) { + this.hitWindows = hitWindows; + hitWindows.SetDifficulty(overallDifficulty); + AutoSizeAxes = Axes.Both; + Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft; + Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft; AddInternal(new FillFlowContainer { @@ -94,74 +86,50 @@ namespace osu.Game.Screens.Play.HUD } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuColour colours) { - config.BindWith(OsuSetting.ScoreMeter, type); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - HitWindows.SetDifficulty(beatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty); - bar.AddRange(new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, Colour = ColourInfo.GradientVertical(colours.Yellow.Opacity(0), colours.Yellow), - Height = (float)((getMehHitWindows() - HitWindows.Good) / (getMehHitWindows() * 2)) + Height = (float)((getMehHitWindows() - hitWindows.Good) / (getMehHitWindows() * 2)) }, new Box { RelativeSizeAxes = Axes.Both, Colour = colours.Green, - Height = (float)((HitWindows.Good - HitWindows.Great) / (getMehHitWindows() * 2)) + Height = (float)((hitWindows.Good - hitWindows.Great) / (getMehHitWindows() * 2)) }, new Box { RelativeSizeAxes = Axes.Both, Colour = colours.BlueLight, - Height = (float)(HitWindows.Great / getMehHitWindows()) + Height = (float)(hitWindows.Great / getMehHitWindows()) }, new Box { RelativeSizeAxes = Axes.Both, Colour = colours.Green, - Height = (float)((HitWindows.Good - HitWindows.Great) / (getMehHitWindows() * 2)) + Height = (float)((hitWindows.Good - hitWindows.Great) / (getMehHitWindows() * 2)) }, new Box { RelativeSizeAxes = Axes.Both, Colour = ColourInfo.GradientVertical(colours.Yellow, colours.Yellow.Opacity(0)), - Height = (float)((getMehHitWindows() - HitWindows.Good) / (getMehHitWindows() * 2)) + Height = (float)((getMehHitWindows() - hitWindows.Good) / (getMehHitWindows() * 2)) } }); - - type.BindValueChanged(onTypeChanged, true); } private double getMehHitWindows() { // In case if ruleset has no Meh hit windows (like Taiko) - if (HitWindows.Meh == 0) - return HitWindows.Good + 40; + if (hitWindows.Meh == 0) + return hitWindows.Good + 40; - return HitWindows.Meh; - } - - private void onTypeChanged(ValueChangedEvent type) - { - switch (type.NewValue) - { - case ScoreMeterType.None: - this.FadeOut(fade_duration, Easing.OutQuint); - break; - - case ScoreMeterType.HitError: - this.FadeIn(fade_duration, Easing.OutQuint); - break; - } + return hitWindows.Meh; } public void OnNewJudgement(JudgementResult newJudgement) diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs new file mode 100644 index 0000000000..09ecbdfc66 --- /dev/null +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.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.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Scoring; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Framework.Extensions.IEnumerableExtensions; +using System.Linq; + +namespace osu.Game.Screens.Play.HitErrorDisplay +{ + public class HitErrorDisplayOverlay : Container + { + private const int fade_duration = 200; + private const int margin = 10; + + private readonly Bindable type = new Bindable(); + + public HitErrorDisplayOverlay(ScoreProcessor processor, WorkingBeatmap workingBeatmap) + { + float overallDifficulty = workingBeatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty; + + RelativeSizeAxes = Axes.Both; + Children = new[] + { + new HitErrorDisplay(overallDifficulty, processor.CreateHitWindows()) + { + Margin = new MarginPadding { Left = margin } + }, + new HitErrorDisplay(overallDifficulty, processor.CreateHitWindows(), true) + { + Margin = new MarginPadding { Right = margin } + }, + }; + + Children.ForEach(t => processor.NewJudgement += t.OnNewJudgement); + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + config.BindWith(OsuSetting.ScoreMeter, type); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + type.BindValueChanged(onTypeChanged, true); + } + + private void onTypeChanged(ValueChangedEvent type) + { + switch (type.NewValue) + { + case ScoreMeterType.None: + InternalChildren.ForEach(t => t.FadeOut(fade_duration, Easing.OutQuint)); + break; + + default: + InternalChildren.ForEach(t => t.FadeIn(fade_duration, Easing.OutQuint)); + break; + } + } + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e7398be176..9ef8cc4509 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -24,6 +24,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; +using osu.Game.Screens.Play.HitErrorDisplay; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osu.Game.Users; @@ -73,6 +74,8 @@ namespace osu.Game.Screens.Play protected HUDOverlay HUDOverlay { get; private set; } + protected HitErrorDisplayOverlay HitErrorDisplayOverlay { get; private set; } + public bool LoadedBeatmapSuccessfully => DrawableRuleset?.Objects.Any() == true; protected GameplayClockContainer GameplayClockContainer { get; private set; } @@ -157,6 +160,7 @@ namespace osu.Game.Screens.Play Anchor = Anchor.Centre, Origin = Anchor.Centre }, + HitErrorDisplayOverlay = new HitErrorDisplayOverlay(ScoreProcessor, working), new SkipOverlay(DrawableRuleset.GameplayStartTime) { RequestSeek = GameplayClockContainer.Seek From 6d3aa0520b6cc6bc999809b123b600414314260f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 19 Aug 2019 20:44:06 +0300 Subject: [PATCH 0769/2815] Make HitErrorDisplay an abstract class --- .../HitErrorDisplay/DefaultHitErrorDisplay.cs | 174 ++++++++++++++++++ .../Play/HitErrorDisplay/HitErrorDisplay.cs | 167 +---------------- .../HitErrorDisplay/HitErrorDisplayOverlay.cs | 4 +- 3 files changed, 182 insertions(+), 163 deletions(-) create mode 100644 osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs diff --git a/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs new file mode 100644 index 0000000000..6cf80b209a --- /dev/null +++ b/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs @@ -0,0 +1,174 @@ +// 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.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Judgements; +using osuTK.Graphics; +using osuTK; +using osu.Framework.Graphics.Sprites; +using System.Collections.Generic; +using osu.Game.Rulesets.Objects; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Colour; +using osu.Game.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using System.Linq; + +namespace osu.Game.Screens.Play.HitErrorDisplay +{ + public class DefaultHitErrorDisplay : HitErrorDisplay + { + private const int stored_judgements_amount = 5; + private const int bar_width = 3; + private const int judgement_line_width = 8; + private const int bar_height = 200; + private const int arrow_move_duration = 500; + private const int judgement_life_time = 10000; + private const int spacing = 3; + + private readonly SpriteIcon arrow; + private readonly FillFlowContainer bar; + private readonly Container judgementsContainer; + private readonly Queue judgementOffsets = new Queue(); + + public DefaultHitErrorDisplay(float overallDifficulty, HitWindows hitWindows, bool reversed = false) + : base(overallDifficulty, hitWindows) + { + AutoSizeAxes = Axes.Both; + Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft; + Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft; + + AddInternal(new FillFlowContainer + { + AutoSizeAxes = Axes.X, + Height = bar_height, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(spacing, 0), + Children = new Drawable[] + { + judgementsContainer = new Container + { + Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Width = judgement_line_width, + RelativeSizeAxes = Axes.Y, + }, + bar = new FillFlowContainer + { + Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Width = bar_width, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + }, + new Container + { + Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Child = arrow = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.Y, + Icon = reversed ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, + Size = new Vector2(8), + } + }, + } + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + bar.AddRange(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(colours.Yellow.Opacity(0), colours.Yellow), + Height = (float)((getMehHitWindows() - HitWindows.Good) / (getMehHitWindows() * 2)) + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Green, + Height = (float)((HitWindows.Good - HitWindows.Great) / (getMehHitWindows() * 2)) + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.BlueLight, + Height = (float)(HitWindows.Great / getMehHitWindows()) + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Green, + Height = (float)((HitWindows.Good - HitWindows.Great) / (getMehHitWindows() * 2)) + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(colours.Yellow, colours.Yellow.Opacity(0)), + Height = (float)((getMehHitWindows() - HitWindows.Good) / (getMehHitWindows() * 2)) + } + }); + } + + private double getMehHitWindows() + { + // In case if ruleset has no Meh hit windows (like Taiko) + if (HitWindows.Meh == 0) + return HitWindows.Good + 40; + + return HitWindows.Meh; + } + + public override void OnNewJudgement(JudgementResult newJudgement) + { + if (!newJudgement.IsHit) + return; + + var judgementLine = CreateJudgementLine(newJudgement); + + judgementsContainer.Add(judgementLine); + + judgementLine.FadeOut(judgement_life_time, Easing.OutQuint).Expire(); + + arrow.MoveToY(calculateArrowPosition(newJudgement), arrow_move_duration, Easing.OutQuint); + } + + protected virtual Container CreateJudgementLine(JudgementResult judgement) => new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + RelativeSizeAxes = Axes.X, + Height = 2, + RelativePositionAxes = Axes.Y, + Y = getRelativeJudgementPosition(judgement.TimeOffset), + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + } + }; + + private float getRelativeJudgementPosition(double value) => (float)(value / getMehHitWindows()); + + private float calculateArrowPosition(JudgementResult newJudgement) + { + if (judgementOffsets.Count > stored_judgements_amount) + judgementOffsets.Dequeue(); + + judgementOffsets.Enqueue(newJudgement.TimeOffset); + + return getRelativeJudgementPosition(judgementOffsets.Average()); + } + } +} diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs index 1a776bc78d..fb22b35736 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs @@ -1,177 +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.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Judgements; -using osuTK.Graphics; -using osuTK; -using osu.Framework.Graphics.Sprites; -using System.Collections.Generic; using osu.Game.Rulesets.Objects; -using osu.Framework.Allocation; -using osu.Framework.Graphics.Colour; -using osu.Game.Graphics; -using osu.Framework.Extensions.Color4Extensions; -using System.Linq; namespace osu.Game.Screens.Play.HitErrorDisplay { - public class HitErrorDisplay : CompositeDrawable + public abstract class HitErrorDisplay : CompositeDrawable { - private const int stored_judgements_amount = 5; - private const int bar_width = 3; - private const int judgement_line_width = 8; - private const int bar_height = 200; - private const int arrow_move_duration = 500; - private const int judgement_life_time = 10000; - private const int spacing = 3; + protected readonly HitWindows HitWindows; - private readonly HitWindows hitWindows; - private readonly SpriteIcon arrow; - private readonly FillFlowContainer bar; - private readonly Container judgementsContainer; - private readonly Queue judgementOffsets = new Queue(); - - public HitErrorDisplay(float overallDifficulty, HitWindows hitWindows, bool reversed = false) + public HitErrorDisplay(float overallDifficulty, HitWindows hitWindows) { - this.hitWindows = hitWindows; - hitWindows.SetDifficulty(overallDifficulty); - - AutoSizeAxes = Axes.Both; - Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft; - Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft; - - AddInternal(new FillFlowContainer - { - AutoSizeAxes = Axes.X, - Height = bar_height, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(spacing, 0), - Children = new Drawable[] - { - judgementsContainer = new Container - { - Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - Width = judgement_line_width, - RelativeSizeAxes = Axes.Y, - }, - bar = new FillFlowContainer - { - Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - Width = bar_width, - RelativeSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - }, - new Container - { - Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Child = arrow = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.Y, - Icon = reversed ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, - Size = new Vector2(8), - } - }, - } - }); + HitWindows = hitWindows; + HitWindows.SetDifficulty(overallDifficulty); } - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - bar.AddRange(new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(colours.Yellow.Opacity(0), colours.Yellow), - Height = (float)((getMehHitWindows() - hitWindows.Good) / (getMehHitWindows() * 2)) - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Green, - Height = (float)((hitWindows.Good - hitWindows.Great) / (getMehHitWindows() * 2)) - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.BlueLight, - Height = (float)(hitWindows.Great / getMehHitWindows()) - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Green, - Height = (float)((hitWindows.Good - hitWindows.Great) / (getMehHitWindows() * 2)) - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(colours.Yellow, colours.Yellow.Opacity(0)), - Height = (float)((getMehHitWindows() - hitWindows.Good) / (getMehHitWindows() * 2)) - } - }); - } - - private double getMehHitWindows() - { - // In case if ruleset has no Meh hit windows (like Taiko) - if (hitWindows.Meh == 0) - return hitWindows.Good + 40; - - return hitWindows.Meh; - } - - public void OnNewJudgement(JudgementResult newJudgement) - { - if (!newJudgement.IsHit) - return; - - var judgementLine = CreateJudgementLine(newJudgement); - - judgementsContainer.Add(judgementLine); - - judgementLine.FadeOut(judgement_life_time, Easing.OutQuint).Expire(); - - arrow.MoveToY(calculateArrowPosition(newJudgement), arrow_move_duration, Easing.OutQuint); - } - - protected virtual Container CreateJudgementLine(JudgementResult judgement) => new CircularContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Masking = true, - RelativeSizeAxes = Axes.X, - Height = 2, - RelativePositionAxes = Axes.Y, - Y = getRelativeJudgementPosition(judgement.TimeOffset), - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - } - }; - - private float getRelativeJudgementPosition(double value) => (float)(value / getMehHitWindows()); - - private float calculateArrowPosition(JudgementResult newJudgement) - { - if (judgementOffsets.Count > stored_judgements_amount) - judgementOffsets.Dequeue(); - - judgementOffsets.Enqueue(newJudgement.TimeOffset); - - return getRelativeJudgementPosition(judgementOffsets.Average()); - } + public abstract void OnNewJudgement(JudgementResult newJudgement); } } diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs index 09ecbdfc66..35b883ff27 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs @@ -27,11 +27,11 @@ namespace osu.Game.Screens.Play.HitErrorDisplay RelativeSizeAxes = Axes.Both; Children = new[] { - new HitErrorDisplay(overallDifficulty, processor.CreateHitWindows()) + new DefaultHitErrorDisplay(overallDifficulty, processor.CreateHitWindows()) { Margin = new MarginPadding { Left = margin } }, - new HitErrorDisplay(overallDifficulty, processor.CreateHitWindows(), true) + new DefaultHitErrorDisplay(overallDifficulty, processor.CreateHitWindows(), true) { Margin = new MarginPadding { Right = margin } }, From 9bd844bf4d41c6dd9b82aa7cbc27079b738b35d2 Mon Sep 17 00:00:00 2001 From: Desconocidosmh Date: Mon, 19 Aug 2019 19:54:22 +0200 Subject: [PATCH 0770/2815] Make comment more informative --- osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index fbc7b75854..4d6730c0e6 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Taiko.Replays { var currentObj = Beatmap.HitObjects[currentIndex]; if (currentObj.GetType().Equals(desiredType) || - currentObj is DrumRoll || currentObj is Swell) // It's best to unpress any remaining keys before DrumRoll or Swell + currentObj is DrumRoll || currentObj is Swell) // Unpress all keys before DrumRoll or Swell return Beatmap.HitObjects[currentIndex]; } From 6d84523bc0d87ba814ddddf1edcdff53812a48be Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 19 Aug 2019 21:10:12 +0300 Subject: [PATCH 0771/2815] Add testing --- .../Gameplay/TestSceneHitErrorDisplay.cs | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs new file mode 100644 index 0000000000..c5e5e96ad9 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs @@ -0,0 +1,110 @@ +// 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.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Screens.Play.HitErrorDisplay; +using System; +using System.Collections.Generic; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; +using osu.Framework.MathUtils; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneHitErrorDisplay : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(HitErrorDisplay), + }; + + private HitErrorDisplay display; + + public TestSceneHitErrorDisplay() + { + recreateDisplay(new OsuHitWindows(), 5); + AddStep("New random judgement", () => newJudgement()); + AddStep("New fixed judgement (50ms)", () => newJudgement(50)); + } + + [Test] + public void TestOsu() + { + AddStep("OD 1", () => recreateDisplay(new OsuHitWindows(), 1)); + AddStep("OD 2", () => recreateDisplay(new OsuHitWindows(), 2)); + AddStep("OD 3", () => recreateDisplay(new OsuHitWindows(), 3)); + AddStep("OD 4", () => recreateDisplay(new OsuHitWindows(), 4)); + AddStep("OD 5", () => recreateDisplay(new OsuHitWindows(), 5)); + AddStep("OD 6", () => recreateDisplay(new OsuHitWindows(), 6)); + AddStep("OD 7", () => recreateDisplay(new OsuHitWindows(), 7)); + AddStep("OD 8", () => recreateDisplay(new OsuHitWindows(), 8)); + AddStep("OD 9", () => recreateDisplay(new OsuHitWindows(), 9)); + AddStep("OD 10", () => recreateDisplay(new OsuHitWindows(), 10)); + } + + [Test] + public void TestTaiko() + { + AddStep("OD 1", () => recreateDisplay(new TaikoHitWindows(), 1)); + AddStep("OD 2", () => recreateDisplay(new TaikoHitWindows(), 2)); + AddStep("OD 3", () => recreateDisplay(new TaikoHitWindows(), 3)); + AddStep("OD 4", () => recreateDisplay(new TaikoHitWindows(), 4)); + AddStep("OD 5", () => recreateDisplay(new TaikoHitWindows(), 5)); + AddStep("OD 6", () => recreateDisplay(new TaikoHitWindows(), 6)); + AddStep("OD 7", () => recreateDisplay(new TaikoHitWindows(), 7)); + AddStep("OD 8", () => recreateDisplay(new TaikoHitWindows(), 8)); + AddStep("OD 9", () => recreateDisplay(new TaikoHitWindows(), 9)); + AddStep("OD 10", () => recreateDisplay(new TaikoHitWindows(), 10)); + } + + [Test] + public void TestMania() + { + AddStep("OD 1", () => recreateDisplay(new ManiaHitWindows(), 1)); + AddStep("OD 2", () => recreateDisplay(new ManiaHitWindows(), 2)); + AddStep("OD 3", () => recreateDisplay(new ManiaHitWindows(), 3)); + AddStep("OD 4", () => recreateDisplay(new ManiaHitWindows(), 4)); + AddStep("OD 5", () => recreateDisplay(new ManiaHitWindows(), 5)); + AddStep("OD 6", () => recreateDisplay(new ManiaHitWindows(), 6)); + AddStep("OD 7", () => recreateDisplay(new ManiaHitWindows(), 7)); + AddStep("OD 8", () => recreateDisplay(new ManiaHitWindows(), 8)); + AddStep("OD 9", () => recreateDisplay(new ManiaHitWindows(), 9)); + AddStep("OD 10", () => recreateDisplay(new ManiaHitWindows(), 10)); + } + + [Test] + public void TestCatch() + { + AddStep("OD 1", () => recreateDisplay(new CatchHitWindows(), 1)); + AddStep("OD 2", () => recreateDisplay(new CatchHitWindows(), 2)); + AddStep("OD 3", () => recreateDisplay(new CatchHitWindows(), 3)); + AddStep("OD 4", () => recreateDisplay(new CatchHitWindows(), 4)); + AddStep("OD 5", () => recreateDisplay(new CatchHitWindows(), 5)); + AddStep("OD 6", () => recreateDisplay(new CatchHitWindows(), 6)); + AddStep("OD 7", () => recreateDisplay(new CatchHitWindows(), 7)); + AddStep("OD 8", () => recreateDisplay(new CatchHitWindows(), 8)); + AddStep("OD 9", () => recreateDisplay(new CatchHitWindows(), 9)); + AddStep("OD 10", () => recreateDisplay(new CatchHitWindows(), 10)); + } + + private void recreateDisplay(HitWindows hitWindows, float overallDifficulty) + { + Clear(); + Add(display = new DefaultHitErrorDisplay(overallDifficulty, hitWindows)); + } + + private void newJudgement(float offset = 0) + { + display?.OnNewJudgement(new JudgementResult(new Judgement()) + { + TimeOffset = offset == 0 ? RNG.Next(-70, 70) : offset, + Type = HitResult.Perfect, + }); + } + } +} From 1bff103d3244aff12c6faf83454cb43bbfd2d852 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 19 Aug 2019 21:25:14 +0300 Subject: [PATCH 0772/2815] CI fixes --- .../Visual/Gameplay/TestSceneHitErrorDisplay.cs | 7 ++++++- osu.Game/Screens/Play/HUDOverlay.cs | 1 - .../Play/HitErrorDisplay/DefaultHitErrorDisplay.cs | 2 -- osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs | 2 +- .../Play/HitErrorDisplay/HitErrorDisplayOverlay.cs | 9 ++++++--- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs index c5e5e96ad9..e86606e4fc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs @@ -13,6 +13,7 @@ using System.Collections.Generic; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Framework.MathUtils; +using osu.Framework.Graphics; namespace osu.Game.Tests.Visual.Gameplay { @@ -95,7 +96,11 @@ namespace osu.Game.Tests.Visual.Gameplay private void recreateDisplay(HitWindows hitWindows, float overallDifficulty) { Clear(); - Add(display = new DefaultHitErrorDisplay(overallDifficulty, hitWindows)); + Add(display = new DefaultHitErrorDisplay(overallDifficulty, hitWindows) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); } private void newJudgement(float offset = 0) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 1124c8f5f0..43b9491750 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.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.Graphics.Containers; using osu.Framework.Input.Events; diff --git a/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs index 6cf80b209a..a0b9787c5f 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs @@ -37,8 +37,6 @@ namespace osu.Game.Screens.Play.HitErrorDisplay : base(overallDifficulty, hitWindows) { AutoSizeAxes = Axes.Both; - Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft; - Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft; AddInternal(new FillFlowContainer { diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs index fb22b35736..10b5e5b1cb 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs @@ -11,7 +11,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay { protected readonly HitWindows HitWindows; - public HitErrorDisplay(float overallDifficulty, HitWindows hitWindows) + protected HitErrorDisplay(float overallDifficulty, HitWindows hitWindows) { HitWindows = hitWindows; HitWindows.SetDifficulty(overallDifficulty); diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs index 35b883ff27..fd118a26e5 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs @@ -9,7 +9,6 @@ using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Framework.Extensions.IEnumerableExtensions; -using System.Linq; namespace osu.Game.Screens.Play.HitErrorDisplay { @@ -29,11 +28,15 @@ namespace osu.Game.Screens.Play.HitErrorDisplay { new DefaultHitErrorDisplay(overallDifficulty, processor.CreateHitWindows()) { - Margin = new MarginPadding { Left = margin } + Margin = new MarginPadding { Left = margin }, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, }, new DefaultHitErrorDisplay(overallDifficulty, processor.CreateHitWindows(), true) { - Margin = new MarginPadding { Right = margin } + Margin = new MarginPadding { Right = margin }, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, }, }; From 77e9e89fecdcac72fd34e607fe4f909b2b83cbf0 Mon Sep 17 00:00:00 2001 From: Desconocidosmh Date: Mon, 19 Aug 2019 20:45:23 +0200 Subject: [PATCH 0773/2815] Refactoring --- osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs | 8 ++++---- osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index b9dd32208e..fd0a876775 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -81,14 +81,14 @@ namespace osu.Game.Rulesets.Mania.Replays for (int i = 0; i < Beatmap.HitObjects.Count; i++) { var currentObject = Beatmap.HitObjects[i]; - var nextObjectInTheSameColumn = GetNextObject(i); + var nextObjectInColumn = GetNextObject(i); // Get the next object that requires pressing the same button double endTime = (currentObject as IHasEndTime)?.EndTime ?? currentObject.StartTime; - bool canDelayKeyUp = nextObjectInTheSameColumn == null || - nextObjectInTheSameColumn.StartTime > endTime + RELEASE_DELAY; + bool canDelayKeyUp = nextObjectInColumn == null || + nextObjectInColumn.StartTime > endTime + RELEASE_DELAY; - double calculatedDelay = canDelayKeyUp ? RELEASE_DELAY : (nextObjectInTheSameColumn.StartTime - endTime) * 0.9; + double calculatedDelay = canDelayKeyUp ? RELEASE_DELAY : (nextObjectInColumn.StartTime - endTime) * 0.9; yield return new HitPoint { Time = currentObject.StartTime, Column = currentObject.Column }; diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index 4d6730c0e6..6720d8b8bf 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Taiko.Replays else throw new InvalidOperationException("Unknown hit object type."); - var nextHitObject = GetNextObject(i); + var nextHitObject = GetNextObject(i); // Get the next object that requires pressing the same button bool canDelayKeyUp = nextHitObject == null || nextHitObject.StartTime > endTime + KEY_UP_DELAY; @@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Taiko.Replays for (; currentIndex < Beatmap.HitObjects.Count; currentIndex++) { var currentObj = Beatmap.HitObjects[currentIndex]; - if (currentObj.GetType().Equals(desiredType) || + if (currentObj.GetType() == desiredType || currentObj is DrumRoll || currentObj is Swell) // Unpress all keys before DrumRoll or Swell return Beatmap.HitObjects[currentIndex]; } From f1c3a60660d035128c8f3e938f88b216d4ab8669 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 19 Aug 2019 22:04:27 +0300 Subject: [PATCH 0774/2815] Add ability to select side --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- osu.Game/Configuration/ScoreMeterType.cs | 10 ++- .../HitErrorDisplay/HitErrorDisplayOverlay.cs | 66 +++++++++++++------ 3 files changed, 54 insertions(+), 24 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index bffbce2a52..fbb17fa7f9 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -79,7 +79,7 @@ namespace osu.Game.Configuration Set(OsuSetting.ShowInterface, true); Set(OsuSetting.ShowHealthDisplayWhenCantFail, true); Set(OsuSetting.KeyOverlay, false); - Set(OsuSetting.ScoreMeter, ScoreMeterType.HitError); + Set(OsuSetting.ScoreMeter, ScoreMeterType.HitErrorBoth); Set(OsuSetting.FloatingComments, false); diff --git a/osu.Game/Configuration/ScoreMeterType.cs b/osu.Game/Configuration/ScoreMeterType.cs index e78220c9c9..b85ef9309d 100644 --- a/osu.Game/Configuration/ScoreMeterType.cs +++ b/osu.Game/Configuration/ScoreMeterType.cs @@ -10,7 +10,13 @@ namespace osu.Game.Configuration [Description("None")] None, - [Description("Hit Error")] - HitError + [Description("Hit Error (left)")] + HitErrorLeft, + + [Description("Hit Error (right)")] + HitErrorRight, + + [Description("Hit Error (both)")] + HitErrorBoth, } } diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs index fd118a26e5..bbed2b1618 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Play.HitErrorDisplay { @@ -18,29 +19,18 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private const int margin = 10; private readonly Bindable type = new Bindable(); + private readonly HitWindows hitWindows; + private readonly ScoreProcessor processor; + private readonly float overallDifficulty; public HitErrorDisplayOverlay(ScoreProcessor processor, WorkingBeatmap workingBeatmap) { - float overallDifficulty = workingBeatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty; + this.processor = processor; + + overallDifficulty = workingBeatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty; + hitWindows = processor.CreateHitWindows(); RelativeSizeAxes = Axes.Both; - Children = new[] - { - new DefaultHitErrorDisplay(overallDifficulty, processor.CreateHitWindows()) - { - Margin = new MarginPadding { Left = margin }, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - new DefaultHitErrorDisplay(overallDifficulty, processor.CreateHitWindows(), true) - { - Margin = new MarginPadding { Right = margin }, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - }, - }; - - Children.ForEach(t => processor.NewJudgement += t.OnNewJudgement); } [BackgroundDependencyLoader] @@ -57,16 +47,50 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private void onTypeChanged(ValueChangedEvent type) { + clear(); + switch (type.NewValue) { case ScoreMeterType.None: - InternalChildren.ForEach(t => t.FadeOut(fade_duration, Easing.OutQuint)); break; - default: - InternalChildren.ForEach(t => t.FadeIn(fade_duration, Easing.OutQuint)); + case ScoreMeterType.HitErrorBoth: + createNew(); + createNew(true); + break; + + case ScoreMeterType.HitErrorLeft: + createNew(); + break; + + case ScoreMeterType.HitErrorRight: + createNew(true); break; } } + + private void clear() + { + Children.ForEach(t => + { + processor.NewJudgement -= t.OnNewJudgement; + t.FadeOut(fade_duration, Easing.OutQuint).Expire(); + }); + } + + private void createNew(bool reversed = false) + { + var display = new DefaultHitErrorDisplay(overallDifficulty, hitWindows, reversed) + { + Margin = new MarginPadding(margin), + Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Alpha = 0, + }; + + processor.NewJudgement += display.OnNewJudgement; + Add(display); + display.FadeInFromZero(fade_duration, Easing.OutQuint); + } } } From 50c47568e434f5a68002d40610d1618f221fbd39 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 19 Aug 2019 22:45:27 +0300 Subject: [PATCH 0775/2815] Don't present Meh hit windows if it has no value --- .../Gameplay/TestSceneHitErrorDisplay.cs | 19 +++++++ .../HitErrorDisplay/DefaultHitErrorDisplay.cs | 54 ++++++++++--------- 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs index e86606e4fc..006773a091 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs @@ -14,6 +14,8 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Framework.MathUtils; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Tests.Visual.Gameplay { @@ -95,7 +97,24 @@ namespace osu.Game.Tests.Visual.Gameplay private void recreateDisplay(HitWindows hitWindows, float overallDifficulty) { + hitWindows.SetDifficulty(overallDifficulty); + Clear(); + + Add(new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Children = new[] + { + new SpriteText { Text = $@"Great: {hitWindows.Great}" }, + new SpriteText { Text = $@"Good: {hitWindows.Good}" }, + new SpriteText { Text = $@"Meh: {hitWindows.Meh}" }, + } + }); + Add(display = new DefaultHitErrorDisplay(overallDifficulty, hitWindows) { Anchor = Anchor.Centre, diff --git a/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs index a0b9787c5f..11de0696d3 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs @@ -32,12 +32,14 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private readonly FillFlowContainer bar; private readonly Container judgementsContainer; private readonly Queue judgementOffsets = new Queue(); + private readonly double maxHitWindows; public DefaultHitErrorDisplay(float overallDifficulty, HitWindows hitWindows, bool reversed = false) : base(overallDifficulty, hitWindows) { - AutoSizeAxes = Axes.Both; + maxHitWindows = HitWindows.Meh == 0 ? HitWindows.Good : HitWindows.Meh; + AutoSizeAxes = Axes.Both; AddInternal(new FillFlowContainer { AutoSizeAxes = Axes.X, @@ -83,48 +85,52 @@ namespace osu.Game.Screens.Play.HitErrorDisplay [BackgroundDependencyLoader] private void load(OsuColour colours) { - bar.AddRange(new Drawable[] - { - new Box + Box topGreenBox; + Box bottomGreenBox; + + if (HitWindows.Meh != 0) + bar.Add(new Box { RelativeSizeAxes = Axes.Both, Colour = ColourInfo.GradientVertical(colours.Yellow.Opacity(0), colours.Yellow), - Height = (float)((getMehHitWindows() - HitWindows.Good) / (getMehHitWindows() * 2)) - }, - new Box + Height = (float)((maxHitWindows - HitWindows.Good) / (maxHitWindows * 2)) + }); + + bar.AddRange(new Drawable[] + { + topGreenBox = new Box { RelativeSizeAxes = Axes.Both, Colour = colours.Green, - Height = (float)((HitWindows.Good - HitWindows.Great) / (getMehHitWindows() * 2)) + Height = (float)((HitWindows.Good - HitWindows.Great) / (maxHitWindows * 2)) }, new Box { RelativeSizeAxes = Axes.Both, Colour = colours.BlueLight, - Height = (float)(HitWindows.Great / getMehHitWindows()) + Height = (float)(HitWindows.Great / maxHitWindows) }, - new Box + bottomGreenBox = new Box { RelativeSizeAxes = Axes.Both, Colour = colours.Green, - Height = (float)((HitWindows.Good - HitWindows.Great) / (getMehHitWindows() * 2)) - }, - new Box + Height = (float)((HitWindows.Good - HitWindows.Great) / (maxHitWindows * 2)) + } + });; + + if (HitWindows.Meh != 0) + bar.Add(new Box { RelativeSizeAxes = Axes.Both, Colour = ColourInfo.GradientVertical(colours.Yellow, colours.Yellow.Opacity(0)), - Height = (float)((getMehHitWindows() - HitWindows.Good) / (getMehHitWindows() * 2)) - } - }); - } + Height = (float)((maxHitWindows - HitWindows.Good) / (maxHitWindows * 2)) + }); - private double getMehHitWindows() - { - // In case if ruleset has no Meh hit windows (like Taiko) if (HitWindows.Meh == 0) - return HitWindows.Good + 40; - - return HitWindows.Meh; + { + topGreenBox.Colour = ColourInfo.GradientVertical(colours.Green.Opacity(0), colours.Green); + bottomGreenBox.Colour = ColourInfo.GradientVertical(colours.Green, colours.Green.Opacity(0)); + } } public override void OnNewJudgement(JudgementResult newJudgement) @@ -157,7 +163,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay } }; - private float getRelativeJudgementPosition(double value) => (float)(value / getMehHitWindows()); + private float getRelativeJudgementPosition(double value) => (float)(value / maxHitWindows); private float calculateArrowPosition(JudgementResult newJudgement) { From 415f1802614dbcd96541746a02a8a61500ba07bb Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 19 Aug 2019 22:53:28 +0300 Subject: [PATCH 0776/2815] Delete extra semicolon --- osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs index 11de0696d3..2d05dc8aba 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs @@ -116,7 +116,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay Colour = colours.Green, Height = (float)((HitWindows.Good - HitWindows.Great) / (maxHitWindows * 2)) } - });; + }); if (HitWindows.Meh != 0) bar.Add(new Box From 62a24bf16d38ffbf9460a8025748c012b20d1939 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Aug 2019 13:18:34 +0900 Subject: [PATCH 0777/2815] Fix a couple of rotation-related issues --- osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs | 4 +++- 1 file changed, 3 insertions(+), 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 332e25750f..02505c3ec0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -174,9 +174,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces var newPos = slider.CurvePositionAt(completionProgress); var diff = lastPosition.HasValue ? lastPosition.Value - newPos : newPos - slider.CurvePositionAt(completionProgress + 0.01f); + if (diff == Vector2.Zero) + return; Position = newPos; - Rotation = 90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI); + Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI); lastPosition = newPos; } From 550311698b74769f95f35f130f72ae84e17c775d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Aug 2019 13:18:59 +0900 Subject: [PATCH 0778/2815] Update slider test scene --- .../TestSceneSlider.cs | 160 ++++++++++-------- 1 file changed, 86 insertions(+), 74 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index c5a27205d6..29c71a8903 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -10,7 +10,6 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Tests.Visual; using osuTK; using osuTK.Graphics; using osu.Game.Rulesets.Mods; @@ -27,83 +26,96 @@ using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestSceneSlider : OsuTestScene + public class TestSceneSlider : SkinnableTestScene { public override IReadOnlyList RequiredTypes => new[] { + typeof(Slider), + typeof(SliderTick), + typeof(SliderTailCircle), typeof(SliderBall), typeof(SliderBody), - typeof(SliderTick), + typeof(SnakingSliderBody), typeof(DrawableSlider), typeof(DrawableSliderTick), + typeof(DrawableSliderTail), + typeof(DrawableSliderHead), typeof(DrawableRepeatPoint), typeof(DrawableOsuHitObject) }; - private readonly Container content; - protected override Container Content => content; + private Container content; + + protected override Container Content + { + get + { + if (content == null) + base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); + + return content; + } + } private int depthIndex; public TestSceneSlider() { - base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); + AddStep("Big Single", () => SetContents(() => testSimpleBig())); + AddStep("Medium Single", () => SetContents(() => testSimpleMedium())); + AddStep("Small Single", () => SetContents(() => testSimpleSmall())); + AddStep("Big 1 Repeat", () => SetContents(() => testSimpleBig(1))); + AddStep("Medium 1 Repeat", () => SetContents(() => testSimpleMedium(1))); + AddStep("Small 1 Repeat", () => SetContents(() => testSimpleSmall(1))); + AddStep("Big 2 Repeats", () => SetContents(() => testSimpleBig(2))); + AddStep("Medium 2 Repeats", () => SetContents(() => testSimpleMedium(2))); + AddStep("Small 2 Repeats", () => SetContents(() => testSimpleSmall(2))); - AddStep("Big Single", () => testSimpleBig()); - AddStep("Medium Single", () => testSimpleMedium()); - AddStep("Small Single", () => testSimpleSmall()); - AddStep("Big 1 Repeat", () => testSimpleBig(1)); - AddStep("Medium 1 Repeat", () => testSimpleMedium(1)); - AddStep("Small 1 Repeat", () => testSimpleSmall(1)); - AddStep("Big 2 Repeats", () => testSimpleBig(2)); - AddStep("Medium 2 Repeats", () => testSimpleMedium(2)); - AddStep("Small 2 Repeats", () => testSimpleSmall(2)); + AddStep("Slow Slider", () => SetContents(testSlowSpeed)); // slow long sliders take ages already so no repeat steps + AddStep("Slow Short Slider", () => SetContents(() => testShortSlowSpeed())); + AddStep("Slow Short Slider 1 Repeats", () => SetContents(() => testShortSlowSpeed(1))); + AddStep("Slow Short Slider 2 Repeats", () => SetContents(() => testShortSlowSpeed(2))); - AddStep("Slow Slider", testSlowSpeed); // slow long sliders take ages already so no repeat steps - AddStep("Slow Short Slider", () => testShortSlowSpeed()); - AddStep("Slow Short Slider 1 Repeats", () => testShortSlowSpeed(1)); - AddStep("Slow Short Slider 2 Repeats", () => testShortSlowSpeed(2)); + AddStep("Fast Slider", () => SetContents(() => testHighSpeed())); + AddStep("Fast Slider 1 Repeat", () => SetContents(() => testHighSpeed(1))); + AddStep("Fast Slider 2 Repeats", () => SetContents(() => testHighSpeed(2))); + AddStep("Fast Short Slider", () => SetContents(() => testShortHighSpeed())); + AddStep("Fast Short Slider 1 Repeat", () => SetContents(() => testShortHighSpeed(1))); + AddStep("Fast Short Slider 2 Repeats", () => SetContents(() => testShortHighSpeed(2))); + AddStep("Fast Short Slider 6 Repeats", () => SetContents(() => testShortHighSpeed(6))); - AddStep("Fast Slider", () => testHighSpeed()); - AddStep("Fast Slider 1 Repeat", () => testHighSpeed(1)); - AddStep("Fast Slider 2 Repeats", () => testHighSpeed(2)); - AddStep("Fast Short Slider", () => testShortHighSpeed()); - AddStep("Fast Short Slider 1 Repeat", () => testShortHighSpeed(1)); - AddStep("Fast Short Slider 2 Repeats", () => testShortHighSpeed(2)); - AddStep("Fast Short Slider 6 Repeats", () => testShortHighSpeed(6)); + AddStep("Perfect Curve", () => SetContents(() => testPerfect())); + AddStep("Perfect Curve 1 Repeat", () => SetContents(() => testPerfect(1))); + AddStep("Perfect Curve 2 Repeats", () => SetContents(() => testPerfect(2))); - AddStep("Perfect Curve", () => testPerfect()); - AddStep("Perfect Curve 1 Repeat", () => testPerfect(1)); - AddStep("Perfect Curve 2 Repeats", () => testPerfect(2)); + AddStep("Linear Slider", () => SetContents(() => testLinear())); + AddStep("Linear Slider 1 Repeat", () => SetContents(() => testLinear(1))); + AddStep("Linear Slider 2 Repeats", () => SetContents(() => testLinear(2))); - AddStep("Linear Slider", () => testLinear()); - AddStep("Linear Slider 1 Repeat", () => testLinear(1)); - AddStep("Linear Slider 2 Repeats", () => testLinear(2)); + AddStep("Bezier Slider", () => SetContents(() => testBezier())); + AddStep("Bezier Slider 1 Repeat", () => SetContents(() => testBezier(1))); + AddStep("Bezier Slider 2 Repeats", () => SetContents(() => testBezier(2))); - AddStep("Bezier Slider", () => testBezier()); - AddStep("Bezier Slider 1 Repeat", () => testBezier(1)); - AddStep("Bezier Slider 2 Repeats", () => testBezier(2)); + AddStep("Linear Overlapping", () => SetContents(() => testLinearOverlapping())); + AddStep("Linear Overlapping 1 Repeat", () => SetContents(() => testLinearOverlapping(1))); + AddStep("Linear Overlapping 2 Repeats", () => SetContents(() => testLinearOverlapping(2))); - AddStep("Linear Overlapping", () => testLinearOverlapping()); - AddStep("Linear Overlapping 1 Repeat", () => testLinearOverlapping(1)); - AddStep("Linear Overlapping 2 Repeats", () => testLinearOverlapping(2)); + AddStep("Catmull Slider", () => SetContents(() => testCatmull())); + AddStep("Catmull Slider 1 Repeat", () => SetContents(() => testCatmull(1))); + AddStep("Catmull Slider 2 Repeats", () => SetContents(() => testCatmull(2))); - AddStep("Catmull Slider", () => testCatmull()); - AddStep("Catmull Slider 1 Repeat", () => testCatmull(1)); - AddStep("Catmull Slider 2 Repeats", () => testCatmull(2)); + AddStep("Big Single, Large StackOffset", () => SetContents(() => testSimpleBigLargeStackOffset())); + AddStep("Big 1 Repeat, Large StackOffset", () => SetContents(() => testSimpleBigLargeStackOffset(1))); - AddStep("Big Single, Large StackOffset", () => testSimpleBigLargeStackOffset()); - AddStep("Big 1 Repeat, Large StackOffset", () => testSimpleBigLargeStackOffset(1)); - - AddStep("Distance Overflow", () => testDistanceOverflow()); - AddStep("Distance Overflow 1 Repeat", () => testDistanceOverflow(1)); + AddStep("Distance Overflow", () => SetContents(() => testDistanceOverflow())); + AddStep("Distance Overflow 1 Repeat", () => SetContents(() => testDistanceOverflow(1))); } - private void testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats); + private Drawable testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats); - private void testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10); + private Drawable testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10); - private void testDistanceOverflow(int repeats = 0) + private Drawable testDistanceOverflow(int repeats = 0) { var slider = new Slider { @@ -120,22 +132,22 @@ namespace osu.Game.Rulesets.Osu.Tests StackHeight = 10 }; - addSlider(slider, 2, 2); + return createDrawable(slider, 2, 2); } - private void testSimpleMedium(int repeats = 0) => createSlider(5, repeats: repeats); + private Drawable testSimpleMedium(int repeats = 0) => createSlider(5, repeats: repeats); - private void testSimpleSmall(int repeats = 0) => createSlider(7, repeats: repeats); + private Drawable testSimpleSmall(int repeats = 0) => createSlider(7, repeats: repeats); - private void testSlowSpeed() => createSlider(speedMultiplier: 0.5); + private Drawable testSlowSpeed() => createSlider(speedMultiplier: 0.5); - private void testShortSlowSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 0.5); + private Drawable testShortSlowSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 0.5); - private void testHighSpeed(int repeats = 0) => createSlider(repeats: repeats, speedMultiplier: 15); + private Drawable testHighSpeed(int repeats = 0) => createSlider(repeats: repeats, speedMultiplier: 15); - private void testShortHighSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 15); + private Drawable testShortHighSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 15); - private void createSlider(float circleSize = 2, float distance = 400, int repeats = 0, double speedMultiplier = 2, int stackHeight = 0) + private Drawable createSlider(float circleSize = 2, float distance = 400, int repeats = 0, double speedMultiplier = 2, int stackHeight = 0) { var slider = new Slider { @@ -151,10 +163,10 @@ namespace osu.Game.Rulesets.Osu.Tests StackHeight = stackHeight }; - addSlider(slider, circleSize, speedMultiplier); + return createDrawable(slider, circleSize, speedMultiplier); } - private void testPerfect(int repeats = 0) + private Drawable testPerfect(int repeats = 0) { var slider = new Slider { @@ -170,12 +182,12 @@ namespace osu.Game.Rulesets.Osu.Tests NodeSamples = createEmptySamples(repeats) }; - addSlider(slider, 2, 3); + return createDrawable(slider, 2, 3); } - private void testLinear(int repeats = 0) => createLinear(repeats); + private Drawable testLinear(int repeats = 0) => createLinear(repeats); - private void createLinear(int repeats) + private Drawable createLinear(int repeats) { var slider = new Slider { @@ -194,12 +206,12 @@ namespace osu.Game.Rulesets.Osu.Tests NodeSamples = createEmptySamples(repeats) }; - addSlider(slider, 2, 3); + return createDrawable(slider, 2, 3); } - private void testBezier(int repeats = 0) => createBezier(repeats); + private Drawable testBezier(int repeats = 0) => createBezier(repeats); - private void createBezier(int repeats) + private Drawable createBezier(int repeats) { var slider = new Slider { @@ -217,12 +229,12 @@ namespace osu.Game.Rulesets.Osu.Tests NodeSamples = createEmptySamples(repeats) }; - addSlider(slider, 2, 3); + return createDrawable(slider, 2, 3); } - private void testLinearOverlapping(int repeats = 0) => createOverlapping(repeats); + private Drawable testLinearOverlapping(int repeats = 0) => createOverlapping(repeats); - private void createOverlapping(int repeats) + private Drawable createOverlapping(int repeats) { var slider = new Slider { @@ -241,12 +253,12 @@ namespace osu.Game.Rulesets.Osu.Tests NodeSamples = createEmptySamples(repeats) }; - addSlider(slider, 2, 3); + return createDrawable(slider, 2, 3); } - private void testCatmull(int repeats = 0) => createCatmull(repeats); + private Drawable testCatmull(int repeats = 0) => createCatmull(repeats); - private void createCatmull(int repeats = 0) + private Drawable createCatmull(int repeats = 0) { var repeatSamples = new List>(); for (int i = 0; i < repeats; i++) @@ -267,7 +279,7 @@ namespace osu.Game.Rulesets.Osu.Tests NodeSamples = repeatSamples }; - addSlider(slider, 3, 1); + return createDrawable(slider, 3, 1); } private List> createEmptySamples(int repeats) @@ -278,7 +290,7 @@ namespace osu.Game.Rulesets.Osu.Tests return repeatSamples; } - private void addSlider(Slider slider, float circleSize, double speedMultiplier) + private Drawable createDrawable(Slider slider, float circleSize, double speedMultiplier) { var cpi = new ControlPointInfo(); cpi.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = speedMultiplier }); @@ -296,7 +308,7 @@ namespace osu.Game.Rulesets.Osu.Tests drawable.OnNewResult += onNewResult; - Add(drawable); + return drawable; } private float judgementOffsetDirection = 1; From b5cb59faf2a3f17602e7c2d81dc1c8970c705cb8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Aug 2019 13:19:23 +0900 Subject: [PATCH 0779/2815] Add default skin sprites and specular/diffuse layer support --- .../Resources/default-skin/sliderb-nd@2x.png | Bin 0 -> 14258 bytes .../default-skin/sliderb-spec@2x.png | Bin 0 -> 13141 bytes .../Resources/default-skin/sliderb0@2x.png | Bin 0 -> 17053 bytes .../Resources/default-skin/sliderb1@2x.png | Bin 0 -> 17792 bytes .../Resources/default-skin/sliderb2@2x.png | Bin 0 -> 18268 bytes .../Resources/default-skin/sliderb3@2x.png | Bin 0 -> 18182 bytes .../Resources/default-skin/sliderb4@2x.png | Bin 0 -> 18062 bytes .../Resources/default-skin/sliderb5@2x.png | Bin 0 -> 16895 bytes .../Resources/default-skin/sliderb6@2x.png | Bin 0 -> 16702 bytes .../Resources/default-skin/sliderb7@2x.png | Bin 0 -> 17139 bytes .../Resources/default-skin/sliderb8@2x.png | Bin 0 -> 17084 bytes .../Resources/default-skin/sliderb9@2x.png | Bin 0 -> 17067 bytes osu.Game/Skinning/LegacySkin.cs | 23 +++++++++++++++--- 13 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb-nd@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb-spec@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb0@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb1@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb2@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb3@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb4@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb5@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb6@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb7@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb8@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb9@2x.png diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb-nd@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb-nd@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..626fd91e386796be54e3ff952006082d90361319 GIT binary patch literal 14258 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb-spec@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb-spec@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..76fd9ab16807c2ef61dd2aa0db47c78262db03b2 GIT binary patch literal 13141 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 O@*I3?M71L9&Hn(Rv4%VV literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb1@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb1@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e99f076947aa7ca1614fe825839dd3d7b8dcc303 GIT binary patch literal 17792 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb2@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb2@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..cd36a0ae169e1d876012402b2b7aa34a13a179d6 GIT binary patch literal 18268 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 SqgKcO0000l1{&5@md}*`-!Dp9 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 Y0Z!VEjc(%0RR91007*qoM6N<$f_m)to&W#< literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb4@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb4@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a5b19887d64f0040da387029545dea15d85669c9 GIT binary patch literal 18062 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 Hu0mjf{m45K literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb5@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4bb01f0e88229c6433c6363d7f7c725776372683 GIT binary patch literal 16895 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

{E0 z^!j92yowBF$lIIG?T054_gO=pLddDCtn5Q$qu*#75T^`{$^5z^@PH_G{zH|gA3jgv zLUKfeDEG7#yregF%P(cE5jVaPgWr6e)f6t^*{OH%aNc`fD^Lj(Ah2eOKrfq9NN2LH zLT|I%#n%AHpRe?*Krs;xe}m{Ldtlq{LpFaYYV3w~(g09JliSUt)0swX> z+_WWW6W<_pEAn)k6q5gtG0G$$`@asfzd~|lFvovpa;uGOxSj#}qk*~k_34~PN3d2R zso%x#Ek{)G!q1;(0y_bhdrfz2T}j{zuaLBBb!e%o(!Yz6*2E0UW$}E!Cf^OoQ=95* zk>LW+AF>Qap zJ7}PO=zgUvaa{*TC(PK%Dk{@}R9GbTt*rMZ(5TYu zGtZI1^tXE{B77ZVqeLxTT3~!xLu&&}URl(Fl^;Kb#m2_U1EkUXnK7bmBPTEqjYjp^ z)Z~}kqq4mNJ0gM(%aEQLhpft3_utSj2R3pJk6Z5fnaI}`sjHk2((+n$wlUF{lVE}% z8NWBzbl-Ok2xcTO8rX9zKl&ZY2jEXNpZc`|`I!R7HnsdvSgOL!sp4Maey;e7{r~d+ zx3bfdlk34Z=NobE=As*a^bn!!)@ktM;rY*wNlk^IZ(z~x^ubVoq-A344Xb=p(s5Uvj z@ySVQq2|r^-Ti-90>-?z@m1opx({VaARmeH@0QdzCigr)&Q% zrhcn5FS&d-43t(fTjY(#1OyQRlNF|h$rLv6AJRe}WthGBXy-YsW$Np5YeGoBoD0(y zd5{nU@7rR}1LcN}MPg8e@DmbFvwt~84QE#Qtn?64fMkk}%})%yCqLlM-P1ELz=!-< z7RNOjgJaz+g+zIF&MvE(!5QQ3kE@ox`b7*Bf4@PlCZx{7ba_~cw0Wm8JqIrOxZ1hw z&HiQ0!At_QW%i`$No`%jFJptog@ZHAk+^E0CY?ogTVK;G{#9VR{~rM){5W58&AbIW zZIX@f4TemeoBgB8srrV!4BTDDxV@zmpsRDGaB8R;sgIE&so0J%&tXTeDn}~83M3Yg z!4fgTSP@@!x4oTA4tj?&o@``g;ROQL@&oATD$LR1Vqy@Cqx`};hdqXZJV1st8e=IK zk(z<2IkQ?jCuZxiKh_L3x9~b*I&i=FZegn0SbcIodoR_UFr2|#Ir&NVyE)|OnAli+ zGMIGr6Uv!FPoa#g{wj5D{qpkitYk9lND?(%3{8n61lz&0hfuEwg&OOsnnA<7-LFGh zh-#~{Uk>qBmm^n#cH|Vu{~vwTr~M@=`Fd5_gb5UT;xIZ>0)%z{!z+BHfi1|WNKzw7 zBRc<9Sa;}oEKi_yaPYO!R73&-o@2ymP)oKjGvj1Bty->BQ(HaXdLB*<(`CiNc$}(1 z>X7nv#;eY0JFX2Z>noQoqK6{9=zvpa_Fv|NP-h&{7LqYU=(92q%%zL1^lx2oA!H-f zjdDlsf|uuWba5M1hvCsz|K52ewOQ&ZXKzh0fQtt?HZEg2;i0y>)=X>v6TO?*cL!am z`T4MmK++fwh~OsHR0{nwDhP^nP6hdK_t)XEAX(&__c%E?i^LkDp>9d$h&xXT~5ujX0zXWl;ZYv+&3s z*=XTsus&!#(QtHi6_~2BwAAV9U3q>F>YY{J>Hi@|??3`YV07x~k`Y98za4+}j*T+K z?dJU)d16EOB*mxOm3Y)wz;m`~!K@*LnEQyQJqmN0T6TLv){<|#H`OA`x#yH2rDWORmNz| z+&s<|ZLS|g9b3hj&OcO^CnqOkJw?oZkUfg~3ZVyJiiMLT9IUA^k@qk=@q~T5-S24i zCl>9zI}K<%1q5os3C~ntpTfn_kyo{q3NqK%n8<_o;M)UM7LLS)F(4Rg&2wikwcPD% zm=b8%+1V9DN&S|4VyF`P+KM>;Hc7wsb<8XECLiE>EQ5ul!VWVCtPU>D(_#LUHFR8T zVv^X`%_;rIwM**n((A&VfaRk;I-adpKx%nanNURy(}p$jeorMf)xu_l^03j)^>ynu zPq1^ca=hV{ZuYPve#I^+pi!Af&~8Z9v%ZGasF>4YV?nGm}WtI7SC^ zM_>EnJRfdzfTeqDpo(K?P2!z>!zl!qW!|Q?=(6H!nC#%YtB^l|3C`{8b=OvJ4CP3+ z{Nn8Dwc$&&reYHIFkpA!^^uRAu-6U~!GrW<4bM_c61wOK4J4oASPDB3WDajLui2Je zhlCCpN5~@pG}(`7;m4GzK!3(pF`DSKc-V!{vh&_4OPgD;w_ln?d07Rh!>gQK%%pf8 ztmNAS~T;+fTes`nNur>F!(RR~Gp zIP>U6dJz*o_CqHS*g1P?I{4Pr&rf8_PM1}+Qx~QlR(J%Lu~5oIS31S$^xH)8EBq8Q zv*+iRkFPU!jP4u@dxC3fY&?^~6pjlC=H>T9MwkqC6_BADa|_i-Co3A%9n5tt9f|^} zM?`Ama0S%sRyn>;^xt(nuX5&j-$jpOp#MhhmmKC~cm{69P%XOV8xc0SgUYg_K&)KT zX)*u*=LJCK#Ahwl97)gxHlJ?HiNs_c-$Y8r$PCiPw0^Fe@M4)h3y=# z@s);Sl7VqVmoX;sM;VJ>l=U35+#kw*)|pE5H#+E1XYAE*iQj=fmX=~xHT^cGP$kVh ziXz9yOpE3!bfV~LtY$i*d8A}+y%_cYlJWL_0;;G?mY{%z@m)~ihgLXgq?d3#(sBJM z+BJFR>k4hv)Sw0#h!49lwFm;V?_F&z))$5h+U#q|YW}_Ng@XPbcAQs-baW^NO?R{j zkEccGvKkt#CPCkG`bjBaYE!4+X{p>qAM*p{E>_WUt;TBV{>UPnmwfKH8KrrUj5rX- zjYMJc*jeNQIZbcveO$ReEp+x5Q$Qq-I_9 zR!)2o0u=q2v517PHjY#Zn#QKG2zp;-)>MwN9yM64 ziV6ygs-_aoEUYl9PcNAx%{2A5js2%-;*VSn_#ffv_PL{XUP*6)Fd84mp( zCwcyNE~Hq6^uD{K`K1ZIDBHX8WDzp9d6T8_rz$fj6{8l>rV{RIKdqve$o9{vkzAZt zkXCj%RyLq#-E{4v)`V(AnP>)@1{f#pjkOGp+@Y)>?*57Z5%Sv>)GwiBU ziqtm-#f%K!>fj7N-UaLfm>g$2K6Dj$~zL)-oa-VD`PTXS!*qvM*hYk#qW z4%EgazYF3Nq`pm;xB8Q5P`K9!urZeej!2PDZU2hTO=*g}@7r$h&d>K-4tj@=t!b#K z3l|S~M0E>O5eSwT){m7jufAf9zaPW=BB0KzP8Q?H3R#KKjRQ6o;I@6B%$B{%tHhT0 zR41X!smpyHtq<%lJ*E0(DlSCm@=q90cBw>7Ua5SAy|tOpTbu? zLKk0j7Qf_iEREwsIuDE^y-+XZ3bN}%o)%)N85*6^@QzXsY;S3h(wrDCh-0C7F%C>E1D2o4vQaD zudhD~Wi#yBIKl8?I!~OHDhKF_*%eFgP9Xig0V@{?H3QWQ?xoeqT4q(7F@eSC)TLh! zA|!+UR{tZjX#p&LE@gvH5q8Di(-dA_Jf#?Ua%^d@tf`=CGj^00lMA~OSqxUs%2Auf zik2E~yRff$O5T>23{Debeso9vW-R%#jgdoMk6L1JOX^A+51g^H-ZVBf(G95> zb@Q?&DW#%SqL8fkQ244+4SI=upfmIovs0;hK=MfvZ|3cG)MrX#dfLQ}hUY82o(5f@ z&FHVXRy;$)qNNVg7g3`cs4GpBz+C7%_-td&M)p z)9|58;ivE<-n`$~+pj@x)Ez7vcN70SN+orQte!7PJ0et|KL+f(en5U81+& zx2{DlN^XnKkYcc!g`2nRf=R;TT@*O?Wl^abVsV+D@}yG^oCmGD zc`X9AKb2rsa_xfJC$LSNxL97A^cxiA=gVQru<8?W5MAk;pY0 z`T6mC;z<+9-QQ{y+Cx+#}`501wPJ4t4$ zp}D14+QlQC!Bhv!M;X^f_<&lNpHc~Up5zcMmj&bzf*4C&o3Z4EGqNlcVFMZE%&8$l zhPiiO_gLUui#A9f3iVHl$q(HH32nWL&r8-<6IMK|`SV zFHcdJllyh)eOl(RObp_cI!fS^fT78hOye`SSdQCOl-$K7 z?MK$m_fclWoKIiY~7!^h7r2OsCPXq>I+Sf594aKaSCmr^~NKV<4@a8kYo1A4Cwq=8D&^w+ri1kl^F z#Zc*V#}8`oSyjZ5&!m2m!3V6*<6Op1jK^KQrg;fljBM_GrrKFi5yGSYSWr;FNUEyB zhOxv7YtXr0>mF3**~qJtEE0-Y&bW0d=tc|a7QJR2$A4EhPkoir@LZL;yYl-x=oDEiwscseTgvjdPZ}d956j8 zNE*|6HS1E z@>7Rc!oHQ~|Ex7&*pjn@#YJP!E)%RIIb^=+OjH!kGd*HG)WVe9)!hQVGj#7N^S)N52?1^dCfeG9bXh1 zD$J5W+;?DKc%eG!qitQLg()T`mJaHF+*$%%LV|+esPa=MI5QcEq#*rBwWH@0tg$08 zmwJ7?rq%Ci@Ed0_0^A90CUTfSVQI1XrA-)ma5@cBWiO#{1*K`zHui^_(@ zl5V`_LkPS<3b4F-cA`6GJhdwDZ?}1h1WAzNOQwy`e2eqR^0tYu5%pdt&#X>HHY%~r zdG|-3dguAFcCClBnad)i=DLc@h=>A421?-LaT#Tu*3(Huxe8?+aqDNRiyg^@G{>wPiak^e(@(tp1| z2F(mwbXbT0Mgs(57GO@ZdijS^L7yexyNSadWS}C_iX@*rE8%o~L%_V&j1b61N@z%Z znj?BR*EP#OEmaQ3!;O?1;Wn;atzu%?9pvdy%kqBtdsEwDH8LJH$_~e0(+#W0ThbxY zgy)}RiMe^5n(y7KNG|BAF(L%MrRZ}+6oH%(1oghpP-0v{?^JTk$_WVx0VT;$|UJignM%XOcT33h)=IU zI%)vV_rcq7_4d8^Kv%Z3fPMqdSJqMC$|cHLt?gdzo{c2RA7usZvNiyQJGS-%_k>0# zt1LB(otDdu>tYQ)i`o50l7h78H>BiHEnAW!nwpyz-TEQ&CrW=S_Cj|j(ya?1yI2JL zc65B)C%+Gm00PK`jWmY5<-(~d^|%|DIM_5NN0iG@Q|5q&lldZi;!<^9JozAP>NKwQ)EAUV1fK%~;Gj(! zrwLQ+2&p!sZk(<(3s-cd$@1s?!ZNF|B0ryL^*aHjBPg?>RL;n`xjA`8((_-y4=KgD zvxEo^mc*q$1a45DFZcffC_#q!U7UKH;u;)t{k|R({bxdZg_+;fFdMn9!gf95Rk?Ls zuAw7B88;(3l?MDJtKRl>>OsFp73q_d4sFzn-|En^ianm{9r0tdO;^H}R zf2%dtp0Nk~tXey)&>0Y8V-f>~HK%u~+;6M#Xb5rSS;;TwV3O>VZLz9GV2 z4Z4+Q+Fc*YwEh}P;j{Ujp3|o^8VT1KaH!zpH!hi8llwV7^;_r;CZw&Uz3ekJgkW1c z^(zSAmMzxHok;KcVny=m%Tk9V@bR0*UEip}GpTG>PXh=7Fw8x_VB^7O*@}WRab>rt z6XsnjGu3I&F7QJG6Lc6x4O{VPQ%lkrPSder#>q zMd*8x=kV6^^-S#ZmQlywc_SN_T<|yYJq=TZ(5 z=@)f!8H-;ETa%E*EQ(ROClxa$YrD1G!^0ZD(3TDSoVO=HQlB#z7}55U-T?9PHOQnE z9}QZMR{By>y|`d)+pB|Uw~PUyQAtw=tqj?D`Q*c!%Y!M{C?=b|zSjpt<13`z4}}74 zq(Z@WP#6Lo5;Xws@Nl0xu!&l~*qg;hSE>RO`d^EKjLLaxYAu&(-E%R^N%ccEn&!cX z&krHF?-XnX)#YWWUX7_bBSQ1v**a5QjlsdDC&9fs`{(*{)C@|e{UmJO5fUt0wOeD` z#|-QqXK?sF6qW&Xx}9fo#*Ou_jCZFGe)=JtjcDxPA^XIMkw*$$A*uEc?B<(1EVEOO z>EX^w4IgL9U1~?1eV2eJbb?LTjWzeDNH?{(ivFU>eWQ6 zudLeG&j(rBy!Ew6D0J8Nuq@^GDhrpo=k;UbEoRO#T zZ_mNZYE_9U63X(Lo6ocV8OX)}Czjgk9~obXm-Qf&YMUAwPQRpa z7~T|>NE;geR%-`)3=aweP@xl?1T;tZ!n646aAdnILcjm!*(HJKV%>im-((R*9+1-k2D`&<@i^*BR5b!{&j~pai59sQ=Di z(2y~!ZNAmvPSa_3Qnf7!GJWwzZF0ix&Z{{;naH0nE6Q=_#>cS`Sit7DH@OIC&fTKaTSb+ZoaVM(^tijsQ<-kM zMXqZp!5`b3?ReF_KcpVN@j0Gib0drUs$s|*DM#-J45PeMgi?pVRMKXmkz28g-(y&5 zh9`%cCF=cb;o`8gtdc*HBr}p^FoNXBhYSl}_NUrOnAfS(ya0#YNkzqLn07}z>A_6h zh8NqK;jT=IZdiYR>Fi4bQygFnl{ozNWMr4MX~eLR1-LOhuDKIiE>eoM(!0Ndi>ur_q*qo&r8S^xGHBfqH94F6-^F^SM}XSc)hDLtwu}H1 zU7GKEHaB1&(1OXov;q-ReDLT=x*NeHC7 zsIWV*hx%|tgLxO8FESPpsj|FvaY*H`@RlgADnht67&1Y#q#g{}}7nwVPgp%?IFw%m~^y8ldAih|Ts&cjH$ zcB0Ca&4oz_adQ7|79_Se18t6M?Z!Cpo;7H)^23epMmeEu0Jjr0U3et!_Y;Ziu#GoQ zjj8Tu9E$$jAHiAAv-7H4$Qs;P4iYie)*dbCzxWkRw+sRuUAoSVk&5v<_gZUIhygwp zW}+nMj#&(+ExotzlKfWy$a+@CtNn1x3|G`TO?*u*+2{-8X4bf)ORZib9_q@UJ!bx` zAYIq9lpg4V7gZ?ecOIXNBneLcX9$&4#68j<6Q}}&ev=%#@wG~l z5?0qQ-un9bPGC(o1ZZt|JHc+RfpW6B?^`~ctdqKO z*T%=GkBl`!epYaDr$+kv@5blp1_uY{GVG}Gw5@GDzo)`!bHrpCnz zxdF%K`tSUU#l6jn+_ovh?r-n;?v$36KBi6V2KdhGdN}GH&F5mLCsek;BG28_kBUS1 zF{C_65>ES!O(=s0O%p*cFgia0cjh0#!O{m&N7!s@X8^Ni9as3h5$v~|zfXwMalTtO zQn|mE+o73SHG~o*09EU|p!_WZg{=*~bGWAlked^2qJxm_u42~7bBzcty#W^URgvrl zb;X5;N-fy119-lSt*xykoFh@8;0!iOT$I}V#CHy~eSnlojvtxh?&5O!56BUnMhb4~ zo)%MDE8~VkGGzu81Ai|XT&6BWHo(qj0X>HD2qg!%d7S)Y0$zNkgn6S`MkUC9MrEfS z6afoIvK9ei5|7zrkwg9}cCtaURySOpA8E1vehm_=G!gX7()hKM@nb2E1qmz&XcLDZ zf!VjWIOnxSU!Tl3KR<0Jr*jY@G=lWRqnhzwXinL4b*5%OY+8RCYiydsFYugJvm!XdHQ| zuKEGv&k^xIw90)bGsR<44Gaf&Kxw4e%YJr>)3P0yln>UkLSK7_$clBL6CU4>t$w*` zAITJRfD^V3V^H`#d(vpzM|AafCDB2dt%D`vGy8m4y%jIjz36VurluxobkO7gir0D^|Y)iKtIU65Q_s zhWFus`D7+lXDVB7D%;xuDw;u22aSj!mRGyvZwC<55@k79KBwK>P?)Z5Z~T_;hV#~s zq8xt-sdvbBC)E011pEV2Q;+fSnGukbm;a^xRWN7Q9iFM@9a$=XM##w^kY8=S18z8Z z(s4fI;SW4kL&(3u!{@**&YNB2%aM4(R;k1iW1h=K8e618@PodA}nt&rrr_I(*kt1kAQ2Z>6O!gkiP>^ z;;o0M<}0m;J=t&IFhff|pH_H6&j(e?*tZ8!#p<9Pm9XpC;M_W?ydO=(0Kx(u0J>{m z0JTy*N20lW>SC)TchbbvbOV>Z-5s@_24w9f9Dh0>U(5l~=jH$`uHVNp9heZA^=PeL ziSi}rh+P3r9xtYiE7hzMP0$J%)!>`_Uxreq$MZ;4<5pT|pCNzAcgozbapx-c_B^*c zf`k}|B+;ZXJ?937Lp7kYj+r#(>JkKqHy_KiVUvQ-Msn^U{S~aXYx-ziK(7t+@cESJ zhT%54QWPd^j?b!lM^?-!t00XDnvO@tL3yVaJ5@Z2`3AieMqHr4Z zh8a`l4V2fGr^NnS=0f^6ErPDxi|w+(XOB!xOb$D4b~?D#8A*x1t{jQm)KDoDjJ^X0 zA{I1AxGsW(&*s~})DEjUdnBvsFl}qJez@=CD1!8Y5RfPRf=!-gqMTg}TVYg+`w( z9eEx3>E#tb=U?0Qy}^Jw|24guo10521Jy%7{j$Dq}pU<=I;~G z4HCvT`R9{naB1JZp%Bb3zHJPMtotsCvglCOolSoH8hMp7lzXd-W(_u&dGcg?EkFQ6x_gMp0^7 zlr(`{x6Ba1~i~#Z+n4VuD@zt z?>PD$xDN49#lf-OQWGmjW}r8_wAG@jz|}ALTXQ@LBkVN3riFy>81(rzveG$EmK*=v zIn_~Dzn3lX^2ckWRPM-qLG`iCecBHEtq-4=SjARB^m>EQ$p;w;+Yh7;&_9hLJPBwc z6WK7_LQ83bmT`0?@gIK)&KXScH6PtfTpi)^M$YnZWUZDi8L;-@1CRS_&=it%9waqe z??eWJ;nS~@X-ddKBuzxv^3&~+?=0PB^g$ymRQT`z0)&WSLZJtSmMV91p-m4G271Ax&#yyrQ@zS7cR<-Lr`SmlDWWwdpp1A88|@ao z3(3F(1ZfjPOMu4i4M4Rs69q6F{WY7lEHX)MkG`e*Eo^~uxHvTj^M#bb|7EC@vwHP^ E0O3MVi~s-t literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb6@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb6@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..859e0aa4c1f0d548eda3fe4c6c02f7fc4af1831f GIT binary patch literal 16702 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb8@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb8@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fcdf4ed4a4a76808dbfe41e54a45d4433dc1320b GIT binary patch literal 17084 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb9@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb9@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c990cf0fe6784265e2419070a27459fa6f7b2fa0 GIT binary patch literal 17067 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 => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? Color4.White; + + InternalChildren = new[] + { + new Sprite + { + Texture = skin.GetTexture("sliderb-nd"), + Colour = new Color4(5, 5, 5, 255), + }, + animationContent, + new Sprite + { + Texture = skin.GetTexture("sliderb-spec"), + Blending = BlendingMode.Additive, + }, + }; } } From 3b7cee2d45ef027b93d71950ff4ccc24843323b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Aug 2019 13:50:16 +0900 Subject: [PATCH 0780/2815] Fix LegacySkin not checking for @2x hitcircle when deciding sizing --- osu.Game/Skinning/LegacySkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 3eda76e40f..7c621ee87f 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -59,7 +59,7 @@ namespace osu.Game.Skinning Samples = audioManager.GetSampleStore(storage); Textures = new TextureStore(new TextureLoaderStore(storage)); - using (var testStream = storage.GetStream("hitcircle")) + using (var testStream = storage.GetStream("hitcircle@2x") ?? storage.GetStream("hitcircle")) hasHitCircle |= testStream != null; if (hasHitCircle) From c4251d512e069cfee758bafe8f85dd7113d360e3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 20 Aug 2019 08:00:09 +0300 Subject: [PATCH 0781/2815] Simplify bar building --- .../HitErrorDisplay/DefaultHitErrorDisplay.cs | 69 ++++++++----------- 1 file changed, 27 insertions(+), 42 deletions(-) diff --git a/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs index 2d05dc8aba..a9dd7ffcca 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private const int spacing = 3; private readonly SpriteIcon arrow; - private readonly FillFlowContainer bar; + private readonly FillFlowContainer bar; private readonly Container judgementsContainer; private readonly Queue judgementOffsets = new Queue(); private readonly double maxHitWindows; @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay Width = judgement_line_width, RelativeSizeAxes = Axes.Y, }, - bar = new FillFlowContainer + bar = new FillFlowContainer { Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, @@ -85,54 +85,39 @@ namespace osu.Game.Screens.Play.HitErrorDisplay [BackgroundDependencyLoader] private void load(OsuColour colours) { - Box topGreenBox; - Box bottomGreenBox; - if (HitWindows.Meh != 0) - bar.Add(new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(colours.Yellow.Opacity(0), colours.Yellow), - Height = (float)((maxHitWindows - HitWindows.Good) / (maxHitWindows * 2)) - }); - - bar.AddRange(new Drawable[] { - topGreenBox = new Box + bar.AddRange(new[] { - RelativeSizeAxes = Axes.Both, - Colour = colours.Green, - Height = (float)((HitWindows.Good - HitWindows.Great) / (maxHitWindows * 2)) - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.BlueLight, - Height = (float)(HitWindows.Great / maxHitWindows) - }, - bottomGreenBox = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Green, - Height = (float)((HitWindows.Good - HitWindows.Great) / (maxHitWindows * 2)) - } - }); - - if (HitWindows.Meh != 0) - bar.Add(new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(colours.Yellow, colours.Yellow.Opacity(0)), - Height = (float)((maxHitWindows - HitWindows.Good) / (maxHitWindows * 2)) + createColoredPiece(ColourInfo.GradientVertical(colours.Yellow.Opacity(0), colours.Yellow), + (maxHitWindows - HitWindows.Good) / (maxHitWindows * 2)), + createColoredPiece(colours.Green, (HitWindows.Good - HitWindows.Great) / (maxHitWindows * 2)), + createColoredPiece(colours.BlueLight, HitWindows.Great / maxHitWindows), + createColoredPiece(colours.Green, (HitWindows.Good - HitWindows.Great) / (maxHitWindows * 2)), + createColoredPiece(ColourInfo.GradientVertical(colours.Yellow, colours.Yellow.Opacity(0)), + (maxHitWindows - HitWindows.Good) / (maxHitWindows * 2)) }); - - if (HitWindows.Meh == 0) + } + else { - topGreenBox.Colour = ColourInfo.GradientVertical(colours.Green.Opacity(0), colours.Green); - bottomGreenBox.Colour = ColourInfo.GradientVertical(colours.Green, colours.Green.Opacity(0)); + bar.AddRange(new[] + { + createColoredPiece(ColourInfo.GradientVertical(colours.Green.Opacity(0), colours.Green), + (HitWindows.Good - HitWindows.Great) / (maxHitWindows * 2)), + createColoredPiece(colours.BlueLight, HitWindows.Great / maxHitWindows), + createColoredPiece(ColourInfo.GradientVertical(colours.Green, colours.Green.Opacity(0)), + (HitWindows.Good - HitWindows.Great) / (maxHitWindows * 2)), + }); } } + private Box createColoredPiece(ColourInfo colour, double height) => new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colour, + Height = (float)height + }; + public override void OnNewJudgement(JudgementResult newJudgement) { if (!newJudgement.IsHit) From d337f9b482a9144d85d8d12682b0a0461a8f3bff Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 20 Aug 2019 08:03:17 +0300 Subject: [PATCH 0782/2815] DefaultHitErrorDisplay -> BarHitErrorDisplay --- osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs | 2 +- .../{DefaultHitErrorDisplay.cs => BarHitErrorDisplay.cs} | 4 ++-- .../Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Screens/Play/HitErrorDisplay/{DefaultHitErrorDisplay.cs => BarHitErrorDisplay.cs} (97%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs index 006773a091..02773e0561 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs @@ -115,7 +115,7 @@ namespace osu.Game.Tests.Visual.Gameplay } }); - Add(display = new DefaultHitErrorDisplay(overallDifficulty, hitWindows) + Add(display = new BarHitErrorDisplay(overallDifficulty, hitWindows) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs similarity index 97% rename from osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs rename to osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs index a9dd7ffcca..26f9e3a5e0 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs @@ -18,7 +18,7 @@ using System.Linq; namespace osu.Game.Screens.Play.HitErrorDisplay { - public class DefaultHitErrorDisplay : HitErrorDisplay + public class BarHitErrorDisplay : HitErrorDisplay { private const int stored_judgements_amount = 5; private const int bar_width = 3; @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private readonly Queue judgementOffsets = new Queue(); private readonly double maxHitWindows; - public DefaultHitErrorDisplay(float overallDifficulty, HitWindows hitWindows, bool reversed = false) + public BarHitErrorDisplay(float overallDifficulty, HitWindows hitWindows, bool reversed = false) : base(overallDifficulty, hitWindows) { maxHitWindows = HitWindows.Meh == 0 ? HitWindows.Good : HitWindows.Meh; diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs index bbed2b1618..e4a630245d 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs @@ -80,7 +80,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private void createNew(bool reversed = false) { - var display = new DefaultHitErrorDisplay(overallDifficulty, hitWindows, reversed) + var display = new BarHitErrorDisplay(overallDifficulty, hitWindows, reversed) { Margin = new MarginPadding(margin), Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, From 9f64e0962534b6a4aa149ad24d2d0d18c1815ee3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 20 Aug 2019 08:45:51 +0300 Subject: [PATCH 0783/2815] Move HitErrorDisplayOverlay back to the HUD --- .../Visual/Gameplay/TestSceneHitErrorDisplay.cs | 2 +- osu.Game/Screens/Play/HUDOverlay.cs | 8 ++++++++ .../Play/HitErrorDisplay/BarHitErrorDisplay.cs | 4 ++-- .../Screens/Play/HitErrorDisplay/HitErrorDisplay.cs | 3 +-- .../Play/HitErrorDisplay/HitErrorDisplayOverlay.cs | 12 ++++-------- osu.Game/Screens/Play/Player.cs | 4 ---- 6 files changed, 16 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs index 02773e0561..a148bdad67 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs @@ -115,7 +115,7 @@ namespace osu.Game.Tests.Visual.Gameplay } }); - Add(display = new BarHitErrorDisplay(overallDifficulty, hitWindows) + Add(display = new BarHitErrorDisplay(hitWindows) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 43b9491750..02432cf64e 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -15,6 +15,7 @@ using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play.HitErrorDisplay; using osu.Game.Screens.Play.HUD; using osuTK; using osuTK.Input; @@ -33,6 +34,7 @@ namespace osu.Game.Screens.Play public readonly HealthDisplay HealthDisplay; public readonly SongProgress Progress; public readonly ModDisplay ModDisplay; + public readonly HitErrorDisplayOverlay HitErrorDisplayOverlay; public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; @@ -84,6 +86,7 @@ namespace osu.Game.Screens.Play HealthDisplay = CreateHealthDisplay(), Progress = CreateProgress(), ModDisplay = CreateModsContainer(), + HitErrorDisplayOverlay = CreateHitErrorDisplayOverlay(), } }, PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), @@ -256,6 +259,11 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20, Right = 10 }, }; + protected virtual HitErrorDisplayOverlay CreateHitErrorDisplayOverlay() => new HitErrorDisplayOverlay(scoreProcessor) + { + RelativeSizeAxes = Axes.Both, + }; + protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay(); protected virtual void BindProcessor(ScoreProcessor processor) diff --git a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs index 26f9e3a5e0..6539ce38a8 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs @@ -34,8 +34,8 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private readonly Queue judgementOffsets = new Queue(); private readonly double maxHitWindows; - public BarHitErrorDisplay(float overallDifficulty, HitWindows hitWindows, bool reversed = false) - : base(overallDifficulty, hitWindows) + public BarHitErrorDisplay(HitWindows hitWindows, bool reversed = false) + : base(hitWindows) { maxHitWindows = HitWindows.Meh == 0 ? HitWindows.Good : HitWindows.Meh; diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs index 10b5e5b1cb..422e151d8a 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs @@ -11,10 +11,9 @@ namespace osu.Game.Screens.Play.HitErrorDisplay { protected readonly HitWindows HitWindows; - protected HitErrorDisplay(float overallDifficulty, HitWindows hitWindows) + protected HitErrorDisplay(HitWindows hitWindows) { HitWindows = hitWindows; - HitWindows.SetDifficulty(overallDifficulty); } public abstract void OnNewJudgement(JudgementResult newJudgement); diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs index e4a630245d..1c61904461 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs @@ -21,22 +21,18 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private readonly Bindable type = new Bindable(); private readonly HitWindows hitWindows; private readonly ScoreProcessor processor; - private readonly float overallDifficulty; - public HitErrorDisplayOverlay(ScoreProcessor processor, WorkingBeatmap workingBeatmap) + public HitErrorDisplayOverlay(ScoreProcessor processor) { this.processor = processor; - - overallDifficulty = workingBeatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty; hitWindows = processor.CreateHitWindows(); - - RelativeSizeAxes = Axes.Both; } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, Bindable workingBeatmap) { config.BindWith(OsuSetting.ScoreMeter, type); + hitWindows.SetDifficulty(workingBeatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty); } protected override void LoadComplete() @@ -80,7 +76,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private void createNew(bool reversed = false) { - var display = new BarHitErrorDisplay(overallDifficulty, hitWindows, reversed) + var display = new BarHitErrorDisplay(hitWindows, reversed) { Margin = new MarginPadding(margin), Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 9ef8cc4509..e7398be176 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -24,7 +24,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; -using osu.Game.Screens.Play.HitErrorDisplay; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osu.Game.Users; @@ -74,8 +73,6 @@ namespace osu.Game.Screens.Play protected HUDOverlay HUDOverlay { get; private set; } - protected HitErrorDisplayOverlay HitErrorDisplayOverlay { get; private set; } - public bool LoadedBeatmapSuccessfully => DrawableRuleset?.Objects.Any() == true; protected GameplayClockContainer GameplayClockContainer { get; private set; } @@ -160,7 +157,6 @@ namespace osu.Game.Screens.Play Anchor = Anchor.Centre, Origin = Anchor.Centre }, - HitErrorDisplayOverlay = new HitErrorDisplayOverlay(ScoreProcessor, working), new SkipOverlay(DrawableRuleset.GameplayStartTime) { RequestSeek = GameplayClockContainer.Seek From 596ee150c6e2db6fc3963ca237afba868432543a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 20 Aug 2019 08:51:41 +0300 Subject: [PATCH 0784/2815] Add xmldoc for not obvious const --- .../Play/HitErrorDisplay/BarHitErrorDisplay.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs index 6539ce38a8..f68e17c908 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs @@ -20,12 +20,15 @@ namespace osu.Game.Screens.Play.HitErrorDisplay { public class BarHitErrorDisplay : HitErrorDisplay { + ///

+ /// The amount of which will be stored to calculate arrow position. + /// private const int stored_judgements_amount = 5; - private const int bar_width = 3; + private const int judgement_fade_duration = 10000; + private const int arrow_move_duration = 500; private const int judgement_line_width = 8; private const int bar_height = 200; - private const int arrow_move_duration = 500; - private const int judgement_life_time = 10000; + private const int bar_width = 3; private const int spacing = 3; private readonly SpriteIcon arrow; @@ -127,7 +130,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay judgementsContainer.Add(judgementLine); - judgementLine.FadeOut(judgement_life_time, Easing.OutQuint).Expire(); + judgementLine.FadeOut(judgement_fade_duration, Easing.OutQuint).Expire(); arrow.MoveToY(calculateArrowPosition(newJudgement), arrow_move_duration, Easing.OutQuint); } From 84530b7839dce1db5481ce52ca86a9803529444f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Aug 2019 15:02:07 +0900 Subject: [PATCH 0785/2815] Colour the correct layer --- osu.Game/Skinning/LegacySkin.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index d6446625a5..26ee434bb4 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -43,6 +43,7 @@ namespace osu.Game.Skinning public LegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager) : this(skin, new LegacySkinResourceStore(skin, storage), audioManager, "skin.ini") { + if (!Configuration.CustomColours.ContainsKey("SliderBall")) Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255); } private readonly bool hasHitCircle; @@ -347,7 +348,7 @@ namespace osu.Game.Skinning [BackgroundDependencyLoader] private void load(ISkinSource skin, DrawableHitObject drawableObject) { - Colour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? Color4.White; + animationContent.Colour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? Color4.White; InternalChildren = new[] { From f72edb8bf8840139adf03a8a125a09373167a20f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 20 Aug 2019 09:03:31 +0300 Subject: [PATCH 0786/2815] Add missing blank line --- osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs index f68e17c908..d8ae3dd9b0 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs @@ -24,6 +24,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay /// The amount of which will be stored to calculate arrow position. ///
private const int stored_judgements_amount = 5; + private const int judgement_fade_duration = 10000; private const int arrow_move_duration = 500; private const int judgement_line_width = 8; From 9fbc8440fc069ce732f6a7961366269062fdcd53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Aug 2019 17:39:16 +0900 Subject: [PATCH 0787/2815] Add actual testing of animations --- .../Resources/special-skin/hit0-0@2x.png | Bin 4020 -> 3009 bytes .../Resources/special-skin/hit0-1@2x.png | Bin 0 -> 8378 bytes .../Resources/special-skin/hit100-0@2x.png | Bin 5653 -> 3461 bytes .../Resources/special-skin/hit100-1@2x.png | Bin 0 -> 9934 bytes .../Resources/special-skin/hit300-0@2x.png | Bin 5004 -> 3928 bytes .../Resources/special-skin/hit300-1@2x.png | Bin 0 -> 13096 bytes .../Resources/special-skin/hit50-0@2x.png | Bin 3861 -> 2966 bytes .../Resources/special-skin/hit50-1@2x.png | Bin 0 -> 9138 bytes .../SkinnableTestScene.cs | 45 +++++++++++------- 9 files changed, 27 insertions(+), 18 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit0-1@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit100-1@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit300-1@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit50-1@2x.png diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit0-0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit0-0@2x.png index 9d2ed3a7fa3d5bc0aa43098e9247fa3f6a7c6f80..37e7e9143f63e30bcfd3de38c46dac951451f43d 100644 GIT binary patch literal 3009 zcmb_eX;c$g7OpI^4Ja}_dKeWlp;b6-_5~7_u*o9BP9vy|#H4^INhKkW00Pl=T5a*r z9vwhI!*nA|;|Qp@MoHm5fMA2qd|^`~yVy7#;He&2od z>R?1zppDfZtpEV92@dj)0ss_Zq5umsyFeLk2be4ndE&>^ z8az(G_rz~zgpxzWeqf3qC`$@PXNAS^vUc%Ud_2bs=b>OD076j4#VLfTqBOR`6aNmE zjo7AT5+3)?MYhWm|GucW&1*a7LmfF(OIkwI4YUqMk2eBC^Q0v z$)=OpR4VSng-5bU`AO_3|9}sH)Gj; zQjjZyq%jbbx-bP1DUb|GOM%2VKR=V|ESz&FmnRUJ7F|q2z3feV(1Aw_M*xx5c(fLbHL1b6ZDwsR8&Sqv4Zmrwe zV!JYIHP#umNonV68E6@SJ|-sm3?42jJ!;=Fms~E_d(cjiblcE}&gDM+s!7^I4_&fB z>o|(G1-wlk11#{Vj8BKOSkZrjR4PwY9LzjZbzz-N-tDe3!?~*Z0*_O#R_G?H2HWc= z?#n$+m)OHK=09h_9NiW3bJzoC+kHxtoYcnns|H);la{P`^+okk)B%V9SipNR52s2R ze+Qszd*hV=4hXUK&V2!!;n1}Y?cw8UYm{?fJEn8W5#=l}dnj>=0Bn^vQ?bgmGkwID z8tdXLOl@`5{+)Rknvzn4THi?mP|hiqiE{-2_PlU*I+oyy?)s+hj-O37pv~^=A+np% z4uMq@w8xPN-=`@5_MBuV^A>{W+Xdsjc0e{nNq}ec6Z|F~J!-uUYuhDnrPceST z*E+y;P0C9lN=k+C8Romnhc#;o!*j}$pJXivGQ)tQR?BLyyy1to!h!RZJm4V5Bbpm z+ALAu@LA1E8~A#~=v-MCXYj%3;8`7{HX76A>Q&@pXYB5}0FzpfH<}9DB?h<759%TF z$QztPqe;{Epf5GH39DIa-A#FuvLI|m_te2TEAMvmN#0w|xhCVt0G9GCwWVXrb3O2= z?s|vueAnvcl2ck^_1}&kt*#uXue;LfI1w*P4yq0r!E`XjwB(qA8zbYhA_ zfX>d{b?eEG+6#!D7wQM(p4ItU>#{oFjiPn~A0xj1L>o*QdO&S^HZ{9%*K=ot&Z|RL zmtWau1E1^DPYN%X4>m(*j7!h&UtSm#c~@X*yL&~gSE=QOL^E-f;_OpZj{I`oiMPW0 zvr~}>J3pz0CP;b9S1B`ZP10VIpDwE#oN_Q!o5vkPXt!^bl~lbY&Ht!03|!zqiAR^! zcKP0O0UFw`ebq8$rAcabWK-`@2d*{!LH<1=$iOU47kf)*A2)1`yIT6vka43g%!D%z zdKX<3SngRZ6}^dADI~%6AVO6*FTDK_8|AEFdbKsQ&FwpB>SZDs2tVE9Guz8nC7wOG zwCc&iVgyGQMCuBVXQ@?(yq+epoEJby?{05P>4nz3g&?2?3juZ-SpbvbvN+qq9- zd%{XP0vsxV4Lq~)Sa^>LJ=5KksGnL6CjatJV zr#)3Kd)bh>(hfn07^Nm|9p0*(Y}Fs%{3KX6xV49J)8tOMUOF=tm=9kbo>F%UoJMx( ziUaH`0SoWj7Ul6R+tr-;_z|5u=dmoiMDtXow^%uqPi={RD=Mit$QqZqn6J}!sb4)( z6`W)n6t>OxTC;|Jq203|QCO`4V7rf=NsFh#r!zhs(&AbAKSL_DhRsS8aNZ)>X>IhT R0@F`*@WwFzYTv}Y{{lUSa}NLj literal 4020 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 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit0-1@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit0-1@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b75c71927c6671c72bc8ad92d25235067d5a2743 GIT binary patch literal 8378 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 FzW}dVh$;X8 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit100-0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit100-0@2x.png index 777af3bd415263824913549c5c4c708c4d6e450a..79326674086568e677792fe57141e52e9944c33b 100644 GIT binary patch literal 3461 zcmb_fc|6qVAOFterbxXgM+RHAtugnBMg}>SY&X*b(Jj?!Ji z5`~tfQ;yP5iAI+~W|T6f5j)!2-@abEzy0I4`~34f&*%I3yg%>fb9}$gHz&Y<&0GTu z0{{T#dV9GC0sus_gaCRvnkk%jI$AR=7I=gTf;iCv5se1|s~Maq5a!LI#ejhzjggda z3v>p6IbvpTs36quTQZ%)a-_}bIEq^%-)IM8GF>|*x`m#kUOiV=3O zPbz{q7rWm`h57}+s2m;$!#U#NbQBf`Baj?Xcnp?AS_wlVQA7lih(KZBC_EX9B%{%= z&%;jRjmL;42fBNH_NCdm*u@G2TrvV75{Vo|7)K5-27w}xNC+evfkwkM8gPCRTR;=T z*?jvi2<{-C&SP=~Ob#11i%5&&2n8;78m7M;fyMRn`yH6g{~Rby$Pi*07lCp_B3P{1 zbA8h03j)Fa()delesB^OL3g7a7QShNSx zo#2LZbHkEQC=Zew!4vC&C!sMIq=y^U4gbZ~o6Q%{*mUrVZ>GlgXIt#w+LEa}kS5^p zf;pV{FI5l_%Moz+u^cXpN}bI*3HG%gjm~7x3YX6&>bG;dgFI#;$nfNGSg=o#CNuwn z1qO-5py*f>98Dn5;hI9C!HEnU79Nd>q7yM_9Fl}(*nPHV{J#-IXhv&6p)s{sK0Qg8Rw!Q$T6{oB^r6Bg;*S-M~6i0LKM)TOSv z+f4VGmS}}T9U-UYgu8_|U*5sjX9(>(iADqOed@hNl+n(Yzv50k=&J3yus-F@hY!8L z%OPV9L=SKU04QJrWB`B`0Q>Z6_{XAu3Fc3f{s+N~tsgh~*qWQ0Kb*eM7*U|!E}1JS z5>D25sC%zbIMbJ7d-8Ru;uNr1t#>K}>wOq4GXTZ!gA-~?J5-VLNQk%xtI3j?$2-J zJdOpgLg$#`wB7FS*3(62!-nOkQNv%x+1KyR)C*e>xJuHR)Lu{`e_ex9hxDl;@)PhR{nCPiRw86iuyS?43v7Yvg5>@;&yRz>3yd@TSofLF zy0Z!p>5Yoc5_Ua4#1vTGGQ$E3B|XPCkKTMSoo6d#32&4(9-UdN<;pr&?NisSN@x~Z zey~~B6*!Q0T<#3@3T?XE-4b)|fk8vJ>YF2*DTz5{HFeKoJ%C^=lxTit4?0{jJv~@6 z_(BiYyyz(FHZt8#RkqDsm~!#0Ho9nSNN3e~TEcq`6+ z>JRK2|5{Beb$+qz(B#E5ixs07MM?eJYqM<=*Q2$)CYu(pS()C}-JK`^+BrxCq!vKJ!x#QQlh`K(c@uxsKHHL7 zcb;rWd3Nk}g!m~2dRl@_diZVZVGb)O1NOpm*O^tvQhKUvCGSlx_0&1#)UGkiXbUVa zJJ1j`a_Hct=*4m8E9I!IWny%`)|+x>T#X7y*zh2TJe}q|J|i{aOIFLcP9UMkW{z|{y+7@g zBZXYOzu@HI>&;b9p|E?{NJ^@){kGytdZ$bULp+-?yq7h$@9NQF*uC0yTZH;mSF`W%UqTPtk>c1@Vx{;{ue>f~$@{=LEbw8)&idtelbUdWHaPqXO z0@3tdSEBIUU-RU2_V&a@+B-;nU*W?iBX{1r?`u`oCfglVlwW;g3&J5()Oz_ilBZIR zS$XQ^O}-mGw;XrfZVm8bF0{_syrfz-@~Cn_46diYXR0qx84uWJEbxTvf0$Z1aJT)Z z+Z)GyMz#>7i-F3!E?8k%`xe1NZGz4weNjmPEYHU@v~bW6lm7K@w>-Yqbe1Wo zBEHB^VhPu>3>8&I8JAi|E)1Ab?d$h$@+f|r0?&6o=%nrySd~e)DEDm=E3}%3wkE5t zWpvG@h3H3WSf&MO4k<$JcvnV?vdz>xn@hss;HF z6U)a6n$o}#S8VuK7QbfC}}ocX1LoO*M{$v6CIPT_W$ zgW$Z%!8&Ghmm+}LUwIvAAy(iAxLrvHop$>S@jbKwdKfXWw`NN-4`|sUO j{)ql>-g}6H6o?KWI_G)Bc-VCS&C9aPtiuX literal 5653 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 Wr=9MwmK5XvL>cIqJgU`pj`<%3F8Egf diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit100-1@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit100-1@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0b0ae85972b22118b1d3116dfc4f2e0915076d73 GIT binary patch literal 9934 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�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 S;=}F{0ZMY}vT$jO@c#lO1J)@3 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit300-0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit300-0@2x.png index 7ee45a7057ca5f1e11d9bea30375c92e139cf01e..441c3ec21f8a88762c00ea80eda09a7a32faa334 100644 GIT binary patch literal 3928 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 AIRF3v literal 5004 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 QCoF)HoVsi+&@%l00vK~Sa{vGU literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit50-0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit50-0@2x.png index 7be5374a4679af322dd3d4c4b331afc8951a1bda..6f92db28d3c20066a8a74ec8c6d8d143945a450a 100644 GIT binary patch literal 2966 zcmdT`X;@Qd7XAX1%^=hk3o7Iqw73wm6A}y{n=FC|h@fB+k{gJS+?WItlqHIVQdFD~ z!O;jhwWxJm!y{0OpaaE%3swbW)u1A>DX1*6%tgh{52w#O^YqW$Ki~PzJ@0$Ycb5Cz ztl*$kW~TE@0RU!xzFr{!U{DbQj1AExN_J9+F4hX~2t_C&R492e7_1Z^F)-vO=EcGx zFi()Q6h#BD!c8UR@#A3y6a&YKBy9Y^>4Bs`sV5vK^4%tbL$?Km@wdhEnF73X4mBwT`Kok<$nMiUblBoo;6N^S- zQ7F)d2akG_352W=FP{&-&>b5er%*^)M50oubW~Cu5m_ve%w#f&Bnpv2A)pond6GoI zQxPO`yBP*ASk9M;qzVxtfuME)UBXv&Bxo|H&-BoW2p z>2tlamMcQw-^2K%wLC0I3KK(MIg%*jqxBHl&4AI|{jsBILo^!J8kqNz&igSmgOmfc?v`p zh9C(uT@W0HC=huZB85CXr>o9{mIU(nBFVIJ>2#sqpW6$TiMGN59~mNs-esC4`a~2| z5{*jc)5ru0gTW`Djl?533+Ob0kQ&2xrc&r6CQX30sCBx zb0HVYgLAx0LQIMbLOqu3X#IM*`7e28E!+FrQaZ3%><0ay-n7fhZHIe5CI{bo1yDv#cEY!S> zU#GU$9`ghl$YNkzxl2EIb=OyVYH;u~W1t6tin(cWuD%84;4>_@d*4FPxi-=L@T48! zAHsyS8h;54xPk`MI>K=LuV(HB+~alXyf?GlflsjkX<`*Hd>l5|Id?HbuP#IPu>NkQ za`si_CgGDpEzWTG%CW_j9d#rZBN4!?ioRW~cWBI_g}rfRMpiM`HgXSmr7u-C?X zvTJtElVw4tzOre(l$jArt1De(0Rq&UF}_WpSJ2$?^5K-kzin{XxMoZ~i=(8ziByBA zth+DyN5BnA3)rpHov<~JZ<4DLm_VG;W`sF!9!v*iV&HmfEG=SCcyxaJFj_aHU}1Y{ zXsi}v)i_b6skSvzaU%94Swp~WJJhHyc`~`hS$divMZOq_6H97 zwFNeF3CTIR+O$&JQ}ZJ(zYrdNbaU0Y;U(Aj6WMi{x*Vl8a&XeSMXT;xSA2K}{V-`Ua8IPy~-&Yr|xQARRCEV6HEVa4)o?WA?0F!X8&^O&0n9XjU?F7>Aak5AIc7S?aartQ_@Skvv zciW6PT5j!bzs4skhvS>o@Z&eJ-8^${)3xGqE2|TRMc1QUW6soWJi;+O*AO~psrasQ za6#5B@?>Q-Lznz>>8@eZ@#p=EfTsq7g~z7GdQ0!c5n>EnIhhc4+^~H^ zYk)jb4;$2`RpQS1XbbPRJTSjImU+56IocjW(znt$r<@z=spOkCCNb&;PF=*cq`J@y zpqfZ`&U_A&eR1H~Uo!Ma)j%`WJi1q_j@sH*((f^?c*QoG+IRIrOHzUwV56VA_rHE} zva|R5+ghZf1BZ!AfPP5e+E7dTd3|j){o9>44T#50DkF|-ZfYaaYlg>rD0&N%)52BN zn+&*$I(2A}dCH#8pT*cuS{PMiPMB7nT(Dts#1U`N(3y5g)pl)Gb}MtJ>$V-n-b!t? z<9u$JOWN4LozzEuo{5}U5cbWHjv@29@nqMWp^TedsXtj(+m9GLDg%e-<0kvoW`tV3 z`rgK#U(Qprf4^)j!2kE{^WWT>N3W!PW(AgCFFNzMzVQJ1qYV7KgS^gqaCiO-X=E-p literal 3861 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 F_J2M}_?iF! diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit50-1@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit50-1@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b28503e9f2eb683cac80a4b5853c6fa884d75964 GIT binary patch literal 9138 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, "Resources/metrics_skin"), audio, true); + defaultSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore(dllStore, "Resources/default_skin"), audio, false); + specialSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore(dllStore, "Resources/special_skin"), audio, true); } public void SetContents(Func creationFunction) @@ -43,23 +43,32 @@ namespace osu.Game.Rulesets.Osu.Tests Cell(3).Child = new LocalSkinOverrideContainer(specialSkin) { RelativeSizeAxes = Axes.Both }.WithChild(creationFunction()); } - private static Skin getSkinFromResources(SkinManager skins, string name) + private class TestLegacySkin : LegacySkin { - using (var storage = new DllResourceStore("osu.Game.Rulesets.Osu.Tests.dll")) + private readonly bool extrapolateAnimations; + + public TestLegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager, bool extrapolateAnimations) + : base(skin, storage, audioManager, "skin.ini") { - var tempName = Path.GetTempFileName(); + this.extrapolateAnimations = extrapolateAnimations; + } - File.Delete(tempName); - Directory.CreateDirectory(tempName); + public override Texture GetTexture(string componentName) + { + // extrapolate frames to test longer animations + if (extrapolateAnimations) + { + var match = Regex.Match(componentName, "-([0-9]*)"); - var files = storage.GetAvailableResources().Where(f => f.StartsWith($"Resources/{name}")); + if (match.Length > 0) + { + var number = int.Parse(match.Groups[1].Value); + if (number < 60) + return base.GetTexture(componentName.Replace($"-{number}", $"-{number % 2}")); + } + } - foreach (var file in files) - using (var stream = storage.GetStream(file)) - using (var newFile = File.Create(Path.Combine(tempName, Path.GetFileName(file)))) - stream.CopyTo(newFile); - - return skins.GetSkin(skins.Import(tempName).Result); + return base.GetTexture(componentName); } } } From 6dd638b32793acb6dd0d2db08f92ec00cae3db39 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Aug 2019 17:39:24 +0900 Subject: [PATCH 0788/2815] Further improve legibility of texture lookup --- osu.Game/Skinning/LegacySkin.cs | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 94421b1251..6e983fe771 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -151,22 +151,33 @@ namespace osu.Game.Skinning Texture getFrameTexture(int frame) => GetTexture($"{componentName}{animationSeparator}{frame}"); - if (animatable && (texture = getFrameTexture(0)) != null) + TextureAnimation animation = null; + + if (animatable) { - var animation = new TextureAnimation { DefaultFrameLength = default_frame_time }; - - for (int i = 1; texture != null; i++) + for (int i = 0;; i++) { + if ((texture = getFrameTexture(i)) == null) + break; + + if (animation == null) + animation = new TextureAnimation + { + DefaultFrameLength = default_frame_time, + Repeat = looping + }; + animation.AddFrame(texture); - texture = getFrameTexture(i); } - - animation.Repeat = looping; - - return animation; } - return (texture = GetTexture(componentName)) == null ? null : new Sprite { Texture = texture }; + if (animation != null) + return animation; + + if ((texture = GetTexture(componentName)) != null) + return new Sprite { Texture = texture }; + + return null; } public class LegacySliderBall : Sprite From 9f0a0b2fcb0055ca5b4cc47334aef18fa0260402 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Aug 2019 17:42:33 +0900 Subject: [PATCH 0789/2815] Fix file extension case #1 --- .../Resources/metrics-skin/hit0@2x.PNG | Bin 9492 -> 0 bytes .../Resources/metrics-skin/hit100@2x.PNG | Bin 8371 -> 0 bytes .../Resources/metrics-skin/hit300@2x.PNG | Bin 9589 -> 0 bytes .../Resources/metrics-skin/hit50@2x.PNG | Bin 9299 -> 0 bytes 4 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit0@2x.PNG delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit100@2x.PNG delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit300@2x.PNG delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit50@2x.PNG diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit0@2x.PNG b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit0@2x.PNG deleted file mode 100644 index a91072eb5b84a8204add39df625c54b703e0d873..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9492 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 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit50@2x.PNG b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit50@2x.PNG deleted file mode 100644 index f64feded0cc2fc38672c081da38fe214d2546df3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9299 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 Date: Tue, 20 Aug 2019 17:42:57 +0900 Subject: [PATCH 0790/2815] Fix file extension case #2 --- .../Resources/metrics-skin/hit0@2x.png | Bin 0 -> 9492 bytes .../Resources/metrics-skin/hit100@2x.png | Bin 0 -> 8371 bytes .../Resources/metrics-skin/hit300@2x.png | Bin 0 -> 9589 bytes .../Resources/metrics-skin/hit50@2x.png | Bin 0 -> 9299 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit0@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit100@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit300@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit50@2x.png diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit0@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a91072eb5b84a8204add39df625c54b703e0d873 GIT binary patch literal 9492 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit50@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit50@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..f64feded0cc2fc38672c081da38fe214d2546df3 GIT binary patch literal 9299 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 Date: Tue, 20 Aug 2019 17:47:24 +0900 Subject: [PATCH 0791/2815] Don't show judgement type "none" --- .../TestSceneDrawableJudgement.cs | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs index 6f6520dafa..82a8d0e5e6 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Game.Rulesets.Judgements; @@ -21,19 +22,13 @@ namespace osu.Game.Rulesets.Osu.Tests public TestSceneDrawableJudgement() { - foreach (HitResult result in Enum.GetValues(typeof(HitResult))) - { - JudgementResult judgement = new JudgementResult(null) - { - Type = result, - }; - - AddStep("Show " + result.GetDescription(), () => SetContents(() => new DrawableOsuJudgement(judgement, null) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - })); - } + foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType().Skip(1)) + AddStep("Show " + result.GetDescription(), () => SetContents(() => + new DrawableOsuJudgement(new JudgementResult(null) { Type = result }, null) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + })); } } } From 7d668c81fcbbeb1cd2ccd7dba538864c764d81a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Aug 2019 18:13:00 +0900 Subject: [PATCH 0792/2815] Avoid test failures This is a bit of a workaround (I'm pretty sure these tests are not running correctly on appveyor) but I think it will do for now. Cannot repro failures locally at all. --- osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs index 8b37ef5876..02716dc1d5 100644 --- a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs +++ b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs @@ -60,12 +60,8 @@ namespace osu.Game.Rulesets.Osu.Tests { var match = Regex.Match(componentName, "-([0-9]*)"); - if (match.Length > 0) - { - var number = int.Parse(match.Groups[1].Value); - if (number < 60) - return base.GetTexture(componentName.Replace($"-{number}", $"-{number % 2}")); - } + if (match.Length > 0 && int.TryParse(match.Groups[1].Value, out var number) && number < 60) + return base.GetTexture(componentName.Replace($"-{number}", $"-{number % 2}")); } return base.GetTexture(componentName); From 33946f045812e67b22681cdedf408008807ac739 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Tue, 20 Aug 2019 11:18:41 +0200 Subject: [PATCH 0793/2815] Fix CI issues and update comments. --- osu.Game/Screens/Menu/IntroScreen.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index a621e29cf8..6a98c78d5f 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -30,14 +30,14 @@ namespace osu.Game.Screens.Menu private LeasedBindable beatmap; - public new Bindable Beatmap { get => beatmap; } + public new Bindable Beatmap => beatmap; protected override BackgroundScreen CreateBackground() => new BackgroundScreenBlack(); [BackgroundDependencyLoader] private void load(OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game) { - //we take a lease on the beatmap bindable to prevent music playback from changing / pausing music during intros, as it is causing weird interactions with certains intros + // we take a lease on the beatmap bindable to prevent music playback from changing / pausing during intros, as it is causing weird interactions with certains intros. beatmap = base.Beatmap.BeginLease(false); menuVoice = config.GetBindable(OsuSetting.MenuVoice); @@ -115,7 +115,7 @@ namespace osu.Game.Screens.Menu protected void LoadMenu() { DidLoadMenu = true; - beatmap.Return(); //we return the lease to the beatmap bindable as we're pushing the main menu. + beatmap.Return(); this.Push(mainMenu); } From 0de219dda48cb8d7a370e0424ebba7a5e9025c0a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 21 Jun 2019 20:47:43 +0900 Subject: [PATCH 0794/2815] Update with spritetext text builder changes --- osu.Game/Skinning/LegacySkin.cs | 50 +++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 3eda76e40f..ee88d3254e 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; +using osu.Framework.Text; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -234,37 +235,50 @@ namespace osu.Game.Skinning private class LegacySpriteText : OsuSpriteText { - private readonly TextureStore textures; - private readonly string font; + private readonly LegacyGlyphStore glyphStore; public LegacySpriteText(TextureStore textures, string font) { - this.textures = textures; - this.font = font; - Shadow = false; UseFullGlyphHeight = false; + + Font = new FontUsage(font, 16); + glyphStore = new LegacyGlyphStore(textures); } - protected override Texture GetTextureForCharacter(char c) + protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore); + + private class LegacyGlyphStore : ITexturedGlyphLookupStore { - string textureName = $"{font}-{c}"; + private readonly TextureStore textures; - // Approximate value that brings character sizing roughly in-line with stable - float ratio = 36; - - var texture = textures.Get($"{textureName}@2x"); - - if (texture == null) + public LegacyGlyphStore(TextureStore textures) { - ratio = 18; - texture = textures.Get(textureName); + this.textures = textures; } - if (texture != null) - texture.ScaleAdjust = ratio; + public ITexturedCharacterGlyph Get(string fontName, char character) + { + string textureName = $"{fontName}-{character}"; - return texture; + // Approximate value that brings character sizing roughly in-line with stable + float ratio = 36; + + var texture = textures.Get($"{textureName}@2x"); + + if (texture == null) + { + ratio = 18; + texture = textures.Get(textureName); + } + + if (texture != null) + texture.ScaleAdjust = ratio; + + return new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture?.Width ?? 0, null), texture, 1f / ratio); + } + + public Task GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character)); } } From f1d02d8169a6b38b6f8019c83a3fbaea8bae2d81 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 20 Aug 2019 15:00:14 +0300 Subject: [PATCH 0795/2815] Update design in line with web --- .../Kudosu/DrawableKudosuHistoryItem.cs | 105 +++++++++++++----- 1 file changed, 78 insertions(+), 27 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index 9b68131515..7c120e60f3 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -12,49 +12,100 @@ using osu.Game.Online.Chat; namespace osu.Game.Overlays.Profile.Sections.Kudosu { - public class DrawableKudosuHistoryItem : DrawableProfileRow + public class DrawableKudosuHistoryItem : CompositeDrawable { + private const int height = 25; + + [Resolved] + private OsuColour colours { get; set; } + private readonly APIKudosuHistory historyItem; - private LinkFlowContainer content; + private readonly LinkFlowContainer linkFlowContainer; + private readonly DrawableDate date; public DrawableKudosuHistoryItem(APIKudosuHistory historyItem) { this.historyItem = historyItem; + + Height = height; + RelativeSizeAxes = Axes.X; + AddRangeInternal(new Drawable[] + { + linkFlowContainer = new LinkFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + }, + date = new DrawableDate(historyItem.CreatedAt) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + } + }); } [BackgroundDependencyLoader] private void load() { - LeftFlowContainer.Padding = new MarginPadding { Left = 10 }; + date.Colour = colours.GreySeafoamLighter; - LeftFlowContainer.Add(content = new LinkFlowContainer + switch (historyItem.Action) { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - }); + case KudosuAction.VoteGive: + case KudosuAction.Give: + linkFlowContainer.AddText($@"Received "); + addKudosuPart(); + addMainPart(); + addPostPart(); + break; - RightFlowContainer.Add(new DrawableDate(historyItem.CreatedAt) - { - Font = OsuFont.GetFont(size: 13), - Colour = OsuColour.Gray(0xAA), - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - }); + case KudosuAction.Reset: + addMainPart(); + addPostPart(); + break; - var formatted = createMessage(); + case KudosuAction.VoteReset: + linkFlowContainer.AddText($@"Lost "); + addKudosuPart(); + addMainPart(); + addPostPart(); + break; - content.AddLinks(formatted.Text, formatted.Links); + case KudosuAction.DenyKudosuReset: + linkFlowContainer.AddText($@"Denied "); + addKudosuPart(); + addMainPart(); + addPostPart(); + break; + + case KudosuAction.Revoke: + addMainPart(); + addPostPart(); + break; + } } - protected override Drawable CreateLeftVisual() => new Container + private void addKudosuPart() { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - }; + linkFlowContainer.AddText($@"{historyItem.Amount} kudosu", t => + { + t.Font = t.Font.With(italics: true); + t.Colour = colours.Blue; + }); + } + + private void addMainPart() + { + var text = createMessage(); + + linkFlowContainer.AddLinks(text.Text, text.Links); + } + + private void addPostPart() => linkFlowContainer.AddLink(historyItem.Post.Title, historyItem.Post.Url); private MessageFormatter.MessageFormatterResult createMessage() { - string postLinkTemplate() => $"[{historyItem.Post.Url} {historyItem.Post.Title}]"; string userLinkTemplate() => $"[{historyItem.Giver?.Url} {historyItem.Giver?.Username}]"; string message; @@ -62,27 +113,27 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu switch (historyItem.Action) { case KudosuAction.Give: - message = $"Received {historyItem.Amount} kudosu from {userLinkTemplate()} for a post at {postLinkTemplate()}"; + message = $" from {userLinkTemplate()} for a post at "; break; case KudosuAction.VoteGive: - message = $"Received {historyItem.Amount} kudosu from obtaining votes in modding post of {postLinkTemplate()}"; + message = $" from obtaining votes in modding post of "; break; case KudosuAction.Reset: - message = $"Kudosu reset by {userLinkTemplate()} for the post {postLinkTemplate()}"; + message = $"Kudosu reset by {userLinkTemplate()} for the post "; break; case KudosuAction.VoteReset: - message = $"Lost {historyItem.Amount} kudosu from losing votes in modding post of {postLinkTemplate()}"; + message = $" from losing votes in modding post of "; break; case KudosuAction.DenyKudosuReset: - message = $"Denied {historyItem.Amount} kudosu from modding post {postLinkTemplate()}"; + message = $" from modding post "; break; case KudosuAction.Revoke: - message = $"Denied kudosu by {userLinkTemplate()} for the post {postLinkTemplate()}"; + message = $"Denied kudosu by {userLinkTemplate()} for the post "; break; default: From 832b365bd08ac76da29707aea9d2d466d745497c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 20 Aug 2019 15:17:31 +0300 Subject: [PATCH 0796/2815] Add testing --- .../Visual/Online/TestSceneKudosuHistory.cs | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs new file mode 100644 index 0000000000..6424db89fa --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs @@ -0,0 +1,156 @@ +// 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.Overlays.Profile.Sections.Kudosu; +using System.Collections.Generic; +using System; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.API.Requests; +using osu.Framework.Extensions.IEnumerableExtensions; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneKudosuHistory : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(DrawableKudosuHistoryItem), + }; + + private readonly Box background; + + public TestSceneKudosuHistory() + { + FillFlowContainer content; + + AddRange(new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + content = new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Width = 0.7f, + AutoSizeAxes = Axes.Y, + } + }); + + items.ForEach(t => content.Add(new DrawableKudosuHistoryItem(t))); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.GreySeafoam; + } + + private IEnumerable items = new[] + { + new APIKudosuHistory + { + Amount = 10, + CreatedAt = new DateTimeOffset(new DateTime(2011, 11, 11)), + Action = KudosuAction.DenyKudosuReset, + Post = new APIKudosuHistory.ModdingPost + { + Title = @"Random post 1", + Url = @"https://osu.ppy.sh/b/1234", + }, + Giver = new APIKudosuHistory.KudosuGiver + { + Username = @"Username1", + Url = @"https://osu.ppy.sh/u/1234" + } + }, + new APIKudosuHistory + { + Amount = 5, + CreatedAt = new DateTimeOffset(new DateTime(2012, 10, 11)), + Action = KudosuAction.Give, + Post = new APIKudosuHistory.ModdingPost + { + Title = @"Random post 2", + Url = @"https://osu.ppy.sh/b/1234", + }, + Giver = new APIKudosuHistory.KudosuGiver + { + Username = @"Username2", + Url = @"https://osu.ppy.sh/u/1234" + } + }, + new APIKudosuHistory + { + Amount = 8, + CreatedAt = new DateTimeOffset(new DateTime(2013, 9, 11)), + Action = KudosuAction.Reset, + Post = new APIKudosuHistory.ModdingPost + { + Title = @"Random post 3", + Url = @"https://osu.ppy.sh/b/1234", + }, + Giver = new APIKudosuHistory.KudosuGiver + { + Username = @"Username3", + Url = @"https://osu.ppy.sh/u/1234" + } + }, + new APIKudosuHistory + { + Amount = 7, + CreatedAt = new DateTimeOffset(new DateTime(2014, 8, 11)), + Action = KudosuAction.Revoke, + Post = new APIKudosuHistory.ModdingPost + { + Title = @"Random post 4", + Url = @"https://osu.ppy.sh/b/1234", + }, + Giver = new APIKudosuHistory.KudosuGiver + { + Username = @"Username4", + Url = @"https://osu.ppy.sh/u/1234" + } + }, + new APIKudosuHistory + { + Amount = 100, + CreatedAt = new DateTimeOffset(new DateTime(2015, 7, 11)), + Action = KudosuAction.VoteGive, + Post = new APIKudosuHistory.ModdingPost + { + Title = @"Random post 5", + Url = @"https://osu.ppy.sh/b/1234", + }, + Giver = new APIKudosuHistory.KudosuGiver + { + Username = @"Username5", + Url = @"https://osu.ppy.sh/u/1234" + } + }, + new APIKudosuHistory + { + Amount = 20, + CreatedAt = new DateTimeOffset(new DateTime(2016, 6, 11)), + Action = KudosuAction.VoteReset, + Post = new APIKudosuHistory.ModdingPost + { + Title = @"Random post 6", + Url = @"https://osu.ppy.sh/b/1234", + }, + Giver = new APIKudosuHistory.KudosuGiver + { + Username = @"Username6", + Url = @"https://osu.ppy.sh/u/1234" + } + } + }; + } +} From c4344f3f7cc985b5f40ad4e1603c59ab910b3e2b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 20 Aug 2019 15:29:32 +0300 Subject: [PATCH 0797/2815] CI fixes --- .../Visual/Online/TestSceneKudosuHistory.cs | 2 +- .../Kudosu/DrawableKudosuHistoryItem.cs | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs index 6424db89fa..dcf2bec239 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Online background.Colour = colours.GreySeafoam; } - private IEnumerable items = new[] + private readonly IEnumerable items = new[] { new APIKudosuHistory { diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index 7c120e60f3..d6dfdc84ec 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -54,7 +54,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu { case KudosuAction.VoteGive: case KudosuAction.Give: - linkFlowContainer.AddText($@"Received "); + linkFlowContainer.AddText(@"Received "); addKudosuPart(); addMainPart(); addPostPart(); @@ -66,14 +66,14 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu break; case KudosuAction.VoteReset: - linkFlowContainer.AddText($@"Lost "); + linkFlowContainer.AddText(@"Lost "); addKudosuPart(); addMainPart(); addPostPart(); break; case KudosuAction.DenyKudosuReset: - linkFlowContainer.AddText($@"Denied "); + linkFlowContainer.AddText(@"Denied "); addKudosuPart(); addMainPart(); addPostPart(); @@ -113,27 +113,27 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu switch (historyItem.Action) { case KudosuAction.Give: - message = $" from {userLinkTemplate()} for a post at "; + message = $@" from {userLinkTemplate()} for a post at "; break; case KudosuAction.VoteGive: - message = $" from obtaining votes in modding post of "; + message = @" from obtaining votes in modding post of "; break; case KudosuAction.Reset: - message = $"Kudosu reset by {userLinkTemplate()} for the post "; + message = $@"Kudosu reset by {userLinkTemplate()} for the post "; break; case KudosuAction.VoteReset: - message = $" from losing votes in modding post of "; + message = @" from losing votes in modding post of "; break; case KudosuAction.DenyKudosuReset: - message = $" from modding post "; + message = @" from modding post "; break; case KudosuAction.Revoke: - message = $"Denied kudosu by {userLinkTemplate()} for the post "; + message = $@"Denied kudosu by {userLinkTemplate()} for the post "; break; default: From 5c7cb4dc21ecb4a3b5961a4bed7c7f4b1562aa1a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 20 Aug 2019 16:11:59 +0300 Subject: [PATCH 0798/2815] Simplify text creation --- .../Kudosu/DrawableKudosuHistoryItem.cs | 64 +++++-------------- 1 file changed, 16 insertions(+), 48 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index d6dfdc84ec..1b0a501db2 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -50,37 +50,45 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu { date.Colour = colours.GreySeafoamLighter; + string userLinkTemplate() => $"[{historyItem.Giver?.Url} {historyItem.Giver?.Username}]"; + switch (historyItem.Action) { case KudosuAction.VoteGive: + linkFlowContainer.AddText(@"Received "); + addKudosuPart(); + addMainPart($@" from {userLinkTemplate()} for a post at "); + addPostPart(); + break; + case KudosuAction.Give: linkFlowContainer.AddText(@"Received "); addKudosuPart(); - addMainPart(); + addMainPart(@" from obtaining votes in modding post of "); addPostPart(); break; case KudosuAction.Reset: - addMainPart(); + addMainPart($@"Kudosu reset by {userLinkTemplate()} for the post "); addPostPart(); break; case KudosuAction.VoteReset: linkFlowContainer.AddText(@"Lost "); addKudosuPart(); - addMainPart(); + addMainPart(@" from losing votes in modding post of "); addPostPart(); break; case KudosuAction.DenyKudosuReset: linkFlowContainer.AddText(@"Denied "); addKudosuPart(); - addMainPart(); + addMainPart(@" from modding post "); addPostPart(); break; case KudosuAction.Revoke: - addMainPart(); + addMainPart($@"Denied kudosu by {userLinkTemplate()} for the post "); addPostPart(); break; } @@ -95,53 +103,13 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu }); } - private void addMainPart() + private void addMainPart(string text) { - var text = createMessage(); + var formatted = MessageFormatter.FormatText(text); - linkFlowContainer.AddLinks(text.Text, text.Links); + linkFlowContainer.AddLinks(formatted.Text, formatted.Links); } private void addPostPart() => linkFlowContainer.AddLink(historyItem.Post.Title, historyItem.Post.Url); - - private MessageFormatter.MessageFormatterResult createMessage() - { - string userLinkTemplate() => $"[{historyItem.Giver?.Url} {historyItem.Giver?.Username}]"; - - string message; - - switch (historyItem.Action) - { - case KudosuAction.Give: - message = $@" from {userLinkTemplate()} for a post at "; - break; - - case KudosuAction.VoteGive: - message = @" from obtaining votes in modding post of "; - break; - - case KudosuAction.Reset: - message = $@"Kudosu reset by {userLinkTemplate()} for the post "; - break; - - case KudosuAction.VoteReset: - message = @" from losing votes in modding post of "; - break; - - case KudosuAction.DenyKudosuReset: - message = @" from modding post "; - break; - - case KudosuAction.Revoke: - message = $@"Denied kudosu by {userLinkTemplate()} for the post "; - break; - - default: - message = string.Empty; - break; - } - - return MessageFormatter.FormatText(message); - } } } From 426c7a48989302453ca7e05b8c91738e06d6ad7e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 20 Aug 2019 16:19:21 +0300 Subject: [PATCH 0799/2815] Fix incorrect templates --- .../Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index 1b0a501db2..e5f5b720c2 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -57,14 +57,14 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu case KudosuAction.VoteGive: linkFlowContainer.AddText(@"Received "); addKudosuPart(); - addMainPart($@" from {userLinkTemplate()} for a post at "); + addMainPart(@" from obtaining votes in modding post of "); addPostPart(); break; case KudosuAction.Give: linkFlowContainer.AddText(@"Received "); addKudosuPart(); - addMainPart(@" from obtaining votes in modding post of "); + addMainPart($@" from {userLinkTemplate()} for a post at "); addPostPart(); break; From 439d825dd1beecf4a9c447974bcacdee82df3c6b Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 20 Aug 2019 18:39:29 +0300 Subject: [PATCH 0800/2815] Disallow adding bonus judgements' result to statistics --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index c8858233aa..e47df6b473 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -325,9 +325,6 @@ namespace osu.Game.Rulesets.Scoring JudgedHits++; - if (result.Type != HitResult.None) - scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) + 1; - if (result.Judgement.AffectsCombo) { switch (result.Type) @@ -352,6 +349,9 @@ namespace osu.Game.Rulesets.Scoring } else { + if (result.HasResult) + scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) + 1; + baseScore += result.Judgement.NumericResultFor(result); rollingMaxBaseScore += result.Judgement.MaxNumericResult; } @@ -371,9 +371,6 @@ namespace osu.Game.Rulesets.Scoring JudgedHits--; - if (result.Type != HitResult.None) - scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) - 1; - if (result.Judgement.IsBonus) { if (result.IsHit) @@ -381,6 +378,9 @@ namespace osu.Game.Rulesets.Scoring } else { + if (result.HasResult) + scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) - 1; + baseScore -= result.Judgement.NumericResultFor(result); rollingMaxBaseScore -= result.Judgement.MaxNumericResult; } From 48716f8f2b6c57639cf2a2223bb78610ecccf403 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Aug 2019 13:29:50 +0900 Subject: [PATCH 0801/2815] Update framework --- osu.Android.props | 2 +- .../Objects/Drawable/DrawableFruit.cs | 2 +- .../Objects/Drawable/Pieces/Pulp.cs | 2 +- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 2 +- .../Objects/Drawables/Pieces/BodyPiece.cs | 4 ++-- .../Objects/Drawables/Pieces/LaneGlowPiece.cs | 4 ++-- .../UI/Components/ColumnBackground.cs | 2 +- .../Objects/Drawables/Connections/FollowPoint.cs | 2 +- .../Objects/Drawables/DrawableRepeatPoint.cs | 2 +- .../Objects/Drawables/Pieces/CirclePiece.cs | 2 +- .../Objects/Drawables/Pieces/ExplodePiece.cs | 4 ++-- .../Objects/Drawables/Pieces/FlashPiece.cs | 2 +- .../Objects/Drawables/Pieces/GlowPiece.cs | 2 +- .../Objects/Drawables/Pieces/SliderBall.cs | 4 ++-- .../Objects/Drawables/DrawableSwell.cs | 4 ++-- .../Objects/Drawables/Pieces/CirclePiece.cs | 2 +- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 4 ++-- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 6 +++--- .../Visual/UserInterface/TestSceneCursors.cs | 2 +- .../Components/TournamentBeatmapPanel.cs | 2 +- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 9 --------- osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs | 2 +- osu.Game/Graphics/Containers/OsuScrollContainer.cs | 2 +- osu.Game/Graphics/Cursor/MenuCursor.cs | 2 +- osu.Game/Graphics/Sprites/GlowingSpriteText.cs | 2 +- osu.Game/Graphics/UserInterface/DialogButton.cs | 2 +- osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs | 2 +- osu.Game/Graphics/UserInterface/OsuButton.cs | 2 +- .../Overlays/Profile/Header/Components/RankGraph.cs | 9 --------- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 2 +- .../Overlays/Toolbar/ToolbarOverlayToggleButton.cs | 2 +- .../Components/RadioButtons/DrawableRadioButton.cs | 2 +- .../Edit/Compose/Components/BeatDivisorControl.cs | 2 +- osu.Game/Screens/Menu/Button.cs | 2 +- osu.Game/Screens/Menu/IntroTriangles.cs | 6 +++--- osu.Game/Screens/Menu/LogoVisualisation.cs | 2 +- osu.Game/Screens/Menu/MenuSideFlashes.cs | 4 ++-- osu.Game/Screens/Menu/OsuLogo.cs | 4 ++-- .../Screens/Multi/Match/Components/HeaderButton.cs | 2 +- osu.Game/Screens/ScreenWhiteBox.cs | 2 +- .../Screens/Select/Carousel/DrawableCarouselItem.cs | 2 +- .../Screens/Select/Options/BeatmapOptionsButton.cs | 2 +- osu.Game/Storyboards/CommandTimelineGroup.cs | 4 ++-- osu.Game/Storyboards/Drawables/DrawablesExtensions.cs | 10 +++++----- osu.Game/Storyboards/StoryboardSprite.cs | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 47 files changed, 63 insertions(+), 81 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index bb283dc0c5..fe6420ead8 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -61,6 +61,6 @@ - + diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs index ce2daebbf1..1af77b75fc 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs @@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable Anchor = Anchor.Centre, Origin = Anchor.Centre, AccentColour = Color4.Red, - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, Alpha = 0.5f, Scale = new Vector2(1.333f) }); diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs index b9b6d5b924..1e9daf18db 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces Anchor = Anchor.Centre; Origin = Anchor.Centre; - Blending = BlendingMode.Additive; + Blending = BlendingParameters.Additive; Colour = Color4.White.Opacity(0.9f); } diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 0b06e958e6..62abe53559 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -201,7 +201,7 @@ namespace osu.Game.Rulesets.Catch.UI additive.Scale = Scale; additive.Colour = HyperDashing ? Color4.Red : Color4.White; additive.RelativePositionAxes = RelativePositionAxes; - additive.Blending = BlendingMode.Additive; + additive.Blending = BlendingParameters.Additive; AdditiveTarget.Add(additive); diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs index a92e56d3c3..31a4857805 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs @@ -26,14 +26,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces public BodyPiece() { - Blending = BlendingMode.Additive; + Blending = BlendingParameters.Additive; Children = new[] { Background = new Box { RelativeSizeAxes = Axes.Both }, Foreground = new BufferedContainer { - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, RelativeSizeAxes = Axes.Both, CacheDrawnFrameBuffer = true, Children = new Drawable[] diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs index 9e0307c5c2..48c7ea7b7f 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces Name = "Top", RelativeSizeAxes = Axes.Both, Height = 0.5f, - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, Colour = ColourInfo.GradientVertical(Color4.Transparent, Color4.White.Opacity(alpha)) }, new Box @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces Origin = Anchor.BottomLeft, RelativeSizeAxes = Axes.Both, Height = 0.5f, - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, Colour = ColourInfo.GradientVertical(Color4.White.Opacity(alpha), Color4.Transparent) } }; diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs index b4e29ae9f9..5ee78aa496 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components Name = "Background Gradient Overlay", RelativeSizeAxes = Axes.Both, Height = 0.5f, - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, Alpha = 0 } }; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index a2a23e9ff7..523e911434 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections Child = new Box { Size = new Vector2(width), - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, Origin = Anchor.Centre, Anchor = Anchor.Centre, Alpha = 0.5f, diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index f75b62eecf..1db1eec33e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); - Blending = BlendingMode.Additive; + Blending = BlendingParameters.Additive; Origin = Anchor.Centre; InternalChild = scaleContainer = new SkinnableDrawable("Play/osu/reversearrow", _ => new SpriteIcon diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs index c92937ef09..a59cfc1123 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces new TrianglesPiece { RelativeSizeAxes = Axes.Both, - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, Alpha = 0.5f, } }; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs index 8ff16f8b84..1d21347cba 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs @@ -17,12 +17,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Anchor = Anchor.Centre; Origin = Anchor.Centre; - Blending = BlendingMode.Additive; + Blending = BlendingParameters.Additive; Alpha = 0; Child = new SkinnableDrawable("Play/osu/hitcircle-explode", _ => new TrianglesPiece { - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, RelativeSizeAxes = Axes.Both, Alpha = 0.2f, }, s => s.GetTexture("Play/osu/hitcircle") == null); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs index c22073f56c..1e3af567fe 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Anchor = Anchor.Centre; Origin = Anchor.Centre; - Blending = BlendingMode.Additive; + Blending = BlendingParameters.Additive; Alpha = 0; Child = new SkinnableDrawable("Play/osu/hitcircle-flash", name => new CircularContainer diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs index 917695c790..a36d9e96c8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Anchor = Anchor.Centre, Origin = Anchor.Centre, Texture = textures.Get(name), - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, Alpha = 0.5f }, s => s.GetTexture("Play/osu/hitcircle") == null); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index 8b72b23ca3..dbab83d24a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces this.drawableSlider = drawableSlider; this.slider = slider; - Blending = BlendingMode.Additive; + Blending = BlendingParameters.Additive; Origin = Anchor.Centre; Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); @@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Masking = true, BorderThickness = 5, BorderColour = Color4.Orange, - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, Child = new Box { Colour = Color4.Orange, diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 5ec9dc61e2..82448ec7d5 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Origin = Anchor.Centre, Alpha = 0, RelativeSizeAxes = Axes.Both, - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, Masking = true, Children = new[] { @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables RelativeSizeAxes = Axes.Both, Masking = true, BorderThickness = target_ring_thick_border, - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, Children = new Drawable[] { new Box diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs index b7db819717..d9c0664ecd 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs @@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, Colour = Color4.White, - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, Alpha = 0, AlwaysPresent = true } diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index aa37ff7008..d6866c7d25 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Taiko.UI Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, Alpha = 0, - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, }, centre = new Sprite { @@ -124,7 +124,7 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, Size = new Vector2(0.7f), Alpha = 0, - Blending = BlendingMode.Additive + Blending = BlendingParameters.Additive } }; } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 7427a3235d..e62dc45cab 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Taiko.UI { RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, }, HitTarget = new HitTarget { @@ -127,14 +127,14 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Blending = BlendingMode.Additive + Blending = BlendingParameters.Additive }, judgementContainer = new JudgementContainer { Name = "Judgements", RelativeSizeAxes = Axes.Y, Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Blending = BlendingMode.Additive + Blending = BlendingParameters.Additive }, } }, diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs index 23d9112b25..e95f4c09c6 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs @@ -249,7 +249,7 @@ namespace osu.Game.Tests.Visual.UserInterface Size = new Vector2(50); Masking = true; - Blending = BlendingMode.Additive; + Blending = BlendingParameters.Additive; Alpha = 0.5f; Child = new Box { RelativeSizeAxes = Axes.Both }; diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs index d5e28c1e3e..f6c1be0e36 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -125,7 +125,7 @@ namespace osu.Game.Tournament.Components { RelativeSizeAxes = Axes.Both, Colour = Color4.Gray, - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, Alpha = 0, }, }); diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 3c3a7c056e..5cce29d609 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -86,11 +86,6 @@ namespace osu.Game.Beatmaps.Drawables private readonly FillFlowContainer difficultyFlow; - public string TooltipText - { - set { } - } - public DifficultyIconTooltip() { AutoSizeAxes = Axes.Both; @@ -168,10 +163,6 @@ namespace osu.Game.Beatmaps.Drawables return true; } - public void Refresh() - { - } - public void Move(Vector2 pos) => Position = pos; protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 3ae1c3ef12..17df9ccc7e 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -246,7 +246,7 @@ namespace osu.Game.Beatmaps.Formats switch (type) { case "A": - timelineGroup?.BlendingMode.Add(easing, startTime, endTime, BlendingMode.Additive, startTime == endTime ? BlendingMode.Additive : BlendingMode.Inherit); + timelineGroup?.BlendingParameters.Add(easing, startTime, endTime, BlendingParameters.Additive, startTime == endTime ? BlendingParameters.Additive : BlendingParameters.Inherit); break; case "H": diff --git a/osu.Game/Graphics/Containers/OsuScrollContainer.cs b/osu.Game/Graphics/Containers/OsuScrollContainer.cs index 8fc8dec9fd..2721ce55dc 100644 --- a/osu.Game/Graphics/Containers/OsuScrollContainer.cs +++ b/osu.Game/Graphics/Containers/OsuScrollContainer.cs @@ -98,7 +98,7 @@ namespace osu.Game.Graphics.Containers public OsuScrollbar(Direction scrollDir) : base(scrollDir) { - Blending = BlendingMode.Additive; + Blending = BlendingParameters.Additive; CornerRadius = 5; diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 092a23e787..e103798355 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -150,7 +150,7 @@ namespace osu.Game.Graphics.Cursor }, AdditiveLayer = new Sprite { - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, Colour = colour.Pink, Alpha = 0, Texture = textures.Get(@"Cursor/menu-cursor-additive"), diff --git a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs index 74e387d60e..24816deeb5 100644 --- a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs +++ b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs @@ -56,7 +56,7 @@ namespace osu.Game.Graphics.Sprites BlurSigma = new Vector2(4), CacheDrawnFrameBuffer = true, RelativeSizeAxes = Axes.Both, - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, Size = new Vector2(3f), Children = new[] { diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index b50bf14bab..927ad13829 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -254,7 +254,7 @@ namespace osu.Game.Graphics.UserInterface colourContainer.Add(flash); flash.Colour = ButtonColour; - flash.Blending = BlendingMode.Additive; + flash.Blending = BlendingParameters.Additive; flash.Alpha = 0.3f; flash.FadeOutFromOne(click_duration); flash.Expire(); diff --git a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs index 1a8fea4ff9..660bd7979f 100644 --- a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs @@ -64,7 +64,7 @@ namespace osu.Game.Graphics.UserInterface { RelativeSizeAxes = Axes.Both, Colour = HoverColour, - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, Alpha = 0, }, } diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index 7a27f825f6..c1810800a0 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -39,7 +39,7 @@ namespace osu.Game.Graphics.UserInterface hover = new Box { RelativeSizeAxes = Axes.Both, - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, Colour = Color4.White.Opacity(0.1f), Alpha = 0, Depth = -1 diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index de760eedfd..24ed0cc022 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -296,15 +296,6 @@ namespace osu.Game.Overlays.Profile.Header.Components this.MoveTo(pos, 200, Easing.OutQuint); } - public void Refresh() - { - } - - public string TooltipText - { - set => throw new InvalidOperationException(); - } - protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 2b2b19b73a..d6b810366d 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -79,7 +79,7 @@ namespace osu.Game.Overlays.Toolbar { RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(80).Opacity(180), - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, Alpha = 0, }, Flow = new FillFlowContainer diff --git a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs index b286cbfb1d..36387bb00d 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs @@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Toolbar { RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(150).Opacity(180), - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, Depth = 2, Alpha = 0, }); diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs index 70c0cf623e..5854d66aa8 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons Scale = new Vector2(0.5f), X = 10, Masking = true, - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, Child = new Box { RelativeSizeAxes = Axes.Both } }; } diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index c615656d60..0d16d8474b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -360,7 +360,7 @@ namespace osu.Game.Screens.Edit.Compose.Components Origin = Anchor.BottomCentre, Anchor = Anchor.BottomCentre, Colour = ColourInfo.GradientVertical(Color4.White.Opacity(0.2f), Color4.White), - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, }, new EquilateralTriangle { diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs index badd1e0549..1bf25a2504 100644 --- a/osu.Game/Screens/Menu/Button.cs +++ b/osu.Game/Screens/Menu/Button.cs @@ -92,7 +92,7 @@ namespace osu.Game.Screens.Menu { EdgeSmoothness = new Vector2(1.5f, 0), RelativeSizeAxes = Axes.Both, - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, Colour = Color4.White, Alpha = 0, }, diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index ba0d624959..f5a72c2c1d 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -296,7 +296,7 @@ namespace osu.Game.Screens.Menu { Colour = Color4.White; RelativeSizeAxes = Axes.Both; - Blending = BlendingMode.Additive; + Blending = BlendingParameters.Additive; } protected override void LoadComplete() @@ -399,11 +399,11 @@ namespace osu.Game.Screens.Menu Origin = Anchor.Centre, Colour = Color4.Black, Size = new Vector2(size - 5), - Blending = BlendingMode.None, + Blending = BlendingParameters.None, }); } - Blending = BlendingMode.Additive; + Blending = BlendingParameters.Additive; CacheDrawnFrameBuffer = true; } } diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 39bda799b5..9d0a5cd05b 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -76,7 +76,7 @@ namespace osu.Game.Screens.Menu public LogoVisualisation() { texture = Texture.WhitePixel; - Blending = BlendingMode.Additive; + Blending = BlendingParameters.Additive; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs index 95d0bf04b4..393964561c 100644 --- a/osu.Game/Screens/Menu/MenuSideFlashes.cs +++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs @@ -70,7 +70,7 @@ namespace osu.Game.Screens.Menu // align off-screen to make sure our edges don't become visible during parallax. X = -box_width, Alpha = 0, - Blending = BlendingMode.Additive + Blending = BlendingParameters.Additive }, rightBox = new Box { @@ -81,7 +81,7 @@ namespace osu.Game.Screens.Menu Height = 1.5f, X = box_width, Alpha = 0, - Blending = BlendingMode.Additive + Blending = BlendingParameters.Additive } }; diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 479b3d80b6..0c5bf12bdb 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -124,7 +124,7 @@ namespace osu.Game.Screens.Menu { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, Alpha = 0 } } @@ -185,7 +185,7 @@ namespace osu.Game.Screens.Menu flashLayer = new Box { RelativeSizeAxes = Axes.Both, - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, Colour = Color4.White, Alpha = 0, }, diff --git a/osu.Game/Screens/Multi/Match/Components/HeaderButton.cs b/osu.Game/Screens/Multi/Match/Components/HeaderButton.cs index f3412d0be7..de6ece6a05 100644 --- a/osu.Game/Screens/Multi/Match/Components/HeaderButton.cs +++ b/osu.Game/Screens/Multi/Match/Components/HeaderButton.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.Multi.Match.Components { RelativeSizeAxes = Axes.Both, Alpha = 0.15f, - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, }, }); } diff --git a/osu.Game/Screens/ScreenWhiteBox.cs b/osu.Game/Screens/ScreenWhiteBox.cs index 5648dd997b..6c5854d17e 100644 --- a/osu.Game/Screens/ScreenWhiteBox.cs +++ b/osu.Game/Screens/ScreenWhiteBox.cs @@ -95,7 +95,7 @@ namespace osu.Game.Screens Colour = getColourFor(GetType()), Alpha = 0.2f, - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, }, textContainer = new FillFlowContainer { diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index b906bd935c..6118191302 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Select.Carousel { RelativeSizeAxes = Axes.Both, Alpha = 0, - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, }, } }; diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs index a8b5bbbd00..ff9beafb23 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs @@ -121,7 +121,7 @@ namespace osu.Game.Screens.Select.Options { RelativeSizeAxes = Axes.Both, EdgeSmoothness = new Vector2(1.5f, 0), - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, Colour = Color4.White, Alpha = 0, }, diff --git a/osu.Game/Storyboards/CommandTimelineGroup.cs b/osu.Game/Storyboards/CommandTimelineGroup.cs index b1cc0436de..461ee762e9 100644 --- a/osu.Game/Storyboards/CommandTimelineGroup.cs +++ b/osu.Game/Storyboards/CommandTimelineGroup.cs @@ -20,7 +20,7 @@ namespace osu.Game.Storyboards public CommandTimeline Rotation = new CommandTimeline(); public CommandTimeline Colour = new CommandTimeline(); public CommandTimeline Alpha = new CommandTimeline(); - public CommandTimeline BlendingMode = new CommandTimeline(); + public CommandTimeline BlendingParameters = new CommandTimeline(); public CommandTimeline FlipH = new CommandTimeline(); public CommandTimeline FlipV = new CommandTimeline(); @@ -35,7 +35,7 @@ namespace osu.Game.Storyboards yield return Rotation; yield return Colour; yield return Alpha; - yield return BlendingMode; + yield return BlendingParameters; yield return FlipH; yield return FlipV; } diff --git a/osu.Game/Storyboards/Drawables/DrawablesExtensions.cs b/osu.Game/Storyboards/Drawables/DrawablesExtensions.cs index 7e31e1135e..bbc55a336d 100644 --- a/osu.Game/Storyboards/Drawables/DrawablesExtensions.cs +++ b/osu.Game/Storyboards/Drawables/DrawablesExtensions.cs @@ -12,19 +12,19 @@ namespace osu.Game.Storyboards.Drawables /// Adjusts after a delay. ///

/// A to which further transforms can be added. - public static TransformSequence TransformBlendingMode(this T drawable, BlendingMode newValue, double delay = 0) + public static TransformSequence TransformBlendingMode(this T drawable, BlendingParameters newValue, double delay = 0) where T : Drawable - => drawable.TransformTo(drawable.PopulateTransform(new TransformBlendingMode(), newValue, delay)); + => drawable.TransformTo(drawable.PopulateTransform(new TransformBlendingParameters(), newValue, delay)); } - public class TransformBlendingMode : Transform + public class TransformBlendingParameters : Transform { - private BlendingMode valueAt(double time) + private BlendingParameters valueAt(double time) => time < EndTime ? StartValue : EndValue; public override string TargetMember => nameof(Drawable.Blending); protected override void Apply(Drawable d, double time) => d.Blending = valueAt(time); - protected override void ReadIntoStartValue(Drawable d) => StartValue = d.Blending.Mode; + protected override void ReadIntoStartValue(Drawable d) => StartValue = d.Blending; } } diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 8f8ec22aae..37c3ff495f 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -69,7 +69,7 @@ namespace osu.Game.Storyboards applyCommands(drawable, getCommands(g => g.Rotation, triggeredGroups), (d, value) => d.Rotation = value, (d, value, duration, easing) => d.RotateTo(value, duration, easing)); applyCommands(drawable, getCommands(g => g.Colour, triggeredGroups), (d, value) => d.Colour = value, (d, value, duration, easing) => d.FadeColour(value, duration, easing)); applyCommands(drawable, getCommands(g => g.Alpha, triggeredGroups), (d, value) => d.Alpha = value, (d, value, duration, easing) => d.FadeTo(value, duration, easing)); - applyCommands(drawable, getCommands(g => g.BlendingMode, triggeredGroups), (d, value) => d.Blending = value, (d, value, duration, easing) => d.TransformBlendingMode(value, duration), false); + applyCommands(drawable, getCommands(g => g.BlendingParameters, triggeredGroups), (d, value) => d.Blending = value, (d, value, duration, easing) => d.TransformBlendingMode(value, duration), false); if (drawable is IFlippable flippable) { diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 758c4dda4c..8176b61fca 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -15,7 +15,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index d6ad35b663..332d891416 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,8 +118,8 @@ - - + + From cca64771dd73030f81a6f9b1019223aacb746294 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Aug 2019 13:56:49 +0900 Subject: [PATCH 0802/2815] Add comment about placement of default loading --- osu.Game/Skinning/LegacySkin.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 5a05420e75..2be31c5ee0 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -43,6 +43,7 @@ namespace osu.Game.Skinning public LegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager) : this(skin, new LegacySkinResourceStore(skin, storage), audioManager, "skin.ini") { + // defaults should only be applied for non-beatmap skins (which are parsed via this constructor). if (!Configuration.CustomColours.ContainsKey("SliderBall")) Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255); } From 724365c6d12e4b1f24824dfc7a20448839066f32 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Aug 2019 14:01:07 +0900 Subject: [PATCH 0803/2815] Minor tidying --- osu.Game/Screens/Menu/IntroScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 6a98c78d5f..4d0f7ff87a 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load(OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game) { - // we take a lease on the beatmap bindable to prevent music playback from changing / pausing during intros, as it is causing weird interactions with certains intros. + // prevent user from changing beatmap while the intro is still runnning. beatmap = base.Beatmap.BeginLease(false); menuVoice = config.GetBindable(OsuSetting.MenuVoice); @@ -114,9 +114,9 @@ namespace osu.Game.Screens.Menu protected void LoadMenu() { - DidLoadMenu = true; beatmap.Return(); + DidLoadMenu = true; this.Push(mainMenu); } } From ae7bedacc1a05d9ea4d70eae3c4faee003f6e497 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 21 Aug 2019 05:20:49 +0000 Subject: [PATCH 0804/2815] Bump ppy.osu.Framework.iOS from 2019.816.0 to 2019.821.0 Bumps [ppy.osu.Framework.iOS](https://github.com/ppy/osu-framework) from 2019.816.0 to 2019.821.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.816.0...2019.821.0) Signed-off-by: dependabot-preview[bot] --- osu.iOS.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.iOS.props b/osu.iOS.props index d6ad35b663..6e459bdd23 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -119,7 +119,7 @@ - + From 9ce783fbf284d8b6e84798a96e336b454bbc367c Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 21 Aug 2019 05:21:25 +0000 Subject: [PATCH 0805/2815] Bump ppy.osu.Framework.Android from 2019.816.0 to 2019.821.0 Bumps [ppy.osu.Framework.Android](https://github.com/ppy/osu-framework) from 2019.816.0 to 2019.821.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.816.0...2019.821.0) Signed-off-by: dependabot-preview[bot] --- osu.Android.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Android.props b/osu.Android.props index bb283dc0c5..fe6420ead8 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -61,6 +61,6 @@ - + From 444f2b9387bf6c850dd95e49ddd7ce3ed09cfbd6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Aug 2019 14:31:19 +0900 Subject: [PATCH 0806/2815] Specify font size in a saner way --- osu.Game/Skinning/LegacySkin.cs | 35 +++++++++++++-------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 21e8995981..de6346c9dc 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -135,7 +135,7 @@ namespace osu.Game.Skinning case "Play/osu/number-text": return !hasFont(Configuration.HitCircleFont) ? null - : new LegacySpriteText(Textures, Configuration.HitCircleFont) + : new LegacySpriteText(this, Configuration.HitCircleFont) { Scale = new Vector2(0.96f), // Spacing value was reverse-engineered from the ratio of the rendered sprite size in the visual inspector vs the actual texture size @@ -282,45 +282,38 @@ namespace osu.Game.Skinning { private readonly LegacyGlyphStore glyphStore; - public LegacySpriteText(TextureStore textures, string font) + public LegacySpriteText(ISkin skin, string font) { Shadow = false; UseFullGlyphHeight = false; - Font = new FontUsage(font, 16); - glyphStore = new LegacyGlyphStore(textures); + Font = new FontUsage(font, OsuFont.DEFAULT_FONT_SIZE); + glyphStore = new LegacyGlyphStore(skin); } protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore); private class LegacyGlyphStore : ITexturedGlyphLookupStore { - private readonly TextureStore textures; + private readonly ISkin skin; - public LegacyGlyphStore(TextureStore textures) + public LegacyGlyphStore(ISkin skin) { - this.textures = textures; + this.skin = skin; } public ITexturedCharacterGlyph Get(string fontName, char character) { - string textureName = $"{fontName}-{character}"; - - // Approximate value that brings character sizing roughly in-line with stable - float ratio = 36; - - var texture = textures.Get($"{textureName}@2x"); - - if (texture == null) - { - ratio = 18; - texture = textures.Get(textureName); - } + var texture = skin.GetTexture($"{fontName}-{character}"); if (texture != null) - texture.ScaleAdjust = ratio; + // Approximate value that brings character sizing roughly in-line with stable + texture.ScaleAdjust *= 18; - return new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture?.Width ?? 0, null), texture, 1f / ratio); + if (texture == null) + return null; + + return new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture?.Width ?? 0, null), texture, 1f / texture.ScaleAdjust); } public Task GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character)); From 4186d2566a4a85d09507804fa90b0a9a67228cea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Aug 2019 15:11:33 +0900 Subject: [PATCH 0807/2815] Remove unnecessary null checks --- 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 e2ef3183da..a81e8c2b67 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.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; @@ -317,7 +317,7 @@ namespace osu.Game.Skinning if (texture == null) return null; - return new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture?.Width ?? 0, null), texture, 1f / texture.ScaleAdjust); + return new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, null), texture, 1f / texture.ScaleAdjust); } public Task GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character)); From 28dfe072a50f5998b5d31ea9b5bba01c0f9846a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Aug 2019 15:11:46 +0900 Subject: [PATCH 0808/2815] Update one more blending mode change post-master merge --- osu.Game/Skinning/LegacySkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index a81e8c2b67..48310cf027 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -380,7 +380,7 @@ namespace osu.Game.Skinning new Sprite { Texture = skin.GetTexture("sliderb-spec"), - Blending = BlendingMode.Additive, + Blending = BlendingParameters.Additive, }, }; } From 71cbc3525d98e3177c60c60f0677dece66ad9105 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 21 Aug 2019 09:16:09 +0300 Subject: [PATCH 0809/2815] Add/remove displays only if necessary --- .../HitErrorDisplay/HitErrorDisplayOverlay.cs | 62 ++++++++++++++----- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs index 1c61904461..fae1b68e4a 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs @@ -8,7 +8,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Configuration; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Play.HitErrorDisplay @@ -22,6 +21,9 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private readonly HitWindows hitWindows; private readonly ScoreProcessor processor; + private BarHitErrorDisplay leftDisplay; + private BarHitErrorDisplay rightDisplay; + public HitErrorDisplayOverlay(ScoreProcessor processor) { this.processor = processor; @@ -43,38 +45,69 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private void onTypeChanged(ValueChangedEvent type) { - clear(); - switch (type.NewValue) { case ScoreMeterType.None: + removeLeftDisplay(); + removeRightDisplay(); break; case ScoreMeterType.HitErrorBoth: - createNew(); - createNew(true); + addLeftDisplay(); + addRightDisplay(); break; case ScoreMeterType.HitErrorLeft: - createNew(); + addLeftDisplay(); + removeRightDisplay(); break; case ScoreMeterType.HitErrorRight: - createNew(true); + addRightDisplay(); + removeLeftDisplay(); break; } } - private void clear() + private void addLeftDisplay() { - Children.ForEach(t => - { - processor.NewJudgement -= t.OnNewJudgement; - t.FadeOut(fade_duration, Easing.OutQuint).Expire(); - }); + if (leftDisplay != null) + return; + + leftDisplay = createNew(); } - private void createNew(bool reversed = false) + private void addRightDisplay() + { + if (rightDisplay != null) + return; + + rightDisplay = createNew(true); + } + + private void removeRightDisplay() + { + if (rightDisplay == null) + return; + + processor.NewJudgement -= rightDisplay.OnNewJudgement; + + rightDisplay.FadeOut(fade_duration, Easing.OutQuint).Expire(); + rightDisplay = null; + } + + private void removeLeftDisplay() + { + if (leftDisplay == null) + return; + + processor.NewJudgement -= leftDisplay.OnNewJudgement; + + leftDisplay.FadeOut(fade_duration, Easing.OutQuint).Expire(); + leftDisplay = null; + } + + private BarHitErrorDisplay createNew(bool reversed = false) { var display = new BarHitErrorDisplay(hitWindows, reversed) { @@ -87,6 +120,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay processor.NewJudgement += display.OnNewJudgement; Add(display); display.FadeInFromZero(fade_duration, Easing.OutQuint); + return display; } } } From a994ad9c84047e583471443c0d68f4ecc0e94dc4 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 21 Aug 2019 09:40:15 +0300 Subject: [PATCH 0810/2815] Use moving average to calculate arrow position --- .../HitErrorDisplay/BarHitErrorDisplay.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs index d8ae3dd9b0..d24982635b 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs @@ -35,7 +35,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private readonly SpriteIcon arrow; private readonly FillFlowContainer bar; private readonly Container judgementsContainer; - private readonly Queue judgementOffsets = new Queue(); + private readonly List judgementOffsets = new List(); private readonly double maxHitWindows; public BarHitErrorDisplay(HitWindows hitWindows, bool reversed = false) @@ -154,14 +154,23 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private float getRelativeJudgementPosition(double value) => (float)(value / maxHitWindows); + private double sum = 0; + private float calculateArrowPosition(JudgementResult newJudgement) { + var offset = newJudgement.TimeOffset; + + sum += offset; + + judgementOffsets.Add(offset); + if (judgementOffsets.Count > stored_judgements_amount) - judgementOffsets.Dequeue(); + { + sum -= judgementOffsets[0]; + judgementOffsets.RemoveAt(0); + } - judgementOffsets.Enqueue(newJudgement.TimeOffset); - - return getRelativeJudgementPosition(judgementOffsets.Average()); + return getRelativeJudgementPosition(sum / stored_judgements_amount); } } } From a5acc913eab464a2f0e313df630a1a1a6736e87b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 21 Aug 2019 09:58:47 +0300 Subject: [PATCH 0811/2815] CI fixes --- osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs index d24982635b..d2bef75fd7 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs @@ -14,7 +14,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Colour; using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; -using System.Linq; namespace osu.Game.Screens.Play.HitErrorDisplay { @@ -154,7 +153,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private float getRelativeJudgementPosition(double value) => (float)(value / maxHitWindows); - private double sum = 0; + private double sum; private float calculateArrowPosition(JudgementResult newJudgement) { From 0ccfaeb8d9896ae901e09a8eb284477514cd5fde Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 21 Aug 2019 10:13:59 +0300 Subject: [PATCH 0812/2815] Simplify moving average --- .../Play/HitErrorDisplay/BarHitErrorDisplay.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs index d2bef75fd7..85d017073a 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs @@ -14,6 +14,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Colour; using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; +using System.Linq; namespace osu.Game.Screens.Play.HitErrorDisplay { @@ -153,21 +154,17 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private float getRelativeJudgementPosition(double value) => (float)(value / maxHitWindows); - private double sum; - private float calculateArrowPosition(JudgementResult newJudgement) { - var offset = newJudgement.TimeOffset; + judgementOffsets.Add(newJudgement.TimeOffset); - sum += offset; + if (judgementOffsets.Count < stored_judgements_amount) + return getRelativeJudgementPosition(judgementOffsets.Average()); - judgementOffsets.Add(offset); + double sum = 0; - if (judgementOffsets.Count > stored_judgements_amount) - { - sum -= judgementOffsets[0]; - judgementOffsets.RemoveAt(0); - } + for (int i = judgementOffsets.Count - stored_judgements_amount; i < judgementOffsets.Count; i++) + sum += judgementOffsets[i]; return getRelativeJudgementPosition(sum / stored_judgements_amount); } From 43c51366637984cf8132fb81c38d21bc8a0ad28e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Aug 2019 17:22:20 +0900 Subject: [PATCH 0813/2815] Remove plist reference --- fastlane/Fastfile | 1 - 1 file changed, 1 deletion(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 9cc5e4aa74..f6eb95ca3d 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -28,7 +28,6 @@ desc 'Deploy to play store' end lane :update_version do |options| - options[:plist_path] = '../osu.iOS/Info.plist' app_version(options) end From fb8d8f9438d8b4b704a3201ccc371c07211b80be Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 21 Aug 2019 08:27:56 +0000 Subject: [PATCH 0814/2815] Bump SharpCompress from 0.23.0 to 0.24.0 Bumps [SharpCompress](https://github.com/adamhathcock/sharpcompress) from 0.23.0 to 0.24.0. - [Release notes](https://github.com/adamhathcock/sharpcompress/releases) - [Commits](https://github.com/adamhathcock/sharpcompress/compare/0.23...0.24) 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 758c4dda4c..9318468c78 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -16,7 +16,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index d6ad35b663..aebef4dc3a 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -120,7 +120,7 @@ - + From 727a6abaf775ff392b4ac135c1f2a188ab1d4d14 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 21 Aug 2019 12:16:30 +0300 Subject: [PATCH 0815/2815] Simplify caching --- .../Graphics/Containers/OsuFocusedOverlayContainer.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 5606328575..0f7b26835b 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -15,6 +15,7 @@ using osu.Game.Overlays; namespace osu.Game.Graphics.Containers { + [Cached(typeof(IPreviewTrackOwner))] public abstract class OsuFocusedOverlayContainer : FocusedOverlayContainer, IPreviewTrackOwner, IKeyBindingHandler { private SampleChannel samplePopIn; @@ -38,13 +39,6 @@ namespace osu.Game.Graphics.Containers protected readonly Bindable OverlayActivationMode = new Bindable(OverlayActivation.All); - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.CacheAs(this); - return dependencies; - } - [BackgroundDependencyLoader(true)] private void load(AudioManager audio) { From 87f3184fccee86fe7d3dc94c5ce38d98108ae8a6 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 21 Aug 2019 15:14:33 +0300 Subject: [PATCH 0816/2815] Try retrieving samples without bank names Fallback for spinner bonus samples --- osu.Game/Skinning/SkinnableSound.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 8e2b5cec98..b50cc19482 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -55,8 +55,16 @@ namespace osu.Game.Skinning foreach (var lookup in info.LookupNames) { var ch = getSampleFunction($"Gameplay/{lookup}"); + if (ch == null) - continue; + { + // Try fallback to non-bank samples. + var bank = lookup.Split('/').Last().Split('-')[0] + '-'; + ch = getSampleFunction($"Gameplay/{lookup.Replace(bank, "")}"); + + if (ch == null) + continue; + } ch.Volume.Value = info.Volume / 100.0; return ch; From 7bf430afd5be08dec4dd0aefb4734a42cd1ebc52 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Aug 2019 21:16:06 +0900 Subject: [PATCH 0817/2815] Add padding back to difficulty icons on carousel --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 4ceb82d4cc..0259f3cd81 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -98,6 +98,7 @@ namespace osu.Game.Screens.Select.Carousel new FillFlowContainer { AutoSizeAxes = Axes.Both, + Spacing = new Vector2(3), Children = ((CarouselBeatmapSet)Item).Beatmaps.Select(b => new FilterableDifficultyIcon(b)).ToList() }, } From acc07c1d6548cd2a26882b73a8fb4ad3db8599c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Aug 2019 22:18:29 +0900 Subject: [PATCH 0818/2815] Remove mod icon offset --- osu.Game/Rulesets/UI/ModIcon.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 5bb1de7a38..88a2338b94 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -52,7 +52,6 @@ namespace osu.Game.Rulesets.UI Anchor = Anchor.Centre, Size = new Vector2(size), Icon = OsuIcon.ModBg, - Y = -6.5f, Shadow = true, }, modIcon = new SpriteIcon From 4f98361da3b3bcf7b5764522f683eb4b116bebdf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Aug 2019 22:22:03 +0900 Subject: [PATCH 0819/2815] One more offset removed --- osu.Game/Overlays/SettingsSubPanel.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/SettingsSubPanel.cs b/osu.Game/Overlays/SettingsSubPanel.cs index 7f794e2927..5000156e97 100644 --- a/osu.Game/Overlays/SettingsSubPanel.cs +++ b/osu.Game/Overlays/SettingsSubPanel.cs @@ -57,7 +57,6 @@ namespace osu.Game.Overlays { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Y = -15, Size = new Vector2(15), Shadow = true, Icon = FontAwesome.Solid.ChevronLeft From db1ff6d211e1d482cea28b922c8a04ac79efab4a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Aug 2019 13:48:27 +0900 Subject: [PATCH 0820/2815] Fix video decoding loop running permanently in the background --- osu.Game/Screens/Menu/IntroTriangles.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index f5a72c2c1d..bb3a8f0013 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -139,7 +139,7 @@ namespace osu.Game.Screens.Menu private RulesetFlow rulesets; private Container rulesetsScale; private Drawable logoContainerSecondary; - private Drawable logoContainer; + private Drawable lazerLogo; private GlitchingTriangles triangles; @@ -191,7 +191,7 @@ namespace osu.Game.Screens.Menu RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Child = logoContainer = new LazerLogo(textures.GetStream("Menu/logo-triangles.mp4")) + Child = lazerLogo = new LazerLogo(textures.GetStream("Menu/logo-triangles.mp4")) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -222,7 +222,7 @@ namespace osu.Game.Screens.Menu const float scale_adjust = 0.8f; rulesets.Hide(); - logoContainer.Hide(); + lazerLogo.Hide(); background.Hide(); using (BeginAbsoluteSequence(0, true)) @@ -269,14 +269,17 @@ namespace osu.Game.Screens.Menu rulesets.FadeOut(); // matching flyte curve y = 0.25x^2 + (max(0, x - 0.7) / 0.3) ^ 5 - logoContainer.FadeIn().ScaleTo(scale_start).Then().Delay(logo_scale_duration * 0.7f).ScaleTo(scale_start - scale_adjust, logo_scale_duration * 0.3f, Easing.InQuint); + lazerLogo.FadeIn().ScaleTo(scale_start).Then().Delay(logo_scale_duration * 0.7f).ScaleTo(scale_start - scale_adjust, logo_scale_duration * 0.3f, Easing.InQuint); logoContainerSecondary.ScaleTo(scale_start).Then().ScaleTo(scale_start - scale_adjust * 0.25f, logo_scale_duration, Easing.InQuad); } using (BeginDelayedSequence(logo_2, true)) { - logoContainer.FadeOut().OnComplete(_ => + lazerLogo.FadeOut().OnComplete(_ => { + lazerLogo.Expire(); + lazerLogo.Dispose(); // explicit disposal as we are pushing a new screen and the expire may not get run. + logo.FadeIn(); background.FadeIn(); From 66d84401678a10e3692c95da2042f7c6a71bda94 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 22 Aug 2019 12:50:47 +0300 Subject: [PATCH 0821/2815] Move non-bank samples logic to LegacySkin --- osu.Game/Skinning/LegacySkin.cs | 14 +++++++++++++- osu.Game/Skinning/SkinnableSound.cs | 10 +--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 48310cf027..73957a203e 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -212,7 +212,19 @@ namespace osu.Game.Skinning return texture; } - public override SampleChannel GetSample(string sampleName) => Samples.Get(sampleName); + public override SampleChannel GetSample(string sampleName) + { + var sample = Samples.Get(sampleName); + + if (sample == null) + { + // Try fallback to non-bank samples. + var bank = sampleName.Split('/').Last().Split('-')[0] + '-'; + sample = Samples.Get($"Gameplay/{sampleName.Replace(bank, "")}"); + } + + return sample; + } private bool hasFont(string fontName) => GetTexture($"{fontName}-0") != null; diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index b50cc19482..8e2b5cec98 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -55,16 +55,8 @@ namespace osu.Game.Skinning foreach (var lookup in info.LookupNames) { var ch = getSampleFunction($"Gameplay/{lookup}"); - if (ch == null) - { - // Try fallback to non-bank samples. - var bank = lookup.Split('/').Last().Split('-')[0] + '-'; - ch = getSampleFunction($"Gameplay/{lookup.Replace(bank, "")}"); - - if (ch == null) - continue; - } + continue; ch.Volume.Value = info.Volume / 100.0; return ch; From f4d2bb036b3bcd9ed7819ad1380e1310bf8a2984 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 22 Aug 2019 16:50:54 +0300 Subject: [PATCH 0822/2815] Expand KudosuAction list --- .../Visual/Online/TestSceneKudosuHistory.cs | 80 +++++++++++++++++++ .../Requests/GetUserKudosuHistoryRequest.cs | 5 ++ .../Kudosu/DrawableKudosuHistoryItem.cs | 35 ++++++++ 3 files changed, 120 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs index dcf2bec239..8badfeaa23 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs @@ -150,6 +150,86 @@ namespace osu.Game.Tests.Visual.Online Username = @"Username6", Url = @"https://osu.ppy.sh/u/1234" } + }, + new APIKudosuHistory + { + Amount = 11, + CreatedAt = new DateTimeOffset(new DateTime(2016, 6, 11)), + Action = KudosuAction.AllowKudosuGive, + Post = new APIKudosuHistory.ModdingPost + { + Title = @"Random post 7", + Url = @"https://osu.ppy.sh/b/1234", + }, + Giver = new APIKudosuHistory.KudosuGiver + { + Username = @"Username7", + Url = @"https://osu.ppy.sh/u/1234" + } + }, + new APIKudosuHistory + { + Amount = 24, + CreatedAt = new DateTimeOffset(new DateTime(2014, 6, 11)), + Action = KudosuAction.DeleteReset, + Post = new APIKudosuHistory.ModdingPost + { + Title = @"Random post 8", + Url = @"https://osu.ppy.sh/b/1234", + }, + Giver = new APIKudosuHistory.KudosuGiver + { + Username = @"Username8", + Url = @"https://osu.ppy.sh/u/1234" + } + }, + new APIKudosuHistory + { + Amount = 12, + CreatedAt = new DateTimeOffset(new DateTime(2016, 6, 11)), + Action = KudosuAction.RestoreGive, + Post = new APIKudosuHistory.ModdingPost + { + Title = @"Random post 9", + Url = @"https://osu.ppy.sh/b/1234", + }, + Giver = new APIKudosuHistory.KudosuGiver + { + Username = @"Username9", + Url = @"https://osu.ppy.sh/u/1234" + } + }, + new APIKudosuHistory + { + Amount = 2, + CreatedAt = new DateTimeOffset(new DateTime(2012, 6, 11)), + Action = KudosuAction.RecalculateGive, + Post = new APIKudosuHistory.ModdingPost + { + Title = @"Random post 10", + Url = @"https://osu.ppy.sh/b/1234", + }, + Giver = new APIKudosuHistory.KudosuGiver + { + Username = @"Username10", + Url = @"https://osu.ppy.sh/u/1234" + } + }, + new APIKudosuHistory + { + Amount = 32, + CreatedAt = new DateTimeOffset(new DateTime(2019, 8, 11)), + Action = KudosuAction.RecalculateReset, + Post = new APIKudosuHistory.ModdingPost + { + Title = @"Random post 11", + Url = @"https://osu.ppy.sh/b/1234", + }, + Giver = new APIKudosuHistory.KudosuGiver + { + Username = @"Username11", + Url = @"https://osu.ppy.sh/u/1234" + } } }; } diff --git a/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs b/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs index af37bd4b51..dd6f2ccf22 100644 --- a/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs @@ -27,5 +27,10 @@ namespace osu.Game.Online.API.Requests VoteReset, DenyKudosuReset, Revoke, + AllowKudosuGive, + DeleteReset, + RestoreGive, + RecalculateGive, + RecalculateReset } } diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index e5f5b720c2..3cc39f0e73 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -91,6 +91,41 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu addMainPart($@"Denied kudosu by {userLinkTemplate()} for the post "); addPostPart(); break; + + case KudosuAction.AllowKudosuGive: + linkFlowContainer.AddText(@"Received "); + addKudosuPart(); + addMainPart(@" from kudosu deny repeal of modding post "); + addPostPart(); + break; + + case KudosuAction.DeleteReset: + linkFlowContainer.AddText(@"Lost "); + addKudosuPart(); + addMainPart(@" from modding post deletion of "); + addPostPart(); + break; + + case KudosuAction.RestoreGive: + linkFlowContainer.AddText(@"Received "); + addKudosuPart(); + addMainPart(@" from modding post restoration of "); + addPostPart(); + break; + + case KudosuAction.RecalculateGive: + linkFlowContainer.AddText(@"Received "); + addKudosuPart(); + addMainPart(@" from votes recalculation in modding post of "); + addPostPart(); + break; + + case KudosuAction.RecalculateReset: + linkFlowContainer.AddText(@"Lost "); + addKudosuPart(); + addMainPart(@" from votes recalculation in modding post of "); + addPostPart(); + break; } } From a30567394ed1973230716b6d6b55090a1097d407 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 23 Aug 2019 12:39:24 +0900 Subject: [PATCH 0823/2815] Remove bottom margin from show more button --- osu.Game/Overlays/Profile/Sections/ShowMoreButton.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/ShowMoreButton.cs b/osu.Game/Overlays/Profile/Sections/ShowMoreButton.cs index 5ed546c62b..cf4e1c0dde 100644 --- a/osu.Game/Overlays/Profile/Sections/ShowMoreButton.cs +++ b/osu.Game/Overlays/Profile/Sections/ShowMoreButton.cs @@ -124,14 +124,12 @@ namespace osu.Game.Overlays.Profile.Sections private class ChevronIcon : SpriteIcon { - private const int bottom_margin = 2; private const int icon_size = 8; public ChevronIcon() { Anchor = Anchor.Centre; Origin = Anchor.Centre; - Margin = new MarginPadding { Bottom = bottom_margin }; Size = new Vector2(icon_size); Icon = FontAwesome.Solid.ChevronDown; } From 900df5d72f576a0f31d0840b3d1672314eb5e02a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Aug 2019 13:02:50 +0900 Subject: [PATCH 0824/2815] Fix crash on closing game --- osu.Game/Screens/Menu/IntroTriangles.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index bb3a8f0013..db970dd76e 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -138,7 +138,7 @@ namespace osu.Game.Screens.Menu private RulesetFlow rulesets; private Container rulesetsScale; - private Drawable logoContainerSecondary; + private Container logoContainerSecondary; private Drawable lazerLogo; private GlitchingTriangles triangles; @@ -158,7 +158,7 @@ namespace osu.Game.Screens.Menu { this.game = game; - InternalChildren = new[] + InternalChildren = new Drawable[] { triangles = new GlitchingTriangles { @@ -277,7 +277,7 @@ namespace osu.Game.Screens.Menu { lazerLogo.FadeOut().OnComplete(_ => { - lazerLogo.Expire(); + logoContainerSecondary.Remove(lazerLogo); lazerLogo.Dispose(); // explicit disposal as we are pushing a new screen and the expire may not get run. logo.FadeIn(); From c41b1e9eb4c900906c3d092c09c29142eb3bd314 Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 22 Aug 2019 21:36:21 -0700 Subject: [PATCH 0825/2815] Fix alignment and size of mute button --- osu.Game/Overlays/Volume/MuteButton.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Volume/MuteButton.cs b/osu.Game/Overlays/Volume/MuteButton.cs index a4884dc2c1..0f69f11985 100644 --- a/osu.Game/Overlays/Volume/MuteButton.cs +++ b/osu.Game/Overlays/Volume/MuteButton.cs @@ -65,13 +65,14 @@ namespace osu.Game.Overlays.Volume { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(20), } }); Current.ValueChanged += muted => { icon.Icon = muted.NewValue ? FontAwesome.Solid.VolumeMute : FontAwesome.Solid.VolumeUp; + icon.Size = new Vector2(muted.NewValue ? 18 : 20); + icon.Margin = new MarginPadding { Right = muted.NewValue ? 2 : 0 }; }; Current.TriggerChange(); From c55d237db6e2db0fc3e468d98ae74bc9a7f6adf3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Aug 2019 17:42:40 +0900 Subject: [PATCH 0826/2815] Use BindValueChanged --- osu.Game/Overlays/Volume/MuteButton.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Volume/MuteButton.cs b/osu.Game/Overlays/Volume/MuteButton.cs index 0f69f11985..6d876a77b1 100644 --- a/osu.Game/Overlays/Volume/MuteButton.cs +++ b/osu.Game/Overlays/Volume/MuteButton.cs @@ -68,14 +68,12 @@ namespace osu.Game.Overlays.Volume } }); - Current.ValueChanged += muted => + Current.BindValueChanged(muted => { icon.Icon = muted.NewValue ? FontAwesome.Solid.VolumeMute : FontAwesome.Solid.VolumeUp; icon.Size = new Vector2(muted.NewValue ? 18 : 20); icon.Margin = new MarginPadding { Right = muted.NewValue ? 2 : 0 }; - }; - - Current.TriggerChange(); + }, true); } protected override bool OnHover(HoverEvent e) From cb54fbee875d7806c3bbc0867242cc2c9e7e1211 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 23 Aug 2019 09:29:35 +0000 Subject: [PATCH 0827/2815] Bump ppy.osu.Framework.Android from 2019.821.0 to 2019.823.0 Bumps [ppy.osu.Framework.Android](https://github.com/ppy/osu-framework) from 2019.821.0 to 2019.823.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.821.0...2019.823.0) Signed-off-by: dependabot-preview[bot] --- osu.Android.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Android.props b/osu.Android.props index fe6420ead8..9c4a308271 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -61,6 +61,6 @@ - + From 4bc05818716e94dc4bc63b8122c127ac29c520f5 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 23 Aug 2019 09:33:38 +0000 Subject: [PATCH 0828/2815] Bump ppy.osu.Framework.iOS from 2019.821.0 to 2019.823.0 Bumps [ppy.osu.Framework.iOS](https://github.com/ppy/osu-framework) from 2019.821.0 to 2019.823.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.821.0...2019.823.0) Signed-off-by: dependabot-preview[bot] --- osu.iOS.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.iOS.props b/osu.iOS.props index 05910f846a..39066cb440 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -119,7 +119,7 @@ - + From 11a61b953b15e669a1124c52b2a1e12140b0d327 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 23 Aug 2019 09:43:34 +0000 Subject: [PATCH 0829/2815] Bump ppy.osu.Framework from 2019.821.0 to 2019.823.0 Bumps [ppy.osu.Framework](https://github.com/ppy/osu-framework) from 2019.821.0 to 2019.823.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.821.0...2019.823.0) 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 905fa57f22..8c692132ad 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -15,7 +15,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 05910f846a..3007a9cb86 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,7 +118,7 @@ - + From c836ef57217d086c22df6aa0d72738e2cc076706 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 23 Aug 2019 10:04:00 +0000 Subject: [PATCH 0830/2815] Bump ppy.osu.Game.Resources from 2019.809.0 to 2019.823.0 Bumps [ppy.osu.Game.Resources](https://github.com/ppy/osu-resources) from 2019.809.0 to 2019.823.0. - [Release notes](https://github.com/ppy/osu-resources/releases) - [Commits](https://github.com/ppy/osu-resources/compare/2019.809.0...2019.823.0) Signed-off-by: dependabot-preview[bot] --- 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 fe6420ead8..232d89b375 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -60,7 +60,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 905fa57f22..22af0a5f6d 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -14,7 +14,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 05910f846a..3fe4d1d613 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -117,7 +117,7 @@ - + From 050130e1591e6ec6991b9027fa51c0d085498307 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 23 Aug 2019 14:11:21 +0300 Subject: [PATCH 0831/2815] Refactor PaginatedContainer to centralise repetitive logic --- .../Beatmaps/PaginatedBeatmapContainer.cs | 48 +++---------- .../PaginatedMostPlayedBeatmapContainer.cs | 41 +++-------- .../Kudosu/PaginatedKudosuHistoryContainer.cs | 38 ++-------- .../Profile/Sections/PaginatedContainer.cs | 72 +++++++++++++++---- .../Sections/Ranks/PaginatedScoreContainer.cs | 65 ++++++----------- .../PaginatedRecentActivityContainer.cs | 39 ++-------- 6 files changed, 110 insertions(+), 193 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index 8a6b52b7ee..3fbd1dacd9 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -1,21 +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 System.Linq; +using System.Collections.Generic; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Direct; using osu.Game.Users; using osuTK; namespace osu.Game.Overlays.Profile.Sections.Beatmaps { - public class PaginatedBeatmapContainer : PaginatedContainer + public class PaginatedBeatmapContainer : PaginatedContainer { private const float panel_padding = 10f; private readonly BeatmapSetType type; - private GetUserBeatmapsRequest request; public PaginatedBeatmapContainer(BeatmapSetType type, Bindable user, string header, string missing = "None... yet.") : base(user, header, missing) @@ -27,40 +28,13 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps ItemsContainer.Spacing = new Vector2(panel_padding); } - protected override void ShowMore() + protected override APIRequest> CreateRequest() + => new GetUserBeatmapsRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); + + protected override Drawable CreateDrawableItem(APIBeatmapSet item) => new DirectGridPanel(item.ToBeatmapSet(Rulesets)) { - request = new GetUserBeatmapsRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); - request.Success += sets => Schedule(() => - { - MoreButton.FadeTo(sets.Count == ItemsPerPage ? 1 : 0); - MoreButton.IsLoading = false; - - if (!sets.Any() && VisiblePages == 1) - { - MissingText.Show(); - return; - } - - foreach (var s in sets) - { - if (!s.OnlineBeatmapSetID.HasValue) - continue; - - ItemsContainer.Add(new DirectGridPanel(s.ToBeatmapSet(Rulesets)) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }); - } - }); - - Api.Queue(request); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - request?.Cancel(); - } + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }; } } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs index 23072f8d90..e444363e52 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs @@ -1,19 +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.Linq; +using System.Collections.Generic; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Users; namespace osu.Game.Overlays.Profile.Sections.Historical { - public class PaginatedMostPlayedBeatmapContainer : PaginatedContainer + public class PaginatedMostPlayedBeatmapContainer : PaginatedContainer { - private GetUserMostPlayedBeatmapsRequest request; - public PaginatedMostPlayedBeatmapContainer(Bindable user) : base(user, "Most Played Beatmaps", "No records. :(") { @@ -22,35 +22,10 @@ namespace osu.Game.Overlays.Profile.Sections.Historical ItemsContainer.Direction = FillDirection.Vertical; } - protected override void ShowMore() - { - request = new GetUserMostPlayedBeatmapsRequest(User.Value.Id, VisiblePages++, ItemsPerPage); - request.Success += beatmaps => Schedule(() => - { - MoreButton.FadeTo(beatmaps.Count == ItemsPerPage ? 1 : 0); - MoreButton.IsLoading = false; + protected override APIRequest> CreateRequest() + => new GetUserMostPlayedBeatmapsRequest(User.Value.Id, VisiblePages++, ItemsPerPage); - if (!beatmaps.Any() && VisiblePages == 1) - { - MissingText.Show(); - return; - } - - MissingText.Hide(); - - foreach (var beatmap in beatmaps) - { - ItemsContainer.Add(new DrawableMostPlayedBeatmap(beatmap.GetBeatmapInfo(Rulesets), beatmap.PlayCount)); - } - }); - - Api.Queue(request); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - request?.Cancel(); - } + protected override Drawable CreateDrawableItem(APIUserMostPlayedBeatmap item) + => new DrawableMostPlayedBeatmap(item.GetBeatmapInfo(Rulesets), item.PlayCount); } } diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs index 29b1d3c5aa..0e7cfc37c0 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs @@ -4,48 +4,24 @@ using osu.Framework.Graphics; using osu.Game.Online.API.Requests; using osu.Game.Users; -using System.Linq; using osu.Framework.Bindables; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.API; +using System.Collections.Generic; namespace osu.Game.Overlays.Profile.Sections.Kudosu { - public class PaginatedKudosuHistoryContainer : PaginatedContainer + public class PaginatedKudosuHistoryContainer : PaginatedContainer { - private GetUserKudosuHistoryRequest request; - public PaginatedKudosuHistoryContainer(Bindable user, string header, string missing) : base(user, header, missing) { ItemsPerPage = 5; } - protected override void ShowMore() - { - request = new GetUserKudosuHistoryRequest(User.Value.Id, VisiblePages++, ItemsPerPage); - request.Success += items => Schedule(() => - { - MoreButton.FadeTo(items.Count == ItemsPerPage ? 1 : 0); - MoreButton.IsLoading = false; + protected override APIRequest> CreateRequest() + => new GetUserKudosuHistoryRequest(User.Value.Id, VisiblePages++, ItemsPerPage); - if (!items.Any() && VisiblePages == 1) - { - MissingText.Show(); - return; - } - - MissingText.Hide(); - - foreach (var item in items) - ItemsContainer.Add(new DrawableKudosuHistoryItem(item)); - }); - - Api.Queue(request); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - request?.Cancel(); - } + protected override Drawable CreateDrawableItem(APIKudosuHistory item) => new DrawableKudosuHistoryItem(item); } } diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index b459afcb49..e329fce67b 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -11,22 +11,25 @@ using osu.Game.Graphics.Sprites; using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Users; +using System.Collections.Generic; +using System.Linq; namespace osu.Game.Overlays.Profile.Sections { - public abstract class PaginatedContainer : FillFlowContainer + public abstract class PaginatedContainer : FillFlowContainer { - protected readonly FillFlowContainer ItemsContainer; - protected readonly ShowMoreButton MoreButton; - protected readonly OsuSpriteText MissingText; + private readonly ShowMoreButton moreButton; + private readonly OsuSpriteText missingText; + private APIRequest> retrievalRequest; + + [Resolved] + private IAPIProvider api { get; set; } protected int VisiblePages; protected int ItemsPerPage; protected readonly Bindable User = new Bindable(); - - protected IAPIProvider Api; - protected APIRequest RetrievalRequest; + protected readonly FillFlowContainer ItemsContainer; protected RulesetStore Rulesets; protected PaginatedContainer(Bindable user, string header, string missing) @@ -51,15 +54,15 @@ namespace osu.Game.Overlays.Profile.Sections RelativeSizeAxes = Axes.X, Spacing = new Vector2(0, 2), }, - MoreButton = new ShowMoreButton + moreButton = new ShowMoreButton { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Alpha = 0, Margin = new MarginPadding { Top = 10 }, - Action = ShowMore, + Action = showMore, }, - MissingText = new OsuSpriteText + missingText = new OsuSpriteText { Font = OsuFont.GetFont(size: 15), Text = missing, @@ -69,9 +72,8 @@ namespace osu.Game.Overlays.Profile.Sections } [BackgroundDependencyLoader] - private void load(IAPIProvider api, RulesetStore rulesets) + private void load(RulesetStore rulesets) { - Api = api; Rulesets = rulesets; User.ValueChanged += onUserChanged; @@ -84,9 +86,51 @@ namespace osu.Game.Overlays.Profile.Sections ItemsContainer.Clear(); if (e.NewValue != null) - ShowMore(); + showMore(); } - protected abstract void ShowMore(); + private void showMore() + { + retrievalRequest = CreateRequest(); + retrievalRequest.Success += items => UpdateItems(items); + + api.Queue(retrievalRequest); + } + + protected virtual void UpdateItems(List items) + { + Schedule(() => + { + moreButton.FadeTo(items.Count == ItemsPerPage ? 1 : 0); + moreButton.IsLoading = false; + + if (!items.Any() && VisiblePages == 1) + { + moreButton.Hide(); + moreButton.IsLoading = false; + missingText.Show(); + return; + } + + LoadComponentsAsync(items.Select(item => CreateDrawableItem(item)), i => + { + missingText.Hide(); + moreButton.FadeTo(items.Count == ItemsPerPage ? 1 : 0); + moreButton.IsLoading = false; + + ItemsContainer.AddRange(i); + }); + }); + } + + protected abstract APIRequest> CreateRequest(); + + protected abstract Drawable CreateDrawableItem(T item); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + retrievalRequest?.Cancel(); + } } } diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 4a9ac6e5c7..dfe300b069 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -5,18 +5,18 @@ using osu.Framework.Graphics.Containers; using osu.Game.Online.API.Requests; using osu.Game.Users; using System; -using System.Collections.Generic; -using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Online.API.Requests.Responses; +using System.Collections.Generic; +using osu.Game.Online.API; namespace osu.Game.Overlays.Profile.Sections.Ranks { - public class PaginatedScoreContainer : PaginatedContainer + public class PaginatedScoreContainer : PaginatedContainer { private readonly bool includeWeight; private readonly ScoreType type; - private GetUserScoresRequest request; public PaginatedScoreContainer(ScoreType type, Bindable user, string header, string missing, bool includeWeight = false) : base(user, header, missing) @@ -29,52 +29,27 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks ItemsContainer.Direction = FillDirection.Vertical; } - protected override void ShowMore() + protected override void UpdateItems(List items) { - request = new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); - request.Success += scores => Schedule(() => - { - foreach (var s in scores) - s.Ruleset = Rulesets.GetRuleset(s.RulesetID); + foreach (var item in items) + item.Ruleset = Rulesets.GetRuleset(item.RulesetID); - if (!scores.Any() && VisiblePages == 1) - { - MoreButton.Hide(); - MoreButton.IsLoading = false; - MissingText.Show(); - return; - } - - IEnumerable drawableScores; - - switch (type) - { - default: - drawableScores = scores.Select(score => new DrawablePerformanceScore(score, includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null)); - break; - - case ScoreType.Recent: - drawableScores = scores.Select(score => new DrawableTotalScore(score)); - break; - } - - LoadComponentsAsync(drawableScores, s => - { - MissingText.Hide(); - MoreButton.FadeTo(scores.Count == ItemsPerPage ? 1 : 0); - MoreButton.IsLoading = false; - - ItemsContainer.AddRange(s); - }); - }); - - Api.Queue(request); + base.UpdateItems(items); } - protected override void Dispose(bool isDisposing) + protected override APIRequest> CreateRequest() + => new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); + + protected override Drawable CreateDrawableItem(APILegacyScoreInfo item) { - base.Dispose(isDisposing); - request?.Cancel(); + switch (type) + { + default: + return new DrawablePerformanceScore(item, includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null); + + case ScoreType.Recent: + return new DrawableTotalScore(item); + } } } } diff --git a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs index f2a778a874..0251dd0740 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs @@ -4,51 +4,24 @@ using osu.Framework.Graphics; using osu.Game.Online.API.Requests; using osu.Game.Users; -using System.Linq; using osu.Framework.Bindables; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.API; +using System.Collections.Generic; namespace osu.Game.Overlays.Profile.Sections.Recent { - public class PaginatedRecentActivityContainer : PaginatedContainer + public class PaginatedRecentActivityContainer : PaginatedContainer { - private GetUserRecentActivitiesRequest request; - public PaginatedRecentActivityContainer(Bindable user, string header, string missing) : base(user, header, missing) { ItemsPerPage = 5; } - protected override void ShowMore() - { - request = new GetUserRecentActivitiesRequest(User.Value.Id, VisiblePages++, ItemsPerPage); - request.Success += activities => Schedule(() => - { - MoreButton.FadeTo(activities.Count == ItemsPerPage ? 1 : 0); - MoreButton.IsLoading = false; + protected override APIRequest> CreateRequest() + => new GetUserRecentActivitiesRequest(User.Value.Id, VisiblePages++, ItemsPerPage); - if (!activities.Any() && VisiblePages == 1) - { - MissingText.Show(); - return; - } - - MissingText.Hide(); - - foreach (APIRecentActivity activity in activities) - { - ItemsContainer.Add(new DrawableRecentActivity(activity)); - } - }); - - Api.Queue(request); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - request?.Cancel(); - } + protected override Drawable CreateDrawableItem(APIRecentActivity item) => new DrawableRecentActivity(item); } } From 45c0826314fb89ec07026037aa93c7e50979cb80 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 23 Aug 2019 14:14:39 +0300 Subject: [PATCH 0832/2815] Remove repetitive code --- osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index e329fce67b..6c444b2e26 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -101,9 +101,6 @@ namespace osu.Game.Overlays.Profile.Sections { Schedule(() => { - moreButton.FadeTo(items.Count == ItemsPerPage ? 1 : 0); - moreButton.IsLoading = false; - if (!items.Any() && VisiblePages == 1) { moreButton.Hide(); From 6bf31e8f91111d762d657dd1f93dddfeb0577b2a Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 23 Aug 2019 13:15:38 +0200 Subject: [PATCH 0833/2815] Make song select grouping & sorting filters persistent --- osu.Game/Configuration/OsuConfigManager.cs | 6 +++ osu.Game/Screens/Select/FilterControl.cs | 54 +++++++--------------- 2 files changed, 23 insertions(+), 37 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 19f46c1d6a..6ebacc642d 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -8,6 +8,7 @@ using osu.Framework.Platform; using osu.Game.Overlays; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Select; +using osu.Game.Screens.Select.Filter; namespace osu.Game.Configuration { @@ -25,6 +26,9 @@ namespace osu.Game.Configuration Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1); Set(OsuSetting.DisplayStarsMaximum, 10.0, 0, 10, 0.1); + Set(OsuSetting.SelectGroupingMode, GroupMode.All); + Set(OsuSetting.SelectSortingMode, SortMode.Title); + Set(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation); Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2, 1); @@ -150,6 +154,8 @@ namespace osu.Game.Configuration SaveUsername, DisplayStarsMinimum, DisplayStarsMaximum, + SelectGroupingMode, + SelectSortingMode, RandomSelectAlgorithm, ShowFpsDisplay, ChatDisplayHeight, diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 84e8e90f54..b97d64f013 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -29,40 +29,14 @@ namespace osu.Game.Screens.Select private readonly TabControl groupTabs; - private SortMode sort = SortMode.Title; + public readonly Bindable SortMode = new Bindable(Filter.SortMode.Title); - public SortMode Sort - { - get => sort; - set - { - if (sort != value) - { - sort = value; - FilterChanged?.Invoke(CreateCriteria()); - } - } - } - - private GroupMode group = GroupMode.All; - - public GroupMode Group - { - get => group; - set - { - if (group != value) - { - group = value; - FilterChanged?.Invoke(CreateCriteria()); - } - } - } + public readonly Bindable GroupMode = new Bindable(Filter.GroupMode.All); public FilterCriteria CreateCriteria() => new FilterCriteria { - Group = group, - Sort = sort, + Group = GroupMode.Value, + Sort = SortMode.Value, SearchText = searchTextBox.Text, AllowConvertedBeatmaps = showConverted.Value, Ruleset = ruleset.Value @@ -122,7 +96,7 @@ namespace osu.Game.Screens.Select Height = 24, Width = 0.5f, AutoSort = true, - Current = { Value = GroupMode.Title } + Current = GroupMode }, //spriteText = new OsuSpriteText //{ @@ -141,7 +115,7 @@ namespace osu.Game.Screens.Select Width = 0.5f, Height = 24, AutoSort = true, - Current = { Value = SortMode.Title } + Current = SortMode } } }, @@ -151,10 +125,8 @@ namespace osu.Game.Screens.Select searchTextBox.Current.ValueChanged += _ => FilterChanged?.Invoke(CreateCriteria()); - groupTabs.PinItem(GroupMode.All); - groupTabs.PinItem(GroupMode.RecentlyPlayed); - groupTabs.Current.ValueChanged += group => Group = group.NewValue; - sortTabs.Current.ValueChanged += sort => Sort = sort.NewValue; + groupTabs.PinItem(Filter.GroupMode.All); + groupTabs.PinItem(Filter.GroupMode.RecentlyPlayed); } public void Deactivate() @@ -184,7 +156,15 @@ namespace osu.Game.Screens.Select showConverted.ValueChanged += _ => updateCriteria(); ruleset.BindTo(parentRuleset); - ruleset.BindValueChanged(_ => updateCriteria(), true); + ruleset.BindValueChanged(_ => updateCriteria()); + + config.BindWith(OsuSetting.SelectGroupingMode, GroupMode); + config.BindWith(OsuSetting.SelectSortingMode, SortMode); + + GroupMode.BindValueChanged(_ => updateCriteria()); + SortMode.BindValueChanged(_ => updateCriteria()); + + updateCriteria(); } private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria()); From 6ea10ada3451d0096c9e4da34f3e5ac51e50aaae Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 23 Aug 2019 13:31:45 +0200 Subject: [PATCH 0834/2815] Fix visual tests. --- .../Visual/SongSelect/TestScenePlaySongSelect.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 680250a226..2dbe53709b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -111,13 +111,13 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); - AddStep(@"Sort by Artist", delegate { songSelect.FilterControl.Sort = SortMode.Artist; }); - AddStep(@"Sort by Title", delegate { songSelect.FilterControl.Sort = SortMode.Title; }); - AddStep(@"Sort by Author", delegate { songSelect.FilterControl.Sort = SortMode.Author; }); - AddStep(@"Sort by DateAdded", delegate { songSelect.FilterControl.Sort = SortMode.DateAdded; }); - AddStep(@"Sort by BPM", delegate { songSelect.FilterControl.Sort = SortMode.BPM; }); - AddStep(@"Sort by Length", delegate { songSelect.FilterControl.Sort = SortMode.Length; }); - AddStep(@"Sort by Difficulty", delegate { songSelect.FilterControl.Sort = SortMode.Difficulty; }); + AddStep(@"Sort by Artist", delegate { songSelect.FilterControl.SortMode.Value = SortMode.Artist; }); + AddStep(@"Sort by Title", delegate { songSelect.FilterControl.SortMode.Value = SortMode.Title; }); + AddStep(@"Sort by Author", delegate { songSelect.FilterControl.SortMode.Value = SortMode.Author; }); + AddStep(@"Sort by DateAdded", delegate { songSelect.FilterControl.SortMode.Value = SortMode.DateAdded; }); + AddStep(@"Sort by BPM", delegate { songSelect.FilterControl.SortMode.Value = SortMode.BPM; }); + AddStep(@"Sort by Length", delegate { songSelect.FilterControl.SortMode.Value = SortMode.Length; }); + AddStep(@"Sort by Difficulty", delegate { songSelect.FilterControl.SortMode.Value = SortMode.Difficulty; }); } [Test] From d8535574d10b26413c18639ccedc93ff15e00a4c Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 23 Aug 2019 14:32:43 +0300 Subject: [PATCH 0835/2815] Pass sample info to Skin.GetSample --- .../TestSceneCatcher.cs | 2 +- .../Gameplay/TestSceneSkinnableDrawable.cs | 7 ++++--- osu.Game/Skinning/DefaultSkin.cs | 3 ++- osu.Game/Skinning/ISkin.cs | 3 ++- osu.Game/Skinning/LegacySkin.cs | 20 +++++++++++-------- .../Skinning/LocalSkinOverrideContainer.cs | 7 ++++--- osu.Game/Skinning/Skin.cs | 3 ++- osu.Game/Skinning/SkinManager.cs | 3 ++- 8 files changed, 29 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 33f93cdb4a..72646e656e 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Catch.Tests return null; } - public SampleChannel GetSample(string sampleName) => + public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); public Texture GetTexture(string componentName) => diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index 0b5978e3eb..6c003e62ec 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Textures; +using osu.Game.Audio; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Skinning; @@ -253,7 +254,7 @@ namespace osu.Game.Tests.Visual.Gameplay public Texture GetTexture(string componentName) => throw new NotImplementedException(); - public SampleChannel GetSample(string sampleName) => throw new NotImplementedException(); + public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException(); } @@ -264,7 +265,7 @@ namespace osu.Game.Tests.Visual.Gameplay public Texture GetTexture(string componentName) => throw new NotImplementedException(); - public SampleChannel GetSample(string sampleName) => throw new NotImplementedException(); + public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException(); } @@ -275,7 +276,7 @@ namespace osu.Game.Tests.Visual.Gameplay public Texture GetTexture(string componentName) => throw new NotImplementedException(); - public SampleChannel GetSample(string sampleName) => throw new NotImplementedException(); + public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException(); } diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index c7556dddd5..6072bb64ed 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -4,6 +4,7 @@ using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; +using osu.Game.Audio; namespace osu.Game.Skinning { @@ -19,6 +20,6 @@ namespace osu.Game.Skinning public override Texture GetTexture(string componentName) => null; - public override SampleChannel GetSample(string sampleName) => null; + public override SampleChannel GetSample(ISampleInfo sampleInfo) => null; } } diff --git a/osu.Game/Skinning/ISkin.cs b/osu.Game/Skinning/ISkin.cs index 0e67a1897c..4867aba0a9 100644 --- a/osu.Game/Skinning/ISkin.cs +++ b/osu.Game/Skinning/ISkin.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; +using osu.Game.Audio; namespace osu.Game.Skinning { @@ -17,7 +18,7 @@ namespace osu.Game.Skinning Texture GetTexture(string componentName); - SampleChannel GetSample(string sampleName); + SampleChannel GetSample(ISampleInfo sampleInfo); TValue GetValue(Func query) where TConfiguration : SkinConfiguration; } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 73957a203e..bf2f382527 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -17,6 +17,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Framework.Text; +using osu.Game.Audio; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -212,18 +213,21 @@ namespace osu.Game.Skinning return texture; } - public override SampleChannel GetSample(string sampleName) + public override SampleChannel GetSample(ISampleInfo sampleInfo) { - var sample = Samples.Get(sampleName); - - if (sample == null) + foreach (var lookup in sampleInfo.LookupNames) { - // Try fallback to non-bank samples. - var bank = sampleName.Split('/').Last().Split('-')[0] + '-'; - sample = Samples.Get($"Gameplay/{sampleName.Replace(bank, "")}"); + var sample = Samples.Get(lookup); + + if (sample != null) + return sample; } - return sample; + if (sampleInfo is HitSampleInfo hsi) + // Try fallback to non-bank samples. + return Samples.Get($"{hsi.Name}"); + + return null; } private bool hasFont(string fontName) => GetTexture($"{fontName}-0") != null; diff --git a/osu.Game/Skinning/LocalSkinOverrideContainer.cs b/osu.Game/Skinning/LocalSkinOverrideContainer.cs index 7882e0f31b..fc36d1c8da 100644 --- a/osu.Game/Skinning/LocalSkinOverrideContainer.cs +++ b/osu.Game/Skinning/LocalSkinOverrideContainer.cs @@ -8,6 +8,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; +using osu.Game.Audio; using osu.Game.Configuration; namespace osu.Game.Skinning @@ -49,13 +50,13 @@ namespace osu.Game.Skinning return fallbackSource.GetTexture(componentName); } - public SampleChannel GetSample(string sampleName) + public SampleChannel GetSample(ISampleInfo sampleInfo) { SampleChannel sourceChannel; - if (beatmapHitsounds.Value && (sourceChannel = skin?.GetSample(sampleName)) != null) + if (beatmapHitsounds.Value && (sourceChannel = skin?.GetSample(sampleInfo)) != null) return sourceChannel; - return fallbackSource?.GetSample(sampleName); + return fallbackSource?.GetSample(sampleInfo); } public TValue GetValue(Func query) where TConfiguration : SkinConfiguration diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 09c0d3d0bc..027d9df8b8 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; +using osu.Game.Audio; namespace osu.Game.Skinning { @@ -16,7 +17,7 @@ namespace osu.Game.Skinning public abstract Drawable GetDrawableComponent(string componentName); - public abstract SampleChannel GetSample(string sampleName); + public abstract SampleChannel GetSample(ISampleInfo sampleInfo); public abstract Texture GetTexture(string componentName); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 19997e8844..e747a8b1ce 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -15,6 +15,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.Platform; +using osu.Game.Audio; using osu.Game.Database; using osu.Game.IO.Archives; @@ -120,7 +121,7 @@ namespace osu.Game.Skinning public Texture GetTexture(string componentName) => CurrentSkin.Value.GetTexture(componentName); - public SampleChannel GetSample(string sampleName) => CurrentSkin.Value.GetSample(sampleName); + public SampleChannel GetSample(ISampleInfo sampleInfo) => CurrentSkin.Value.GetSample(sampleInfo); public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => CurrentSkin.Value.GetValue(query); } From 7e34afeab874911ef0fd3edb1ad94388fc681d9d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 23 Aug 2019 14:38:18 +0300 Subject: [PATCH 0836/2815] Conver to method group --- osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index 6c444b2e26..0e2b4c986a 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -82,6 +82,8 @@ namespace osu.Game.Overlays.Profile.Sections private void onUserChanged(ValueChangedEvent e) { + retrievalRequest?.Cancel(); + VisiblePages = 0; ItemsContainer.Clear(); @@ -92,7 +94,7 @@ namespace osu.Game.Overlays.Profile.Sections private void showMore() { retrievalRequest = CreateRequest(); - retrievalRequest.Success += items => UpdateItems(items); + retrievalRequest.Success += UpdateItems; api.Queue(retrievalRequest); } @@ -109,13 +111,13 @@ namespace osu.Game.Overlays.Profile.Sections return; } - LoadComponentsAsync(items.Select(item => CreateDrawableItem(item)), i => + LoadComponentsAsync(items.Select(CreateDrawableItem), drawables => { missingText.Hide(); moreButton.FadeTo(items.Count == ItemsPerPage ? 1 : 0); moreButton.IsLoading = false; - ItemsContainer.AddRange(i); + ItemsContainer.AddRange(drawables); }); }); } From 0cde0982e595da7dc28240b78ed534e83f171507 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 23 Aug 2019 14:52:26 +0300 Subject: [PATCH 0837/2815] Use cansellation token --- osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index 0e2b4c986a..75601041e8 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets; using osu.Game.Users; using System.Collections.Generic; using System.Linq; +using System.Threading; namespace osu.Game.Overlays.Profile.Sections { @@ -21,6 +22,7 @@ namespace osu.Game.Overlays.Profile.Sections private readonly ShowMoreButton moreButton; private readonly OsuSpriteText missingText; private APIRequest> retrievalRequest; + private CancellationTokenSource loadCancellation; [Resolved] private IAPIProvider api { get; set; } @@ -82,6 +84,7 @@ namespace osu.Game.Overlays.Profile.Sections private void onUserChanged(ValueChangedEvent e) { + loadCancellation?.Cancel(); retrievalRequest?.Cancel(); VisiblePages = 0; @@ -93,6 +96,8 @@ namespace osu.Game.Overlays.Profile.Sections private void showMore() { + loadCancellation = new CancellationTokenSource(); + retrievalRequest = CreateRequest(); retrievalRequest.Success += UpdateItems; @@ -118,7 +123,7 @@ namespace osu.Game.Overlays.Profile.Sections moreButton.IsLoading = false; ItemsContainer.AddRange(drawables); - }); + }, loadCancellation.Token); }); } From 7b04fb1690fa32ccdd2f75d6bb4b4b33f5b6806f Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 23 Aug 2019 14:54:39 +0300 Subject: [PATCH 0838/2815] StoryboardSample -> StoryboardSampleInfo --- .../Formats/LegacyStoryboardDecoder.cs | 2 +- .../Drawables/DrawableStoryboardSample.cs | 24 +++++++++---------- osu.Game/Storyboards/StoryboardSample.cs | 10 ++++---- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 17df9ccc7e..14c6ea5c8e 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -121,7 +121,7 @@ namespace osu.Game.Beatmaps.Formats var layer = parseLayer(split[2]); var path = cleanFilename(split[3]); var volume = split.Length > 4 ? float.Parse(split[4], CultureInfo.InvariantCulture) : 100; - storyboard.GetLayer(layer).Add(new StoryboardSample(path, time, volume)); + storyboard.GetLayer(layer).Add(new StoryboardSampleInfo(path, time, (int)volume)); break; } } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index ffd238d4e1..b04f1d4518 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.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.IO; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; @@ -17,25 +16,24 @@ namespace osu.Game.Storyboards.Drawables ///
private const double allowable_late_start = 100; - private readonly StoryboardSample sample; + private readonly StoryboardSampleInfo sampleInfo; private SampleChannel channel; public override bool RemoveWhenNotAlive => false; - public DrawableStoryboardSample(StoryboardSample sample) + public DrawableStoryboardSample(StoryboardSampleInfo sampleInfo) { - this.sample = sample; - LifetimeStart = sample.StartTime; + this.sampleInfo = sampleInfo; + LifetimeStart = sampleInfo.StartTime; } [BackgroundDependencyLoader] private void load(IBindable beatmap) { - // Try first with the full name, then attempt with no path - channel = beatmap.Value.Skin.GetSample(sample.Path) ?? beatmap.Value.Skin.GetSample(Path.ChangeExtension(sample.Path, null)); + channel = beatmap.Value.Skin.GetSample(sampleInfo); if (channel != null) - channel.Volume.Value = sample.Volume / 100; + channel.Volume.Value = sampleInfo.Volume / 100.0; } protected override void Update() @@ -43,27 +41,27 @@ namespace osu.Game.Storyboards.Drawables base.Update(); // TODO: this logic will need to be consolidated with other game samples like hit sounds. - if (Time.Current < sample.StartTime) + if (Time.Current < sampleInfo.StartTime) { // We've rewound before the start time of the sample channel?.Stop(); // In the case that the user fast-forwards to a point far beyond the start time of the sample, // we want to be able to fall into the if-conditional below (therefore we must not have a life time end) - LifetimeStart = sample.StartTime; + LifetimeStart = sampleInfo.StartTime; LifetimeEnd = double.MaxValue; } - else if (Time.Current - Time.Elapsed < sample.StartTime) + else if (Time.Current - Time.Elapsed < sampleInfo.StartTime) { // We've passed the start time of the sample. We only play the sample if we're within an allowable range // from the sample's start, to reduce layering if we've been fast-forwarded far into the future - if (Time.Current - sample.StartTime < allowable_late_start) + if (Time.Current - sampleInfo.StartTime < allowable_late_start) channel?.Play(); // In the case that the user rewinds to a point far behind the start time of the sample, // we want to be able to fall into the if-conditional above (therefore we must not have a life time start) LifetimeStart = double.MinValue; - LifetimeEnd = sample.StartTime; + LifetimeEnd = sampleInfo.StartTime; } } } diff --git a/osu.Game/Storyboards/StoryboardSample.cs b/osu.Game/Storyboards/StoryboardSample.cs index 24231cdca6..4635109d51 100644 --- a/osu.Game/Storyboards/StoryboardSample.cs +++ b/osu.Game/Storyboards/StoryboardSample.cs @@ -1,21 +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.Framework.Graphics; +using osu.Game.Audio; using osu.Game.Storyboards.Drawables; namespace osu.Game.Storyboards { - public class StoryboardSample : IStoryboardElement + public class StoryboardSampleInfo : IStoryboardElement, ISampleInfo { - public string Path { get; set; } + public string Path { get; } public bool IsDrawable => true; public double StartTime { get; } - public float Volume; + public int Volume { get; } - public StoryboardSample(string path, double time, float volume) + public StoryboardSampleInfo(string path, double time, int volume) { Path = path; StartTime = time; From b6b050d5e9662155924c3dfa5c41a22a7fbeb1cd Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 23 Aug 2019 14:55:06 +0300 Subject: [PATCH 0839/2815] Add sample path to the lookup names --- osu.Game/Storyboards/StoryboardSample.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Storyboards/StoryboardSample.cs b/osu.Game/Storyboards/StoryboardSample.cs index 4635109d51..5d6ce215f5 100644 --- a/osu.Game/Storyboards/StoryboardSample.cs +++ b/osu.Game/Storyboards/StoryboardSample.cs @@ -17,6 +17,13 @@ namespace osu.Game.Storyboards public int Volume { get; } + public IEnumerable LookupNames => new[] + { + // Try first with the full name, then attempt with no path + Path, + System.IO.Path.ChangeExtension(Path, null), + }; + public StoryboardSampleInfo(string path, double time, int volume) { Path = path; From da72806693cc9688f189f61daa17f2f45ead6145 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 23 Aug 2019 14:55:38 +0300 Subject: [PATCH 0840/2815] Remove lookup logic from SkinnableSound --- osu.Game/Skinning/SkinnableSound.cs | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 8e2b5cec98..23093d9bd9 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.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 osu.Framework.Allocation; @@ -43,28 +42,21 @@ namespace osu.Game.Skinning { channels = hitSamples.Select(s => { - var ch = loadChannel(s, skin.GetSample); + var ch = skin.GetSample(s); + if (ch == null && allowFallback) - ch = loadChannel(s, audio.Samples.Get); + if (s is HitSampleInfo hsi) + ch = audio.Samples.Get(string.IsNullOrEmpty(hsi.Namespace) + ? $"Gameplay/{hsi.Namespace}/{hsi.Bank}-{hsi.Name}" + : $"Gameplay/{hsi.Bank}-{hsi.Name}"); + + if (ch != null) + ch.Volume.Value = s.Volume / 100.0; + return ch; }).Where(c => c != null).ToArray(); } - private SampleChannel loadChannel(ISampleInfo info, Func getSampleFunction) - { - foreach (var lookup in info.LookupNames) - { - var ch = getSampleFunction($"Gameplay/{lookup}"); - if (ch == null) - continue; - - ch.Volume.Value = info.Volume / 100.0; - return ch; - } - - return null; - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From ce5ee095b96111f95bb191dda6e3ea04342c8393 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 23 Aug 2019 14:03:56 +0200 Subject: [PATCH 0841/2815] Fetch config bindables in BDL. --- osu.Game/Screens/Select/FilterControl.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index b97d64f013..44bc3235ac 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -29,9 +29,9 @@ namespace osu.Game.Screens.Select private readonly TabControl groupTabs; - public readonly Bindable SortMode = new Bindable(Filter.SortMode.Title); + public Bindable SortMode; - public readonly Bindable GroupMode = new Bindable(Filter.GroupMode.All); + public Bindable GroupMode; public FilterCriteria CreateCriteria() => new FilterCriteria { @@ -96,7 +96,6 @@ namespace osu.Game.Screens.Select Height = 24, Width = 0.5f, AutoSort = true, - Current = GroupMode }, //spriteText = new OsuSpriteText //{ @@ -115,7 +114,6 @@ namespace osu.Game.Screens.Select Width = 0.5f, Height = 24, AutoSort = true, - Current = SortMode } } }, @@ -158,8 +156,11 @@ namespace osu.Game.Screens.Select ruleset.BindTo(parentRuleset); ruleset.BindValueChanged(_ => updateCriteria()); - config.BindWith(OsuSetting.SelectGroupingMode, GroupMode); - config.BindWith(OsuSetting.SelectSortingMode, SortMode); + SortMode = config.GetBindable(OsuSetting.SelectSortingMode); + GroupMode = config.GetBindable(OsuSetting.SelectGroupingMode); + + sortTabs.Current.BindTo(SortMode); + groupTabs.Current.BindTo(GroupMode); GroupMode.BindValueChanged(_ => updateCriteria()); SortMode.BindValueChanged(_ => updateCriteria()); From 94b5caf7404e072353884b2b278ddea8adce6baf Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 23 Aug 2019 15:18:56 +0300 Subject: [PATCH 0842/2815] Fix build issues --- osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs | 1 + osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 72646e656e..406c0af28d 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics.Sprites; using osuTK.Graphics; using osu.Framework.Audio.Sample; using osu.Framework.Graphics.Textures; +using osu.Game.Audio; namespace osu.Game.Rulesets.Catch.Tests { diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs index 971518909d..953763c95d 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Beatmaps.Formats int spriteCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSprite)); int animationCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardAnimation)); - int sampleCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSample)); + int sampleCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSampleInfo)); Assert.AreEqual(15, spriteCount); Assert.AreEqual(1, animationCount); From 72a644996c830cd5e1479024a02b81d58fe1e6f0 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 23 Aug 2019 13:41:27 +0000 Subject: [PATCH 0843/2815] Bump NUnit3TestAdapter from 3.14.0 to 3.15.0 Bumps [NUnit3TestAdapter](https://github.com/nunit/nunit3-vs-adapter) from 3.14.0 to 3.15.0. - [Release notes](https://github.com/nunit/nunit3-vs-adapter/releases) - [Commits](https://github.com/nunit/nunit3-vs-adapter/compare/V3.14...V3.15) Signed-off-by: dependabot-preview[bot] --- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 4100404da6..7c282f449b 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index 013d2a71d4..4dcfc1b81f 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index 92c5c77aac..197309c7c4 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index 82055ecaee..a5db1625d9 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 50530088c2..4a9d88f3a6 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 257db89a20..2a8bd393da 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -7,7 +7,7 @@ - + WinExe From 1295ec490f7c1e5aedba2d349cfc181ef4710114 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 23 Aug 2019 17:57:31 +0300 Subject: [PATCH 0844/2815] Fix difficulty icon not wrapping correctly in beatmap set overlay --- osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index 104315f1c2..28947b6f22 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -91,7 +91,8 @@ namespace osu.Game.Overlays.BeatmapSet { difficulties = new DifficultiesContainer { - AutoSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Margin = new MarginPadding { Left = -(tile_icon_padding + tile_spacing / 2) }, OnLostHover = () => { From 67acf20805e083264d039d5c063f8bdd94026979 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 23 Aug 2019 18:31:53 +0300 Subject: [PATCH 0845/2815] Add test beatmap set with many difficulties --- .../Online/TestSceneBeatmapSetOverlay.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index daee419b52..8f19df65a9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -135,6 +135,9 @@ namespace osu.Game.Tests.Visual.Online }); downloadAssert(true); + + AddStep("show many difficulties", () => overlay.ShowBeatmapSet(createManyDifficultiesBeatmapSet())); + downloadAssert(true); } [Test] @@ -222,6 +225,56 @@ namespace osu.Game.Tests.Visual.Online AddStep(@"show without reload", overlay.Show); } + private BeatmapSetInfo createManyDifficultiesBeatmapSet() + { + var beatmaps = new List(); + + for (int i = 1; i < 41; i++) + { + beatmaps.Add(new BeatmapInfo + { + OnlineBeatmapID = i * 10, + Version = $"Test #{i}", + Ruleset = Ruleset.Value, + StarDifficulty = 2 + i * 0.1, + BaseDifficulty = new BeatmapDifficulty + { + OverallDifficulty = 3.5f, + }, + OnlineInfo = new BeatmapOnlineInfo(), + Metrics = new BeatmapMetrics + { + Fails = Enumerable.Range(1, 100).Select(j => j % 12 - 6).ToArray(), + Retries = Enumerable.Range(-2, 100).Select(j => j % 12 - 6).ToArray(), + }, + }); + } + + return new BeatmapSetInfo + { + OnlineBeatmapSetID = 123, + Metadata = new BeatmapMetadata + { + Title = @"many difficulties beatmap", + Artist = @"none", + Author = new User + { + Username = @"BanchoBot", + Id = 3, + }, + }, + OnlineInfo = new BeatmapSetOnlineInfo + { + Preview = @"https://b.ppy.sh/preview/123.mp3", + HasVideo = true, + HasStoryboard = true, + Covers = new BeatmapSetOnlineCovers(), + }, + Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }, + Beatmaps = beatmaps, + }; + } + private void downloadAssert(bool shown) { AddAssert($"is download button {(shown ? "shown" : "hidden")}", () => overlay.DownloadButtonsVisible == shown); From f639df849fa3094a27a35fa08763e6eb501a0def Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 23 Aug 2019 23:11:36 +0300 Subject: [PATCH 0846/2815] Allow for difficulty icon to contain content --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 5cce29d609..81f517dd86 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -19,23 +19,33 @@ using osuTK.Graphics; namespace osu.Game.Beatmaps.Drawables { - public class DifficultyIcon : Container, IHasCustomTooltip + public class DifficultyIcon : CompositeDrawable, IHasCustomTooltip { private readonly BeatmapInfo beatmap; private readonly RulesetInfo ruleset; + private readonly Container iconContainer; + + /// + /// Size of this difficulty icon. + /// + public new Vector2 Size + { + get => iconContainer.Size; + set => iconContainer.Size = value; + } + public DifficultyIcon(BeatmapInfo beatmap, RulesetInfo ruleset = null, bool shouldShowTooltip = true) { - if (beatmap == null) - throw new ArgumentNullException(nameof(beatmap)); - - this.beatmap = beatmap; + this.beatmap = beatmap ?? throw new ArgumentNullException(nameof(beatmap)); this.ruleset = ruleset ?? beatmap.Ruleset; if (shouldShowTooltip) TooltipContent = beatmap; - Size = new Vector2(20); + AutoSizeAxes = Axes.Both; + + InternalChild = iconContainer = new Container { Size = new Vector2(20f) }; } public string TooltipText { get; set; } @@ -47,7 +57,7 @@ namespace osu.Game.Beatmaps.Drawables [BackgroundDependencyLoader] private void load(OsuColour colours) { - Children = new Drawable[] + iconContainer.Children = new Drawable[] { new CircularContainer { From d4236c574f99853de259925ebdd44e9dc004200b Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 23 Aug 2019 23:30:54 +0300 Subject: [PATCH 0847/2815] Allow difficulty icon to be updateable --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 93 ++++++++++++------- 1 file changed, 57 insertions(+), 36 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 81f517dd86..6d9d0afc78 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -1,7 +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.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -21,10 +22,22 @@ namespace osu.Game.Beatmaps.Drawables { public class DifficultyIcon : CompositeDrawable, IHasCustomTooltip { - private readonly BeatmapInfo beatmap; - private readonly RulesetInfo ruleset; + private BeatmapInfo beatmap; private readonly Container iconContainer; + private readonly Box iconBg; + + protected BeatmapInfo Beatmap + { + get => beatmap; + set + { + beatmap = value; + + if (IsLoaded) + updateIconColour(); + } + } /// /// Size of this difficulty icon. @@ -37,15 +50,46 @@ namespace osu.Game.Beatmaps.Drawables public DifficultyIcon(BeatmapInfo beatmap, RulesetInfo ruleset = null, bool shouldShowTooltip = true) { - this.beatmap = beatmap ?? throw new ArgumentNullException(nameof(beatmap)); + this.beatmap = beatmap; - this.ruleset = ruleset ?? beatmap.Ruleset; if (shouldShowTooltip) TooltipContent = beatmap; AutoSizeAxes = Axes.Both; - InternalChild = iconContainer = new Container { Size = new Vector2(20f) }; + InternalChild = iconContainer = new Container + { + Size = new Vector2(20f), + Children = new Drawable[] + { + new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(0.84f), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.08f), + Type = EdgeEffectType.Shadow, + Radius = 5, + }, + Child = iconBg = new Box + { + RelativeSizeAxes = Axes.Both, + }, + }, + new ConstrainedIconContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + // the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment) + Icon = (ruleset ?? beatmap?.Ruleset)?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle } + } + } + }; } public string TooltipText { get; set; } @@ -54,41 +98,18 @@ namespace osu.Game.Beatmaps.Drawables public object TooltipContent { get; set; } + private OsuColour colours; + [BackgroundDependencyLoader] private void load(OsuColour colours) { - iconContainer.Children = new Drawable[] - { - new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Scale = new Vector2(0.84f), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Masking = true, - EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.Black.Opacity(0.08f), - Type = EdgeEffectType.Shadow, - Radius = 5, - }, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.ForDifficultyRating(beatmap.DifficultyRating), - }, - }, - new ConstrainedIconContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - // the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment) - Icon = ruleset?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle } - } - }; + this.colours = colours; + + updateIconColour(); } + private void updateIconColour() => iconBg.Colour = colours.ForDifficultyRating(beatmap.DifficultyRating); + private class DifficultyIconTooltip : VisibilityContainer, ITooltip { private readonly OsuSpriteText difficultyName, starRating; From 83b6e0f30c674b19607cac50b79d899e036721a5 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 23 Aug 2019 23:36:23 +0300 Subject: [PATCH 0848/2815] Implement grouped difficulty icon --- .../Drawables/GroupedDifficultyIcon.cs | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs diff --git a/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs new file mode 100644 index 0000000000..17f2bf719d --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.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.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; +using osuTK.Graphics; + +namespace osu.Game.Beatmaps.Drawables +{ + public class GroupedDifficultyIcon : DifficultyIcon + { + private readonly OsuSpriteText counter; + + private List beatmaps; + + protected List Beatmaps + { + get => beatmaps; + set + { + beatmaps = value; + + updateDisplay(); + } + } + + public GroupedDifficultyIcon(List beatmaps, RulesetInfo ruleset, Color4 counterColour) + : base(null, ruleset, false) + { + this.beatmaps = beatmaps; + + AddInternal(counter = new OsuSpriteText + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Padding = new MarginPadding { Left = Size.X }, + Margin = new MarginPadding { Left = 2, Right = 5 }, + Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold), + Colour = counterColour, + }); + + updateDisplay(); + } + + private void updateDisplay() + { + if (beatmaps == null || beatmaps.Count == 0) + return; + + Beatmap = beatmaps.OrderBy(b => b.StarDifficulty).Last(); + counter.Text = beatmaps.Count.ToString(); + } + } +} From 63e6aca61b6f4a246bc05d17d1fbc1cf6e9c2b57 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 23 Aug 2019 23:40:41 +0300 Subject: [PATCH 0849/2815] Add logic to create grouped difficulty icons in direct panel --- osu.Game/Overlays/Direct/DirectPanel.cs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index 8199d80528..b2f3c6befb 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -18,6 +18,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; using osuTK; using osuTK.Graphics; @@ -28,10 +29,12 @@ namespace osu.Game.Overlays.Direct public readonly BeatmapSetInfo SetInfo; private const double hover_transition_time = 400; + private const int maximum_difficulty_icons = 15; private Container content; private BeatmapSetOverlay beatmapSetOverlay; + private RulesetStore rulesets; public PreviewTrack Preview => PlayButton.Preview; public Bindable PreviewPlaying => PlayButton.Playing; @@ -67,9 +70,10 @@ namespace osu.Game.Overlays.Direct }; [BackgroundDependencyLoader(permitNulls: true)] - private void load(BeatmapManager beatmaps, OsuColour colours, BeatmapSetOverlay beatmapSetOverlay) + private void load(BeatmapManager beatmaps, OsuColour colours, BeatmapSetOverlay beatmapSetOverlay, RulesetStore rulesets) { this.beatmapSetOverlay = beatmapSetOverlay; + this.rulesets = rulesets; AddInternal(content = new Container { @@ -142,8 +146,18 @@ namespace osu.Game.Overlays.Direct { var icons = new List(); - foreach (var b in SetInfo.Beatmaps.OrderBy(beatmap => beatmap.StarDifficulty)) - icons.Add(new DifficultyIcon(b)); + if (SetInfo.Beatmaps.Count > maximum_difficulty_icons) + { + foreach (var ruleset in rulesets.AvailableRulesets) + { + List list; + if ((list = SetInfo.Beatmaps.FindAll(b => b.Ruleset.Equals(ruleset))).Count > 0) + icons.Add(new GroupedDifficultyIcon(list, ruleset, this is DirectListPanel ? Color4.White : Color4.Black)); + } + } + else + foreach (var b in SetInfo.Beatmaps.OrderBy(beatmap => beatmap.StarDifficulty)) + icons.Add(new DifficultyIcon(b)); return icons; } From 87340061e16d6d41c59802c5c9f9e3b1f0e90e01 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sat, 24 Aug 2019 00:06:28 +0300 Subject: [PATCH 0850/2815] Add logic to create grouped difficulty icons in carousel beatmap set --- .../Carousel/DrawableCarouselBeatmapSet.cs | 44 +++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 0259f3cd81..c6bd726632 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -19,6 +19,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; +using osu.Game.Rulesets; using osuTK; using osuTK.Graphics; @@ -39,7 +40,7 @@ namespace osu.Game.Screens.Select.Carousel } [BackgroundDependencyLoader(true)] - private void load(BeatmapManager manager, BeatmapSetOverlay beatmapOverlay, DialogOverlay overlay) + private void load(BeatmapManager manager, BeatmapSetOverlay beatmapOverlay, DialogOverlay overlay, RulesetStore rulesets) { restoreHiddenRequested = s => s.Beatmaps.ForEach(manager.Restore); dialogOverlay = overlay; @@ -95,11 +96,11 @@ namespace osu.Game.Screens.Select.Carousel TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, Status = beatmapSet.Status }, - new FillFlowContainer + new FillFlowContainer { AutoSizeAxes = Axes.Both, Spacing = new Vector2(3), - Children = ((CarouselBeatmapSet)Item).Beatmaps.Select(b => new FilterableDifficultyIcon(b)).ToList() + Children = getDifficultyIcons(rulesets), }, } } @@ -108,6 +109,27 @@ namespace osu.Game.Screens.Select.Carousel }; } + private const int maximum_difficulty_icons = 18; + + private List getDifficultyIcons(RulesetStore rulesets) + { + var beatmaps = ((CarouselBeatmapSet)Item).Beatmaps.ToList(); + var icons = new List(); + + if (beatmaps.Count > maximum_difficulty_icons) + { + foreach (var ruleset in rulesets.AvailableRulesets.OrderBy(r => r.ID)) + { + List list; + if ((list = beatmaps.FindAll(b => b.Beatmap.Ruleset.Equals(ruleset))).Count > 0) + icons.Add(new FilterableGroupedDifficultyIcon(list, ruleset)); + } + } + else beatmaps.ForEach(b => icons.Add(new FilterableDifficultyIcon(b))); + + return icons; + } + public MenuItem[] ContextMenuItems { get @@ -205,5 +227,21 @@ namespace osu.Game.Screens.Select.Carousel filtered.TriggerChange(); } } + + public class FilterableGroupedDifficultyIcon : GroupedDifficultyIcon + { + public FilterableGroupedDifficultyIcon(List items, RulesetInfo ruleset) + : base(items.Select(i => i.Beatmap).ToList(), ruleset, Color4.White) + { + items.ForEach(item => item.Filtered.ValueChanged += _ => + { + var hiddenItems = items.FindAll(i => !i.Filtered.Value); + var hasHidden = hiddenItems.Count > 0; + + this.FadeTo(hasHidden ? 1 : 0.1f, 100); + Beatmaps = (hasHidden ? hiddenItems : items).Select(i => i.Beatmap).ToList(); + }); + } + } } } From 8584d3ba231e6b8c67d7ea3f85bf3c5075c996c1 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sat, 24 Aug 2019 00:31:36 +0300 Subject: [PATCH 0851/2815] Add many difficulties beatmap direct panel to the tests --- .../Visual/Online/TestSceneDirectPanel.cs | 62 +++++++++++++++---- 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs index 53dbaeddda..731cb62518 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Overlays.Direct; using osu.Game.Rulesets; -using osu.Game.Rulesets.Osu; using osu.Game.Users; using osuTK; @@ -24,7 +23,7 @@ namespace osu.Game.Tests.Visual.Online typeof(IconPill) }; - private BeatmapSetInfo getUndownloadableBeatmapSet(RulesetInfo ruleset) => new BeatmapSetInfo + private BeatmapSetInfo getUndownloadableBeatmapSet() => new BeatmapSetInfo { OnlineBeatmapSetID = 123, Metadata = new BeatmapMetadata @@ -56,23 +55,62 @@ namespace osu.Game.Tests.Visual.Online { new BeatmapInfo { - Ruleset = ruleset, + Ruleset = Ruleset.Value, Version = "Test", StarDifficulty = 6.42, } } }; - [BackgroundDependencyLoader] - private void load() + private BeatmapSetInfo getManyDifficultiesBeatmapSet(RulesetStore rulesets) { - var ruleset = new OsuRuleset().RulesetInfo; + var beatmaps = new List(); - var normal = CreateWorkingBeatmap(ruleset).BeatmapSetInfo; + for (int i = 0; i < 100; i++) + { + beatmaps.Add(new BeatmapInfo + { + Ruleset = rulesets.GetRuleset(i % 4), + StarDifficulty = 2 + i % 4 * 2, + BaseDifficulty = new BeatmapDifficulty + { + OverallDifficulty = 3.5f, + } + }); + } + + return new BeatmapSetInfo + { + OnlineBeatmapSetID = 1, + Metadata = new BeatmapMetadata + { + Title = "many difficulties beatmap", + Artist = "test", + Author = new User + { + Username = "BanchoBot", + Id = 3, + } + }, + OnlineInfo = new BeatmapSetOnlineInfo + { + HasVideo = true, + HasStoryboard = true, + Covers = new BeatmapSetOnlineCovers(), + }, + Beatmaps = beatmaps, + }; + } + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + var normal = CreateWorkingBeatmap(Ruleset.Value).BeatmapSetInfo; normal.OnlineInfo.HasVideo = true; normal.OnlineInfo.HasStoryboard = true; - var undownloadable = getUndownloadableBeatmapSet(ruleset); + var undownloadable = getUndownloadableBeatmapSet(); + var manyDifficulties = getManyDifficultiesBeatmapSet(rulesets); Child = new BasicScrollContainer { @@ -81,15 +119,17 @@ namespace osu.Game.Tests.Visual.Online { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, + Direction = FillDirection.Full, Padding = new MarginPadding(20), - Spacing = new Vector2(0, 20), + Spacing = new Vector2(5, 20), Children = new Drawable[] { new DirectGridPanel(normal), - new DirectListPanel(normal), new DirectGridPanel(undownloadable), + new DirectGridPanel(manyDifficulties), + new DirectListPanel(normal), new DirectListPanel(undownloadable), + new DirectListPanel(manyDifficulties), }, }, }; From f6feef6b5661cb3b40276311ac2745ee5f3b1053 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sat, 24 Aug 2019 00:33:14 +0300 Subject: [PATCH 0852/2815] Remove redundant using directive --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 6d9d0afc78..7732aa96f8 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.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 System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; From 8ccbe84f67b87ba97b45576c495a53e509fa878f Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sat, 24 Aug 2019 01:30:33 +0300 Subject: [PATCH 0853/2815] Loop on distinct rulesets of beatmap instead of all --- osu.Game/Overlays/Direct/DirectPanel.cs | 12 +++--------- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 14 +++++--------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index b2f3c6befb..d6258061e1 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -34,7 +34,6 @@ namespace osu.Game.Overlays.Direct private Container content; private BeatmapSetOverlay beatmapSetOverlay; - private RulesetStore rulesets; public PreviewTrack Preview => PlayButton.Preview; public Bindable PreviewPlaying => PlayButton.Playing; @@ -70,10 +69,9 @@ namespace osu.Game.Overlays.Direct }; [BackgroundDependencyLoader(permitNulls: true)] - private void load(BeatmapManager beatmaps, OsuColour colours, BeatmapSetOverlay beatmapSetOverlay, RulesetStore rulesets) + private void load(BeatmapManager beatmaps, OsuColour colours, BeatmapSetOverlay beatmapSetOverlay) { this.beatmapSetOverlay = beatmapSetOverlay; - this.rulesets = rulesets; AddInternal(content = new Container { @@ -148,12 +146,8 @@ namespace osu.Game.Overlays.Direct if (SetInfo.Beatmaps.Count > maximum_difficulty_icons) { - foreach (var ruleset in rulesets.AvailableRulesets) - { - List list; - if ((list = SetInfo.Beatmaps.FindAll(b => b.Ruleset.Equals(ruleset))).Count > 0) - icons.Add(new GroupedDifficultyIcon(list, ruleset, this is DirectListPanel ? Color4.White : Color4.Black)); - } + foreach (var ruleset in SetInfo.Beatmaps.Select(b => b.Ruleset).Distinct()) + icons.Add(new GroupedDifficultyIcon(SetInfo.Beatmaps.FindAll(b => b.Ruleset.Equals(ruleset)), ruleset, this is DirectListPanel ? Color4.White : Color4.Black)); } else foreach (var b in SetInfo.Beatmaps.OrderBy(beatmap => beatmap.StarDifficulty)) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index c6bd726632..69b9aa399a 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -40,7 +40,7 @@ namespace osu.Game.Screens.Select.Carousel } [BackgroundDependencyLoader(true)] - private void load(BeatmapManager manager, BeatmapSetOverlay beatmapOverlay, DialogOverlay overlay, RulesetStore rulesets) + private void load(BeatmapManager manager, BeatmapSetOverlay beatmapOverlay, DialogOverlay overlay) { restoreHiddenRequested = s => s.Beatmaps.ForEach(manager.Restore); dialogOverlay = overlay; @@ -100,7 +100,7 @@ namespace osu.Game.Screens.Select.Carousel { AutoSizeAxes = Axes.Both, Spacing = new Vector2(3), - Children = getDifficultyIcons(rulesets), + Children = getDifficultyIcons(), }, } } @@ -111,19 +111,15 @@ namespace osu.Game.Screens.Select.Carousel private const int maximum_difficulty_icons = 18; - private List getDifficultyIcons(RulesetStore rulesets) + private List getDifficultyIcons() { var beatmaps = ((CarouselBeatmapSet)Item).Beatmaps.ToList(); var icons = new List(); if (beatmaps.Count > maximum_difficulty_icons) { - foreach (var ruleset in rulesets.AvailableRulesets.OrderBy(r => r.ID)) - { - List list; - if ((list = beatmaps.FindAll(b => b.Beatmap.Ruleset.Equals(ruleset))).Count > 0) - icons.Add(new FilterableGroupedDifficultyIcon(list, ruleset)); - } + foreach (var ruleset in beatmaps.Select(b => b.Beatmap.Ruleset).Distinct()) + icons.Add(new FilterableGroupedDifficultyIcon(beatmaps.FindAll(b => b.Beatmap.Ruleset.Equals(ruleset)), ruleset)); } else beatmaps.ForEach(b => icons.Add(new FilterableDifficultyIcon(b))); From 830ddd6ed9a99c42bac7ba4dfa9714751b46076b Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sat, 24 Aug 2019 01:31:53 +0300 Subject: [PATCH 0854/2815] Use all rulesets for many difficulties test --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 7c9b7c7815..6669ec7da3 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -516,6 +516,7 @@ namespace osu.Game.Tests.Visual.SongSelect OnlineBeatmapID = b * 10, Path = $"extra{b}.osu", Version = $"Extra {b}", + Ruleset = rulesets.GetRuleset((b - 1) % 4), StarDifficulty = 2, BaseDifficulty = new BeatmapDifficulty { From 008e39b7381c0b0b7ffcee28df42019ab967ba49 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sat, 24 Aug 2019 01:40:40 +0300 Subject: [PATCH 0855/2815] Remove redundant using directive --- osu.Game/Overlays/Direct/DirectPanel.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index d6258061e1..7e16daee75 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -18,7 +18,6 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets; using osuTK; using osuTK.Graphics; From 0bcd323d17b12d1c48a13bbc7de2d674a156d513 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sat, 24 Aug 2019 09:30:43 +0300 Subject: [PATCH 0856/2815] Remove unnecessary string literal --- osu.Game/Skinning/LegacySkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index bf2f382527..d699b1f3c6 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -225,7 +225,7 @@ namespace osu.Game.Skinning if (sampleInfo is HitSampleInfo hsi) // Try fallback to non-bank samples. - return Samples.Get($"{hsi.Name}"); + return Samples.Get(hsi.Name); return null; } From af4adb6339e807ee71be35766c1366fb67fcbf38 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sat, 24 Aug 2019 09:43:55 +0300 Subject: [PATCH 0857/2815] Add xmldoc --- osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs index 17f2bf719d..b48b966918 100644 --- a/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs @@ -11,6 +11,12 @@ using osuTK.Graphics; namespace osu.Game.Beatmaps.Drawables { + /// + /// A difficulty icon that contains a counter on the right-side of it. + /// + /// + /// Used in cases when there are too many difficulty icons to show. + /// public class GroupedDifficultyIcon : DifficultyIcon { private readonly OsuSpriteText counter; From b204e4419a9dab314f49a4dc52682b95fe8e66e2 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 24 Aug 2019 10:34:54 +0200 Subject: [PATCH 0858/2815] Rename SelectSortingMode & SelectGroupingMode -> SongSelectSortingMode & SongSelectGroupingMode --- osu.Game/Configuration/OsuConfigManager.cs | 8 ++++---- osu.Game/Screens/Select/FilterControl.cs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 6ebacc642d..b13e115387 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -26,8 +26,8 @@ namespace osu.Game.Configuration Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1); Set(OsuSetting.DisplayStarsMaximum, 10.0, 0, 10, 0.1); - Set(OsuSetting.SelectGroupingMode, GroupMode.All); - Set(OsuSetting.SelectSortingMode, SortMode.Title); + Set(OsuSetting.SongSelectGroupingMode, GroupMode.All); + Set(OsuSetting.SongSelectSortingMode, SortMode.Title); Set(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation); @@ -154,8 +154,8 @@ namespace osu.Game.Configuration SaveUsername, DisplayStarsMinimum, DisplayStarsMaximum, - SelectGroupingMode, - SelectSortingMode, + SongSelectGroupingMode, + SongSelectSortingMode, RandomSelectAlgorithm, ShowFpsDisplay, ChatDisplayHeight, diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 44bc3235ac..06b6cccf96 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -156,8 +156,8 @@ namespace osu.Game.Screens.Select ruleset.BindTo(parentRuleset); ruleset.BindValueChanged(_ => updateCriteria()); - SortMode = config.GetBindable(OsuSetting.SelectSortingMode); - GroupMode = config.GetBindable(OsuSetting.SelectGroupingMode); + SortMode = config.GetBindable(OsuSetting.SongSelectSortingMode); + GroupMode = config.GetBindable(OsuSetting.SongSelectGroupingMode); sortTabs.Current.BindTo(SortMode); groupTabs.Current.BindTo(GroupMode); From a19a9b90ede7f812335433b8f171a56fab578a29 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 25 Aug 2019 11:38:26 +0900 Subject: [PATCH 0859/2815] Simplify group filter display --- .../Drawables/GroupedDifficultyIcon.cs | 33 ++----------------- .../Carousel/DrawableCarouselBeatmapSet.cs | 7 ++-- 2 files changed, 5 insertions(+), 35 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs index b48b966918..fbad113caa 100644 --- a/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs @@ -19,46 +19,19 @@ namespace osu.Game.Beatmaps.Drawables /// public class GroupedDifficultyIcon : DifficultyIcon { - private readonly OsuSpriteText counter; - - private List beatmaps; - - protected List Beatmaps - { - get => beatmaps; - set - { - beatmaps = value; - - updateDisplay(); - } - } - public GroupedDifficultyIcon(List beatmaps, RulesetInfo ruleset, Color4 counterColour) - : base(null, ruleset, false) + : base(beatmaps.OrderBy(b => b.StarDifficulty).Last(), ruleset, false) { - this.beatmaps = beatmaps; - - AddInternal(counter = new OsuSpriteText + AddInternal(new OsuSpriteText { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, Padding = new MarginPadding { Left = Size.X }, Margin = new MarginPadding { Left = 2, Right = 5 }, Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold), + Text = beatmaps.Count.ToString(), Colour = counterColour, }); - - updateDisplay(); - } - - private void updateDisplay() - { - if (beatmaps == null || beatmaps.Count == 0) - return; - - Beatmap = beatmaps.OrderBy(b => b.StarDifficulty).Last(); - counter.Text = beatmaps.Count.ToString(); } } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 69b9aa399a..79e5debdd2 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -231,11 +231,8 @@ namespace osu.Game.Screens.Select.Carousel { items.ForEach(item => item.Filtered.ValueChanged += _ => { - var hiddenItems = items.FindAll(i => !i.Filtered.Value); - var hasHidden = hiddenItems.Count > 0; - - this.FadeTo(hasHidden ? 1 : 0.1f, 100); - Beatmaps = (hasHidden ? hiddenItems : items).Select(i => i.Beatmap).ToList(); + // for now, fade the whole group based on the ratio of hidden items. + this.FadeTo(1 - 0.9f * ((float)items.Count(i => i.Filtered.Value) / items.Count), 100); }); } } From 2e21fbcf6a2ce843172a1f58be10dbf01ffa758f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 25 Aug 2019 11:44:56 +0900 Subject: [PATCH 0860/2815] Fix incorrect colour usage --- osu.Game/Overlays/Direct/DirectGridPanel.cs | 2 +- osu.Game/Overlays/Direct/DirectListPanel.cs | 2 +- osu.Game/Overlays/Direct/DirectPanel.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs index 243e79eb9b..7bf94c1483 100644 --- a/osu.Game/Overlays/Direct/DirectGridPanel.cs +++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs @@ -151,7 +151,7 @@ namespace osu.Game.Overlays.Direct AutoSizeAxes = Axes.X, Height = 20, Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding }, - Children = GetDifficultyIcons(), + Children = GetDifficultyIcons(colours), }, }, }, diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs index 5757e1445b..158ff648dd 100644 --- a/osu.Game/Overlays/Direct/DirectListPanel.cs +++ b/osu.Game/Overlays/Direct/DirectListPanel.cs @@ -129,7 +129,7 @@ namespace osu.Game.Overlays.Direct AutoSizeAxes = Axes.X, Height = 20, Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding }, - Children = GetDifficultyIcons(), + Children = GetDifficultyIcons(colours), }, }, }, diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index 7e16daee75..641423f21f 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -139,14 +139,14 @@ namespace osu.Game.Overlays.Direct }; } - protected List GetDifficultyIcons() + protected List GetDifficultyIcons(OsuColour colours) { var icons = new List(); if (SetInfo.Beatmaps.Count > maximum_difficulty_icons) { foreach (var ruleset in SetInfo.Beatmaps.Select(b => b.Ruleset).Distinct()) - icons.Add(new GroupedDifficultyIcon(SetInfo.Beatmaps.FindAll(b => b.Ruleset.Equals(ruleset)), ruleset, this is DirectListPanel ? Color4.White : Color4.Black)); + icons.Add(new GroupedDifficultyIcon(SetInfo.Beatmaps.FindAll(b => b.Ruleset.Equals(ruleset)), ruleset, this is DirectListPanel ? Color4.White : colours.Gray5)); } else foreach (var b in SetInfo.Beatmaps.OrderBy(beatmap => beatmap.StarDifficulty)) From 1d34124667666f291957df354b362873c534fce1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 25 Aug 2019 11:56:07 +0900 Subject: [PATCH 0861/2815] Revert all DifficultyIcon changes --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 91 ++++++++----------- 1 file changed, 36 insertions(+), 55 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 7732aa96f8..81f517dd86 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.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.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -20,22 +21,10 @@ namespace osu.Game.Beatmaps.Drawables { public class DifficultyIcon : CompositeDrawable, IHasCustomTooltip { - private BeatmapInfo beatmap; + private readonly BeatmapInfo beatmap; + private readonly RulesetInfo ruleset; private readonly Container iconContainer; - private readonly Box iconBg; - - protected BeatmapInfo Beatmap - { - get => beatmap; - set - { - beatmap = value; - - if (IsLoaded) - updateIconColour(); - } - } /// /// Size of this difficulty icon. @@ -48,46 +37,15 @@ namespace osu.Game.Beatmaps.Drawables public DifficultyIcon(BeatmapInfo beatmap, RulesetInfo ruleset = null, bool shouldShowTooltip = true) { - this.beatmap = beatmap; + this.beatmap = beatmap ?? throw new ArgumentNullException(nameof(beatmap)); + this.ruleset = ruleset ?? beatmap.Ruleset; if (shouldShowTooltip) TooltipContent = beatmap; AutoSizeAxes = Axes.Both; - InternalChild = iconContainer = new Container - { - Size = new Vector2(20f), - Children = new Drawable[] - { - new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Scale = new Vector2(0.84f), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Masking = true, - EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.Black.Opacity(0.08f), - Type = EdgeEffectType.Shadow, - Radius = 5, - }, - Child = iconBg = new Box - { - RelativeSizeAxes = Axes.Both, - }, - }, - new ConstrainedIconContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - // the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment) - Icon = (ruleset ?? beatmap?.Ruleset)?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle } - } - } - }; + InternalChild = iconContainer = new Container { Size = new Vector2(20f) }; } public string TooltipText { get; set; } @@ -96,18 +54,41 @@ namespace osu.Game.Beatmaps.Drawables public object TooltipContent { get; set; } - private OsuColour colours; - [BackgroundDependencyLoader] private void load(OsuColour colours) { - this.colours = colours; - - updateIconColour(); + iconContainer.Children = new Drawable[] + { + new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(0.84f), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.08f), + Type = EdgeEffectType.Shadow, + Radius = 5, + }, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.ForDifficultyRating(beatmap.DifficultyRating), + }, + }, + new ConstrainedIconContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + // the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment) + Icon = ruleset?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle } + } + }; } - private void updateIconColour() => iconBg.Colour = colours.ForDifficultyRating(beatmap.DifficultyRating); - private class DifficultyIconTooltip : VisibilityContainer, ITooltip { private readonly OsuSpriteText difficultyName, starRating; From ef397434f68b3e6ccd4fca8dc8ddead3f31928f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 25 Aug 2019 12:00:06 +0900 Subject: [PATCH 0862/2815] use GroupBy instead of Distinct+FindAll --- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 79e5debdd2..c9419107a2 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -118,8 +118,8 @@ namespace osu.Game.Screens.Select.Carousel if (beatmaps.Count > maximum_difficulty_icons) { - foreach (var ruleset in beatmaps.Select(b => b.Beatmap.Ruleset).Distinct()) - icons.Add(new FilterableGroupedDifficultyIcon(beatmaps.FindAll(b => b.Beatmap.Ruleset.Equals(ruleset)), ruleset)); + foreach (var group in beatmaps.GroupBy(b => b.Beatmap.Ruleset)) + icons.Add(new FilterableGroupedDifficultyIcon(group.ToList(), group.Key)); } else beatmaps.ForEach(b => icons.Add(new FilterableDifficultyIcon(b))); From 9881d3677df5896d28bb6e95d5ba7b4e65b0744d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 25 Aug 2019 12:05:46 +0900 Subject: [PATCH 0863/2815] Simplify icon creation --- .../Carousel/DrawableCarouselBeatmapSet.cs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index c9419107a2..0a8c61e3d2 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -100,7 +100,7 @@ namespace osu.Game.Screens.Select.Carousel { AutoSizeAxes = Axes.Both, Spacing = new Vector2(3), - Children = getDifficultyIcons(), + ChildrenEnumerable = getDifficultyIcons(), }, } } @@ -111,19 +111,13 @@ namespace osu.Game.Screens.Select.Carousel private const int maximum_difficulty_icons = 18; - private List getDifficultyIcons() + private IEnumerable getDifficultyIcons() { var beatmaps = ((CarouselBeatmapSet)Item).Beatmaps.ToList(); - var icons = new List(); - if (beatmaps.Count > maximum_difficulty_icons) - { - foreach (var group in beatmaps.GroupBy(b => b.Beatmap.Ruleset)) - icons.Add(new FilterableGroupedDifficultyIcon(group.ToList(), group.Key)); - } - else beatmaps.ForEach(b => icons.Add(new FilterableDifficultyIcon(b))); - - return icons; + return beatmaps.Count > maximum_difficulty_icons + ? (IEnumerable)beatmaps.GroupBy(b => b.Beatmap.Ruleset).Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Key)) + : beatmaps.Select(b => new FilterableDifficultyIcon(b)); } public MenuItem[] ContextMenuItems From de2c6aa23d30e0480755f1d592f18c864f8b4316 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Aug 2019 12:21:11 +0900 Subject: [PATCH 0864/2815] Rename and expand usability of SkinProvidingContainer --- .../Skinning/BeatmapSkinProvidingContainer.cs | 38 ++++++++++++++++ ...Container.cs => SkinProvidingContainer.cs} | 44 ++++++++----------- 2 files changed, 57 insertions(+), 25 deletions(-) create mode 100644 osu.Game/Skinning/BeatmapSkinProvidingContainer.cs rename osu.Game/Skinning/{LocalSkinOverrideContainer.cs => SkinProvidingContainer.cs} (60%) diff --git a/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs b/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs new file mode 100644 index 0000000000..dc25a33f1e --- /dev/null +++ b/osu.Game/Skinning/BeatmapSkinProvidingContainer.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.Bindables; +using osu.Game.Configuration; + +namespace osu.Game.Skinning +{ + /// + /// A container which overrides existing skin options with beatmap-local values. + /// + public class BeatmapSkinProvidingContainer : SkinProvidingContainer + { + private readonly Bindable beatmapSkins = new Bindable(); + private readonly Bindable beatmapHitsounds = new Bindable(); + + protected override bool AllowConfigurationLookup => beatmapSkins.Value; + protected override bool AllowDrawableLookup(string componentName) => beatmapSkins.Value; + protected override bool AllowTextureLookup(string componentName) => beatmapSkins.Value; + protected override bool AllowSampleLookup(string componentName) => beatmapHitsounds.Value; + + public BeatmapSkinProvidingContainer(ISkin skin) + : base(skin) + { + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + config.BindWith(OsuSetting.BeatmapSkins, beatmapSkins); + config.BindWith(OsuSetting.BeatmapHitsounds, beatmapHitsounds); + + beatmapSkins.BindValueChanged(_ => TriggerSourceChanged()); + beatmapHitsounds.BindValueChanged(_ => TriggerSourceChanged()); + } + } +} diff --git a/osu.Game/Skinning/LocalSkinOverrideContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs similarity index 60% rename from osu.Game/Skinning/LocalSkinOverrideContainer.cs rename to osu.Game/Skinning/SkinProvidingContainer.cs index 7882e0f31b..a92a0ae0d1 100644 --- a/osu.Game/Skinning/LocalSkinOverrideContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -4,29 +4,32 @@ using System; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; -using osu.Game.Configuration; namespace osu.Game.Skinning { /// - /// A container which overrides existing skin options with beatmap-local values. + /// A container which adds a local to the hierarchy. /// - public class LocalSkinOverrideContainer : Container, ISkinSource + public class SkinProvidingContainer : Container, ISkinSource { public event Action SourceChanged; - private readonly Bindable beatmapSkins = new Bindable(); - private readonly Bindable beatmapHitsounds = new Bindable(); - private readonly ISkin skin; private ISkinSource fallbackSource; - public LocalSkinOverrideContainer(ISkin skin) + protected virtual bool AllowDrawableLookup(string componentName) => true; + + protected virtual bool AllowTextureLookup(string componentName) => true; + + protected virtual bool AllowSampleLookup(string componentName) => true; + + protected virtual bool AllowConfigurationLookup => true; + + public SkinProvidingContainer(ISkin skin) { this.skin = skin; } @@ -34,7 +37,7 @@ namespace osu.Game.Skinning public Drawable GetDrawableComponent(string componentName) { Drawable sourceDrawable; - if (beatmapSkins.Value && (sourceDrawable = skin?.GetDrawableComponent(componentName)) != null) + if (AllowDrawableLookup(componentName) && (sourceDrawable = skin?.GetDrawableComponent(componentName)) != null) return sourceDrawable; return fallbackSource?.GetDrawableComponent(componentName); @@ -43,7 +46,7 @@ namespace osu.Game.Skinning public Texture GetTexture(string componentName) { Texture sourceTexture; - if (beatmapSkins.Value && (sourceTexture = skin?.GetTexture(componentName)) != null) + if (AllowTextureLookup(componentName) && (sourceTexture = skin?.GetTexture(componentName)) != null) return sourceTexture; return fallbackSource.GetTexture(componentName); @@ -52,7 +55,7 @@ namespace osu.Game.Skinning public SampleChannel GetSample(string sampleName) { SampleChannel sourceChannel; - if (beatmapHitsounds.Value && (sourceChannel = skin?.GetSample(sampleName)) != null) + if (AllowSampleLookup(sampleName) && (sourceChannel = skin?.GetSample(sampleName)) != null) return sourceChannel; return fallbackSource?.GetSample(sampleName); @@ -61,14 +64,13 @@ namespace osu.Game.Skinning public TValue GetValue(Func query) where TConfiguration : SkinConfiguration { TValue val; - if ((skin as Skin)?.Configuration is TConfiguration conf) - if (beatmapSkins.Value && (val = query.Invoke(conf)) != null) - return val; + if (AllowConfigurationLookup && skin != null && (val = skin.GetValue(query)) != null) + return val; return fallbackSource == null ? default : fallbackSource.GetValue(query); } - private void onSourceChanged() => SourceChanged?.Invoke(); + protected virtual void TriggerSourceChanged() => SourceChanged?.Invoke(); protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { @@ -76,18 +78,10 @@ namespace osu.Game.Skinning fallbackSource = dependencies.Get(); if (fallbackSource != null) - fallbackSource.SourceChanged += onSourceChanged; + fallbackSource.SourceChanged += TriggerSourceChanged; dependencies.CacheAs(this); - var config = dependencies.Get(); - - config.BindWith(OsuSetting.BeatmapSkins, beatmapSkins); - config.BindWith(OsuSetting.BeatmapHitsounds, beatmapHitsounds); - - beatmapSkins.BindValueChanged(_ => onSourceChanged()); - beatmapHitsounds.BindValueChanged(_ => onSourceChanged()); - return dependencies; } @@ -99,7 +93,7 @@ namespace osu.Game.Skinning base.Dispose(isDisposing); if (fallbackSource != null) - fallbackSource.SourceChanged -= onSourceChanged; + fallbackSource.SourceChanged -= TriggerSourceChanged; } } } From 5e362d10b1c26443bb6c95c3f61c0b5984e81b7d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Aug 2019 12:21:49 +0900 Subject: [PATCH 0865/2815] Add ruleset-specific legacy skin providers This moves implementation of osu! skinnables to OsuLegacySkin. --- osu.Game.Rulesets.Osu/OsuLegacySkin.cs | 228 +++++++++++++++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 3 + osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 3 + osu.Game/Screens/Play/Player.cs | 41 ++-- osu.Game/Skinning/LegacySkin.cs | 137 -------------- 6 files changed, 261 insertions(+), 153 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/OsuLegacySkin.cs diff --git a/osu.Game.Rulesets.Osu/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/OsuLegacySkin.cs new file mode 100644 index 0000000000..0085d64353 --- /dev/null +++ b/osu.Game.Rulesets.Osu/OsuLegacySkin.cs @@ -0,0 +1,228 @@ +// 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.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Animations; +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; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu +{ + public class OsuLegacySkin : ISkin + { + private readonly ISkin source; + + private Lazy configuration; + + private Lazy hasHitCircle; + + /// + /// On osu-stable, hitcircles have 5 pixels of transparent padding on each side to allow for shadows etc. + /// Their hittable area is 128px, but the actual circle portion is 118px. + /// We must account for some gameplay elements such as slider bodies, where this padding is not present. + /// + private const float legacy_circle_radius = 64 - 5; + + public OsuLegacySkin(ISkinSource source) + { + this.source = source; + + source.SourceChanged += sourceChanged; + sourceChanged(); + } + + private void sourceChanged() + { + configuration = new Lazy(() => + { + var config = new SkinConfiguration(); + if (hasHitCircle.Value) + config.SliderPathRadius = legacy_circle_radius; + return config; + }); + + hasHitCircle = new Lazy(() => source.GetTexture("hitcircle") != null); + } + + private const double default_frame_time = 1000 / 60d; + + public Drawable GetDrawableComponent(string componentName) + { + switch (componentName) + { + case "Play/osu/sliderball": + var sliderBallContent = getAnimation("sliderb", true, true, ""); + + if (sliderBallContent != null) + { + var size = sliderBallContent.Size; + + sliderBallContent.RelativeSizeAxes = Axes.Both; + sliderBallContent.Size = Vector2.One; + + return new LegacySliderBall(sliderBallContent) + { + Size = size + }; + } + + return null; + + case "Play/osu/hitcircle": + if (hasHitCircle.Value) + return new LegacyMainCirclePiece(); + + return null; + } + + return null; + } + + private Drawable getAnimation(string componentName, bool animatable, bool looping, string animationSeparator = "-") + { + Texture texture; + + Texture getFrameTexture(int frame) => source.GetTexture($"{componentName}{animationSeparator}{frame}"); + + TextureAnimation animation = null; + + if (animatable) + { + for (int i = 0;; i++) + { + if ((texture = getFrameTexture(i)) == null) + break; + + if (animation == null) + animation = new TextureAnimation + { + DefaultFrameLength = default_frame_time, + Repeat = looping + }; + + animation.AddFrame(texture); + } + } + + if (animation != null) + return animation; + + if ((texture = source.GetTexture(componentName)) != null) + return new Sprite { Texture = texture }; + + return null; + } + + public Texture GetTexture(string componentName) => null; + + public SampleChannel GetSample(string sampleName) => null; + + public TValue GetValue(Func query) where TConfiguration : SkinConfiguration + => configuration.Value is TConfiguration conf ? query.Invoke(conf) : default; + + public class LegacySliderBall : CompositeDrawable + { + private readonly Drawable animationContent; + + public LegacySliderBall(Drawable animationContent) + { + this.animationContent = animationContent; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin, DrawableHitObject drawableObject) + { + animationContent.Colour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? Color4.White; + + InternalChildren = new[] + { + new Sprite + { + Texture = skin.GetTexture("sliderb-nd"), + Colour = new Color4(5, 5, 5, 255), + }, + animationContent, + new Sprite + { + Texture = skin.GetTexture("sliderb-spec"), + Blending = BlendingParameters.Additive, + }, + }; + } + } + + public class LegacyMainCirclePiece : CompositeDrawable + { + public LegacyMainCirclePiece() + { + Size = new Vector2(128); + } + + private readonly IBindable state = new Bindable(); + + private readonly Bindable accentColour = new Bindable(); + + [BackgroundDependencyLoader] + private void load(DrawableHitObject drawableObject, ISkinSource skin) + { + Sprite hitCircleSprite; + + InternalChildren = new Drawable[] + { + hitCircleSprite = new Sprite + { + Texture = skin.GetTexture("hitcircle"), + Colour = drawableObject.AccentColour.Value, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new SkinnableSpriteText("Play/osu/number-text", _ => new OsuSpriteText + { + Font = OsuFont.Numeric.With(size: 40), + UseFullGlyphHeight = false, + }, confineMode: ConfineMode.NoScaling) + { + Text = (((IHasComboInformation)drawableObject.HitObject).IndexInCurrentCombo + 1).ToString() + }, + new Sprite + { + Texture = skin.GetTexture("hitcircleoverlay"), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }; + + state.BindTo(drawableObject.State); + state.BindValueChanged(updateState, true); + + accentColour.BindTo(drawableObject.AccentColour); + accentColour.BindValueChanged(colour => hitCircleSprite.Colour = colour.NewValue, true); + } + + private void updateState(ValueChangedEvent state) + { + const double legacy_fade_duration = 240; + + switch (state.NewValue) + { + case ArmedState.Hit: + this.FadeOut(legacy_fade_duration, Easing.Out); + this.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); + break; + } + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index d50d4f401c..4211ae253c 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -24,6 +24,7 @@ using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Difficulty; using osu.Game.Scoring; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu { @@ -163,6 +164,8 @@ namespace osu.Game.Rulesets.Osu public override RulesetSettingsSubsection CreateSettings() => new OsuSettingsSubsection(this); + public override ISkin CreateLegacySkinProvider(ISkinSource source) => new OsuLegacySkin(source); + public override int? LegacyID => 0; public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame(); diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 9037faf606..178183bd22 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.UI }, // Todo: This should not exist, but currently helps to reduce LOH allocations due to unbinding skin source events on judgement disposal // Todo: Remove when hitobjects are properly pooled - new LocalSkinOverrideContainer(null) + new SkinProvidingContainer(null) { RelativeSizeAxes = Axes.Both, Child = HitObjectContainer, diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 42b1322cae..2fbbd74d7a 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -18,6 +18,7 @@ using osu.Game.Configuration; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; using osu.Game.Scoring; +using osu.Game.Skinning; namespace osu.Game.Rulesets { @@ -44,6 +45,8 @@ namespace osu.Game.Rulesets public ModAutoplay GetAutoplayMod() => GetAllMods().OfType().First(); + public virtual ISkin CreateLegacySkinProvider(ISkinSource source) => null; + protected Ruleset(RulesetInfo rulesetInfo = null) { RulesetInfo = rulesetInfo ?? createRulesetInfo(); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e7398be176..deed17a049 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -60,7 +60,9 @@ namespace osu.Game.Screens.Play [Resolved] private ScoreManager scoreManager { get; set; } - private RulesetInfo ruleset; + private RulesetInfo rulesetInfo; + + private Ruleset ruleset; private IAPIProvider api; @@ -121,20 +123,29 @@ namespace osu.Game.Screens.Play InternalChild = GameplayClockContainer = new GameplayClockContainer(working, Mods.Value, DrawableRuleset.GameplayStartTime); + SkinProvidingContainer skinProvidingContainer = new BeatmapSkinProvidingContainer(working.Skin) + { + RelativeSizeAxes = Axes.Both, + }; + GameplayClockContainer.Children = new[] { DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }, new ScalingContainer(ScalingMode.Gameplay) { - Child = new LocalSkinOverrideContainer(working.Skin) - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + Child = skinProvidingContainer.WithChild( + // the skinProvidingContainer is used as the fallback source here to allow the ruleset-specific skin implementation + // full access to all skin sources. + new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(skinProvidingContainer)) { - DrawableRuleset, - new ComboEffects(ScoreProcessor) + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + DrawableRuleset, + new ComboEffects(ScoreProcessor) + } } - } + ) }, breakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) { @@ -222,20 +233,20 @@ namespace osu.Game.Screens.Play if (beatmap == null) throw new InvalidOperationException("Beatmap was not loaded"); - ruleset = Ruleset.Value ?? beatmap.BeatmapInfo.Ruleset; - var rulesetInstance = ruleset.CreateInstance(); + rulesetInfo = Ruleset.Value ?? beatmap.BeatmapInfo.Ruleset; + ruleset = rulesetInfo.CreateInstance(); try { - DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(working, Mods.Value); + DrawableRuleset = ruleset.CreateDrawableRulesetWith(working, Mods.Value); } catch (BeatmapInvalidForRulesetException) { // we may fail to create a DrawableRuleset if the beatmap cannot be loaded with the user's preferred ruleset // let's try again forcing the beatmap's ruleset. - ruleset = beatmap.BeatmapInfo.Ruleset; - rulesetInstance = ruleset.CreateInstance(); - DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(Beatmap.Value, Mods.Value); + rulesetInfo = beatmap.BeatmapInfo.Ruleset; + ruleset = rulesetInfo.CreateInstance(); + DrawableRuleset = ruleset.CreateDrawableRulesetWith(Beatmap.Value, Mods.Value); } if (!DrawableRuleset.Objects.Any()) @@ -313,7 +324,7 @@ namespace osu.Game.Screens.Play var score = DrawableRuleset.ReplayScore?.ScoreInfo ?? new ScoreInfo { Beatmap = Beatmap.Value.BeatmapInfo, - Ruleset = ruleset, + Ruleset = rulesetInfo, Mods = Mods.Value.ToArray(), User = api.LocalUser.Value, }; diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 48310cf027..785daba878 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; @@ -20,8 +19,6 @@ using osu.Framework.Text; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.UI; using osuTK; using osuTK.Graphics; @@ -34,13 +31,6 @@ namespace osu.Game.Skinning protected IResourceStore Samples; - /// - /// On osu-stable, hitcircles have 5 pixels of transparent padding on each side to allow for shadows etc. - /// Their hittable area is 128px, but the actual circle portion is 118px. - /// We must account for some gameplay elements such as slider bodies, where this padding is not present. - /// - private const float legacy_circle_radius = 64 - 5; - public LegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager) : this(skin, new LegacySkinResourceStore(skin, storage), audioManager, "skin.ini") { @@ -48,8 +38,6 @@ namespace osu.Game.Skinning if (!Configuration.CustomColours.ContainsKey("SliderBall")) Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255); } - private readonly bool hasHitCircle; - protected LegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager, string filename) : base(skin) { @@ -62,14 +50,6 @@ namespace osu.Game.Skinning Samples = audioManager.GetSampleStore(storage); Textures = new TextureStore(new TextureLoaderStore(storage)); - - using (var testStream = storage.GetStream("hitcircle@2x") ?? storage.GetStream("hitcircle")) - hasHitCircle |= testStream != null; - - if (hasHitCircle) - { - Configuration.SliderPathRadius = legacy_circle_radius; - } } protected override void Dispose(bool isDisposing) @@ -94,30 +74,6 @@ namespace osu.Game.Skinning return null; - case "Play/osu/sliderball": - var sliderBallContent = getAnimation("sliderb", true, true, ""); - - if (sliderBallContent != null) - { - var size = sliderBallContent.Size; - - sliderBallContent.RelativeSizeAxes = Axes.Both; - sliderBallContent.Size = Vector2.One; - - return new LegacySliderBall(sliderBallContent) - { - Size = size - }; - } - - return null; - - case "Play/osu/hitcircle": - if (hasHitCircle) - return new LegacyMainCirclePiece(); - - return null; - case "Play/osu/sliderfollowcircle": animatable = true; break; @@ -355,99 +311,6 @@ namespace osu.Game.Skinning } } - public class LegacySliderBall : CompositeDrawable - { - private readonly Drawable animationContent; - - public LegacySliderBall(Drawable animationContent) - { - this.animationContent = animationContent; - } - - [BackgroundDependencyLoader] - private void load(ISkinSource skin, DrawableHitObject drawableObject) - { - animationContent.Colour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? Color4.White; - - InternalChildren = new[] - { - new Sprite - { - Texture = skin.GetTexture("sliderb-nd"), - Colour = new Color4(5, 5, 5, 255), - }, - animationContent, - new Sprite - { - Texture = skin.GetTexture("sliderb-spec"), - Blending = BlendingParameters.Additive, - }, - }; - } - } - - public class LegacyMainCirclePiece : CompositeDrawable - { - public LegacyMainCirclePiece() - { - Size = new Vector2(128); - } - - private readonly IBindable state = new Bindable(); - - private readonly Bindable accentColour = new Bindable(); - - [BackgroundDependencyLoader] - private void load(DrawableHitObject drawableObject, ISkinSource skin) - { - Sprite hitCircleSprite; - - InternalChildren = new Drawable[] - { - hitCircleSprite = new Sprite - { - Texture = skin.GetTexture("hitcircle"), - Colour = drawableObject.AccentColour.Value, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new SkinnableSpriteText("Play/osu/number-text", _ => new OsuSpriteText - { - Font = OsuFont.Numeric.With(size: 40), - UseFullGlyphHeight = false, - }, confineMode: ConfineMode.NoScaling) - { - Text = (((IHasComboInformation)drawableObject.HitObject).IndexInCurrentCombo + 1).ToString() - }, - new Sprite - { - Texture = skin.GetTexture("hitcircleoverlay"), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - } - }; - - state.BindTo(drawableObject.State); - state.BindValueChanged(updateState, true); - - accentColour.BindTo(drawableObject.AccentColour); - accentColour.BindValueChanged(colour => hitCircleSprite.Colour = colour.NewValue, true); - } - - private void updateState(ValueChangedEvent state) - { - const double legacy_fade_duration = 240; - - switch (state.NewValue) - { - case ArmedState.Hit: - this.FadeOut(legacy_fade_duration, Easing.Out); - this.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); - break; - } - } - } - /// /// A sprite which is displayed within the playfield, but historically was not considered part of the playfield. /// Performs scale adjustment to undo the scale applied by (osu! ruleset specifically). From 6e3a63dae8a9dbc59d47993d884b97b2414de7df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Aug 2019 12:31:51 +0900 Subject: [PATCH 0866/2815] Update tests --- .../SkinnableTestScene.cs | 20 +++++++++++++++---- .../Gameplay/TestSceneSkinnableDrawable.cs | 8 ++++---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs index 02716dc1d5..c3a64f8b54 100644 --- a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs +++ b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs @@ -37,10 +37,22 @@ namespace osu.Game.Rulesets.Osu.Tests public void SetContents(Func creationFunction) { - Cell(0).Child = new LocalSkinOverrideContainer(null) { RelativeSizeAxes = Axes.Both }.WithChild(creationFunction()); - Cell(1).Child = new LocalSkinOverrideContainer(metricsSkin) { RelativeSizeAxes = Axes.Both }.WithChild(creationFunction()); - Cell(2).Child = new LocalSkinOverrideContainer(defaultSkin) { RelativeSizeAxes = Axes.Both }.WithChild(creationFunction()); - Cell(3).Child = new LocalSkinOverrideContainer(specialSkin) { RelativeSizeAxes = Axes.Both }.WithChild(creationFunction()); + Cell(0).Child = createProvider(null, creationFunction); + Cell(1).Child = createProvider(metricsSkin, creationFunction); + Cell(2).Child = createProvider(defaultSkin, creationFunction); + Cell(3).Child = createProvider(specialSkin, creationFunction); + } + + private Drawable createProvider(Skin skin, Func creationFunction) + { + var mainProvider = new SkinProvidingContainer(skin) { RelativeSizeAxes = Axes.Both }; + + return mainProvider + .WithChild(new SkinProvidingContainer(Ruleset.Value.CreateInstance().CreateLegacySkinProvider(mainProvider)) + { + RelativeSizeAxes = Axes.Both, + Child = creationFunction() + }); } private class TestLegacySkin : LegacySkin diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index 0b5978e3eb..b18eca0600 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("setup layout larger source", () => { - Child = new LocalSkinOverrideContainer(new SizedSource(50)) + Child = new SkinProvidingContainer(new SizedSource(50)) { RelativeSizeAxes = Axes.Both, Child = fill = new FillFlowContainer @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("setup layout larger source", () => { - Child = new LocalSkinOverrideContainer(new SizedSource(30)) + Child = new SkinProvidingContainer(new SizedSource(30)) { RelativeSizeAxes = Axes.Both, Child = fill = new FillFlowContainer @@ -95,7 +95,7 @@ namespace osu.Game.Tests.Visual.Gameplay Child = new SkinSourceContainer { RelativeSizeAxes = Axes.Both, - Child = new LocalSkinOverrideContainer(secondarySource) + Child = new SkinProvidingContainer(secondarySource) { RelativeSizeAxes = Axes.Both, Child = consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true) @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Gameplay Child = new SkinSourceContainer { RelativeSizeAxes = Axes.Both, - Child = target = new LocalSkinOverrideContainer(secondarySource) + Child = target = new SkinProvidingContainer(secondarySource) { RelativeSizeAxes = Axes.Both, } From d99c60adc7cf97d11de29631ec46c1c652a1b62e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Aug 2019 12:51:13 +0900 Subject: [PATCH 0867/2815] Provide a way to scale beat lengths relative to each other --- .../Rulesets/Timing/MultiplierControlPoint.cs | 8 +++- .../UI/Scrolling/DrawableScrollingRuleset.cs | 38 +++++++++++++++---- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs index 9bab065d1e..ffa35c24cd 100644 --- a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs +++ b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs @@ -20,7 +20,13 @@ namespace osu.Game.Rulesets.Timing /// /// The aggregate multiplier which this provides. /// - public double Multiplier => Velocity * DifficultyPoint.SpeedMultiplier * 1000 / TimingPoint.BeatLength; + public double Multiplier => Velocity * DifficultyPoint.SpeedMultiplier * BaseBeatLength / TimingPoint.BeatLength; + + /// + /// The base beat length to scale the provided multiplier relative to. + /// + /// For a of 1000, a with a beat length of 500 will increase the multiplier by 2. + public double BaseBeatLength = 1000; /// /// The velocity multiplier. diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 42ec0b79b9..385f824ef5 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -69,6 +69,11 @@ namespace osu.Game.Rulesets.UI.Scrolling /// protected virtual bool UserScrollSpeedAdjustment => true; + /// + /// Whether beat lengths should scale relative to the most common beat length in the . + /// + protected virtual bool RelativeScaleBeatLengths => false; + /// /// Provides the default s that adjust the scrolling rate of s /// inside this . @@ -107,16 +112,35 @@ namespace osu.Game.Rulesets.UI.Scrolling [BackgroundDependencyLoader] private void load() { - // Calculate default multiplier control points + double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; + double baseBeatLength = 1000; + + if (RelativeScaleBeatLengths) + { + IReadOnlyList timingPoints = Beatmap.ControlPointInfo.TimingPoints; + double maxDuration = 0; + + for (int i = 0; i < timingPoints.Count; i++) + { + double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time : lastObjectTime; + double duration = endTime - timingPoints[i].Time; + + if (duration > maxDuration) + { + maxDuration = duration; + baseBeatLength = timingPoints[i].BeatLength; + } + } + } + + // Merge sequences of timing and difficulty control points to create the aggregate "multiplier" control point var lastTimingPoint = new TimingControlPoint(); var lastDifficultyPoint = new DifficultyControlPoint(); - - // Merge timing + difficulty points var allPoints = new SortedList(Comparer.Default); allPoints.AddRange(Beatmap.ControlPointInfo.TimingPoints); allPoints.AddRange(Beatmap.ControlPointInfo.DifficultyPoints); - // Generate the timing points, making non-timing changes use the previous timing change + // Generate the timing points, making non-timing changes use the previous timing change and vice-versa var timingChanges = allPoints.Select(c => { var timingPoint = c as TimingControlPoint; @@ -131,14 +155,13 @@ namespace osu.Game.Rulesets.UI.Scrolling return new MultiplierControlPoint(c.Time) { Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier, + BaseBeatLength = baseBeatLength, TimingPoint = lastTimingPoint, DifficultyPoint = lastDifficultyPoint }; }); - double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; - - // Perform some post processing of the timing changes + // Trim unwanted sequences of timing changes timingChanges = timingChanges // Collapse sections after the last hit object .Where(s => s.StartTime <= lastObjectTime) @@ -147,7 +170,6 @@ namespace osu.Game.Rulesets.UI.Scrolling controlPoints.AddRange(timingChanges); - // If we have no control points, add a default one if (controlPoints.Count == 0) controlPoints.Add(new MultiplierControlPoint { Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier }); } From e30ae57ea66325491eb1f94c7314d6270da5d3c8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Aug 2019 12:51:23 +0900 Subject: [PATCH 0868/2815] Scale mania beat lengths relative to each other --- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index c8aeda8fe4..0718de2c7d 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -36,6 +36,8 @@ namespace osu.Game.Rulesets.Mania.UI public IEnumerable BarLines; + protected override bool RelativeScaleBeatLengths => true; + protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config; private readonly Bindable configDirection = new Bindable(); From 7b82121b8503f81de85013e9c82435d4b584d34c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Aug 2019 13:03:00 +0900 Subject: [PATCH 0869/2815] Add comment about lazy usage --- osu.Game.Rulesets.Osu/OsuLegacySkin.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/OsuLegacySkin.cs index 0085d64353..988eb6a439 100644 --- a/osu.Game.Rulesets.Osu/OsuLegacySkin.cs +++ b/osu.Game.Rulesets.Osu/OsuLegacySkin.cs @@ -45,6 +45,7 @@ namespace osu.Game.Rulesets.Osu private void sourceChanged() { + // these need to be lazy in order to ensure they aren't called before the dependencies have been loaded into our source. configuration = new Lazy(() => { var config = new SkinConfiguration(); From 289bd8e6b0c9367ae4cb9bbb15a4f3be7c424213 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Aug 2019 14:25:35 +0900 Subject: [PATCH 0870/2815] Don't return DefaultSkin on beatmap skin parsing failure --- osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 5657b8fb8a..2d8a0b1249 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -138,19 +138,15 @@ namespace osu.Game.Beatmaps protected override Skin GetSkin() { - Skin skin; - try { - skin = new LegacyBeatmapSkin(BeatmapInfo, store, AudioManager); + return new LegacyBeatmapSkin(BeatmapInfo, store, AudioManager); } catch (Exception e) { Logger.Error(e, "Skin failed to load"); - skin = new DefaultSkin(); + return null; } - - return skin; } } } From db987c6077153102848ffe9f16c79de8480c39e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Aug 2019 14:24:48 +0900 Subject: [PATCH 0871/2815] Move SliderBall colour logic to OsuLegacySkinProvider --- osu.Game.Rulesets.Osu/OsuLegacySkin.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Osu/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/OsuLegacySkin.cs index 988eb6a439..1957156918 100644 --- a/osu.Game.Rulesets.Osu/OsuLegacySkin.cs +++ b/osu.Game.Rulesets.Osu/OsuLegacySkin.cs @@ -51,6 +51,12 @@ namespace osu.Game.Rulesets.Osu var config = new SkinConfiguration(); if (hasHitCircle.Value) config.SliderPathRadius = legacy_circle_radius; + + // defaults should only be applied for non-beatmap skins (which are parsed via this constructor). + config.CustomColours["SliderBall"] = + source.GetValue(s => s.CustomColours.TryGetValue("SliderBall", out var val) ? val : (Color4?)null) + ?? new Color4(2, 170, 255, 255); + return config; }); From 1cfe2b7de8388ade173269519e7947400ef18778 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Aug 2019 16:25:23 +0900 Subject: [PATCH 0872/2815] Fix timing points beyond the end time potentially becoming dominant --- osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 385f824ef5..65a22b10b7 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -122,6 +122,9 @@ namespace osu.Game.Rulesets.UI.Scrolling for (int i = 0; i < timingPoints.Count; i++) { + if (timingPoints[i].Time > lastObjectTime) + break; + double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time : lastObjectTime; double duration = endTime - timingPoints[i].Time; From fb8b5ee1060dfd0ec8467ed15ddcc4c84806603f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Aug 2019 16:31:46 +0900 Subject: [PATCH 0873/2815] Add test --- .../TestSceneDrawableScrollingRuleset.cs | 305 ++++++++++++++++++ 1 file changed, 305 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs new file mode 100644 index 0000000000..ee11fc0d06 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -0,0 +1,305 @@ +// 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.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Framework.MathUtils; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Configuration; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneDrawableScrollingRuleset : OsuTestScene + { + /// + /// The amount of time visible by the "view window" of the playfield. + /// All hitobjects added through are spaced apart by this value, such that for a beat length of 1000, + /// there will be at most 2 hitobjects visible in the "view window". + /// + private const double time_range = 1000; + + private readonly ManualClock testClock = new ManualClock(); + private TestDrawableScrollingRuleset drawableRuleset; + + [SetUp] + public void Setup() => Schedule(() => testClock.CurrentTime = 0); + + [Test] + public void TestRelativeBeatLengthScaleSingleTimingPoint() + { + var beatmap = createBeatmap(new TimingControlPoint { BeatLength = time_range / 2 }); + + createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true); + + assertPosition(0, 0f); + + // The single timing point is 1x speed relative to itself, such that the hitobject occurring time_range milliseconds later should appear + // at the bottom of the view window regardless of the timing point's beat length + assertPosition(1, 1f); + } + + [Test] + public void TestRelativeBeatLengthScaleTimingPointBeyondEndDoesNotBecomeDominant() + { + var beatmap = createBeatmap( + new TimingControlPoint { BeatLength = time_range / 2 }, + new TimingControlPoint { Time = 12000, BeatLength = time_range }, + new TimingControlPoint { Time = 100000, BeatLength = time_range }); + + createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true); + + assertPosition(0, 0f); + assertPosition(1, 1f); + } + + [Test] + public void TestRelativeBeatLengthScaleFromSecondTimingPoint() + { + var beatmap = createBeatmap( + new TimingControlPoint { BeatLength = time_range }, + new TimingControlPoint { Time = 3 * time_range, BeatLength = time_range / 2 }); + + createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true); + + // The first timing point should have a relative velocity of 2 + assertPosition(0, 0f); + assertPosition(1, 0.5f); + assertPosition(2, 1f); + + // Move to the second timing point + setTime(3 * time_range); + assertPosition(3, 0f); + + // As above, this is the timing point that is 1x speed relative to itself, so the hitobject occurring time_range milliseconds later should be at the bottom of the view window + assertPosition(4, 1f); + } + + [Test] + public void TestNonRelativeScale() + { + var beatmap = createBeatmap( + new TimingControlPoint { BeatLength = time_range }, + new TimingControlPoint { Time = 3 * time_range, BeatLength = time_range / 2 }); + + createTest(beatmap); + + assertPosition(0, 0f); + assertPosition(1, 1); + + // Move to the second timing point + setTime(3 * time_range); + assertPosition(3, 0f); + + // For a beat length of 500, the view window of this timing point is elongated 2x (1000 / 500), such that the second hitobject is two TimeRanges away (offscreen) + // To bring it on-screen, half TimeRange is added to the current time, bringing the second half of the view window into view, and the hitobject should appear at the bottom + setTime(3 * time_range + time_range / 2); + assertPosition(4, 1f); + } + + private void assertPosition(int index, float relativeY) => AddAssert($"hitobject {index} at {relativeY}", + () => Precision.AlmostEquals(drawableRuleset.Playfield.AllHitObjects.ElementAt(index).DrawPosition.Y, drawableRuleset.Playfield.HitObjectContainer.DrawHeight * relativeY)); + + private void setTime(double time) + { + AddStep($"set time = {time}", () => testClock.CurrentTime = time); + } + + /// + /// Creates an , containing 10 hitobjects and user-provided timing points. + /// The hitobjects are spaced milliseconds apart. + /// + /// The timing points to add to the beatmap. + /// The . + private IBeatmap createBeatmap(params TimingControlPoint[] timingControlPoints) + { + var beatmap = new Beatmap { BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo } }; + + beatmap.ControlPointInfo.TimingPoints.AddRange(timingControlPoints); + + for (int i = 0; i < 10; i++) + beatmap.HitObjects.Add(new HitObject { StartTime = i * time_range }); + + return beatmap; + } + + private void createTest(IBeatmap beatmap, Action overrideAction = null) => AddStep("create test", () => + { + var ruleset = new TestScrollingRuleset(); + + drawableRuleset = (TestDrawableScrollingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap), Array.Empty()); + drawableRuleset.FrameStablePlayback = false; + + overrideAction?.Invoke(drawableRuleset); + + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Height = 0.75f, + Width = 400, + Masking = true, + Clock = new FramedClock(testClock), + Child = drawableRuleset + }; + }); + + #region Ruleset + + private class TestScrollingRuleset : Ruleset + { + public TestScrollingRuleset(RulesetInfo rulesetInfo = null) + : base(rulesetInfo) + { + } + + public override IEnumerable GetModsFor(ModType type) => throw new NotImplementedException(); + + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new TestDrawableScrollingRuleset(this, beatmap, mods); + + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TestBeatmapConverter(beatmap); + + public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException(); + + public override string Description { get; } = string.Empty; + + public override string ShortName { get; } = string.Empty; + } + + private class TestDrawableScrollingRuleset : DrawableScrollingRuleset + { + public bool RelativeScaleBeatLengthsOverride { get; set; } + + protected override bool RelativeScaleBeatLengths => RelativeScaleBeatLengthsOverride; + + protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping; + + public TestDrawableScrollingRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + : base(ruleset, beatmap, mods) + { + TimeRange.Value = time_range; + } + + public override DrawableHitObject CreateDrawableRepresentation(TestHitObject h) => new DrawableTestHitObject(h); + + protected override PassThroughInputManager CreateInputManager() => new PassThroughInputManager(); + + protected override Playfield CreatePlayfield() => new TestPlayfield(); + } + + private class TestPlayfield : ScrollingPlayfield + { + public TestPlayfield() + { + AddInternal(new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.2f, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 150 }, + Children = new Drawable[] + { + new Box + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = 2, + Colour = Color4.Green + }, + HitObjectContainer + } + } + } + }); + } + } + + private class TestBeatmapConverter : BeatmapConverter + { + public TestBeatmapConverter(IBeatmap beatmap) + : base(beatmap) + { + } + + protected override IEnumerable ValidConversionTypes => new[] { typeof(HitObject) }; + + protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap) + { + yield return new TestHitObject + { + StartTime = original.StartTime, + EndTime = (original as IHasEndTime)?.EndTime ?? (original.StartTime + 100) + }; + } + } + + #endregion + + #region HitObject + + private class TestHitObject : HitObject, IHasEndTime + { + public double EndTime { get; set; } + + public double Duration => EndTime - StartTime; + } + + private class DrawableTestHitObject : DrawableHitObject + { + public DrawableTestHitObject(TestHitObject hitObject) + : base(hitObject) + { + Anchor = Anchor.TopCentre; + Origin = Anchor.TopCentre; + + Size = new Vector2(100, 25); + + AddRangeInternal(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.LightPink + }, + new Box + { + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = 2, + Colour = Color4.Red + } + }); + } + } + + #endregion + } +} From 6596d7fc463d3a73cbf5605d1074135db9c230f8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Aug 2019 16:33:24 +0900 Subject: [PATCH 0874/2815] Add nullref safety to FrameStablePlayback boolean --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index eb14bd1f24..ccfd89adca 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -62,13 +62,20 @@ namespace osu.Game.Rulesets.UI public override GameplayClock FrameStableClock => frameStabilityContainer.GameplayClock; + private bool frameStablePlayback = true; + /// /// Whether to enable frame-stable playback. /// internal bool FrameStablePlayback { - get => frameStabilityContainer.FrameStablePlayback; - set => frameStabilityContainer.FrameStablePlayback = value; + get => frameStablePlayback; + set + { + frameStablePlayback = false; + if (frameStabilityContainer != null) + frameStabilityContainer.FrameStablePlayback = value; + } } /// @@ -156,6 +163,7 @@ namespace osu.Game.Rulesets.UI { frameStabilityContainer = new FrameStabilityContainer(GameplayStartTime) { + FrameStablePlayback = FrameStablePlayback, Child = KeyBindingInputManager .WithChild(CreatePlayfieldAdjustmentContainer() .WithChild(Playfield) From 6dfe95db74b6d12d1ed5b9567a0652eb6303d589 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Aug 2019 16:34:12 +0900 Subject: [PATCH 0875/2815] Allow RulesetConfigCache to return null configs --- osu.Game/Rulesets/RulesetConfigCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/RulesetConfigCache.cs b/osu.Game/Rulesets/RulesetConfigCache.cs index 9a5a4d4acd..8c9e3c94e2 100644 --- a/osu.Game/Rulesets/RulesetConfigCache.cs +++ b/osu.Game/Rulesets/RulesetConfigCache.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets public IRulesetConfigManager GetConfigFor(Ruleset ruleset) { if (ruleset.RulesetInfo.ID == null) - throw new InvalidOperationException("The provided ruleset doesn't have a valid id."); + return null; return configCache.GetOrAdd(ruleset.RulesetInfo.ID.Value, _ => ruleset.CreateConfig(settingsStore)); } From fb1cd9e5e7a9050a8b2af1882ecf342f4928baf8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Aug 2019 16:47:23 +0900 Subject: [PATCH 0876/2815] Add a sane default lifetime end for scrolling hitobjects --- .../UI/Scrolling/ScrollingHitObjectContainer.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 19247d8a37..14a4869a98 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -12,8 +12,13 @@ namespace osu.Game.Rulesets.UI.Scrolling { public class ScrollingHitObjectContainer : HitObjectContainer { - private readonly IBindable timeRange = new BindableDouble(); + /// + /// A multiplier applied to the length of the scrolling area to determine a safe default lifetime end for hitobjects. + /// This is only used to limit the lifetime end within reason, as proper lifetime management should be implemented on hitobjects themselves. + /// + private const float safe_lifetime_end_multiplier = 2; + private readonly IBindable timeRange = new BindableDouble(); private readonly IBindable direction = new Bindable(); [Resolved] @@ -92,6 +97,8 @@ namespace osu.Game.Rulesets.UI.Scrolling if (hitObject.HitObject is IHasEndTime endTime) { + hitObject.LifetimeEnd = scrollingInfo.Algorithm.TimeAt(scrollLength * safe_lifetime_end_multiplier, endTime.EndTime, timeRange.Value, scrollLength); + switch (direction.Value) { case ScrollingDirection.Up: @@ -105,6 +112,8 @@ namespace osu.Game.Rulesets.UI.Scrolling break; } } + else + hitObject.LifetimeEnd = scrollingInfo.Algorithm.TimeAt(scrollLength * safe_lifetime_end_multiplier, hitObject.HitObject.StartTime, timeRange.Value, scrollLength); foreach (var obj in hitObject.NestedHitObjects) { From d4a296f9116fc9ce35695e9f2c89bfebd3cd8041 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Aug 2019 17:45:12 +0900 Subject: [PATCH 0877/2815] Slight refactoring --- .../Replays/ManiaAutoGenerator.cs | 8 ++++---- .../Replays/TaikoAutoGenerator.cs | 14 +++++++++----- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index fd0a876775..7b8bbc2095 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -98,12 +98,12 @@ namespace osu.Game.Rulesets.Mania.Replays protected override HitObject GetNextObject(int currentIndex) { - int desiredColumn = Beatmap.HitObjects[currentIndex++].Column; + int desiredColumn = Beatmap.HitObjects[currentIndex].Column; - for (; currentIndex < Beatmap.HitObjects.Count; currentIndex++) + for (int i = currentIndex + 1; i < Beatmap.HitObjects.Count; i++) { - if (Beatmap.HitObjects[currentIndex].Column == desiredColumn) - return Beatmap.HitObjects[currentIndex]; + if (Beatmap.HitObjects[i].Column == desiredColumn) + return Beatmap.HitObjects[i]; } return null; diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index 6720d8b8bf..299679b2c1 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -137,14 +137,18 @@ namespace osu.Game.Rulesets.Taiko.Replays protected override HitObject GetNextObject(int currentIndex) { - Type desiredType = Beatmap.HitObjects[currentIndex++].GetType(); + Type desiredType = Beatmap.HitObjects[currentIndex].GetType(); - for (; currentIndex < Beatmap.HitObjects.Count; currentIndex++) + for (int i = currentIndex + 1; i < Beatmap.HitObjects.Count; i++) { - var currentObj = Beatmap.HitObjects[currentIndex]; + var currentObj = Beatmap.HitObjects[i]; + if (currentObj.GetType() == desiredType || - currentObj is DrumRoll || currentObj is Swell) // Unpress all keys before DrumRoll or Swell - return Beatmap.HitObjects[currentIndex]; + // Un-press all keys before a DrumRoll or Swell + currentObj is DrumRoll || currentObj is Swell) + { + return Beatmap.HitObjects[i]; + } } return null; From 62641c149d96d33ec56542764b3d2a8ddd7258cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Aug 2019 18:18:14 +0900 Subject: [PATCH 0878/2815] Fx component lookups being incorrect for non-databased legacy skins --- .../metrics-skin/approachcircle@2x.png | Bin 0 -> 13816 bytes osu.Game/Skinning/LegacySkin.cs | 10 ++++++---- 2 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/approachcircle@2x.png diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/approachcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/approachcircle@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..72ef665478b758c364197aa79f238230e229b3c4 GIT binary patch literal 13816 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 X;|*6ltxe$d|7PiG>1$T1+eQB$HM;D+ literal 0 HcmV?d00001 diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 48310cf027..7bffe5d321 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -155,6 +155,11 @@ namespace osu.Game.Skinning // Spacing value was reverse-engineered from the ratio of the rendered sprite size in the visual inspector vs the actual texture size Spacing = new Vector2(-Configuration.HitCircleOverlap * 0.89f, 0) }; + + default: + string lastPiece = componentName.Split('/').Last(); + componentName = componentName.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece; + break; } return getAnimation(componentName, animatable, looping); @@ -226,11 +231,8 @@ namespace osu.Game.Skinning { bool hasExtension = filename.Contains('.'); - string lastPiece = filename.Split('/').Last(); - var legacyName = filename.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece; - var file = source.Files.Find(f => - string.Equals(hasExtension ? f.Filename : Path.ChangeExtension(f.Filename, null), legacyName, StringComparison.InvariantCultureIgnoreCase)); + string.Equals(hasExtension ? f.Filename : Path.ChangeExtension(f.Filename, null), filename, StringComparison.InvariantCultureIgnoreCase)); return file?.FileInfo.StoragePath; } From 0422c19c2fc524d03f1d159f780f01ae6457365a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Aug 2019 19:06:23 +0900 Subject: [PATCH 0879/2815] Group lifetime setters together --- .../UI/Scrolling/ScrollingHitObjectContainer.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 14a4869a98..1df8c8218f 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -93,27 +93,28 @@ namespace osu.Game.Rulesets.UI.Scrolling private void computeInitialStateRecursive(DrawableHitObject hitObject) { - hitObject.LifetimeStart = scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, timeRange.Value); + double endTime = hitObject.HitObject.StartTime; - if (hitObject.HitObject is IHasEndTime endTime) + if (hitObject.HitObject is IHasEndTime e) { - hitObject.LifetimeEnd = scrollingInfo.Algorithm.TimeAt(scrollLength * safe_lifetime_end_multiplier, endTime.EndTime, timeRange.Value, scrollLength); + endTime = e.EndTime; switch (direction.Value) { case ScrollingDirection.Up: case ScrollingDirection.Down: - hitObject.Height = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, endTime.EndTime, timeRange.Value, scrollLength); + hitObject.Height = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, endTime, timeRange.Value, scrollLength); break; case ScrollingDirection.Left: case ScrollingDirection.Right: - hitObject.Width = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, endTime.EndTime, timeRange.Value, scrollLength); + hitObject.Width = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, endTime, timeRange.Value, scrollLength); break; } } - else - hitObject.LifetimeEnd = scrollingInfo.Algorithm.TimeAt(scrollLength * safe_lifetime_end_multiplier, hitObject.HitObject.StartTime, timeRange.Value, scrollLength); + + hitObject.LifetimeStart = scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, timeRange.Value); + hitObject.LifetimeEnd = scrollingInfo.Algorithm.TimeAt(scrollLength * safe_lifetime_end_multiplier, endTime, timeRange.Value, scrollLength); foreach (var obj in hitObject.NestedHitObjects) { From fd0f42eee4ffd88e1b65e69e8187d906b596b25e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 26 Aug 2019 14:11:24 +0300 Subject: [PATCH 0880/2815] Fix filtered grouped difficulty items in DrawableCarouselBeatmapSet aren't hidden on first load --- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 0a8c61e3d2..137fce7acf 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -223,11 +223,9 @@ namespace osu.Game.Screens.Select.Carousel public FilterableGroupedDifficultyIcon(List items, RulesetInfo ruleset) : base(items.Select(i => i.Beatmap).ToList(), ruleset, Color4.White) { - items.ForEach(item => item.Filtered.ValueChanged += _ => - { - // for now, fade the whole group based on the ratio of hidden items. - this.FadeTo(1 - 0.9f * ((float)items.Count(i => i.Filtered.Value) / items.Count), 100); - }); + // for now, fade the whole group based on the ratio of hidden items. + items.ForEach(item => item.Filtered.BindValueChanged(_ + => this.FadeTo(1 - 0.9f * ((float)items.Count(i => i.Filtered.Value) / items.Count), 100), true)); } } } From 9321f208841a9b66a8f7a1441f9812a5f7d4c157 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 26 Aug 2019 14:32:27 +0300 Subject: [PATCH 0881/2815] Move arrow to the first line --- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 137fce7acf..5ef20bd0f2 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -224,8 +224,8 @@ namespace osu.Game.Screens.Select.Carousel : base(items.Select(i => i.Beatmap).ToList(), ruleset, Color4.White) { // for now, fade the whole group based on the ratio of hidden items. - items.ForEach(item => item.Filtered.BindValueChanged(_ - => this.FadeTo(1 - 0.9f * ((float)items.Count(i => i.Filtered.Value) / items.Count), 100), true)); + items.ForEach(item => item.Filtered.BindValueChanged(_ => + this.FadeTo(1 - 0.9f * ((float)items.Count(i => i.Filtered.Value) / items.Count), 100), true)); } } } From fb69755869775abfbbb7ae79c4ca8087696c6aa8 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 26 Aug 2019 15:15:23 +0300 Subject: [PATCH 0882/2815] Use transform management for catch hit objects --- .../Objects/Drawable/DrawableCatchHitObject.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs index a1279e8443..f4218061d4 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs @@ -58,14 +58,12 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable ApplyResult(r => r.Type = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss); } - protected override bool UseTransformStateManagement => false; + protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt; - protected override void UpdateState(ArmedState state) + protected override void UpdateInitialTransforms() => this.FadeIn(200); + + protected override void UpdateStateTransforms(ArmedState state) { - // TODO: update to use new state management. - using (BeginAbsoluteSequence(HitObject.StartTime - HitObject.TimePreempt)) - this.FadeIn(200); - var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; using (BeginAbsoluteSequence(endTime, true)) From 3f93780306769c6a6bee07c57065052178f41bef Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 26 Aug 2019 15:15:48 +0300 Subject: [PATCH 0883/2815] Override transform update functions in bar line --- .../Objects/Drawables/DrawableBarLine.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs index 9c3197504f..dee7bddcb3 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs @@ -69,7 +69,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Alpha = 0.2f; } - protected override void UpdateState(ArmedState state) + protected override void UpdateInitialTransforms() + { + } + + protected override void UpdateStateTransforms(ArmedState state) { } } From 62edfe7327672f2b56de7bfaf797555a3676927f Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 26 Aug 2019 15:16:01 +0300 Subject: [PATCH 0884/2815] Correct comment --- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 62abe53559..ceda643335 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.UI if (lastPlateableFruit == null) return; - // this is required to make this run after the last caught fruit runs UpdateState at least once. + // this is required to make this run after the last caught fruit runs updateState() at least once. // TODO: find a better alternative if (lastPlateableFruit.IsLoaded) action(); From 24e7146a976297fabe3d56093cda75d16e412a4c Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 26 Aug 2019 15:26:19 +0300 Subject: [PATCH 0885/2815] Revert unintended change --- .../Objects/Drawables/DrawableBarLine.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs index dee7bddcb3..9c3197504f 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs @@ -69,11 +69,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Alpha = 0.2f; } - protected override void UpdateInitialTransforms() - { - } - - protected override void UpdateStateTransforms(ArmedState state) + protected override void UpdateState(ArmedState state) { } } From c11f5084004520256a56663d7fdb5d7c3e80869b Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 26 Aug 2019 22:06:30 +0300 Subject: [PATCH 0886/2815] Use transform management for mania hit objects --- .../Objects/Drawables/DrawableBarLine.cs | 2 +- .../Drawables/DrawableManiaHitObject.cs | 29 +++++++++---------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs index 9c3197504f..e9c352c97e 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Alpha = 0.2f; } - protected override void UpdateState(ArmedState state) + protected override void UpdateStateTransforms(ArmedState state) { } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index db6b53e76d..e5b114ca81 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -45,24 +45,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { Anchor = Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; } - } - public abstract class DrawableManiaHitObject : DrawableManiaHitObject - where TObject : ManiaHitObject - { - public new readonly TObject HitObject; - - protected DrawableManiaHitObject(TObject hitObject) - : base(hitObject) + protected override void UpdateStateTransforms(ArmedState state) { - HitObject = hitObject; - } - - protected override bool UseTransformStateManagement => false; - - protected override void UpdateState(ArmedState state) - { - // TODO: update to use new state management. switch (state) { case ArmedState.Miss: @@ -75,4 +60,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables } } } + + public abstract class DrawableManiaHitObject : DrawableManiaHitObject + where TObject : ManiaHitObject + { + public new readonly TObject HitObject; + + protected DrawableManiaHitObject(TObject hitObject) + : base(hitObject) + { + HitObject = hitObject; + } + } } From 843da26dbafc7c1605981bcf15f9c3006d5b5361 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 27 Aug 2019 05:03:56 +0300 Subject: [PATCH 0887/2815] Use transform management for taiko hit objects --- .../Objects/Drawables/DrawableDrumRoll.cs | 4 +- .../Objects/Drawables/DrawableDrumRollTick.cs | 2 +- .../Objects/Drawables/DrawableHit.cs | 64 ++++++++----------- .../Objects/Drawables/DrawableSwell.cs | 21 +++--- .../Drawables/DrawableTaikoHitObject.cs | 2 - 5 files changed, 41 insertions(+), 52 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 9b4df74a61..f4407a7b54 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -88,13 +88,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables ApplyResult(r => r.Type = HitResult.Miss); } - protected override void UpdateState(ArmedState state) + protected override void UpdateStateTransforms(ArmedState state) { switch (state) { case ArmedState.Hit: case ArmedState.Miss: - this.FadeOut(100).Expire(); + this.Delay(HitObject.Duration).FadeOut(100).Expire(); break; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index 9259c693d9..cef9a53deb 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables ApplyResult(r => r.Type = HitResult.Great); } - protected override void UpdateState(ArmedState state) + protected override void UpdateStateTransforms(ArmedState state) { switch (state) { diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 34ae7db984..fa45067210 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -92,56 +92,42 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Size = BaseSize * Parent.RelativeChildSize; } - protected override void UpdateState(ArmedState state) + protected override void UpdateStateTransforms(ArmedState state) { - // TODO: update to use new state management. - var circlePiece = MainPiece as CirclePiece; - circlePiece?.FlashBox.FinishTransforms(); - - var offset = !AllJudged ? 0 : Time.Current - HitObject.StartTime; - - using (BeginDelayedSequence(HitObject.StartTime - Time.Current + offset, true)) + switch (state) { - switch (State.Value) - { - case ArmedState.Idle: - validActionPressed = false; + case ArmedState.Idle: + validActionPressed = false; - UnproxyContent(); - this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire(); - break; + UnproxyContent(); + this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire(); + break; - case ArmedState.Miss: - this.FadeOut(100) - .Expire(); - break; + case ArmedState.Miss: + this.FadeOut(100) + .Expire(); + break; - case ArmedState.Hit: - // If we're far enough away from the left stage, we should bring outselves in front of it - ProxyContent(); + case ArmedState.Hit: + // If we're far enough away from the left stage, we should bring outselves in front of it + ProxyContent(); - var flash = circlePiece?.FlashBox; + var flash = (MainPiece as CirclePiece)?.FlashBox; + flash?.FadeTo(0.9f).FadeOut(300); - if (flash != null) - { - flash.FadeTo(0.9f); - flash.FadeOut(300); - } + const float gravity_time = 300; + const float gravity_travel_height = 200; - const float gravity_time = 300; - const float gravity_travel_height = 200; + this.ScaleTo(0.8f, gravity_time * 2, Easing.OutQuad); - this.ScaleTo(0.8f, gravity_time * 2, Easing.OutQuad); + this.MoveToY(-gravity_travel_height, gravity_time, Easing.Out) + .Then() + .MoveToY(gravity_travel_height * 2, gravity_time * 2, Easing.In); - this.MoveToY(-gravity_travel_height, gravity_time, Easing.Out) - .Then() - .MoveToY(gravity_travel_height * 2, gravity_time * 2, Easing.In); + this.FadeOut(800) + .Expire(); - this.FadeOut(800) - .Expire(); - - break; - } + break; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 82448ec7d5..88769bfff6 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -179,26 +179,31 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } } - protected override void UpdateState(ArmedState state) + protected override double InitialLifetimeOffset => 100; + + protected override void UpdateInitialTransforms() => targetRing.ScaleTo(target_ring_scale, InitialLifetimeOffset * 4, Easing.OutQuint); + + protected override void UpdateStateTransforms(ArmedState state) { - const float preempt = 100; - const float out_transition_time = 300; + const double transition_duration = 300; switch (state) { case ArmedState.Idle: UnproxyContent(); expandingRing.FadeTo(0); - using (BeginAbsoluteSequence(HitObject.StartTime - preempt, true)) - targetRing.ScaleTo(target_ring_scale, preempt * 4, Easing.OutQuint); break; case ArmedState.Miss: case ArmedState.Hit: - this.FadeOut(out_transition_time, Easing.Out); - bodyContainer.ScaleTo(1.4f, out_transition_time); + using (BeginAbsoluteSequence(Time.Current, true)) + { + this.FadeOut(transition_duration, Easing.Out); + bodyContainer.ScaleTo(1.4f, transition_duration); + + Expire(); + } - Expire(); break; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index b46738c69a..bd45b52d7b 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -121,8 +121,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } } - protected override bool UseTransformStateManagement => false; - // Normal and clap samples are handled by the drum protected override IEnumerable GetSamples() => HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP); From cd68d030bd186fe3e9ef98489d876f258b76a4e1 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 27 Aug 2019 05:04:34 +0300 Subject: [PATCH 0888/2815] Remove unnecessary UpdateState overrides --- osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs | 4 ---- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs | 4 ---- .../Objects/Drawables/DrawableStrongNestedHit.cs | 4 ---- .../Objects/Drawables/DrawableSwellTick.cs | 4 ---- 4 files changed, 16 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs index 6f9856df83..3c84d900a6 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs @@ -247,10 +247,6 @@ namespace osu.Game.Rulesets.Taiko.Tests : base(hitObject) { } - - protected override void UpdateState(ArmedState state) - { - } } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs index f8909fb98c..bf89f7e15b 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs @@ -53,9 +53,5 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Alpha = 0.75f }); } - - protected override void UpdateState(ArmedState state) - { - } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs index 98a2e8a721..108e42eea5 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs @@ -18,9 +18,5 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { MainObject = mainObject; } - - protected override void UpdateState(ArmedState state) - { - } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs index 41a8fd9a75..fb80a9a5b4 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs @@ -21,10 +21,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { } - protected override void UpdateState(ArmedState state) - { - } - public override bool OnPressed(TaikoAction action) => false; } } From 0eef398ca74ebf8d860ae81b86eab3ee0f0d9636 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 27 Aug 2019 05:19:21 +0300 Subject: [PATCH 0889/2815] Remove redundant using directive --- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs index fb80a9a5b4..8b27d78101 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.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 osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Taiko.Objects.Drawables From 6368189d4660c188ae2360c856268d029ea5de91 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 27 Aug 2019 11:59:25 +0900 Subject: [PATCH 0890/2815] Refactor --- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 5ef20bd0f2..97b6a78804 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -220,12 +220,23 @@ namespace osu.Game.Screens.Select.Carousel public class FilterableGroupedDifficultyIcon : GroupedDifficultyIcon { + private readonly List items; + public FilterableGroupedDifficultyIcon(List items, RulesetInfo ruleset) : base(items.Select(i => i.Beatmap).ToList(), ruleset, Color4.White) + { + this.items = items; + + foreach (var item in items) + item.Filtered.BindValueChanged(_ => Scheduler.AddOnce(updateFilteredDisplay)); + + updateFilteredDisplay(); + } + + private void updateFilteredDisplay() { // for now, fade the whole group based on the ratio of hidden items. - items.ForEach(item => item.Filtered.BindValueChanged(_ => - this.FadeTo(1 - 0.9f * ((float)items.Count(i => i.Filtered.Value) / items.Count), 100), true)); + this.FadeTo(1 - 0.9f * ((float)items.Count(i => i.Filtered.Value) / items.Count), 100); } } } From 6014a6680411e2d553a8b09ef2beb86d2f0ee928 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 27 Aug 2019 12:22:31 +0900 Subject: [PATCH 0891/2815] Add initial fade transform to fix rewind --- .../Objects/Drawables/DrawableManiaHitObject.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index e5b114ca81..ce1484d460 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -46,6 +46,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Anchor = Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; } + protected override void UpdateInitialTransforms() => this.FadeIn(); + protected override void UpdateStateTransforms(ArmedState state) { switch (state) From fff2da728926758c7ce6502ab0558b888289f979 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 27 Aug 2019 12:59:57 +0900 Subject: [PATCH 0892/2815] Fix hold notes disappearing instantaneously --- .../Objects/Drawables/DrawableHoldNote.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 952c6e128e..fc3b6885d7 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI.Scrolling; @@ -104,6 +105,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2; } + protected override void UpdateStateTransforms(ArmedState state) + { + using (BeginDelayedSequence(HitObject.Duration, true)) + base.UpdateStateTransforms(state); + } + protected void BeginHold() { holdStartTime = Time.Current; From 7885c79467aa732110d1cc6be57daf5a7521336a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Aug 2019 14:31:34 +0900 Subject: [PATCH 0893/2815] Make bindables private --- .../SongSelect/TestScenePlaySongSelect.cs | 21 ++++++++++------ osu.Game/Screens/Select/FilterControl.cs | 24 +++++++++---------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 2dbe53709b..263eada07c 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -15,6 +15,7 @@ using osu.Framework.MathUtils; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; @@ -79,8 +80,12 @@ namespace osu.Game.Tests.Visual.SongSelect Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, defaultBeatmap = Beatmap.Default)); Beatmap.SetDefault(); + + Dependencies.Cache(config = new OsuConfigManager(LocalStorage)); } + private OsuConfigManager config; + [SetUp] public virtual void SetUp() => Schedule(() => { @@ -111,13 +116,15 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); - AddStep(@"Sort by Artist", delegate { songSelect.FilterControl.SortMode.Value = SortMode.Artist; }); - AddStep(@"Sort by Title", delegate { songSelect.FilterControl.SortMode.Value = SortMode.Title; }); - AddStep(@"Sort by Author", delegate { songSelect.FilterControl.SortMode.Value = SortMode.Author; }); - AddStep(@"Sort by DateAdded", delegate { songSelect.FilterControl.SortMode.Value = SortMode.DateAdded; }); - AddStep(@"Sort by BPM", delegate { songSelect.FilterControl.SortMode.Value = SortMode.BPM; }); - AddStep(@"Sort by Length", delegate { songSelect.FilterControl.SortMode.Value = SortMode.Length; }); - AddStep(@"Sort by Difficulty", delegate { songSelect.FilterControl.SortMode.Value = SortMode.Difficulty; }); + var sortMode = config.GetBindable(OsuSetting.SongSelectSortingMode); + + AddStep(@"Sort by Artist", delegate { sortMode.Value = SortMode.Artist; }); + AddStep(@"Sort by Title", delegate { sortMode.Value = SortMode.Title; }); + AddStep(@"Sort by Author", delegate { sortMode.Value = SortMode.Author; }); + AddStep(@"Sort by DateAdded", delegate { sortMode.Value = SortMode.DateAdded; }); + AddStep(@"Sort by BPM", delegate { sortMode.Value = SortMode.BPM; }); + AddStep(@"Sort by Length", delegate { sortMode.Value = SortMode.Length; }); + AddStep(@"Sort by Difficulty", delegate { sortMode.Value = SortMode.Difficulty; }); } [Test] diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 06b6cccf96..ed74b01fc9 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -29,14 +29,14 @@ namespace osu.Game.Screens.Select private readonly TabControl groupTabs; - public Bindable SortMode; + private Bindable sortMode; - public Bindable GroupMode; + private Bindable groupMode; public FilterCriteria CreateCriteria() => new FilterCriteria { - Group = GroupMode.Value, - Sort = SortMode.Value, + Group = groupMode.Value, + Sort = sortMode.Value, SearchText = searchTextBox.Text, AllowConvertedBeatmaps = showConverted.Value, Ruleset = ruleset.Value @@ -123,8 +123,8 @@ namespace osu.Game.Screens.Select searchTextBox.Current.ValueChanged += _ => FilterChanged?.Invoke(CreateCriteria()); - groupTabs.PinItem(Filter.GroupMode.All); - groupTabs.PinItem(Filter.GroupMode.RecentlyPlayed); + groupTabs.PinItem(GroupMode.All); + groupTabs.PinItem(GroupMode.RecentlyPlayed); } public void Deactivate() @@ -156,14 +156,14 @@ namespace osu.Game.Screens.Select ruleset.BindTo(parentRuleset); ruleset.BindValueChanged(_ => updateCriteria()); - SortMode = config.GetBindable(OsuSetting.SongSelectSortingMode); - GroupMode = config.GetBindable(OsuSetting.SongSelectGroupingMode); + sortMode = config.GetBindable(OsuSetting.SongSelectSortingMode); + groupMode = config.GetBindable(OsuSetting.SongSelectGroupingMode); - sortTabs.Current.BindTo(SortMode); - groupTabs.Current.BindTo(GroupMode); + sortTabs.Current.BindTo(sortMode); + groupTabs.Current.BindTo(groupMode); - GroupMode.BindValueChanged(_ => updateCriteria()); - SortMode.BindValueChanged(_ => updateCriteria()); + groupMode.BindValueChanged(_ => updateCriteria()); + sortMode.BindValueChanged(_ => updateCriteria()); updateCriteria(); } From b85e62a6e2da4f861788a67da8ae64495f30038b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 27 Aug 2019 14:19:55 +0900 Subject: [PATCH 0894/2815] Fix taiko proxies being removed on rewind --- .../Drawables/DrawableTaikoHitObject.cs | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index b46738c69a..ea3ea4a9ad 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -78,10 +78,29 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public abstract bool OnPressed(TaikoAction action); public virtual bool OnReleased(TaikoAction action) => false; + public override double LifetimeStart + { + get => base.LifetimeStart; + set + { + base.LifetimeStart = value; + proxiedContent.LifetimeStart = value; + } + } + + public override double LifetimeEnd + { + get => base.LifetimeEnd; + set + { + base.LifetimeEnd = value; + proxiedContent.LifetimeEnd = value; + } + } + private class ProxiedContentContainer : Container { - public override double LifetimeStart => Parent?.LifetimeStart ?? base.LifetimeStart; - public override double LifetimeEnd => Parent?.LifetimeEnd ?? base.LifetimeEnd; + public override bool RemoveWhenNotAlive => false; } } From dba367981759de0db52b7b54bfc2046bfc15a154 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 27 Aug 2019 15:19:16 +0900 Subject: [PATCH 0895/2815] Fix DrawableSwell not proxying its content in time/correctly --- .../Objects/Drawables/DrawableSwell.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 88769bfff6..7d5b928b4f 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -179,9 +179,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } } - protected override double InitialLifetimeOffset => 100; + protected override void UpdateInitialTransforms() + { + base.UpdateInitialTransforms(); - protected override void UpdateInitialTransforms() => targetRing.ScaleTo(target_ring_scale, InitialLifetimeOffset * 4, Easing.OutQuint); + using (BeginAbsoluteSequence(HitObject.StartTime - 100, true)) + targetRing.ScaleTo(target_ring_scale, 400, Easing.OutQuint); + } protected override void UpdateStateTransforms(ArmedState state) { @@ -190,7 +194,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables switch (state) { case ArmedState.Idle: - UnproxyContent(); expandingRing.FadeTo(0); break; @@ -217,9 +220,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables // Make the swell stop at the hit target X = Math.Max(0, X); - double t = Math.Min(HitObject.StartTime, Time.Current); - if (t == HitObject.StartTime) + if (Time.Current >= HitObject.StartTime - 100) ProxyContent(); + else + UnproxyContent(); } private bool? lastWasCentre; From bc7a81e733c21adb600120065e78eff7f65ac5da Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 27 Aug 2019 15:19:29 +0900 Subject: [PATCH 0896/2815] Add initial fade transform --- .../Objects/Drawables/DrawableTaikoHitObject.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index bd45b52d7b..a0a71a4c42 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -78,6 +78,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public abstract bool OnPressed(TaikoAction action); public virtual bool OnReleased(TaikoAction action) => false; + protected override void UpdateInitialTransforms() => this.FadeIn(); + private class ProxiedContentContainer : Container { public override double LifetimeStart => Parent?.LifetimeStart ?? base.LifetimeStart; From 2b22fd799dd4f16393f746aa9ea39819025ac9c0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 27 Aug 2019 15:21:54 +0900 Subject: [PATCH 0897/2815] Use constant value for offset --- .../Objects/Drawables/DrawableSwell.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 7d5b928b4f..094ad1230f 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -25,6 +25,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private const float target_ring_scale = 5f; private const float inner_ring_alpha = 0.65f; + /// + /// Offset away from the start time of the swell at which the ring starts appearing. + /// + private const double ring_appear_offset = 100; + private readonly List ticks = new List(); private readonly Container bodyContainer; @@ -183,7 +188,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { base.UpdateInitialTransforms(); - using (BeginAbsoluteSequence(HitObject.StartTime - 100, true)) + using (BeginAbsoluteSequence(HitObject.StartTime - ring_appear_offset, true)) targetRing.ScaleTo(target_ring_scale, 400, Easing.OutQuint); } @@ -220,7 +225,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables // Make the swell stop at the hit target X = Math.Max(0, X); - if (Time.Current >= HitObject.StartTime - 100) + if (Time.Current >= HitObject.StartTime - ring_appear_offset) ProxyContent(); else UnproxyContent(); From 9e926d44c0b68c86dadc031940a440ae86ccbabd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 27 Aug 2019 16:09:47 +0900 Subject: [PATCH 0898/2815] Obsolete legacy UpdateState() method --- .../Visual/Gameplay/TestSceneScrollingHitObjects.cs | 8 -------- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 6 ++++++ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs index 0a9cdc6a8e..aa80819694 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs @@ -200,10 +200,6 @@ namespace osu.Game.Tests.Visual.Gameplay break; } } - - protected override void UpdateState(ArmedState state) - { - } } private class TestDrawableHitObject : DrawableHitObject @@ -216,10 +212,6 @@ namespace osu.Game.Tests.Visual.Gameplay AddInternal(new Box { Size = new Vector2(75) }); } - - protected override void UpdateState(ArmedState state) - { - } } } } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index b72a55b9ed..80e70589bd 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -132,6 +132,8 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public event Action ApplyCustomUpdateState; +#pragma warning disable 618 // (legacy state management) - can be removed 20200227 + /// /// Enables automatic transform management of this hitobject. Implementation of transforms should be done in and only. Rewinding and removing previous states is done automatically. /// @@ -139,6 +141,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// Going forward, this is the preferred way of implementing s. Previous functionality /// is offered as a compatibility layer until all rulesets have been migrated across. /// + [Obsolete("Use UpdateInitialTransforms()/UpdateStateTransforms() instead")] // can be removed 20200227 protected virtual bool UseTransformStateManagement => true; protected override void ClearInternal(bool disposeChildren = true) => throw new InvalidOperationException($"Should never clear a {nameof(DrawableHitObject)}"); @@ -219,10 +222,13 @@ namespace osu.Game.Rulesets.Objects.Drawables /// Should generally not be used when is true; use instead. /// /// The new armed state. + [Obsolete("Use UpdateInitialTransforms()/UpdateStateTransforms() instead")] // can be removed 20200227 protected virtual void UpdateState(ArmedState state) { } +#pragma warning restore 618 + #endregion protected override void SkinChanged(ISkinSource skin, bool allowFallback) From 0ea10a49223ab178cd4fb520ffd7fc2ad87c1464 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Aug 2019 17:18:32 +0900 Subject: [PATCH 0899/2815] Fix skin sample reading failing --- osu.Game/Skinning/LegacySkin.cs | 55 +++++++++++++++++---------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 7bffe5d321..37a3059160 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -155,16 +155,40 @@ namespace osu.Game.Skinning // Spacing value was reverse-engineered from the ratio of the rendered sprite size in the visual inspector vs the actual texture size Spacing = new Vector2(-Configuration.HitCircleOverlap * 0.89f, 0) }; - - default: - string lastPiece = componentName.Split('/').Last(); - componentName = componentName.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece; - break; } return getAnimation(componentName, animatable, looping); } + public override Texture GetTexture(string componentName) + { + componentName = getFallbackName(componentName); + + float ratio = 2; + var texture = Textures.Get($"{componentName}@2x"); + + if (texture == null) + { + ratio = 1; + texture = Textures.Get(componentName); + } + + if (texture != null) + texture.ScaleAdjust = ratio; + + return texture; + } + + public override SampleChannel GetSample(string sampleName) => Samples.Get(getFallbackName(sampleName)); + + private bool hasFont(string fontName) => GetTexture($"{fontName}-0") != null; + + private string getFallbackName(string componentName) + { + string lastPiece = componentName.Split('/').Last(); + return componentName.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece; + } + private Drawable getAnimation(string componentName, bool animatable, bool looping, string animationSeparator = "-") { Texture texture; @@ -200,27 +224,6 @@ namespace osu.Game.Skinning return null; } - public override Texture GetTexture(string componentName) - { - float ratio = 2; - var texture = Textures.Get($"{componentName}@2x"); - - if (texture == null) - { - ratio = 1; - texture = Textures.Get(componentName); - } - - if (texture != null) - texture.ScaleAdjust = ratio; - - return texture; - } - - public override SampleChannel GetSample(string sampleName) => Samples.Get(sampleName); - - private bool hasFont(string fontName) => GetTexture($"{fontName}-0") != null; - protected class LegacySkinResourceStore : IResourceStore where T : INamedFileInfo { From 7aeeb65ae7fe6df164155f31414997c8c64629ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Aug 2019 18:27:21 +0900 Subject: [PATCH 0900/2815] Tidy up Player's container loading logic Fixes drawable ruleset being loaded before skin sources are finished, by loading them as a separate operation (to avoid children being loaded first). --- .../SkinnableTestScene.cs | 3 +- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 1 - osu.Game/Screens/Play/Player.cs | 81 +++++++++++-------- osu.Game/Skinning/SkinProvidingContainer.cs | 2 + 4 files changed, 49 insertions(+), 38 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs index c3a64f8b54..29e5146ff1 100644 --- a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs +++ b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs @@ -45,12 +45,11 @@ namespace osu.Game.Rulesets.Osu.Tests private Drawable createProvider(Skin skin, Func creationFunction) { - var mainProvider = new SkinProvidingContainer(skin) { RelativeSizeAxes = Axes.Both }; + var mainProvider = new SkinProvidingContainer(skin); return mainProvider .WithChild(new SkinProvidingContainer(Ruleset.Value.CreateInstance().CreateLegacySkinProvider(mainProvider)) { - RelativeSizeAxes = Axes.Both, Child = creationFunction() }); } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 178183bd22..ea7eee8bb8 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -44,7 +44,6 @@ namespace osu.Game.Rulesets.Osu.UI // Todo: Remove when hitobjects are properly pooled new SkinProvidingContainer(null) { - RelativeSizeAxes = Axes.Both, Child = HitObjectContainer, }, approachCircles = new ApproachCircleProxyContainer diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index deed17a049..b487f3e61b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -123,30 +123,53 @@ namespace osu.Game.Screens.Play InternalChild = GameplayClockContainer = new GameplayClockContainer(working, Mods.Value, DrawableRuleset.GameplayStartTime); - SkinProvidingContainer skinProvidingContainer = new BeatmapSkinProvidingContainer(working.Skin) - { - RelativeSizeAxes = Axes.Both, - }; + addUnderlayComponents(GameplayClockContainer); + addGameplayComponents(GameplayClockContainer, working); + addOverlayComponents(GameplayClockContainer, working); - GameplayClockContainer.Children = new[] + DrawableRuleset.HasReplayLoaded.BindValueChanged(e => HUDOverlay.HoldToQuit.PauseOnFocusLost = !e.NewValue && PauseOnFocusLost, true); + + // bind clock into components that require it + DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); + + // Bind ScoreProcessor to ourselves + ScoreProcessor.AllJudged += onCompletion; + ScoreProcessor.Failed += onFail; + + foreach (var mod in Mods.Value.OfType()) + mod.ApplyToScoreProcessor(ScoreProcessor); + } + + private void addUnderlayComponents(Container target) + { + target.Add(DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }); + } + + private void addGameplayComponents(Container target, WorkingBeatmap working) + { + 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)); + + // 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. + target.Add(new ScalingContainer(ScalingMode.Gameplay) + .WithChild(beatmapSkinProvider + .WithChild(target = rulesetSkinProvider))); + + target.AddRange(new Drawable[] + { + DrawableRuleset, + new ComboEffects(ScoreProcessor) + }); + } + + private void addOverlayComponents(Container target, WorkingBeatmap working) + { + target.AddRange(new[] { - DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }, - new ScalingContainer(ScalingMode.Gameplay) - { - Child = skinProvidingContainer.WithChild( - // the skinProvidingContainer is used as the fallback source here to allow the ruleset-specific skin implementation - // full access to all skin sources. - new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(skinProvidingContainer)) - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - DrawableRuleset, - new ComboEffects(ScoreProcessor) - } - } - ) - }, breakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) { Anchor = Anchor.Centre, @@ -205,19 +228,7 @@ namespace osu.Game.Screens.Play }, }, failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, } - }; - - DrawableRuleset.HasReplayLoaded.BindValueChanged(e => HUDOverlay.HoldToQuit.PauseOnFocusLost = !e.NewValue && PauseOnFocusLost, true); - - // bind clock into components that require it - DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); - - // Bind ScoreProcessor to ourselves - ScoreProcessor.AllJudged += onCompletion; - ScoreProcessor.Failed += onFail; - - foreach (var mod in Mods.Value.OfType()) - mod.ApplyToScoreProcessor(ScoreProcessor); + }); } private WorkingBeatmap loadBeatmap() diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index a92a0ae0d1..9d6fbfdc74 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -32,6 +32,8 @@ namespace osu.Game.Skinning public SkinProvidingContainer(ISkin skin) { this.skin = skin; + + RelativeSizeAxes = Axes.Both; } public Drawable GetDrawableComponent(string componentName) From b59973c712a6213b103ad2cb4ecda79b267d610e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Aug 2019 18:42:49 +0900 Subject: [PATCH 0901/2815] Update in line with framework changes --- osu.Game/Graphics/ScreenshotManager.cs | 2 +- osu.Game/Input/Bindings/GlobalActionContainer.cs | 2 +- osu.Game/Input/IdleTracker.cs | 2 +- osu.Game/Overlays/Volume/VolumeControlReceptor.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index 524a4742c0..f532302de2 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -22,7 +22,7 @@ using SixLabors.ImageSharp; namespace osu.Game.Graphics { - public class ScreenshotManager : Container, IKeyBindingHandler, IHandleGlobalInput + public class ScreenshotManager : Container, IKeyBindingHandler, IHandleGlobalKeyboardInput { private readonly BindableBool cursorVisibility = new BindableBool(true); diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index b70072a222..bf758e21d9 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -10,7 +10,7 @@ using osu.Framework.Input.Bindings; namespace osu.Game.Input.Bindings { - public class GlobalActionContainer : DatabasedKeyBindingContainer, IHandleGlobalInput + public class GlobalActionContainer : DatabasedKeyBindingContainer, IHandleGlobalKeyboardInput { private readonly Drawable handler; diff --git a/osu.Game/Input/IdleTracker.cs b/osu.Game/Input/IdleTracker.cs index cbc446a126..39ccf9fe1c 100644 --- a/osu.Game/Input/IdleTracker.cs +++ b/osu.Game/Input/IdleTracker.cs @@ -12,7 +12,7 @@ namespace osu.Game.Input /// /// Track whether the end-user is in an idle state, based on their last interaction with the game. /// - public class IdleTracker : Component, IKeyBindingHandler, IHandleGlobalInput + public class IdleTracker : Component, IKeyBindingHandler, IHandleGlobalKeyboardInput { private readonly double timeToIdle; diff --git a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs index 26235fa280..9cd3aac2cb 100644 --- a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs +++ b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs @@ -9,7 +9,7 @@ using osu.Game.Input.Bindings; namespace osu.Game.Overlays.Volume { - public class VolumeControlReceptor : Container, IScrollBindingHandler, IHandleGlobalInput + public class VolumeControlReceptor : Container, IScrollBindingHandler, IHandleGlobalKeyboardInput { public Func ActionRequested; public Func ScrollActionRequested; From f49b58c102e641ada62971df2413b8e83e1aefb6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Aug 2019 15:30:41 +0300 Subject: [PATCH 0902/2815] Simplify text building --- .../Kudosu/DrawableKudosuHistoryItem.cs | 46 ++++++------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index 3cc39f0e73..4dba07713f 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -55,83 +55,67 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu switch (historyItem.Action) { case KudosuAction.VoteGive: - linkFlowContainer.AddText(@"Received "); - addKudosuPart(); + addKudosuPart(@"Received"); addMainPart(@" from obtaining votes in modding post of "); - addPostPart(); break; case KudosuAction.Give: - linkFlowContainer.AddText(@"Received "); - addKudosuPart(); + addKudosuPart(@"Received"); addMainPart($@" from {userLinkTemplate()} for a post at "); - addPostPart(); break; case KudosuAction.Reset: addMainPart($@"Kudosu reset by {userLinkTemplate()} for the post "); - addPostPart(); break; case KudosuAction.VoteReset: - linkFlowContainer.AddText(@"Lost "); - addKudosuPart(); + addKudosuPart(@"Lost"); addMainPart(@" from losing votes in modding post of "); - addPostPart(); break; case KudosuAction.DenyKudosuReset: - linkFlowContainer.AddText(@"Denied "); - addKudosuPart(); + addKudosuPart(@"Denied"); addMainPart(@" from modding post "); - addPostPart(); break; case KudosuAction.Revoke: addMainPart($@"Denied kudosu by {userLinkTemplate()} for the post "); - addPostPart(); break; case KudosuAction.AllowKudosuGive: - linkFlowContainer.AddText(@"Received "); - addKudosuPart(); + addKudosuPart(@"Received"); addMainPart(@" from kudosu deny repeal of modding post "); - addPostPart(); break; case KudosuAction.DeleteReset: - linkFlowContainer.AddText(@"Lost "); - addKudosuPart(); + addKudosuPart(@"Lost"); addMainPart(@" from modding post deletion of "); - addPostPart(); break; case KudosuAction.RestoreGive: - linkFlowContainer.AddText(@"Received "); - addKudosuPart(); + addKudosuPart(@"Received"); addMainPart(@" from modding post restoration of "); - addPostPart(); break; case KudosuAction.RecalculateGive: - linkFlowContainer.AddText(@"Received "); - addKudosuPart(); + addKudosuPart(@"Received"); addMainPart(@" from votes recalculation in modding post of "); - addPostPart(); break; case KudosuAction.RecalculateReset: - linkFlowContainer.AddText(@"Lost "); - addKudosuPart(); + addKudosuPart(@"Lost"); addMainPart(@" from votes recalculation in modding post of "); - addPostPart(); break; } + + addPostPart(); } - private void addKudosuPart() + private void addKudosuPart(string prefix) { - linkFlowContainer.AddText($@"{historyItem.Amount} kudosu", t => + linkFlowContainer.AddText(prefix); + + linkFlowContainer.AddText($@" {historyItem.Amount} kudosu", t => { t.Font = t.Font.With(italics: true); t.Colour = colours.Blue; From ed827d514f42d2d774f9b592dfee85c962036b94 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Aug 2019 15:36:08 +0300 Subject: [PATCH 0903/2815] Add comments --- osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs index d02f71c339..19ce11aa13 100644 --- a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs +++ b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs @@ -15,6 +15,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("amount")] private int amount { + //We can receive negative values. However "action" is enough to build needed items set => Amount = Math.Abs(value); } @@ -49,6 +50,7 @@ namespace osu.Game.Online.API.Requests.Responses { set { + //We will receive something like "foo.bar" or just "foo" string parsed = value.Contains(".") ? value.Split('.')[0].Pascalize() + value.Split('.')[1].Pascalize() : value.Pascalize(); Action = (KudosuAction)Enum.Parse(typeof(KudosuAction), parsed); From 22ee7db805c1be3876c6d625d6107e98d4425cbf Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Aug 2019 15:47:37 +0300 Subject: [PATCH 0904/2815] Refactor PaginatedContainer to avoid code duplication --- .../Beatmaps/PaginatedBeatmapContainer.cs | 48 +++--------- .../PaginatedMostPlayedBeatmapContainer.cs | 41 ++-------- .../Profile/Sections/PaginatedContainer.cs | 76 +++++++++++++++---- .../Sections/Ranks/PaginatedScoreContainer.cs | 65 +++++----------- .../PaginatedRecentActivityContainer.cs | 39 ++-------- 5 files changed, 107 insertions(+), 162 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index 8a6b52b7ee..d0ba3e6ba5 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -1,21 +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 System.Linq; +using System.Collections.Generic; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Direct; using osu.Game.Users; using osuTK; namespace osu.Game.Overlays.Profile.Sections.Beatmaps { - public class PaginatedBeatmapContainer : PaginatedContainer + public class PaginatedBeatmapContainer : PaginatedContainer { private const float panel_padding = 10f; private readonly BeatmapSetType type; - private GetUserBeatmapsRequest request; public PaginatedBeatmapContainer(BeatmapSetType type, Bindable user, string header, string missing = "None... yet.") : base(user, header, missing) @@ -27,40 +28,13 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps ItemsContainer.Spacing = new Vector2(panel_padding); } - protected override void ShowMore() + protected override APIRequest> CreateRequest() => + new GetUserBeatmapsRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); + + protected override Drawable CreateDrawableItem(APIBeatmapSet item) => new DirectGridPanel(item.ToBeatmapSet(Rulesets)) { - request = new GetUserBeatmapsRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); - request.Success += sets => Schedule(() => - { - MoreButton.FadeTo(sets.Count == ItemsPerPage ? 1 : 0); - MoreButton.IsLoading = false; - - if (!sets.Any() && VisiblePages == 1) - { - MissingText.Show(); - return; - } - - foreach (var s in sets) - { - if (!s.OnlineBeatmapSetID.HasValue) - continue; - - ItemsContainer.Add(new DirectGridPanel(s.ToBeatmapSet(Rulesets)) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }); - } - }); - - Api.Queue(request); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - request?.Cancel(); - } + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }; } } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs index 23072f8d90..10aa31225f 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs @@ -1,19 +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.Linq; +using System.Collections.Generic; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Users; namespace osu.Game.Overlays.Profile.Sections.Historical { - public class PaginatedMostPlayedBeatmapContainer : PaginatedContainer + public class PaginatedMostPlayedBeatmapContainer : PaginatedContainer { - private GetUserMostPlayedBeatmapsRequest request; - public PaginatedMostPlayedBeatmapContainer(Bindable user) : base(user, "Most Played Beatmaps", "No records. :(") { @@ -22,35 +22,10 @@ namespace osu.Game.Overlays.Profile.Sections.Historical ItemsContainer.Direction = FillDirection.Vertical; } - protected override void ShowMore() - { - request = new GetUserMostPlayedBeatmapsRequest(User.Value.Id, VisiblePages++, ItemsPerPage); - request.Success += beatmaps => Schedule(() => - { - MoreButton.FadeTo(beatmaps.Count == ItemsPerPage ? 1 : 0); - MoreButton.IsLoading = false; + protected override APIRequest> CreateRequest() => + new GetUserMostPlayedBeatmapsRequest(User.Value.Id, VisiblePages++, ItemsPerPage); - if (!beatmaps.Any() && VisiblePages == 1) - { - MissingText.Show(); - return; - } - - MissingText.Hide(); - - foreach (var beatmap in beatmaps) - { - ItemsContainer.Add(new DrawableMostPlayedBeatmap(beatmap.GetBeatmapInfo(Rulesets), beatmap.PlayCount)); - } - }); - - Api.Queue(request); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - request?.Cancel(); - } + protected override Drawable CreateDrawableItem(APIUserMostPlayedBeatmap item) => + new DrawableMostPlayedBeatmap(item.GetBeatmapInfo(Rulesets), item.PlayCount); } } diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index b459afcb49..75601041e8 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -11,22 +11,27 @@ using osu.Game.Graphics.Sprites; using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Users; +using System.Collections.Generic; +using System.Linq; +using System.Threading; namespace osu.Game.Overlays.Profile.Sections { - public abstract class PaginatedContainer : FillFlowContainer + public abstract class PaginatedContainer : FillFlowContainer { - protected readonly FillFlowContainer ItemsContainer; - protected readonly ShowMoreButton MoreButton; - protected readonly OsuSpriteText MissingText; + private readonly ShowMoreButton moreButton; + private readonly OsuSpriteText missingText; + private APIRequest> retrievalRequest; + private CancellationTokenSource loadCancellation; + + [Resolved] + private IAPIProvider api { get; set; } protected int VisiblePages; protected int ItemsPerPage; protected readonly Bindable User = new Bindable(); - - protected IAPIProvider Api; - protected APIRequest RetrievalRequest; + protected readonly FillFlowContainer ItemsContainer; protected RulesetStore Rulesets; protected PaginatedContainer(Bindable user, string header, string missing) @@ -51,15 +56,15 @@ namespace osu.Game.Overlays.Profile.Sections RelativeSizeAxes = Axes.X, Spacing = new Vector2(0, 2), }, - MoreButton = new ShowMoreButton + moreButton = new ShowMoreButton { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Alpha = 0, Margin = new MarginPadding { Top = 10 }, - Action = ShowMore, + Action = showMore, }, - MissingText = new OsuSpriteText + missingText = new OsuSpriteText { Font = OsuFont.GetFont(size: 15), Text = missing, @@ -69,9 +74,8 @@ namespace osu.Game.Overlays.Profile.Sections } [BackgroundDependencyLoader] - private void load(IAPIProvider api, RulesetStore rulesets) + private void load(RulesetStore rulesets) { - Api = api; Rulesets = rulesets; User.ValueChanged += onUserChanged; @@ -80,13 +84,57 @@ namespace osu.Game.Overlays.Profile.Sections private void onUserChanged(ValueChangedEvent e) { + loadCancellation?.Cancel(); + retrievalRequest?.Cancel(); + VisiblePages = 0; ItemsContainer.Clear(); if (e.NewValue != null) - ShowMore(); + showMore(); } - protected abstract void ShowMore(); + private void showMore() + { + loadCancellation = new CancellationTokenSource(); + + retrievalRequest = CreateRequest(); + retrievalRequest.Success += UpdateItems; + + api.Queue(retrievalRequest); + } + + protected virtual void UpdateItems(List items) + { + Schedule(() => + { + if (!items.Any() && VisiblePages == 1) + { + moreButton.Hide(); + moreButton.IsLoading = false; + missingText.Show(); + return; + } + + LoadComponentsAsync(items.Select(CreateDrawableItem), drawables => + { + missingText.Hide(); + moreButton.FadeTo(items.Count == ItemsPerPage ? 1 : 0); + moreButton.IsLoading = false; + + ItemsContainer.AddRange(drawables); + }, loadCancellation.Token); + }); + } + + protected abstract APIRequest> CreateRequest(); + + protected abstract Drawable CreateDrawableItem(T item); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + retrievalRequest?.Cancel(); + } } } diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 4a9ac6e5c7..96291f5a32 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -5,18 +5,18 @@ using osu.Framework.Graphics.Containers; using osu.Game.Online.API.Requests; using osu.Game.Users; using System; -using System.Collections.Generic; -using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Online.API.Requests.Responses; +using System.Collections.Generic; +using osu.Game.Online.API; namespace osu.Game.Overlays.Profile.Sections.Ranks { - public class PaginatedScoreContainer : PaginatedContainer + public class PaginatedScoreContainer : PaginatedContainer { private readonly bool includeWeight; private readonly ScoreType type; - private GetUserScoresRequest request; public PaginatedScoreContainer(ScoreType type, Bindable user, string header, string missing, bool includeWeight = false) : base(user, header, missing) @@ -29,52 +29,27 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks ItemsContainer.Direction = FillDirection.Vertical; } - protected override void ShowMore() + protected override void UpdateItems(List items) { - request = new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); - request.Success += scores => Schedule(() => - { - foreach (var s in scores) - s.Ruleset = Rulesets.GetRuleset(s.RulesetID); + foreach (var item in items) + item.Ruleset = Rulesets.GetRuleset(item.RulesetID); - if (!scores.Any() && VisiblePages == 1) - { - MoreButton.Hide(); - MoreButton.IsLoading = false; - MissingText.Show(); - return; - } - - IEnumerable drawableScores; - - switch (type) - { - default: - drawableScores = scores.Select(score => new DrawablePerformanceScore(score, includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null)); - break; - - case ScoreType.Recent: - drawableScores = scores.Select(score => new DrawableTotalScore(score)); - break; - } - - LoadComponentsAsync(drawableScores, s => - { - MissingText.Hide(); - MoreButton.FadeTo(scores.Count == ItemsPerPage ? 1 : 0); - MoreButton.IsLoading = false; - - ItemsContainer.AddRange(s); - }); - }); - - Api.Queue(request); + base.UpdateItems(items); } - protected override void Dispose(bool isDisposing) + protected override APIRequest> CreateRequest() => + new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); + + protected override Drawable CreateDrawableItem(APILegacyScoreInfo item) { - base.Dispose(isDisposing); - request?.Cancel(); + switch (type) + { + default: + return new DrawablePerformanceScore(item, includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null); + + case ScoreType.Recent: + return new DrawableTotalScore(item); + } } } } diff --git a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs index f2a778a874..8cc762e3a7 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs @@ -4,51 +4,24 @@ using osu.Framework.Graphics; using osu.Game.Online.API.Requests; using osu.Game.Users; -using System.Linq; using osu.Framework.Bindables; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.API; +using System.Collections.Generic; namespace osu.Game.Overlays.Profile.Sections.Recent { - public class PaginatedRecentActivityContainer : PaginatedContainer + public class PaginatedRecentActivityContainer : PaginatedContainer { - private GetUserRecentActivitiesRequest request; - public PaginatedRecentActivityContainer(Bindable user, string header, string missing) : base(user, header, missing) { ItemsPerPage = 5; } - protected override void ShowMore() - { - request = new GetUserRecentActivitiesRequest(User.Value.Id, VisiblePages++, ItemsPerPage); - request.Success += activities => Schedule(() => - { - MoreButton.FadeTo(activities.Count == ItemsPerPage ? 1 : 0); - MoreButton.IsLoading = false; + protected override APIRequest> CreateRequest() => + new GetUserRecentActivitiesRequest(User.Value.Id, VisiblePages++, ItemsPerPage); - if (!activities.Any() && VisiblePages == 1) - { - MissingText.Show(); - return; - } - - MissingText.Hide(); - - foreach (APIRecentActivity activity in activities) - { - ItemsContainer.Add(new DrawableRecentActivity(activity)); - } - }); - - Api.Queue(request); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - request?.Cancel(); - } + protected override Drawable CreateDrawableItem(APIRecentActivity item) => new DrawableRecentActivity(item); } } From 9a383eee1a802f85819504e31da54852816df45d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Aug 2019 15:58:57 +0300 Subject: [PATCH 0905/2815] Add AllowCreate function --- .../Sections/Beatmaps/PaginatedBeatmapContainer.cs | 2 ++ osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index d0ba3e6ba5..0b424057ae 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -31,6 +31,8 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps protected override APIRequest> CreateRequest() => new GetUserBeatmapsRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); + protected override bool AllowCreate(APIBeatmapSet item) => item.OnlineBeatmapSetID.HasValue; + protected override Drawable CreateDrawableItem(APIBeatmapSet item) => new DirectGridPanel(item.ToBeatmapSet(Rulesets)) { Anchor = Anchor.TopCentre, diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index 75601041e8..adc1107b32 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -116,7 +116,7 @@ namespace osu.Game.Overlays.Profile.Sections return; } - LoadComponentsAsync(items.Select(CreateDrawableItem), drawables => + LoadComponentsAsync(items.Where(item => AllowCreate(item)).Select(CreateDrawableItem), drawables => { missingText.Hide(); moreButton.FadeTo(items.Count == ItemsPerPage ? 1 : 0); @@ -127,6 +127,13 @@ namespace osu.Game.Overlays.Profile.Sections }); } + /// + /// Used to check whether the item is suitable for drawable creation. + /// + /// An item to check + /// + protected virtual bool AllowCreate(T item) => true; + protected abstract APIRequest> CreateRequest(); protected abstract Drawable CreateDrawableItem(T item); From 5fd43d42f441c54787ba132012297f8e6de9e956 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Aug 2019 16:09:37 +0300 Subject: [PATCH 0906/2815] CI fixes --- osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs | 2 +- .../Profile/Sections/Ranks/PaginatedScoreContainer.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index adc1107b32..a3a22d6248 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -116,7 +116,7 @@ namespace osu.Game.Overlays.Profile.Sections return; } - LoadComponentsAsync(items.Where(item => AllowCreate(item)).Select(CreateDrawableItem), drawables => + LoadComponentsAsync(items.Where(AllowCreate).Select(CreateDrawableItem), drawables => { missingText.Hide(); moreButton.FadeTo(items.Count == ItemsPerPage ? 1 : 0); diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 96291f5a32..5f023cfa4b 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -45,10 +45,10 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks switch (type) { default: - return new DrawablePerformanceScore(item, includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null); + return new DrawablePerformanceScore(item, includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null); case ScoreType.Recent: - return new DrawableTotalScore(item); + return new DrawableTotalScore(item); } } } From 4ef991b97309868a507a63097caa9de1611da9be Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 27 Aug 2019 19:42:17 +0300 Subject: [PATCH 0907/2815] Loop over lookup names on default-samples retrieval --- osu.Game/Skinning/SkinnableSound.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 23093d9bd9..0c20241c4c 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -45,10 +45,9 @@ namespace osu.Game.Skinning var ch = skin.GetSample(s); if (ch == null && allowFallback) - if (s is HitSampleInfo hsi) - ch = audio.Samples.Get(string.IsNullOrEmpty(hsi.Namespace) - ? $"Gameplay/{hsi.Namespace}/{hsi.Bank}-{hsi.Name}" - : $"Gameplay/{hsi.Bank}-{hsi.Name}"); + foreach (var lookup in s.LookupNames) + if ((ch = audio.Samples.Get($"Gameplay/{lookup}")) != null) + break; if (ch != null) ch.Volume.Value = s.Volume / 100.0; From 4add1727b7458b9e36584fafdb753793c41ee447 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2019 03:25:57 +0900 Subject: [PATCH 0908/2815] Fix hitsounds not updating immediately after switching skins --- osu.Game/Skinning/SkinnableSound.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 8e2b5cec98..aa1c596a51 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -37,7 +37,7 @@ namespace osu.Game.Skinning public void Play() => channels?.ForEach(c => c.Play()); - public override bool IsPresent => false; // We don't need to receive updates. + public override bool IsPresent => Scheduler.HasPendingTasks; protected override void SkinChanged(ISkinSource skin, bool allowFallback) { From 7cc92e2fad4cdfbeefd12d718d73e3cf0ad27af8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 28 Aug 2019 13:20:28 +0900 Subject: [PATCH 0909/2815] Make taiko proxy containers use LifetimeManagementContainer --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index e62dc45cab..a10f70a344 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -44,9 +44,8 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly JudgementContainer judgementContainer; internal readonly HitTarget HitTarget; - private readonly Container topLevelHitContainer; - - private readonly Container barlineContainer; + private readonly ProxyContainer topLevelHitContainer; + private readonly ProxyContainer barlineContainer; private readonly Container overlayBackgroundContainer; private readonly Container backgroundContainer; @@ -108,7 +107,7 @@ namespace osu.Game.Rulesets.Taiko.UI } } }, - barlineContainer = new Container + barlineContainer = new ProxyContainer { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Left = HIT_TARGET_OFFSET } @@ -183,7 +182,7 @@ namespace osu.Game.Rulesets.Taiko.UI } } }, - topLevelHitContainer = new Container + topLevelHitContainer = new ProxyContainer { Name = "Top level hit objects", RelativeSizeAxes = Axes.Both, @@ -256,5 +255,15 @@ namespace osu.Game.Rulesets.Taiko.UI break; } } + + private class ProxyContainer : LifetimeManagementContainer + { + public new MarginPadding Padding + { + set => base.Padding = value; + } + + public void Add(Drawable proxy) => AddInternal(proxy); + } } } From f18b5a3c02ff44dd78db54fc9eae2f2c4979fac0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2019 13:27:44 +0900 Subject: [PATCH 0910/2815] Remove "AllowCreate" function by instead handling nulls --- .../Sections/Beatmaps/PaginatedBeatmapContainer.cs | 14 +++++++------- .../Profile/Sections/PaginatedContainer.cs | 9 +-------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index 0b424057ae..fe6822440f 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -31,12 +31,12 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps protected override APIRequest> CreateRequest() => new GetUserBeatmapsRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); - protected override bool AllowCreate(APIBeatmapSet item) => item.OnlineBeatmapSetID.HasValue; - - protected override Drawable CreateDrawableItem(APIBeatmapSet item) => new DirectGridPanel(item.ToBeatmapSet(Rulesets)) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }; + protected override Drawable CreateDrawableItem(APIBeatmapSet item) => !item.OnlineBeatmapSetID.HasValue + ? null + : new DirectGridPanel(item.ToBeatmapSet(Rulesets)) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }; } } diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index a3a22d6248..4a10946ec2 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -116,7 +116,7 @@ namespace osu.Game.Overlays.Profile.Sections return; } - LoadComponentsAsync(items.Where(AllowCreate).Select(CreateDrawableItem), drawables => + LoadComponentsAsync(items.Select(CreateDrawableItem).Where(d => d != null), drawables => { missingText.Hide(); moreButton.FadeTo(items.Count == ItemsPerPage ? 1 : 0); @@ -127,13 +127,6 @@ namespace osu.Game.Overlays.Profile.Sections }); } - /// - /// Used to check whether the item is suitable for drawable creation. - /// - /// An item to check - /// - protected virtual bool AllowCreate(T item) => true; - protected abstract APIRequest> CreateRequest(); protected abstract Drawable CreateDrawableItem(T item); From 27633c8dbd28b50a9f7796617d426c563d4dd5e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2019 13:28:21 +0900 Subject: [PATCH 0911/2815] T -> TModel --- .../Overlays/Profile/Sections/PaginatedContainer.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index 4a10946ec2..b91f22d1e7 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -17,11 +17,11 @@ using System.Threading; namespace osu.Game.Overlays.Profile.Sections { - public abstract class PaginatedContainer : FillFlowContainer + public abstract class PaginatedContainer : FillFlowContainer { private readonly ShowMoreButton moreButton; private readonly OsuSpriteText missingText; - private APIRequest> retrievalRequest; + private APIRequest> retrievalRequest; private CancellationTokenSource loadCancellation; [Resolved] @@ -104,7 +104,7 @@ namespace osu.Game.Overlays.Profile.Sections api.Queue(retrievalRequest); } - protected virtual void UpdateItems(List items) + protected virtual void UpdateItems(List items) { Schedule(() => { @@ -127,9 +127,9 @@ namespace osu.Game.Overlays.Profile.Sections }); } - protected abstract APIRequest> CreateRequest(); + protected abstract APIRequest> CreateRequest(); - protected abstract Drawable CreateDrawableItem(T item); + protected abstract Drawable CreateDrawableItem(TModel item); protected override void Dispose(bool isDisposing) { From 3942c83c187034750131d527e5b839106e973160 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2019 13:28:57 +0900 Subject: [PATCH 0912/2815] Simplify schedule layout --- .../Profile/Sections/PaginatedContainer.cs | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index b91f22d1e7..e26f7f3601 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -104,28 +104,25 @@ namespace osu.Game.Overlays.Profile.Sections api.Queue(retrievalRequest); } - protected virtual void UpdateItems(List items) + protected virtual void UpdateItems(List items) => Schedule(() => { - Schedule(() => + if (!items.Any() && VisiblePages == 1) { - if (!items.Any() && VisiblePages == 1) - { - moreButton.Hide(); - moreButton.IsLoading = false; - missingText.Show(); - return; - } + moreButton.Hide(); + moreButton.IsLoading = false; + missingText.Show(); + return; + } - LoadComponentsAsync(items.Select(CreateDrawableItem).Where(d => d != null), drawables => - { - missingText.Hide(); - moreButton.FadeTo(items.Count == ItemsPerPage ? 1 : 0); - moreButton.IsLoading = false; + LoadComponentsAsync(items.Select(CreateDrawableItem).Where(d => d != null), drawables => + { + missingText.Hide(); + moreButton.FadeTo(items.Count == ItemsPerPage ? 1 : 0); + moreButton.IsLoading = false; - ItemsContainer.AddRange(drawables); - }, loadCancellation.Token); - }); - } + ItemsContainer.AddRange(drawables); + }, loadCancellation.Token); + }); protected abstract APIRequest> CreateRequest(); From bef44b8e58581a6e5d51871c8ef5acd13aca2502 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2019 13:31:12 +0900 Subject: [PATCH 0913/2815] item -> model --- .../Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs | 4 ++-- .../Historical/PaginatedMostPlayedBeatmapContainer.cs | 4 ++-- osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs | 2 +- .../Profile/Sections/Ranks/PaginatedScoreContainer.cs | 6 +++--- .../Sections/Recent/PaginatedRecentActivityContainer.cs | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index fe6822440f..919f8a2fa0 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -31,9 +31,9 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps protected override APIRequest> CreateRequest() => new GetUserBeatmapsRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); - protected override Drawable CreateDrawableItem(APIBeatmapSet item) => !item.OnlineBeatmapSetID.HasValue + protected override Drawable CreateDrawableItem(APIBeatmapSet model) => !model.OnlineBeatmapSetID.HasValue ? null - : new DirectGridPanel(item.ToBeatmapSet(Rulesets)) + : new DirectGridPanel(model.ToBeatmapSet(Rulesets)) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs index 10aa31225f..6e6d6272c7 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical protected override APIRequest> CreateRequest() => new GetUserMostPlayedBeatmapsRequest(User.Value.Id, VisiblePages++, ItemsPerPage); - protected override Drawable CreateDrawableItem(APIUserMostPlayedBeatmap item) => - new DrawableMostPlayedBeatmap(item.GetBeatmapInfo(Rulesets), item.PlayCount); + protected override Drawable CreateDrawableItem(APIUserMostPlayedBeatmap model) => + new DrawableMostPlayedBeatmap(model.GetBeatmapInfo(Rulesets), model.PlayCount); } } diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index e26f7f3601..bb221bd43a 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -126,7 +126,7 @@ namespace osu.Game.Overlays.Profile.Sections protected abstract APIRequest> CreateRequest(); - protected abstract Drawable CreateDrawableItem(TModel item); + protected abstract Drawable CreateDrawableItem(TModel model); protected override void Dispose(bool isDisposing) { diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 5f023cfa4b..853b9db0a7 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -40,15 +40,15 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks protected override APIRequest> CreateRequest() => new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); - protected override Drawable CreateDrawableItem(APILegacyScoreInfo item) + protected override Drawable CreateDrawableItem(APILegacyScoreInfo model) { switch (type) { default: - return new DrawablePerformanceScore(item, includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null); + return new DrawablePerformanceScore(model, includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null); case ScoreType.Recent: - return new DrawableTotalScore(item); + return new DrawableTotalScore(model); } } } diff --git a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs index 8cc762e3a7..3f9d4dc93e 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs @@ -22,6 +22,6 @@ namespace osu.Game.Overlays.Profile.Sections.Recent protected override APIRequest> CreateRequest() => new GetUserRecentActivitiesRequest(User.Value.Id, VisiblePages++, ItemsPerPage); - protected override Drawable CreateDrawableItem(APIRecentActivity item) => new DrawableRecentActivity(item); + protected override Drawable CreateDrawableItem(APIRecentActivity model) => new DrawableRecentActivity(model); } } From 70da25cfca5664965951c27bbd41600cb13898eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2019 17:03:54 +0900 Subject: [PATCH 0914/2815] 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 215a9a8090..0f6e32d664 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -61,6 +61,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 4fe9119cef..d791909372 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -15,7 +15,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 82301549d7..9fc472bf40 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,8 +118,8 @@ - - + + From bb22c2d6e48b8039b967bd7c7320f84e382af794 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2019 17:29:18 +0900 Subject: [PATCH 0915/2815] Tidy up text construction --- .../Kudosu/DrawableKudosuHistoryItem.cs | 116 ++++++++++-------- 1 file changed, 66 insertions(+), 50 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index 4dba07713f..fb7d597012 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -50,85 +50,101 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu { date.Colour = colours.GreySeafoamLighter; - string userLinkTemplate() => $"[{historyItem.Giver?.Url} {historyItem.Giver?.Username}]"; + string prefix = getPrefix(historyItem); + var formattedSource = MessageFormatter.FormatText(getSource(historyItem)); + + if (!string.IsNullOrEmpty(prefix)) + { + linkFlowContainer.AddText(prefix); + linkFlowContainer.AddText($@" {historyItem.Amount} kudosu", t => + { + t.Font = t.Font.With(italics: true); + t.Colour = colours.Blue; + }); + } + + linkFlowContainer.AddLinks(formattedSource.Text + " ", formattedSource.Links); + linkFlowContainer.AddLink(historyItem.Post.Title, historyItem.Post.Url); + } + + private string getSource(APIKudosuHistory historyItem) + { + string userLink() => $"[{historyItem.Giver?.Url} {historyItem.Giver?.Username}]"; switch (historyItem.Action) { case KudosuAction.VoteGive: - addKudosuPart(@"Received"); - addMainPart(@" from obtaining votes in modding post of "); - break; + return @" from obtaining votes in modding post of"; case KudosuAction.Give: - addKudosuPart(@"Received"); - addMainPart($@" from {userLinkTemplate()} for a post at "); - break; + return $@" from {userLink()} for a post at"; case KudosuAction.Reset: - addMainPart($@"Kudosu reset by {userLinkTemplate()} for the post "); - break; + return $@"Kudosu reset by {userLink()} for the post"; case KudosuAction.VoteReset: - addKudosuPart(@"Lost"); - addMainPart(@" from losing votes in modding post of "); - break; + return @" from losing votes in modding post of"; case KudosuAction.DenyKudosuReset: - addKudosuPart(@"Denied"); - addMainPart(@" from modding post "); - break; + return @" from modding post"; case KudosuAction.Revoke: - addMainPart($@"Denied kudosu by {userLinkTemplate()} for the post "); - break; + return $@"Denied kudosu by {userLink()} for the post"; case KudosuAction.AllowKudosuGive: - addKudosuPart(@"Received"); - addMainPart(@" from kudosu deny repeal of modding post "); - break; + return @" from kudosu deny repeal of modding post"; case KudosuAction.DeleteReset: - addKudosuPart(@"Lost"); - addMainPart(@" from modding post deletion of "); - break; + return @" from modding post deletion of"; case KudosuAction.RestoreGive: - addKudosuPart(@"Received"); - addMainPart(@" from modding post restoration of "); - break; + return @" from modding post restoration of"; case KudosuAction.RecalculateGive: - addKudosuPart(@"Received"); - addMainPart(@" from votes recalculation in modding post of "); - break; + return @" from votes recalculation in modding post of"; case KudosuAction.RecalculateReset: - addKudosuPart(@"Lost"); - addMainPart(@" from votes recalculation in modding post of "); - break; + return @" from votes recalculation in modding post of"; + + default: + return @" from unknown event "; } - - addPostPart(); } - private void addKudosuPart(string prefix) + private string getPrefix(APIKudosuHistory historyItem) { - linkFlowContainer.AddText(prefix); - - linkFlowContainer.AddText($@" {historyItem.Amount} kudosu", t => + switch (historyItem.Action) { - t.Font = t.Font.With(italics: true); - t.Colour = colours.Blue; - }); + case KudosuAction.VoteGive: + return @"Received"; + + case KudosuAction.Give: + return @"Received"; + + case KudosuAction.VoteReset: + return @"Lost"; + + case KudosuAction.DenyKudosuReset: + return @"Denied"; + + case KudosuAction.AllowKudosuGive: + return @"Received"; + + case KudosuAction.DeleteReset: + return @"Lost"; + + case KudosuAction.RestoreGive: + return @"Received"; + + case KudosuAction.RecalculateGive: + return @"Received"; + + case KudosuAction.RecalculateReset: + return @"Lost"; + + default: + return null; + } } - - private void addMainPart(string text) - { - var formatted = MessageFormatter.FormatText(text); - - linkFlowContainer.AddLinks(formatted.Text, formatted.Links); - } - - private void addPostPart() => linkFlowContainer.AddLink(historyItem.Post.Title, historyItem.Post.Url); } } From 9752dbf9505ba614d6609c42fa874aeb73aad026 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2019 18:10:58 +0900 Subject: [PATCH 0916/2815] Fix osu! approach circles fading in too late --- .../Objects/Drawables/DrawableHitCircle.cs | 6 +++++- .../Objects/Drawables/DrawableOsuHitObject.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 7 +++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index ca124e9214..0af278f6a4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -29,6 +29,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly HitArea hitArea; + private readonly SkinnableDrawable mainContent; + public DrawableHitCircle(HitCircle h) : base(h) { @@ -56,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return true; }, }, - new SkinnableDrawable("Play/osu/hitcircle", _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)), + mainContent = new SkinnableDrawable("Play/osu/hitcircle", _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)), ApproachCircle = new ApproachCircle { Alpha = 0, @@ -108,6 +110,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdateInitialTransforms(); + mainContent.FadeInFromZero(HitObject.TimeFadeIn); + ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadeIn * 2, HitObject.TimePreempt)); ApproachCircle.ScaleTo(1f, HitObject.TimePreempt); ApproachCircle.Expire(true); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index a89fb8b682..17efefa959 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt; - protected override void UpdateInitialTransforms() => this.FadeIn(HitObject.TimeFadeIn); + protected override void UpdateInitialTransforms() => this.FadeInFromZero(); private OsuInputManager osuActionInputManager; internal OsuInputManager OsuActionInputManager => osuActionInputManager ?? (osuActionInputManager = GetContainingInputManager() as OsuInputManager); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index a0626707af..1749ea1f60 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -93,6 +93,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } + protected override void UpdateInitialTransforms() + { + base.UpdateInitialTransforms(); + + Body.FadeInFromZero(HitObject.TimeFadeIn); + } + [BackgroundDependencyLoader] private void load() { From 9a98f39f06623a181bf4298e8ffb1c8f80e238a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2019 18:12:47 +0900 Subject: [PATCH 0917/2815] Share logic with other rulesets (and make default) --- .../Objects/Drawable/DrawableCatchHitObject.cs | 2 +- .../Objects/Drawables/DrawableManiaHitObject.cs | 2 -- .../Objects/Drawables/DrawableOsuHitObject.cs | 2 -- .../Objects/Drawables/DrawableTaikoHitObject.cs | 2 -- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 ++++ 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs index f4218061d4..00734810b3 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt; - protected override void UpdateInitialTransforms() => this.FadeIn(200); + protected override void UpdateInitialTransforms() => this.FadeInFromZero(200); protected override void UpdateStateTransforms(ArmedState state) { diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index ce1484d460..e5b114ca81 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -46,8 +46,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Anchor = Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; } - protected override void UpdateInitialTransforms() => this.FadeIn(); - protected override void UpdateStateTransforms(ArmedState state) { switch (state) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 17efefa959..b4f5642f45 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -36,8 +36,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt; - protected override void UpdateInitialTransforms() => this.FadeInFromZero(); - private OsuInputManager osuActionInputManager; internal OsuInputManager OsuActionInputManager => osuActionInputManager ?? (osuActionInputManager = GetContainingInputManager() as OsuInputManager); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 5f3bab5b28..5424ccb4de 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -78,8 +78,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public abstract bool OnPressed(TaikoAction action); public virtual bool OnReleased(TaikoAction action) => false; - protected override void UpdateInitialTransforms() => this.FadeIn(); - public override double LifetimeStart { get => base.LifetimeStart; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 80e70589bd..4a6f261905 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; +using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Game.Audio; using osu.Game.Rulesets.Judgements; @@ -186,6 +187,8 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Apply (generally fade-in) transforms leading into the start time. /// The local drawable hierarchy is recursively delayed to for convenience. + /// + /// By default this will fade in the object from zero with no duration. /// /// /// This is called once before every . This is to ensure a good state in the case @@ -193,6 +196,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// protected virtual void UpdateInitialTransforms() { + this.FadeInFromZero(); } /// From cb8fe89b8bf9d82008db4853cbd6ba680a194de7 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 28 Aug 2019 13:09:53 +0300 Subject: [PATCH 0918/2815] Allow setting looping to existing channels --- osu.Game/Skinning/SkinnableSound.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index ac2cb16a6a..1491f2a989 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -35,7 +35,18 @@ namespace osu.Game.Skinning this.audio = audio; } - public bool Looping; + private bool looping; + + public bool Looping + { + get => looping; + set + { + looping = value; + + channels.ForEach(c => c.Looping = looping); + } + } public void Play() => channels?.ForEach(c => c.Play()); From 2f3fb8cf88823e906df851d9da5d5ca9c1eb3f9a Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 28 Aug 2019 13:10:11 +0300 Subject: [PATCH 0919/2815] Add Stop + RemoveAdjustment functions --- osu.Game/Skinning/SkinnableSound.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 1491f2a989..74804d5a06 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -49,8 +49,10 @@ namespace osu.Game.Skinning } public void Play() => channels?.ForEach(c => c.Play()); + public void Stop() => channels?.ForEach(c => c.Stop()); public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => channels?.ForEach(c => c.AddAdjustment(type, adjustBindable)); + public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => channels?.ForEach(c => c.RemoveAdjustment(type, adjustBindable)); public override bool IsPresent => Scheduler.HasPendingTasks; From 01aede3e299f53f0fd2c86f00a5c5087559ace88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2019 19:57:17 +0900 Subject: [PATCH 0920/2815] Add comprehensive skin fallback integration testing --- .../TestSceneOsuPlayer.cs | 140 ++++++++++++++++++ .../Beatmaps/BeatmapManager_WorkingBeatmap.cs | 2 +- osu.Game/Beatmaps/WorkingBeatmap.cs | 8 +- osu.Game/Skinning/SkinnableDrawable.cs | 2 +- osu.Game/Tests/Visual/PlayerTestScene.cs | 7 +- osu.Game/Tests/Visual/TestPlayer.cs | 3 + 6 files changed, 153 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs index 0a33b09ba8..7c84ae80c8 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs @@ -1,7 +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.Linq; using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Timing; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Screens.Play; +using osu.Game.Skinning; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests @@ -9,9 +25,133 @@ namespace osu.Game.Rulesets.Osu.Tests [TestFixture] public class TestSceneOsuPlayer : PlayerTestScene { + private readonly TestSource testUserSkin; + private readonly TestSource testBeatmapSkin; + public TestSceneOsuPlayer() : base(new OsuRuleset()) { + testUserSkin = new TestSource("user"); + testBeatmapSkin = new TestSource("beatmap"); + } + + [Test] + public void TestBeatmapSkinDefault() + { + AddStep("enable user provider", () => testUserSkin.Enabled = true); + + AddStep("enable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, true)); + checkNextHitObject("beatmap"); + + AddStep("disable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, false)); + checkNextHitObject("user"); + + AddStep("disable user provider", () => testUserSkin.Enabled = false); + checkNextHitObject(null); + } + + private void checkNextHitObject(string skin) => + AddUntilStep($"check skin from {skin}", () => + { + var firstObject = ((TestPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.OfType().FirstOrDefault(); + + if (firstObject == null) + return false; + + var skinnable = firstObject?.ApproachCircle.Child as SkinnableDrawable; + + if (skin == null && skinnable?.Drawable is Sprite) + // check for default skin provider + return true; + + var text = skinnable?.Drawable as SpriteText; + + return text?.Text == skin; + }); + + [Resolved] + private AudioManager audio { get; set; } + + protected override Player CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(testUserSkin); + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) => new CustomSkinWorkingBeatmap(beatmap, Clock, audio, testBeatmapSkin); + + public class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap + { + private readonly ISkinSource skin; + + public CustomSkinWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock frameBasedClock, AudioManager audio, ISkinSource skin) + : base(beatmap, frameBasedClock, audio) + { + this.skin = skin; + } + + protected override ISkin GetSkin() => skin; + } + + public class SkinProvidingPlayer : TestPlayer + { + private readonly TestSource userSkin; + + public SkinProvidingPlayer(TestSource userSkin) + { + this.userSkin = userSkin; + } + + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + dependencies.CacheAs(userSkin); + + return dependencies; + } + } + + public class TestSource : ISkinSource + { + private readonly string identifier; + + public TestSource(string identifier) + { + this.identifier = identifier; + } + + public Drawable GetDrawableComponent(string componentName) + { + if (!enabled) return null; + + return new SpriteText + { + Text = identifier, + Font = OsuFont.Default.With(size: 30), + }; + } + + public Texture GetTexture(string componentName) => null; + + public SampleChannel GetSample(ISampleInfo sampleInfo) => null; + + public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => default; + + public event Action SourceChanged; + + private bool enabled = true; + + public bool Enabled + { + get => enabled; + set + { + if (value == enabled) + return; + + enabled = value; + SourceChanged?.Invoke(); + } + } } } } diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 2d8a0b1249..5bbffc2f77 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -136,7 +136,7 @@ namespace osu.Game.Beatmaps return storyboard; } - protected override Skin GetSkin() + protected override ISkin GetSkin() { try { diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 8605caa5fe..9addcfbdd7 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -46,7 +46,7 @@ namespace osu.Game.Beatmaps background = new RecyclableLazy(GetBackground, BackgroundStillValid); waveform = new RecyclableLazy(GetWaveform); storyboard = new RecyclableLazy(GetStoryboard); - skin = new RecyclableLazy(GetSkin); + skin = new RecyclableLazy(GetSkin); total_count.Value++; } @@ -214,10 +214,10 @@ namespace osu.Game.Beatmaps private readonly RecyclableLazy storyboard; public bool SkinLoaded => skin.IsResultAvailable; - public Skin Skin => skin.Value; + public ISkin Skin => skin.Value; - protected virtual Skin GetSkin() => new DefaultSkin(); - private readonly RecyclableLazy skin; + protected virtual ISkin GetSkin() => new DefaultSkin(); + private readonly RecyclableLazy skin; /// /// Transfer pieces of a beatmap to a new one, where possible, to save on loading. diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs index 0c635a3d2f..07f802944b 100644 --- a/osu.Game/Skinning/SkinnableDrawable.cs +++ b/osu.Game/Skinning/SkinnableDrawable.cs @@ -16,7 +16,7 @@ namespace osu.Game.Skinning /// /// The displayed component. /// - protected Drawable Drawable { get; private set; } + public Drawable Drawable { get; private set; } private readonly string componentName; diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 03e17a819c..1ab20ecd48 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -22,12 +22,13 @@ namespace osu.Game.Tests.Visual this.ruleset = ruleset; } + protected OsuConfigManager LocalConfig; + [BackgroundDependencyLoader] private void load() { - OsuConfigManager manager; - Dependencies.Cache(manager = new OsuConfigManager(LocalStorage)); - manager.GetBindable(OsuSetting.DimLevel).Value = 1.0; + Dependencies.Cache(LocalConfig = new OsuConfigManager(LocalStorage)); + LocalConfig.GetBindable(OsuSetting.DimLevel).Value = 1.0; } [SetUpSteps] diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index b93a1466e0..31f6edadec 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.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.Game.Rulesets.UI; using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual @@ -9,6 +10,8 @@ namespace osu.Game.Tests.Visual { protected override bool PauseOnFocusLost => false; + public new DrawableRuleset DrawableRuleset => base.DrawableRuleset; + public TestPlayer(bool allowPause = true, bool showResults = true) : base(allowPause, showResults) { From c7e20b34bae62458749b973acb1e0cd54f1e8c7b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2019 20:15:28 +0900 Subject: [PATCH 0921/2815] Cleanup --- osu.Game/Utils/HumanizerUtils.cs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/osu.Game/Utils/HumanizerUtils.cs b/osu.Game/Utils/HumanizerUtils.cs index 398c76a09f..5b7c3630d9 100644 --- a/osu.Game/Utils/HumanizerUtils.cs +++ b/osu.Game/Utils/HumanizerUtils.cs @@ -10,27 +10,21 @@ namespace osu.Game.Utils public static class HumanizerUtils { /// - /// Humanizes a string using the system culture, then falls back if one cannot be found. - /// - /// A localization lookup failure will throw an exception of type - /// + /// Turns the current or provided date into a human readable sentence /// - /// The time to humanize. - /// A humanized string of the given time. - public static string Humanize(DateTimeOffset dateTimeOffset) + /// The date to be humanized + /// distance of time in words + public static string Humanize(DateTimeOffset input) { - string offset; - + // this works around https://github.com/xamarin/xamarin-android/issues/2012 and https://github.com/Humanizr/Humanizer/issues/690#issuecomment-368536282 try { - offset = dateTimeOffset.Humanize(); + return input.Humanize(); } catch (ArgumentException) { - offset = dateTimeOffset.Humanize(culture: new CultureInfo("en-US")); + return input.Humanize(culture: new CultureInfo("en-US")); } - - return offset; } } } From 8b42890644d503b32e663e028bd711434b1969c6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2019 20:19:19 +0900 Subject: [PATCH 0922/2815] Fix unnecessary null check --- osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs index 7c84ae80c8..d61378f4f8 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Tests if (firstObject == null) return false; - var skinnable = firstObject?.ApproachCircle.Child as SkinnableDrawable; + var skinnable = firstObject.ApproachCircle.Child as SkinnableDrawable; if (skin == null && skinnable?.Drawable is Sprite) // check for default skin provider From 348d88846da7d530b992a651512843d123f07bb7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 28 Aug 2019 20:19:22 +0900 Subject: [PATCH 0923/2815] Add IBeatmap interface for typed hitobject retrieval --- osu.Game/Beatmaps/Beatmap.cs | 12 ++++-------- osu.Game/Beatmaps/IBeatmap.cs | 9 +++++++++ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index a09a1bb9cb..5435e86dfd 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -14,7 +14,7 @@ namespace osu.Game.Beatmaps /// /// A Beatmap containing converted HitObjects. /// - public class Beatmap : IBeatmap + public class Beatmap : IBeatmap where T : HitObject { public BeatmapInfo BeatmapInfo { get; set; } = new BeatmapInfo @@ -36,17 +36,13 @@ namespace osu.Game.Beatmaps public List Breaks { get; set; } = new List(); - /// - /// Total amount of break time in the beatmap. - /// [JsonIgnore] public double TotalBreakTime => Breaks.Sum(b => b.Duration); - /// - /// The HitObjects this Beatmap contains. - /// [JsonConverter(typeof(TypedListConverter))] - public List HitObjects = new List(); + public List HitObjects { get; set; } = new List(); + + IReadOnlyList IBeatmap.HitObjects => HitObjects; IReadOnlyList IBeatmap.HitObjects => HitObjects; diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 512fe25809..8f27e0b0e9 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -53,4 +53,13 @@ namespace osu.Game.Beatmaps /// The shallow-cloned beatmap. IBeatmap Clone(); } + + public interface IBeatmap : IBeatmap + where T : HitObject + { + /// + /// The hitobjects contained by this beatmap. + /// + new IReadOnlyList HitObjects { get; } + } } From f6ad95018adda03061931a74039c8a375ae887df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2019 20:22:16 +0900 Subject: [PATCH 0924/2815] Centralise default beat length specification --- osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs | 4 +++- .../Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs | 2 +- osu.Game/Rulesets/Timing/MultiplierControlPoint.cs | 2 +- osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index e5815a3f3b..ccb8a92b3a 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -14,6 +14,8 @@ namespace osu.Game.Beatmaps.ControlPoints /// public TimeSignatures TimeSignature = TimeSignatures.SimpleQuadruple; + public const double DEFAULT_BEAT_LENGTH = 1000; + /// /// The beat length at this control point. /// @@ -23,7 +25,7 @@ namespace osu.Game.Beatmaps.ControlPoints set => beatLength = MathHelper.Clamp(value, 6, 60000); } - private double beatLength = 1000; + private double beatLength = DEFAULT_BEAT_LENGTH; public bool Equals(TimingControlPoint other) => base.Equals(other) diff --git a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs index 540f616ea9..8775c15f17 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs @@ -31,7 +31,7 @@ namespace osu.Game.Beatmaps.Formats private class LegacyDifficultyCalculatorControlPoint : TimingControlPoint { - public override double BeatLength { get; set; } = 1000; + public override double BeatLength { get; set; } = TimingControlPoint.DEFAULT_BEAT_LENGTH; } } } diff --git a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs index ffa35c24cd..4b3c3f90f0 100644 --- a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs +++ b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Timing /// The base beat length to scale the provided multiplier relative to. /// /// For a of 1000, a with a beat length of 500 will increase the multiplier by 2. - public double BaseBeatLength = 1000; + public double BaseBeatLength = TimingControlPoint.DEFAULT_BEAT_LENGTH; /// /// The velocity multiplier. diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 65a22b10b7..c1a4c9520e 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.UI.Scrolling private void load() { double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; - double baseBeatLength = 1000; + double baseBeatLength = TimingControlPoint.DEFAULT_BEAT_LENGTH; if (RelativeScaleBeatLengths) { From 3d6200338263bdaca6e39410bd52df1a0dab7e79 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 28 Aug 2019 15:39:45 +0300 Subject: [PATCH 0925/2815] Add null check --- osu.Game/Skinning/SkinnableSound.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 74804d5a06..cb511fc775 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -44,7 +44,7 @@ namespace osu.Game.Skinning { looping = value; - channels.ForEach(c => c.Looping = looping); + channels?.ForEach(c => c.Looping = looping); } } From c6e757fdae6c89b70927a26a83db61ed2cb8e572 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2019 22:11:23 +0900 Subject: [PATCH 0926/2815] Remove redundant qualifier --- .../Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs index 8775c15f17..2c493254e0 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs @@ -31,7 +31,7 @@ namespace osu.Game.Beatmaps.Formats private class LegacyDifficultyCalculatorControlPoint : TimingControlPoint { - public override double BeatLength { get; set; } = TimingControlPoint.DEFAULT_BEAT_LENGTH; + public override double BeatLength { get; set; } = DEFAULT_BEAT_LENGTH; } } } From 73fd3cf03ca93a0a73b9ae4d55e726d3e3c8089f Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 28 Aug 2019 19:00:01 -0700 Subject: [PATCH 0927/2815] Fix gameplay menu button initial hover animation --- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index c7e762714c..f93d5d8b02 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -304,8 +304,6 @@ namespace osu.Game.Screens.Play private class Button : DialogButton { - protected override bool OnHover(HoverEvent e) => true; - protected override bool OnMouseMove(MouseMoveEvent e) { Selected.Value = true; From 03a4acaf4ca0071c9885e5176e67296bb7b68073 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Aug 2019 12:07:58 +0900 Subject: [PATCH 0928/2815] Fix drags outside of overlay container bounds not hiding overlay --- .../Containers/OsuFocusedOverlayContainer.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 0f7b26835b..9c948d6f90 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -62,15 +62,23 @@ namespace osu.Game.Graphics.Containers protected override bool OnClick(ClickEvent e) { - if (!base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) - { - Hide(); - return true; - } + closeIfOutside(e); return base.OnClick(e); } + protected override bool OnDragEnd(DragEndEvent e) + { + closeIfOutside(e); + return base.OnDragEnd(e); + } + + private void closeIfOutside(MouseEvent e) + { + if (!base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) + Hide(); + } + public virtual bool OnPressed(GlobalAction action) { switch (action) From 6949c96aaa51952258a335de43b73fc0ba7a793f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 12:43:43 +0900 Subject: [PATCH 0929/2815] Add initial EditorBeatmap structure --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 14 ++++ osu.Game/Screens/Edit/EditorBeatmap.cs | 79 +++++++++++++++++++++ osu.Game/Screens/Edit/IEditorBeatmap.cs | 17 +++++ 3 files changed, 110 insertions(+) create mode 100644 osu.Game/Screens/Edit/EditorBeatmap.cs create mode 100644 osu.Game/Screens/Edit/IEditorBeatmap.cs diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 38ec09535d..6d98f45187 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -18,6 +18,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.RadioButtons; using osu.Game.Screens.Edit.Compose.Components; @@ -185,6 +186,19 @@ namespace osu.Game.Rulesets.Edit { } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var workingBeatmap = parent.Get>(); + var playableBeatmap = (Beatmap)workingBeatmap.Value.GetPlayableBeatmap(Ruleset.RulesetInfo, Array.Empty()); + var editorBeatmap = new EditorBeatmap(playableBeatmap); + + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.CacheAs(editorBeatmap); + dependencies.CacheAs(editorBeatmap); + + return dependencies; + } + internal override DrawableEditRuleset CreateDrawableRuleset() => new DrawableEditRuleset(CreateDrawableRuleset(Ruleset, Beatmap.Value, Array.Empty())); diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs new file mode 100644 index 0000000000..99dd441578 --- /dev/null +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -0,0 +1,79 @@ +// 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.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Screens.Edit +{ + public class EditorBeatmap : IBeatmap, IEditorBeatmap + where T : HitObject + { + public event Action HitObjectRemoved; + public event Action HitObjectAdded; + + event Action IEditorBeatmap.HitObjectAdded + { + add => HitObjectAdded += value; + remove => HitObjectAdded -= value; + } + + event Action IEditorBeatmap.HitObjectRemoved + { + add => HitObjectRemoved += value; + remove => HitObjectRemoved -= value; + } + + private readonly Beatmap beatmap; + + public EditorBeatmap(Beatmap beatmap) + { + this.beatmap = beatmap; + } + + public BeatmapInfo BeatmapInfo + { + get => beatmap.BeatmapInfo; + set => beatmap.BeatmapInfo = value; + } + + public BeatmapMetadata Metadata => beatmap.Metadata; + + public ControlPointInfo ControlPointInfo => beatmap.ControlPointInfo; + + public List Breaks => beatmap.Breaks; + + public double TotalBreakTime => beatmap.TotalBreakTime; + + IReadOnlyList IBeatmap.HitObjects => beatmap.HitObjects; + + IReadOnlyList IBeatmap.HitObjects => beatmap.HitObjects; + + public IEnumerable GetStatistics() => beatmap.GetStatistics(); + + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); + + public void Add(T hitObject) + { + // Preserve existing sorting order in the beatmap + var insertionIndex = beatmap.HitObjects.FindLastIndex(h => h.StartTime <= hitObject.StartTime); + beatmap.HitObjects.Insert(insertionIndex + 1, hitObject); + + HitObjectAdded?.Invoke(hitObject); + } + + public void Remove(T hitObject) + { + if (beatmap.HitObjects.Remove(hitObject)) + HitObjectRemoved?.Invoke(hitObject); + } + + public void Add(HitObject hitObject) => Add((T)hitObject); + + public void Remove(HitObject hitObject) => Remove((T)hitObject); + } +} diff --git a/osu.Game/Screens/Edit/IEditorBeatmap.cs b/osu.Game/Screens/Edit/IEditorBeatmap.cs new file mode 100644 index 0000000000..602a096d65 --- /dev/null +++ b/osu.Game/Screens/Edit/IEditorBeatmap.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 System; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Screens.Edit +{ + public interface IEditorBeatmap + { + event Action HitObjectAdded; + event Action HitObjectRemoved; + + void Add(HitObject hitObject); + void Remove(HitObject hitObject); + } +} From 840f2246199d7818dc29771de2dd81628f689294 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 16:05:44 +0900 Subject: [PATCH 0930/2815] Remove typed events to reduce complexity --- osu.Game/Screens/Edit/EditorBeatmap.cs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 99dd441578..d9f17abfa5 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -13,20 +13,8 @@ namespace osu.Game.Screens.Edit public class EditorBeatmap : IBeatmap, IEditorBeatmap where T : HitObject { - public event Action HitObjectRemoved; - public event Action HitObjectAdded; - - event Action IEditorBeatmap.HitObjectAdded - { - add => HitObjectAdded += value; - remove => HitObjectAdded -= value; - } - - event Action IEditorBeatmap.HitObjectRemoved - { - add => HitObjectRemoved += value; - remove => HitObjectRemoved -= value; - } + public event Action HitObjectAdded; + public event Action HitObjectRemoved; private readonly Beatmap beatmap; From 7927b684d3c0ace143ae033f20b944c37c9f1f41 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 16:06:40 +0900 Subject: [PATCH 0931/2815] Hook up + use editor beatmap --- .../Editor/TestSceneHitObjectComposer.cs | 16 +--- osu.Game/Rulesets/Edit/DrawableEditRuleset.cs | 63 ++++----------- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 66 ++++++++++++---- .../Compose/Components/BlueprintContainer.cs | 76 +++++++++++++------ .../Screens/Edit/Compose/ComposeScreen.cs | 12 +-- 5 files changed, 118 insertions(+), 115 deletions(-) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs index 7accbe2fa8..0ea73fb3de 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs @@ -16,15 +16,13 @@ using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose.Components; using osuTK; namespace osu.Game.Tests.Visual.Editor { [TestFixture] - [Cached(Type = typeof(IPlacementHandler))] - public class TestSceneHitObjectComposer : OsuTestScene, IPlacementHandler + public class TestSceneHitObjectComposer : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -39,8 +37,6 @@ namespace osu.Game.Tests.Visual.Editor typeof(HitCirclePlacementBlueprint), }; - private HitObjectComposer composer; - [BackgroundDependencyLoader] private void load() { @@ -67,15 +63,7 @@ namespace osu.Game.Tests.Visual.Editor Dependencies.CacheAs(clock); Dependencies.CacheAs(clock); - Child = composer = new OsuHitObjectComposer(new OsuRuleset()); + Child = new OsuHitObjectComposer(new OsuRuleset()); } - - public void BeginPlacement(HitObject hitObject) - { - } - - public void EndPlacement(HitObject hitObject) => composer.Add(hitObject); - - public void Delete(HitObject hitObject) => composer.Remove(hitObject); } } diff --git a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs index e85ebb5f3a..c9d7b2cd81 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs @@ -5,10 +5,9 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit; namespace osu.Game.Rulesets.Edit { @@ -25,20 +24,6 @@ namespace osu.Game.Rulesets.Edit { RelativeSizeAxes = Axes.Both; } - - /// - /// Adds a to the and displays a visual representation of it. - /// - /// The to add. - /// The visual representation of . - internal abstract DrawableHitObject Add(HitObject hitObject); - - /// - /// Removes a from the and the display. - /// - /// The to remove. - /// The visual representation of the removed . - internal abstract DrawableHitObject Remove(HitObject hitObject); } public class DrawableEditRuleset : DrawableEditRuleset @@ -48,11 +33,11 @@ namespace osu.Game.Rulesets.Edit public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => drawableRuleset.CreatePlayfieldAdjustmentContainer(); - private Ruleset ruleset => drawableRuleset.Ruleset; - private Beatmap beatmap => drawableRuleset.Beatmap; - private readonly DrawableRuleset drawableRuleset; + [Resolved] + private EditorBeatmap beatmap { get; set; } + public DrawableEditRuleset(DrawableRuleset drawableRuleset) { this.drawableRuleset = drawableRuleset; @@ -67,50 +52,28 @@ namespace osu.Game.Rulesets.Edit Playfield.DisplayJudgements.Value = false; } - internal override DrawableHitObject Add(HitObject hitObject) + protected override void LoadComplete() { - var tObject = (TObject)hitObject; + base.LoadComplete(); - // Add to beatmap, preserving sorting order - var insertionIndex = beatmap.HitObjects.FindLastIndex(h => h.StartTime <= hitObject.StartTime); - beatmap.HitObjects.Insert(insertionIndex + 1, tObject); + beatmap.HitObjectAdded += addHitObject; + beatmap.HitObjectRemoved += removeHitObject; + } - // Process object - var processor = ruleset.CreateBeatmapProcessor(beatmap); - - processor?.PreProcess(); - tObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); - processor?.PostProcess(); - - // Add visual representation - var drawableObject = drawableRuleset.CreateDrawableRepresentation(tObject); + private void addHitObject(HitObject hitObject) + { + var drawableObject = drawableRuleset.CreateDrawableRepresentation((TObject)hitObject); drawableRuleset.Playfield.Add(drawableObject); drawableRuleset.Playfield.PostProcess(); - - return drawableObject; } - internal override DrawableHitObject Remove(HitObject hitObject) + private void removeHitObject(HitObject hitObject) { - var tObject = (TObject)hitObject; - - // Remove from beatmap - beatmap.HitObjects.Remove(tObject); - - // Process the beatmap - var processor = ruleset.CreateBeatmapProcessor(beatmap); - - processor?.PreProcess(); - processor?.PostProcess(); - - // Remove visual representation var drawableObject = Playfield.AllHitObjects.Single(d => d.HitObject == hitObject); drawableRuleset.Playfield.Remove(drawableObject); drawableRuleset.Playfield.PostProcess(); - - return drawableObject; } } } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 6d98f45187..fe81f6747d 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -20,6 +20,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.RadioButtons; +using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Edit @@ -154,14 +155,6 @@ namespace osu.Game.Rulesets.Edit /// public virtual bool CursorInPlacementArea => DrawableRuleset.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); - /// - /// Adds a to the and visualises it. - /// - /// The to add. - public void Add(HitObject hitObject) => blueprintContainer.AddBlueprintFor(DrawableRuleset.Add(hitObject)); - - public void Remove(HitObject hitObject) => blueprintContainer.RemoveBlueprintFor(DrawableRuleset.Remove(hitObject)); - internal abstract DrawableEditRuleset CreateDrawableRuleset(); protected abstract IReadOnlyList CompositionTools { get; } @@ -178,9 +171,16 @@ namespace osu.Game.Rulesets.Edit public virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler(); } - public abstract class HitObjectComposer : HitObjectComposer + [Cached(Type = typeof(IPlacementHandler))] + public abstract class HitObjectComposer : HitObjectComposer, IPlacementHandler where TObject : HitObject { + private Beatmap playableBeatmap; + + [Cached] + [Cached(typeof(IEditorBeatmap))] + private EditorBeatmap editorBeatmap; + protected HitObjectComposer(Ruleset ruleset) : base(ruleset) { @@ -189,19 +189,55 @@ namespace osu.Game.Rulesets.Edit protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var workingBeatmap = parent.Get>(); - var playableBeatmap = (Beatmap)workingBeatmap.Value.GetPlayableBeatmap(Ruleset.RulesetInfo, Array.Empty()); - var editorBeatmap = new EditorBeatmap(playableBeatmap); + playableBeatmap = (Beatmap)workingBeatmap.Value.GetPlayableBeatmap(Ruleset.RulesetInfo, Array.Empty()); - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.CacheAs(editorBeatmap); - dependencies.CacheAs(editorBeatmap); + editorBeatmap = new EditorBeatmap(playableBeatmap); + editorBeatmap.HitObjectAdded += addHitObject; + editorBeatmap.HitObjectRemoved += removeHitObject; - return dependencies; + return base.CreateChildDependencies(parent); + } + + private void addHitObject(HitObject hitObject) + { + // Process object + var processor = Ruleset.CreateBeatmapProcessor(playableBeatmap); + + processor?.PreProcess(); + hitObject.ApplyDefaults(playableBeatmap.ControlPointInfo, playableBeatmap.BeatmapInfo.BaseDifficulty); + processor?.PostProcess(); + } + + private void removeHitObject(HitObject hitObject) + { + var processor = Ruleset.CreateBeatmapProcessor(playableBeatmap); + + processor?.PreProcess(); + processor?.PostProcess(); } internal override DrawableEditRuleset CreateDrawableRuleset() => new DrawableEditRuleset(CreateDrawableRuleset(Ruleset, Beatmap.Value, Array.Empty())); protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods); + + public void BeginPlacement(HitObject hitObject) + { + } + + public void EndPlacement(HitObject hitObject) => editorBeatmap.Add(hitObject); + + public void Delete(HitObject hitObject) => editorBeatmap.Remove(hitObject); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (editorBeatmap != null) + { + editorBeatmap.HitObjectAdded -= addHitObject; + editorBeatmap.HitObjectRemoved -= removeHitObject; + } + } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index a1e62cd38b..7d25fd5283 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -11,6 +11,7 @@ using osu.Framework.Input.Events; using osu.Framework.Input.States; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Screens.Edit.Compose.Components @@ -29,6 +30,9 @@ namespace osu.Game.Screens.Edit.Compose.Components [Resolved] private HitObjectComposer composer { get; set; } + [Resolved] + private IEditorBeatmap beatmap { get; set; } + public BlueprintContainer() { RelativeSizeAxes = Axes.Both; @@ -53,7 +57,15 @@ namespace osu.Game.Screens.Edit.Compose.Components }; foreach (var obj in composer.HitObjects) - AddBlueprintFor(obj); + addBlueprintFor(obj); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + beatmap.HitObjectAdded += addBlueprintFor; + beatmap.HitObjectRemoved += removeBlueprintFor; } private HitObjectCompositionTool currentTool; @@ -75,11 +87,32 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - /// - /// Adds a blueprint for a which adds movement support. - /// - /// The to create a blueprint for. - public void AddBlueprintFor(DrawableHitObject hitObject) + private void addBlueprintFor(HitObject hitObject) + { + var drawable = composer.HitObjects.FirstOrDefault(d => d.HitObject == hitObject); + if (drawable == null) + return; + + addBlueprintFor(drawable); + } + + private void removeBlueprintFor(HitObject hitObject) + { + var blueprint = selectionBlueprints.Single(m => m.HitObject.HitObject == hitObject); + if (blueprint == null) + return; + + blueprint.Deselect(); + + blueprint.Selected -= onBlueprintSelected; + blueprint.Deselected -= onBlueprintDeselected; + blueprint.SelectionRequested -= onSelectionRequested; + blueprint.DragRequested -= onDragRequested; + + selectionBlueprints.Remove(blueprint); + } + + private void addBlueprintFor(DrawableHitObject hitObject) { refreshTool(); @@ -95,25 +128,7 @@ namespace osu.Game.Screens.Edit.Compose.Components selectionBlueprints.Add(blueprint); } - /// - /// Removes a blueprint for a . - /// - /// The for which to remove the blueprint. - public void RemoveBlueprintFor(DrawableHitObject hitObject) - { - var blueprint = selectionBlueprints.Single(m => m.HitObject == hitObject); - if (blueprint == null) - return; - - blueprint.Deselect(); - - blueprint.Selected -= onBlueprintSelected; - blueprint.Deselected -= onBlueprintDeselected; - blueprint.SelectionRequested -= onSelectionRequested; - blueprint.DragRequested -= onDragRequested; - - selectionBlueprints.Remove(blueprint); - } + private void removeBlueprintFor(DrawableHitObject hitObject) => removeBlueprintFor(hitObject.HitObject); protected override bool OnClick(ClickEvent e) { @@ -183,6 +198,17 @@ namespace osu.Game.Screens.Edit.Compose.Components private void onDragRequested(SelectionBlueprint blueprint, DragEvent dragEvent) => selectionHandler.HandleDrag(blueprint, dragEvent); + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (beatmap != null) + { + beatmap.HitObjectAdded -= addBlueprintFor; + beatmap.HitObjectRemoved -= removeBlueprintFor; + } + } + private class SelectionBlueprintContainer : Container { protected override int Compare(Drawable x, Drawable y) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 5699ef0a84..ec4dda5c23 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -9,15 +9,13 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Logging; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose { - [Cached(Type = typeof(IPlacementHandler))] - public class ComposeScreen : EditorScreen, IPlacementHandler + public class ComposeScreen : EditorScreen { private const float vertical_margins = 10; private const float horizontal_margins = 20; @@ -119,13 +117,5 @@ namespace osu.Game.Screens.Edit.Compose composerContainer.Child = composer; } - - public void BeginPlacement(HitObject hitObject) - { - } - - public void EndPlacement(HitObject hitObject) => composer.Add(hitObject); - - public void Delete(HitObject hitObject) => composer.Remove(hitObject); } } From 5db813b7a411b94d61676cb325e883f929861755 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 16:26:39 +0900 Subject: [PATCH 0932/2815] Add secondary interface for further abstraction --- osu.Game/Rulesets/Edit/DrawableEditRuleset.cs | 2 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 9 ++++--- osu.Game/Screens/Edit/EditorBeatmap.cs | 2 +- osu.Game/Screens/Edit/IEditorBeatmap.cs | 27 ++++++++++++++++--- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs index c9d7b2cd81..95a1492a45 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Edit private readonly DrawableRuleset drawableRuleset; [Resolved] - private EditorBeatmap beatmap { get; set; } + private IEditorBeatmap beatmap { get; set; } public DrawableEditRuleset(DrawableRuleset drawableRuleset) { diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index fe81f6747d..fb7021bfa8 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -176,9 +176,6 @@ namespace osu.Game.Rulesets.Edit where TObject : HitObject { private Beatmap playableBeatmap; - - [Cached] - [Cached(typeof(IEditorBeatmap))] private EditorBeatmap editorBeatmap; protected HitObjectComposer(Ruleset ruleset) @@ -195,7 +192,11 @@ namespace osu.Game.Rulesets.Edit editorBeatmap.HitObjectAdded += addHitObject; editorBeatmap.HitObjectRemoved += removeHitObject; - return base.CreateChildDependencies(parent); + var dependencies = new DependencyContainer(parent); + dependencies.CacheAs(editorBeatmap); + dependencies.CacheAs>(editorBeatmap); + + return base.CreateChildDependencies(dependencies); } private void addHitObject(HitObject hitObject) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index d9f17abfa5..2261f6f45c 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Edit { - public class EditorBeatmap : IBeatmap, IEditorBeatmap + public class EditorBeatmap : IEditorBeatmap where T : HitObject { public event Action HitObjectAdded; diff --git a/osu.Game/Screens/Edit/IEditorBeatmap.cs b/osu.Game/Screens/Edit/IEditorBeatmap.cs index 602a096d65..2f250ba446 100644 --- a/osu.Game/Screens/Edit/IEditorBeatmap.cs +++ b/osu.Game/Screens/Edit/IEditorBeatmap.cs @@ -2,16 +2,35 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Edit { - public interface IEditorBeatmap + /// + /// Interface for the contained by the see . + /// Children of may resolve the beatmap via or . + /// + public interface IEditorBeatmap : IBeatmap { + /// + /// Invoked when a is added to this . + /// event Action HitObjectAdded; - event Action HitObjectRemoved; - void Add(HitObject hitObject); - void Remove(HitObject hitObject); + /// + /// Invoked when a is removed from this . + /// + event Action HitObjectRemoved; + } + + /// + /// Interface for the contained by the see . + /// Children of may resolve the beatmap via or . + /// + public interface IEditorBeatmap : IEditorBeatmap, IBeatmap + where T : HitObject + { } } From dad0fa2dca16f672569e1a59bf4d16c54456346b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 16:31:40 +0900 Subject: [PATCH 0933/2815] Bind disposal --- osu.Game/Rulesets/Edit/DrawableEditRuleset.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs index 95a1492a45..a12e4ba3ab 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs @@ -75,5 +75,16 @@ namespace osu.Game.Rulesets.Edit drawableRuleset.Playfield.Remove(drawableObject); drawableRuleset.Playfield.PostProcess(); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (beatmap != null) + { + beatmap.HitObjectAdded -= addHitObject; + beatmap.HitObjectRemoved -= removeHitObject; + } + } } } From b04a8ae8560e467ca8f3172e0261b9f3f4b05591 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 16:31:43 +0900 Subject: [PATCH 0934/2815] Add xmldocs --- osu.Game/Screens/Edit/EditorBeatmap.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 2261f6f45c..f0b6c62154 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -45,6 +45,10 @@ namespace osu.Game.Screens.Edit public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); + /// + /// Adds a to this . + /// + /// The to add. public void Add(T hitObject) { // Preserve existing sorting order in the beatmap @@ -54,14 +58,26 @@ namespace osu.Game.Screens.Edit HitObjectAdded?.Invoke(hitObject); } + /// + /// Removes a from this . + /// + /// The to add. public void Remove(T hitObject) { if (beatmap.HitObjects.Remove(hitObject)) HitObjectRemoved?.Invoke(hitObject); } + /// + /// Adds a to this . + /// + /// The to add. public void Add(HitObject hitObject) => Add((T)hitObject); + /// + /// Removes a from this . + /// + /// The to add. public void Remove(HitObject hitObject) => Remove((T)hitObject); } } From 40c1c6072ed587b0f9f3c8ec6d720117788fd62b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Aug 2019 16:38:39 +0900 Subject: [PATCH 0935/2815] Add "osu!classic" as a bundled skin choice --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- osu.Game/OsuGame.cs | 21 +++++++++++++++++++- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Skinning/DefaultLegacySkin.cs | 23 ++++++++++++++++++++++ osu.Game/Skinning/LegacySkin.cs | 3 +++ osu.Game/Skinning/SkinInfo.cs | 6 +++++- osu.Game/Skinning/SkinManager.cs | 18 +++++++++++++++-- 7 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Skinning/DefaultLegacySkin.cs diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index b13e115387..fb472f3f89 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -18,7 +18,7 @@ namespace osu.Game.Configuration { // UI/selection defaults Set(OsuSetting.Ruleset, 0, 0, int.MaxValue); - Set(OsuSetting.Skin, 0, 0, int.MaxValue); + Set(OsuSetting.Skin, 0, -1, int.MaxValue); Set(OsuSetting.BeatmapDetailTab, BeatmapDetailTab.Details); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 0e804ecbaf..8fa8ffaf9b 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -182,7 +182,26 @@ namespace osu.Game // bind config int to database SkinInfo configSkin = LocalConfig.GetBindable(OsuSetting.Skin); SkinManager.CurrentSkinInfo.ValueChanged += skin => configSkin.Value = skin.NewValue.ID; - configSkin.ValueChanged += skinId => SkinManager.CurrentSkinInfo.Value = SkinManager.Query(s => s.ID == skinId.NewValue) ?? SkinInfo.Default; + configSkin.ValueChanged += skinId => + { + var skinInfo = SkinManager.Query(s => s.ID == skinId.NewValue); + + if (skinInfo == null) + { + switch (skinId.NewValue) + { + case -1: + skinInfo = DefaultLegacySkin.Info; + break; + + default: + skinInfo = SkinInfo.Default; + break; + } + } + + SkinManager.CurrentSkinInfo.Value = skinInfo; + }; configSkin.TriggerChange(); IsActive.BindValueChanged(active => updateActiveState(active.NewValue), true); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 076c9ada78..de8f316b06 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -158,7 +158,7 @@ namespace osu.Game runMigrations(); - dependencies.Cache(SkinManager = new SkinManager(Host.Storage, contextFactory, Host, Audio)); + dependencies.Cache(SkinManager = new SkinManager(Host.Storage, contextFactory, Host, Audio, new NamespacedResourceStore(Resources, "Skins/Legacy"))); dependencies.CacheAs(SkinManager); API = new APIAccess(LocalConfig); diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs new file mode 100644 index 0000000000..b35c9c7b97 --- /dev/null +++ b/osu.Game/Skinning/DefaultLegacySkin.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 osu.Framework.Audio; +using osu.Framework.IO.Stores; + +namespace osu.Game.Skinning +{ + public class DefaultLegacySkin : LegacySkin + { + public DefaultLegacySkin(IResourceStore storage, AudioManager audioManager) + : base(Info, storage, audioManager, string.Empty) + { + } + + public static SkinInfo Info { get; } = new SkinInfo + { + ID = -1, // this is temporary until database storage is decided upon. + Name = "osu!classic", + Creator = "team osu!" + }; + } +} diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 570ba1ced7..0cc5e9c9b6 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -248,6 +248,9 @@ namespace osu.Game.Skinning private string getPathForFile(string filename) { + if (source.Files == null) + return null; + bool hasExtension = filename.Contains('.'); var file = source.Files.Find(f => diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index 187ea910a7..6b9627188e 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -26,7 +26,11 @@ namespace osu.Game.Skinning public string FullName => $"\"{Name}\" by {Creator}"; - public static SkinInfo Default { get; } = new SkinInfo { Name = "osu!lazer", Creator = "team osu!" }; + public static SkinInfo Default { get; } = new SkinInfo + { + Name = "osu!lazer", + Creator = "team osu!" + }; public bool Equals(SkinInfo other) => other != null && ID == other.ID; diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index e747a8b1ce..0e40eb5376 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -14,6 +14,7 @@ using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; +using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Game.Audio; using osu.Game.Database; @@ -25,6 +26,8 @@ namespace osu.Game.Skinning { private readonly AudioManager audio; + private readonly IResourceStore legacyDefaultResources; + public readonly Bindable CurrentSkin = new Bindable(new DefaultSkin()); public readonly Bindable CurrentSkinInfo = new Bindable(SkinInfo.Default) { Default = SkinInfo.Default }; @@ -34,10 +37,11 @@ namespace osu.Game.Skinning protected override string ImportFromStablePath => "Skins"; - public SkinManager(Storage storage, DatabaseContextFactory contextFactory, IIpcHost importHost, AudioManager audio) + public SkinManager(Storage storage, DatabaseContextFactory contextFactory, IIpcHost importHost, AudioManager audio, IResourceStore legacyDefaultResources) : base(storage, contextFactory, new SkinStore(contextFactory, storage), importHost) { this.audio = audio; + this.legacyDefaultResources = legacyDefaultResources; ItemRemoved += removedInfo => { @@ -56,6 +60,9 @@ namespace osu.Game.Skinning }; } + private Skin createIfNotExisting(SkinInfo skinInfo) => + GetSkin(Query(s => s.Name == skinInfo.Name) ?? Import(skinInfo).Result); + protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == ".osk"; /// @@ -66,6 +73,7 @@ namespace osu.Game.Skinning { var userSkins = GetAllUserSkins(); userSkins.Insert(0, SkinInfo.Default); + userSkins.Insert(1, DefaultLegacySkin.Info); return userSkins; } @@ -91,7 +99,7 @@ namespace osu.Game.Skinning else { model.Name = model.Name.Replace(".osk", ""); - model.Creator = "Unknown"; + model.Creator = model.Creator ?? "Unknown"; } } @@ -102,9 +110,15 @@ namespace osu.Game.Skinning /// A instance correlating to the provided . public Skin GetSkin(SkinInfo skinInfo) { + if (skinInfo == null) + return null; + if (skinInfo == SkinInfo.Default) return new DefaultSkin(); + if (skinInfo == DefaultLegacySkin.Info) + return new DefaultLegacySkin(legacyDefaultResources, audio); + return new LegacySkin(skinInfo, Files.Store, audio); } From b40143cb73d8d5b8a0eaf8605b81b40ee30c05e7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 17:18:34 +0900 Subject: [PATCH 0936/2815] Remove unnecessary comment --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index fb7021bfa8..d5a0e052a5 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -201,7 +201,6 @@ namespace osu.Game.Rulesets.Edit private void addHitObject(HitObject hitObject) { - // Process object var processor = Ruleset.CreateBeatmapProcessor(playableBeatmap); processor?.PreProcess(); From 0fbdcabb6faa374e20fd43f1a762c839b73d4ced Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 17:21:52 +0900 Subject: [PATCH 0937/2815] Re-use a single beatmap processor --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index d5a0e052a5..ed2ef5d9f8 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -177,6 +177,7 @@ namespace osu.Game.Rulesets.Edit { private Beatmap playableBeatmap; private EditorBeatmap editorBeatmap; + private IBeatmapProcessor beatmapProcessor; protected HitObjectComposer(Ruleset ruleset) : base(ruleset) @@ -188,6 +189,8 @@ namespace osu.Game.Rulesets.Edit var workingBeatmap = parent.Get>(); playableBeatmap = (Beatmap)workingBeatmap.Value.GetPlayableBeatmap(Ruleset.RulesetInfo, Array.Empty()); + beatmapProcessor = Ruleset.CreateBeatmapProcessor(playableBeatmap); + editorBeatmap = new EditorBeatmap(playableBeatmap); editorBeatmap.HitObjectAdded += addHitObject; editorBeatmap.HitObjectRemoved += removeHitObject; @@ -201,19 +204,15 @@ namespace osu.Game.Rulesets.Edit private void addHitObject(HitObject hitObject) { - var processor = Ruleset.CreateBeatmapProcessor(playableBeatmap); - - processor?.PreProcess(); + beatmapProcessor?.PreProcess(); hitObject.ApplyDefaults(playableBeatmap.ControlPointInfo, playableBeatmap.BeatmapInfo.BaseDifficulty); - processor?.PostProcess(); + beatmapProcessor?.PostProcess(); } private void removeHitObject(HitObject hitObject) { - var processor = Ruleset.CreateBeatmapProcessor(playableBeatmap); - - processor?.PreProcess(); - processor?.PostProcess(); + beatmapProcessor?.PreProcess(); + beatmapProcessor?.PostProcess(); } internal override DrawableEditRuleset CreateDrawableRuleset() From 59296d12f3af1e2d107c0b0c3206e47cd1feab08 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 18:02:50 +0900 Subject: [PATCH 0938/2815] Refactor HitObjectComposer --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 150 +++++++++----------- 1 file changed, 70 insertions(+), 80 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index ed2ef5d9f8..239ec572b2 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -25,40 +25,40 @@ using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Edit { - public abstract class HitObjectComposer : CompositeDrawable + [Cached(Type = typeof(IPlacementHandler))] + public abstract class HitObjectComposer : HitObjectComposer, IPlacementHandler + where TObject : HitObject { - public IEnumerable HitObjects => DrawableRuleset.Playfield.AllHitObjects; + protected IRulesetConfigManager Config { get; private set; } + protected DrawableEditRuleset DrawableRuleset { get; private set; } protected readonly Ruleset Ruleset; - protected readonly IBindable Beatmap = new Bindable(); - - protected IRulesetConfigManager Config { get; private set; } - - private readonly List layerContainers = new List(); - - protected DrawableEditRuleset DrawableRuleset { get; private set; } + private IBindable workingBeatmap; + private Beatmap playableBeatmap; + private EditorBeatmap editorBeatmap; + private IBeatmapProcessor beatmapProcessor; private BlueprintContainer blueprintContainer; + private readonly List layerContainers = new List(); private InputManager inputManager; - internal HitObjectComposer(Ruleset ruleset) + protected HitObjectComposer(Ruleset ruleset) { Ruleset = ruleset; - RelativeSizeAxes = Axes.Both; } [BackgroundDependencyLoader] - private void load(IBindable beatmap, IFrameBasedClock framedClock) + private void load(IFrameBasedClock framedClock) { - Beatmap.BindTo(beatmap); - try { - DrawableRuleset = CreateDrawableRuleset(); - DrawableRuleset.Clock = framedClock; + DrawableRuleset = new DrawableEditRuleset(CreateDrawableRuleset(Ruleset, workingBeatmap.Value, Array.Empty())) + { + Clock = framedClock + }; } catch (Exception e) { @@ -120,6 +120,26 @@ namespace osu.Game.Rulesets.Edit toolboxCollection.Items[0].Select(); } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + workingBeatmap = parent.Get>().GetBoundCopy(); + playableBeatmap = (Beatmap)workingBeatmap.Value.GetPlayableBeatmap(Ruleset.RulesetInfo, Array.Empty()); + + beatmapProcessor = Ruleset.CreateBeatmapProcessor(playableBeatmap); + + editorBeatmap = new EditorBeatmap(playableBeatmap); + editorBeatmap.HitObjectAdded += addHitObject; + editorBeatmap.HitObjectRemoved += removeHitObject; + + var dependencies = new DependencyContainer(parent); + dependencies.CacheAs(editorBeatmap); + dependencies.CacheAs>(editorBeatmap); + + Config = dependencies.Get().GetConfigFor(Ruleset); + + return base.CreateChildDependencies(dependencies); + } + protected override void LoadComplete() { base.LoadComplete(); @@ -127,16 +147,6 @@ namespace osu.Game.Rulesets.Edit inputManager = GetContainingInputManager(); } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - - dependencies.CacheAs(this); - Config = dependencies.Get().GetConfigFor(Ruleset); - - return dependencies; - } - protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); @@ -150,58 +160,6 @@ namespace osu.Game.Rulesets.Edit }); } - /// - /// Whether the user's cursor is currently in an area of the that is valid for placement. - /// - public virtual bool CursorInPlacementArea => DrawableRuleset.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); - - internal abstract DrawableEditRuleset CreateDrawableRuleset(); - - protected abstract IReadOnlyList CompositionTools { get; } - - /// - /// Creates a for a specific . - /// - /// The to create the overlay for. - public virtual SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => null; - - /// - /// Creates a which outlines s and handles movement of selections. - /// - public virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler(); - } - - [Cached(Type = typeof(IPlacementHandler))] - public abstract class HitObjectComposer : HitObjectComposer, IPlacementHandler - where TObject : HitObject - { - private Beatmap playableBeatmap; - private EditorBeatmap editorBeatmap; - private IBeatmapProcessor beatmapProcessor; - - protected HitObjectComposer(Ruleset ruleset) - : base(ruleset) - { - } - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var workingBeatmap = parent.Get>(); - playableBeatmap = (Beatmap)workingBeatmap.Value.GetPlayableBeatmap(Ruleset.RulesetInfo, Array.Empty()); - - beatmapProcessor = Ruleset.CreateBeatmapProcessor(playableBeatmap); - - editorBeatmap = new EditorBeatmap(playableBeatmap); - editorBeatmap.HitObjectAdded += addHitObject; - editorBeatmap.HitObjectRemoved += removeHitObject; - - var dependencies = new DependencyContainer(parent); - dependencies.CacheAs(editorBeatmap); - dependencies.CacheAs>(editorBeatmap); - - return base.CreateChildDependencies(dependencies); - } - private void addHitObject(HitObject hitObject) { beatmapProcessor?.PreProcess(); @@ -215,8 +173,10 @@ namespace osu.Game.Rulesets.Edit beatmapProcessor?.PostProcess(); } - internal override DrawableEditRuleset CreateDrawableRuleset() - => new DrawableEditRuleset(CreateDrawableRuleset(Ruleset, Beatmap.Value, Array.Empty())); + public override IEnumerable HitObjects => DrawableRuleset.Playfield.AllHitObjects; + public override bool CursorInPlacementArea => DrawableRuleset.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); + + protected abstract IReadOnlyList CompositionTools { get; } protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods); @@ -239,4 +199,34 @@ namespace osu.Game.Rulesets.Edit } } } + + [Cached(typeof(HitObjectComposer))] + public abstract class HitObjectComposer : CompositeDrawable + { + internal HitObjectComposer() + { + RelativeSizeAxes = Axes.Both; + } + + /// + /// All the s. + /// + public abstract IEnumerable HitObjects { get; } + + /// + /// Whether the user's cursor is currently in an area of the that is valid for placement. + /// + public abstract bool CursorInPlacementArea { get; } + + /// + /// Creates a for a specific . + /// + /// The to create the overlay for. + public virtual SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => null; + + /// + /// Creates a which outlines s and handles movement of selections. + /// + public virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler(); + } } From 87e28ab1f97869de9fef85deac8ec2be1072f0e0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 18:12:29 +0900 Subject: [PATCH 0939/2815] Remove non-generic DrawableEditRuleset --- .../Edit/ManiaHitObjectComposer.cs | 12 ++++----- osu.Game/Rulesets/Edit/DrawableEditRuleset.cs | 25 +++++-------------- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 22 ++++++++-------- 3 files changed, 23 insertions(+), 36 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 2729621ab3..3a28149946 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Edit [Cached(Type = typeof(IManiaHitObjectComposer))] public class ManiaHitObjectComposer : HitObjectComposer, IManiaHitObjectComposer { - protected new DrawableManiaEditRuleset DrawableRuleset { get; private set; } + private DrawableManiaEditRuleset drawableRuleset; public ManiaHitObjectComposer(Ruleset ruleset) : base(ruleset) @@ -33,23 +33,23 @@ namespace osu.Game.Rulesets.Mania.Edit /// /// The screen-space position. /// The column which intersects with . - public Column ColumnAt(Vector2 screenSpacePosition) => DrawableRuleset.GetColumnByPosition(screenSpacePosition); + public Column ColumnAt(Vector2 screenSpacePosition) => drawableRuleset.GetColumnByPosition(screenSpacePosition); private DependencyContainer dependencies; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - public int TotalColumns => ((ManiaPlayfield)DrawableRuleset.Playfield).TotalColumns; + public int TotalColumns => ((ManiaPlayfield)drawableRuleset.Playfield).TotalColumns; protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) { - DrawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap, mods); + drawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap, mods); // This is the earliest we can cache the scrolling info to ourselves, before masks are added to the hierarchy and inject it - dependencies.CacheAs(DrawableRuleset.ScrollingInfo); + dependencies.CacheAs(drawableRuleset.ScrollingInfo); - return DrawableRuleset; + return drawableRuleset; } protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] diff --git a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs index a12e4ba3ab..a9f0dd4197 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs @@ -11,27 +11,10 @@ using osu.Game.Screens.Edit; namespace osu.Game.Rulesets.Edit { - public abstract class DrawableEditRuleset : CompositeDrawable - { - /// - /// The contained by this . - /// - public abstract Playfield Playfield { get; } - - public abstract PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer(); - - internal DrawableEditRuleset() - { - RelativeSizeAxes = Axes.Both; - } - } - - public class DrawableEditRuleset : DrawableEditRuleset + public class DrawableEditRuleset : CompositeDrawable where TObject : HitObject { - public override Playfield Playfield => drawableRuleset.Playfield; - - public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => drawableRuleset.CreatePlayfieldAdjustmentContainer(); + public Playfield Playfield => drawableRuleset.Playfield; private readonly DrawableRuleset drawableRuleset; @@ -42,6 +25,8 @@ namespace osu.Game.Rulesets.Edit { this.drawableRuleset = drawableRuleset; + RelativeSizeAxes = Axes.Both; + InternalChild = drawableRuleset; } @@ -76,6 +61,8 @@ namespace osu.Game.Rulesets.Edit drawableRuleset.Playfield.PostProcess(); } + public PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => drawableRuleset.CreatePlayfieldAdjustmentContainer(); + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 239ec572b2..35c5a63ef1 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -30,7 +30,6 @@ namespace osu.Game.Rulesets.Edit where TObject : HitObject { protected IRulesetConfigManager Config { get; private set; } - protected DrawableEditRuleset DrawableRuleset { get; private set; } protected readonly Ruleset Ruleset; @@ -39,6 +38,7 @@ namespace osu.Game.Rulesets.Edit private EditorBeatmap editorBeatmap; private IBeatmapProcessor beatmapProcessor; + private DrawableEditRuleset drawableRuleset; private BlueprintContainer blueprintContainer; private readonly List layerContainers = new List(); @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Edit { try { - DrawableRuleset = new DrawableEditRuleset(CreateDrawableRuleset(Ruleset, workingBeatmap.Value, Array.Empty())) + drawableRuleset = new DrawableEditRuleset(CreateDrawableRuleset(Ruleset, workingBeatmap.Value, Array.Empty())) { Clock = framedClock }; @@ -66,10 +66,10 @@ namespace osu.Game.Rulesets.Edit return; } - var layerBelowRuleset = DrawableRuleset.CreatePlayfieldAdjustmentContainer(); + var layerBelowRuleset = drawableRuleset.CreatePlayfieldAdjustmentContainer(); layerBelowRuleset.Child = new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both }; - var layerAboveRuleset = DrawableRuleset.CreatePlayfieldAdjustmentContainer(); + var layerAboveRuleset = drawableRuleset.CreatePlayfieldAdjustmentContainer(); layerAboveRuleset.Child = blueprintContainer = new BlueprintContainer(); layerContainers.Add(layerBelowRuleset); @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Edit Children = new Drawable[] { layerBelowRuleset, - DrawableRuleset, + drawableRuleset, layerAboveRuleset } } @@ -153,10 +153,10 @@ namespace osu.Game.Rulesets.Edit layerContainers.ForEach(l => { - l.Anchor = DrawableRuleset.Playfield.Anchor; - l.Origin = DrawableRuleset.Playfield.Origin; - l.Position = DrawableRuleset.Playfield.Position; - l.Size = DrawableRuleset.Playfield.Size; + l.Anchor = drawableRuleset.Playfield.Anchor; + l.Origin = drawableRuleset.Playfield.Origin; + l.Position = drawableRuleset.Playfield.Position; + l.Size = drawableRuleset.Playfield.Size; }); } @@ -173,8 +173,8 @@ namespace osu.Game.Rulesets.Edit beatmapProcessor?.PostProcess(); } - public override IEnumerable HitObjects => DrawableRuleset.Playfield.AllHitObjects; - public override bool CursorInPlacementArea => DrawableRuleset.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); + public override IEnumerable HitObjects => drawableRuleset.Playfield.AllHitObjects; + public override bool CursorInPlacementArea => drawableRuleset.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); protected abstract IReadOnlyList CompositionTools { get; } From 714ee312da0338bb768ac543ad5ffb4cf43328f0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 18:20:43 +0900 Subject: [PATCH 0940/2815] Rename DrawableEditRuleset -> DrawableEditRulesetWrapper --- ...leset.cs => DrawableEditRulesetWrapper.cs} | 7 ++++-- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 22 +++++++++---------- 2 files changed, 16 insertions(+), 13 deletions(-) rename osu.Game/Rulesets/Edit/{DrawableEditRuleset.cs => DrawableEditRulesetWrapper.cs} (85%) diff --git a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs similarity index 85% rename from osu.Game/Rulesets/Edit/DrawableEditRuleset.cs rename to osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs index a9f0dd4197..af565f8896 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs @@ -11,7 +11,10 @@ using osu.Game.Screens.Edit; namespace osu.Game.Rulesets.Edit { - public class DrawableEditRuleset : CompositeDrawable + /// + /// A wrapper for a . Handles adding visual representations of s to the underlying . + /// + internal class DrawableEditRulesetWrapper : CompositeDrawable where TObject : HitObject { public Playfield Playfield => drawableRuleset.Playfield; @@ -21,7 +24,7 @@ namespace osu.Game.Rulesets.Edit [Resolved] private IEditorBeatmap beatmap { get; set; } - public DrawableEditRuleset(DrawableRuleset drawableRuleset) + public DrawableEditRulesetWrapper(DrawableRuleset drawableRuleset) { this.drawableRuleset = drawableRuleset; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 35c5a63ef1..d7ee63d4a4 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Edit private EditorBeatmap editorBeatmap; private IBeatmapProcessor beatmapProcessor; - private DrawableEditRuleset drawableRuleset; + private DrawableEditRulesetWrapper drawableRulesetWrapper; private BlueprintContainer blueprintContainer; private readonly List layerContainers = new List(); @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Edit { try { - drawableRuleset = new DrawableEditRuleset(CreateDrawableRuleset(Ruleset, workingBeatmap.Value, Array.Empty())) + drawableRulesetWrapper = new DrawableEditRulesetWrapper(CreateDrawableRuleset(Ruleset, workingBeatmap.Value, Array.Empty())) { Clock = framedClock }; @@ -66,10 +66,10 @@ namespace osu.Game.Rulesets.Edit return; } - var layerBelowRuleset = drawableRuleset.CreatePlayfieldAdjustmentContainer(); + var layerBelowRuleset = drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer(); layerBelowRuleset.Child = new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both }; - var layerAboveRuleset = drawableRuleset.CreatePlayfieldAdjustmentContainer(); + var layerAboveRuleset = drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer(); layerAboveRuleset.Child = blueprintContainer = new BlueprintContainer(); layerContainers.Add(layerBelowRuleset); @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Edit Children = new Drawable[] { layerBelowRuleset, - drawableRuleset, + drawableRulesetWrapper, layerAboveRuleset } } @@ -153,10 +153,10 @@ namespace osu.Game.Rulesets.Edit layerContainers.ForEach(l => { - l.Anchor = drawableRuleset.Playfield.Anchor; - l.Origin = drawableRuleset.Playfield.Origin; - l.Position = drawableRuleset.Playfield.Position; - l.Size = drawableRuleset.Playfield.Size; + l.Anchor = drawableRulesetWrapper.Playfield.Anchor; + l.Origin = drawableRulesetWrapper.Playfield.Origin; + l.Position = drawableRulesetWrapper.Playfield.Position; + l.Size = drawableRulesetWrapper.Playfield.Size; }); } @@ -173,8 +173,8 @@ namespace osu.Game.Rulesets.Edit beatmapProcessor?.PostProcess(); } - public override IEnumerable HitObjects => drawableRuleset.Playfield.AllHitObjects; - public override bool CursorInPlacementArea => drawableRuleset.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); + public override IEnumerable HitObjects => drawableRulesetWrapper.Playfield.AllHitObjects; + public override bool CursorInPlacementArea => drawableRulesetWrapper.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); protected abstract IReadOnlyList CompositionTools { get; } From d3030831793dc68f7d8e04fce2d05209e6fab796 Mon Sep 17 00:00:00 2001 From: StanR Date: Thu, 29 Aug 2019 12:29:31 +0300 Subject: [PATCH 0941/2815] Update to match api --- .../Online/TestSceneBeatmapSetOverlay.cs | 4 +- osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs | 51 +++---------------- .../API/Requests/Responses/APIBeatmapSet.cs | 4 +- osu.Game/Overlays/BeatmapSet/Info.cs | 4 +- 4 files changed, 12 insertions(+), 51 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index 2d918442eb..ee9e088dcc 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -176,8 +176,8 @@ namespace osu.Game.Tests.Visual.Online HasVideo = true, HasStoryboard = true, Covers = new BeatmapSetOnlineCovers(), - Language = BeatmapSetOnlineLanguage.English, - Genre = BeatmapSetOnlineGenre.Rock, + Language = new BeatmapSetOnlineLanguage { Id = 3, Name = "English" }, + Genre = new BeatmapSetOnlineGenre { Id = 4, Name = "Rock" }, }, Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }, Beatmaps = new List diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs index decf2d10db..500e42096c 100644 --- a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs @@ -88,55 +88,16 @@ namespace osu.Game.Beatmaps public BeatmapSetOnlineLanguage Language { get; set; } } - public enum BeatmapSetOnlineGenre + public class BeatmapSetOnlineGenre { - [Description("Any")] - Any = 0, - - [Description("Unspecified")] - Unspecified = 1, - - [Description("Video Game")] - VideoGame = 2, - - [Description("Anime")] - Anime = 3, - - [Description("Rock")] - Rock = 4, - - [Description("Pop")] - Pop = 5, - - [Description("Other")] - Other = 6, - - [Description("Novelty")] - Novelty = 7, - - // genre_id 8 doesn't exist - - [Description("Hip-Hop")] - HipHop = 9, - - [Description("Electronic")] - Electronic = 10 + public int Id { get; set; } + public string Name { get; set; } } - public enum BeatmapSetOnlineLanguage + public class BeatmapSetOnlineLanguage { - Any, - Other, - English, - Japanese, - Chinese, - Instrumental, - Korean, - French, - German, - Swedish, - Spanish, - Italian + public int Id { get; set; } + public string Name { get; set; } } public class BeatmapSetOnlineCovers diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs index 1526cccf31..1ca14256e5 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs @@ -69,10 +69,10 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"availability")] private BeatmapSetOnlineAvailability availability { get; set; } - [JsonProperty(@"genre_id")] + [JsonProperty(@"genre")] private BeatmapSetOnlineGenre genre { get; set; } - [JsonProperty(@"language_id")] + [JsonProperty(@"language")] private BeatmapSetOnlineLanguage language { get; set; } [JsonProperty(@"beatmaps")] diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 0e4e9db948..9c5cce89f9 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -133,8 +133,8 @@ namespace osu.Game.Overlays.BeatmapSet { source.Text = b.NewValue?.Metadata.Source ?? string.Empty; tags.Text = b.NewValue?.Metadata.Tags ?? string.Empty; - genre.Text = (b.NewValue?.OnlineInfo.Genre ?? BeatmapSetOnlineGenre.Unspecified).GetDescription(); - language.Text = (b.NewValue?.OnlineInfo.Language ?? BeatmapSetOnlineLanguage.Other).ToString(); + genre.Text = b.NewValue?.OnlineInfo.Genre.Name ?? "Unspecified"; + language.Text = b.NewValue?.OnlineInfo.Language.Name ?? "Other"; }; } From 68ee7346b213b099e6eceed2cd8478181e15b484 Mon Sep 17 00:00:00 2001 From: StanR Date: Thu, 29 Aug 2019 12:49:44 +0300 Subject: [PATCH 0942/2815] Remove usings --- osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs | 1 - osu.Game/Overlays/BeatmapSet/Info.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs index 500e42096c..06dee4d3f5 100644 --- a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.ComponentModel; using Newtonsoft.Json; namespace osu.Game.Beatmaps diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 9c5cce89f9..f17b44c8f7 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -3,7 +3,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; From c1c1c7874bab0786ffeaa6f3a99e63c9b2317853 Mon Sep 17 00:00:00 2001 From: StanR Date: Thu, 29 Aug 2019 13:25:05 +0300 Subject: [PATCH 0943/2815] Nullcheck --- osu.Game/Overlays/BeatmapSet/Info.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index f17b44c8f7..72db03a5a6 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -132,8 +132,8 @@ namespace osu.Game.Overlays.BeatmapSet { source.Text = b.NewValue?.Metadata.Source ?? string.Empty; tags.Text = b.NewValue?.Metadata.Tags ?? string.Empty; - genre.Text = b.NewValue?.OnlineInfo.Genre.Name ?? "Unspecified"; - language.Text = b.NewValue?.OnlineInfo.Language.Name ?? "Other"; + genre.Text = b.NewValue?.OnlineInfo?.Genre?.Name ?? "Unspecified"; + language.Text = b.NewValue?.OnlineInfo?.Language?.Name ?? "Other"; }; } From 6ab2b20c70dcea4dd98ec7c7ebc9f477170ea95d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 19:38:44 +0900 Subject: [PATCH 0944/2815] Add an interface for working beatmaps --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- .../UI/DrawableCatchRuleset.cs | 4 +- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- .../UI/DrawableManiaRuleset.cs | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- .../UI/DrawableOsuRuleset.cs | 2 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- .../UI/DrawableTaikoRuleset.cs | 2 +- .../TestSceneDrawableScrollingRuleset.cs | 4 +- osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 2 +- osu.Game/Beatmaps/IWorkingBeatmap.cs | 61 +++++++++++++++++++ osu.Game/Beatmaps/WorkingBeatmap.cs | 16 +---- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 12 ++-- osu.Game/Rulesets/Ruleset.cs | 2 +- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- .../UI/Scrolling/DrawableScrollingRuleset.cs | 2 +- 16 files changed, 85 insertions(+), 34 deletions(-) create mode 100644 osu.Game/Beatmaps/IWorkingBeatmap.cs diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 6f1a7873ec..71e05083be 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Catch { public class CatchRuleset : Ruleset { - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableCatchRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableCatchRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap); diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index f48b84e344..6b7f00c5d0 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -25,11 +25,11 @@ namespace osu.Game.Rulesets.Catch.UI protected override bool UserScrollSpeedAdjustment => false; - public DrawableCatchRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + public DrawableCatchRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { Direction.Value = ScrollingDirection.Down; - TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450); + TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450); } public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 8966b5058f..0de86c2149 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mania { public class ManiaRuleset : Ruleset { - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableManiaRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableManiaRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score); diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 0718de2c7d..f26526fe70 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.UI private readonly Bindable configDirection = new Bindable(); - public DrawableManiaRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + public DrawableManiaRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { // Generate the bar lines diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index d50d4f401c..3bbfc25d22 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu { public class OsuRuleset : Ruleset { - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableOsuRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableOsuRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap); diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index d185d7d4c9..aa61fb6922 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.UI { protected new OsuRulesetConfigManager Config => (OsuRulesetConfigManager)base.Config; - public DrawableOsuRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + public DrawableOsuRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 83356b77c2..6d0a5eb1e1 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko { public class TaikoRuleset : Ruleset { - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableTaikoRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableTaikoRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap); public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index ec3a56e9c7..b03bea578e 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool UserScrollSpeedAdjustment => false; - public DrawableTaikoRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + public DrawableTaikoRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { Direction.Value = ScrollingDirection.Left; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index ee11fc0d06..60ace8ea69 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -174,7 +174,7 @@ namespace osu.Game.Tests.Visual.Gameplay public override IEnumerable GetModsFor(ModType type) => throw new NotImplementedException(); - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new TestDrawableScrollingRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new TestDrawableScrollingRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TestBeatmapConverter(beatmap); @@ -193,7 +193,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping; - public TestDrawableScrollingRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + public TestDrawableScrollingRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { TimeRange.Value = time_range; diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 3a4c677bd1..29ade24328 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -54,7 +54,7 @@ namespace osu.Game.Beatmaps { public override IEnumerable GetModsFor(ModType type) => new Mod[] { }; - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) + public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) { throw new NotImplementedException(); } diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs new file mode 100644 index 0000000000..aea3751bb5 --- /dev/null +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -0,0 +1,61 @@ +// 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.Audio.Track; +using osu.Framework.Graphics.Textures; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.UI; +using osu.Game.Skinning; +using osu.Game.Storyboards; + +namespace osu.Game.Beatmaps +{ + public interface IWorkingBeatmap + { + /// + /// Retrieves the which this represents. + /// + IBeatmap Beatmap { get; } + + /// + /// Retrieves the background for this . + /// + Texture Background { get; } + + /// + /// Retrieves the audio track for this . + /// + Track Track { get; } + + /// + /// Retrieves the for the of this . + /// + Waveform Waveform { get; } + + /// + /// Retrieves the which this provides. + /// + Storyboard Storyboard { get; } + + /// + /// Retrieves the which this provides. + /// + Skin Skin { get; } + + /// + /// Constructs a playable from using the applicable converters for a specific . + /// + /// The returned is in a playable state - all and s + /// have been applied, and s have been fully constructed. + /// + /// + /// The to create a playable for. + /// The s to apply to the . + /// The converted . + /// If could not be converted to . + IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods); + } +} diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 8605caa5fe..1cce3dc5fe 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -16,14 +16,13 @@ using osu.Framework.Audio; using osu.Framework.Statistics; using osu.Game.IO.Serialization; using osu.Game.Rulesets; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.UI; using osu.Game.Skinning; namespace osu.Game.Beatmaps { - public abstract class WorkingBeatmap : IDisposable + public abstract class WorkingBeatmap : IWorkingBeatmap, IDisposable { public readonly BeatmapInfo BeatmapInfo; @@ -97,18 +96,7 @@ namespace osu.Game.Beatmaps /// The applicable . protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap); - /// - /// Constructs a playable from using the applicable converters for a specific . - /// - /// The returned is in a playable state - all and s - /// have been applied, and s have been fully constructed. - /// - /// - /// The to create a playable for. - /// The s to apply to the . - /// The converted . - /// If could not be converted to . - public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods) + public virtual IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods) { var rulesetInstance = ruleset.CreateInstance(); diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index d7ee63d4a4..fc324d7021 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Edit protected readonly Ruleset Ruleset; - private IBindable workingBeatmap; + private IWorkingBeatmap workingBeatmap; private Beatmap playableBeatmap; private EditorBeatmap editorBeatmap; private IBeatmapProcessor beatmapProcessor; @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Edit { try { - drawableRulesetWrapper = new DrawableEditRulesetWrapper(CreateDrawableRuleset(Ruleset, workingBeatmap.Value, Array.Empty())) + drawableRulesetWrapper = new DrawableEditRulesetWrapper(CreateDrawableRuleset(Ruleset, workingBeatmap, Array.Empty())) { Clock = framedClock }; @@ -122,8 +122,10 @@ namespace osu.Game.Rulesets.Edit protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - workingBeatmap = parent.Get>().GetBoundCopy(); - playableBeatmap = (Beatmap)workingBeatmap.Value.GetPlayableBeatmap(Ruleset.RulesetInfo, Array.Empty()); + var parentWorkingBeatmap = parent.Get>().Value; + + playableBeatmap = (Beatmap)parentWorkingBeatmap.GetPlayableBeatmap(Ruleset.RulesetInfo, Array.Empty()); + workingBeatmap = new EditorWorkingBeatmap(playableBeatmap, parentWorkingBeatmap); beatmapProcessor = Ruleset.CreateBeatmapProcessor(playableBeatmap); @@ -178,7 +180,7 @@ namespace osu.Game.Rulesets.Edit protected abstract IReadOnlyList CompositionTools { get; } - protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods); + protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods); public void BeginPlacement(HitObject hitObject) { diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 42b1322cae..b089840f1e 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets /// The s to apply. /// Unable to successfully load the beatmap to be usable with this ruleset. /// - public abstract DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods); + public abstract DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods); /// /// Creates a to convert a to one that is applicable for this . diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index ccfd89adca..021bd515b5 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.UI /// The ruleset being represented. /// The beatmap to create the hit renderer for. /// The s to apply. - protected DrawableRuleset(Ruleset ruleset, WorkingBeatmap workingBeatmap, IReadOnlyList mods) + protected DrawableRuleset(Ruleset ruleset, IWorkingBeatmap workingBeatmap, IReadOnlyList mods) : base(ruleset) { if (workingBeatmap == null) diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index c1a4c9520e..64e491858b 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.UI.Scrolling [Cached(Type = typeof(IScrollingInfo))] private readonly LocalScrollingInfo scrollingInfo; - protected DrawableScrollingRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + protected DrawableScrollingRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { scrollingInfo = new LocalScrollingInfo(); From 6641811125b938aa1511325938504f4be4e876a0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 19:40:56 +0900 Subject: [PATCH 0945/2815] Add EditorWorkingBeatmap for reuse of the playable beatmap --- .../Edit/DrawableManiaEditRuleset.cs | 2 +- .../Edit/ManiaHitObjectComposer.cs | 2 +- .../Edit/DrawableOsuEditRuleset.cs | 2 +- .../Edit/OsuHitObjectComposer.cs | 2 +- osu.Game/Screens/Edit/EditorWorkingBeatmap.cs | 42 +++++++++++++++++++ 5 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Screens/Edit/EditorWorkingBeatmap.cs diff --git a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs index e5f379f608..97d8aaa052 100644 --- a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs +++ b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Edit { public new IScrollingInfo ScrollingInfo => base.ScrollingInfo; - public DrawableManiaEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + public DrawableManiaEditRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 3a28149946..0bfe6f9517 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.Edit public int TotalColumns => ((ManiaPlayfield)drawableRuleset.Playfield).TotalColumns; - protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) { drawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap, mods); diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index bcb6099cfb..cc08d356f9 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Edit { public class DrawableOsuEditRuleset : DrawableOsuRuleset { - public DrawableOsuEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + public DrawableOsuEditRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index c5452ae0aa..1c040e9dee 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Edit { } - protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableOsuEditRuleset(ruleset, beatmap, mods); protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] diff --git a/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs b/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs new file mode 100644 index 0000000000..8bec68596c --- /dev/null +++ b/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs @@ -0,0 +1,42 @@ +// 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.Audio.Track; +using osu.Framework.Graphics.Textures; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Skinning; +using osu.Game.Storyboards; + +namespace osu.Game.Screens.Edit +{ + public class EditorWorkingBeatmap : IWorkingBeatmap + where TObject : HitObject + { + private readonly Beatmap playableBeatmap; + private readonly WorkingBeatmap workingBeatmap; + + public EditorWorkingBeatmap(Beatmap playableBeatmap, WorkingBeatmap workingBeatmap) + { + this.playableBeatmap = playableBeatmap; + this.workingBeatmap = workingBeatmap; + } + + public IBeatmap Beatmap => workingBeatmap.Beatmap; + + public Texture Background => workingBeatmap.Background; + + public Track Track => workingBeatmap.Track; + + public Waveform Waveform => workingBeatmap.Waveform; + + public Storyboard Storyboard => workingBeatmap.Storyboard; + + public Skin Skin => workingBeatmap.Skin; + + public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods) => playableBeatmap; + } +} From ae0a5504d705fb4ed1dd95751e71d581f40cf636 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 19:43:33 +0900 Subject: [PATCH 0946/2815] Revert unnecessary change --- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 1cce3dc5fe..90dde4239c 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -96,7 +96,7 @@ namespace osu.Game.Beatmaps /// The applicable . protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap); - public virtual IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods) + public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods) { var rulesetInstance = ruleset.CreateInstance(); From ec6a40af339a78b2d172b69089ea77b6afca8c5e Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 29 Aug 2019 15:32:21 +0300 Subject: [PATCH 0947/2815] Add adjustments on channel creation if there is --- osu.Game/Skinning/SkinnableSound.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index cb511fc775..e1d9b231dc 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -15,6 +15,9 @@ namespace osu.Game.Skinning public class SkinnableSound : SkinReloadableDrawable { private readonly ISampleInfo[] hitSamples; + + private readonly List<(AdjustableProperty, BindableDouble)> adjustments = new List<(AdjustableProperty, BindableDouble)>(); + private SampleChannel[] channels; private AudioManager audio; @@ -51,8 +54,17 @@ namespace osu.Game.Skinning public void Play() => channels?.ForEach(c => c.Play()); public void Stop() => channels?.ForEach(c => c.Stop()); - public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => channels?.ForEach(c => c.AddAdjustment(type, adjustBindable)); - public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => channels?.ForEach(c => c.RemoveAdjustment(type, adjustBindable)); + public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable) + { + adjustments.Add((type, adjustBindable)); + channels?.ForEach(c => c.AddAdjustment(type, adjustBindable)); + } + + public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable) + { + adjustments.Remove((type, adjustBindable)); + channels?.ForEach(c => c.RemoveAdjustment(type, adjustBindable)); + } public override bool IsPresent => Scheduler.HasPendingTasks; @@ -71,6 +83,9 @@ namespace osu.Game.Skinning { ch.Looping = looping; ch.Volume.Value = s.Volume / 100.0; + + foreach (var adjustment in adjustments) + ch.AddAdjustment(adjust.Item1, adjust.Item2); } return ch; From 06224a7d4ee9921180946040f62811d75689a406 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Thu, 29 Aug 2019 15:38:33 +0300 Subject: [PATCH 0948/2815] Fix build issue --- osu.Game/Skinning/SkinnableSound.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index e1d9b231dc..bf647baeec 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -85,7 +85,7 @@ namespace osu.Game.Skinning ch.Volume.Value = s.Volume / 100.0; foreach (var adjustment in adjustments) - ch.AddAdjustment(adjust.Item1, adjust.Item2); + ch.AddAdjustment(adjustment.Item1, adjustment.Item2); } return ch; From 3f500131d483b5ebbdc60fb4c905de2e754965d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 11:33:50 +0900 Subject: [PATCH 0949/2815] Add basic xmldoc --- osu.Game/Screens/Edit/EditorWorkingBeatmap.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs b/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs index 8bec68596c..45fca493a2 100644 --- a/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs @@ -13,6 +13,10 @@ using osu.Game.Storyboards; namespace osu.Game.Screens.Edit { + /// + /// Encapsulates a while providing an overridden . + /// + /// public class EditorWorkingBeatmap : IWorkingBeatmap where TObject : HitObject { From bfbec067b13ba7546b1ece8f460b4c433200564c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 11:54:36 +0900 Subject: [PATCH 0950/2815] Remove remnants of user skin PR --- osu.Game/Skinning/SkinManager.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 0e40eb5376..a713933c6e 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -60,9 +60,6 @@ namespace osu.Game.Skinning }; } - private Skin createIfNotExisting(SkinInfo skinInfo) => - GetSkin(Query(s => s.Name == skinInfo.Name) ?? Import(skinInfo).Result); - protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == ".osk"; /// @@ -110,9 +107,6 @@ namespace osu.Game.Skinning /// A instance correlating to the provided . public Skin GetSkin(SkinInfo skinInfo) { - if (skinInfo == null) - return null; - if (skinInfo == SkinInfo.Default) return new DefaultSkin(); From 8fe37d0c43aec1182f9b437ba16cce2103a6a526 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 12:35:44 +0900 Subject: [PATCH 0951/2815] 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 0f6e32d664..3854cab34f 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -60,7 +60,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d791909372..5563f9efeb 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -14,7 +14,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 9fc472bf40..a430ff59ab 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -117,7 +117,7 @@ - + From 9c622680e32a5f21777ffb4aee13acbee1a7fe86 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 12:35:53 +0900 Subject: [PATCH 0952/2815] 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 3854cab34f..2c3c8bcaad 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -61,6 +61,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 5563f9efeb..8e6ce2d1ba 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -15,7 +15,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index a430ff59ab..47cc6ec97a 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,8 +118,8 @@ - - + + From 84e474826817f394594a0973016492d3bda740d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 12:59:58 +0900 Subject: [PATCH 0953/2815] Remove duplicate getAnimation function and improve namespacing --- osu.Game.Rulesets.Osu/OsuLegacySkin.cs | 236 ------------------ osu.Game.Rulesets.Osu/OsuRuleset.cs | 1 + .../Skinning/LegacyMainCirclePiece.cs | 81 ++++++ .../Skinning/LegacySliderBall.cs | 44 ++++ .../Skinning/OsuLegacySkin.cs | 97 +++++++ osu.Game/Beatmaps/IWorkingBeatmap.cs | 2 +- osu.Game/Screens/Edit/EditorWorkingBeatmap.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 40 +-- osu.Game/Skinning/LegacySkinExtensions.cs | 53 ++++ 9 files changed, 279 insertions(+), 277 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/OsuLegacySkin.cs create mode 100644 osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs create mode 100644 osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs create mode 100644 osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs create mode 100644 osu.Game/Skinning/LegacySkinExtensions.cs diff --git a/osu.Game.Rulesets.Osu/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/OsuLegacySkin.cs deleted file mode 100644 index d4b00ab911..0000000000 --- a/osu.Game.Rulesets.Osu/OsuLegacySkin.cs +++ /dev/null @@ -1,236 +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 System; -using osu.Framework.Allocation; -using osu.Framework.Audio.Sample; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Animations; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Game.Audio; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Skinning; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Rulesets.Osu -{ - public class OsuLegacySkin : ISkin - { - private readonly ISkin source; - - private Lazy configuration; - - private Lazy hasHitCircle; - - /// - /// On osu-stable, hitcircles have 5 pixels of transparent padding on each side to allow for shadows etc. - /// Their hittable area is 128px, but the actual circle portion is 118px. - /// We must account for some gameplay elements such as slider bodies, where this padding is not present. - /// - private const float legacy_circle_radius = 64 - 5; - - public OsuLegacySkin(ISkinSource source) - { - this.source = source; - - source.SourceChanged += sourceChanged; - sourceChanged(); - } - - private void sourceChanged() - { - // these need to be lazy in order to ensure they aren't called before the dependencies have been loaded into our source. - configuration = new Lazy(() => - { - var config = new SkinConfiguration(); - if (hasHitCircle.Value) - config.SliderPathRadius = legacy_circle_radius; - - // defaults should only be applied for non-beatmap skins (which are parsed via this constructor). - config.CustomColours["SliderBall"] = - source.GetValue(s => s.CustomColours.TryGetValue("SliderBall", out var val) ? val : (Color4?)null) - ?? new Color4(2, 170, 255, 255); - - return config; - }); - - hasHitCircle = new Lazy(() => source.GetTexture("hitcircle") != null); - } - - private const double default_frame_time = 1000 / 60d; - - public Drawable GetDrawableComponent(string componentName) - { - switch (componentName) - { - case "Play/osu/sliderball": - var sliderBallContent = getAnimation("sliderb", true, true, ""); - - if (sliderBallContent != null) - { - var size = sliderBallContent.Size; - - sliderBallContent.RelativeSizeAxes = Axes.Both; - sliderBallContent.Size = Vector2.One; - - return new LegacySliderBall(sliderBallContent) - { - Size = size - }; - } - - return null; - - case "Play/osu/hitcircle": - if (hasHitCircle.Value) - return new LegacyMainCirclePiece(); - - return null; - } - - return null; - } - - private Drawable getAnimation(string componentName, bool animatable, bool looping, string animationSeparator = "-") - { - Texture texture; - - Texture getFrameTexture(int frame) => source.GetTexture($"{componentName}{animationSeparator}{frame}"); - - TextureAnimation animation = null; - - if (animatable) - { - for (int i = 0;; i++) - { - if ((texture = getFrameTexture(i)) == null) - break; - - if (animation == null) - animation = new TextureAnimation - { - DefaultFrameLength = default_frame_time, - Repeat = looping - }; - - animation.AddFrame(texture); - } - } - - if (animation != null) - return animation; - - if ((texture = source.GetTexture(componentName)) != null) - return new Sprite { Texture = texture }; - - return null; - } - - public Texture GetTexture(string componentName) => null; - - public SampleChannel GetSample(ISampleInfo sample) => null; - - public TValue GetValue(Func query) where TConfiguration : SkinConfiguration - => configuration.Value is TConfiguration conf ? query.Invoke(conf) : default; - - public class LegacySliderBall : CompositeDrawable - { - private readonly Drawable animationContent; - - public LegacySliderBall(Drawable animationContent) - { - this.animationContent = animationContent; - } - - [BackgroundDependencyLoader] - private void load(ISkinSource skin, DrawableHitObject drawableObject) - { - animationContent.Colour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? Color4.White; - - InternalChildren = new[] - { - new Sprite - { - Texture = skin.GetTexture("sliderb-nd"), - Colour = new Color4(5, 5, 5, 255), - }, - animationContent, - new Sprite - { - Texture = skin.GetTexture("sliderb-spec"), - Blending = BlendingParameters.Additive, - }, - }; - } - } - - public class LegacyMainCirclePiece : CompositeDrawable - { - public LegacyMainCirclePiece() - { - Size = new Vector2(128); - } - - private readonly IBindable state = new Bindable(); - - private readonly Bindable accentColour = new Bindable(); - - [BackgroundDependencyLoader] - private void load(DrawableHitObject drawableObject, ISkinSource skin) - { - Sprite hitCircleSprite; - - InternalChildren = new Drawable[] - { - hitCircleSprite = new Sprite - { - Texture = skin.GetTexture("hitcircle"), - Colour = drawableObject.AccentColour.Value, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new SkinnableSpriteText("Play/osu/number-text", _ => new OsuSpriteText - { - Font = OsuFont.Numeric.With(size: 40), - UseFullGlyphHeight = false, - }, confineMode: ConfineMode.NoScaling) - { - Text = (((IHasComboInformation)drawableObject.HitObject).IndexInCurrentCombo + 1).ToString() - }, - new Sprite - { - Texture = skin.GetTexture("hitcircleoverlay"), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - } - }; - - state.BindTo(drawableObject.State); - state.BindValueChanged(updateState, true); - - accentColour.BindTo(drawableObject.AccentColour); - accentColour.BindValueChanged(colour => hitCircleSprite.Colour = colour.NewValue, true); - } - - private void updateState(ValueChangedEvent state) - { - const double legacy_fade_duration = 240; - - switch (state.NewValue) - { - case ArmedState.Hit: - this.FadeOut(legacy_fade_duration, Easing.Out); - this.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); - break; - } - } - } - } -} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index e2c64bbedf..49676933e1 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -23,6 +23,7 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Difficulty; +using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Scoring; using osu.Game.Skinning; diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs new file mode 100644 index 0000000000..a7906ddd24 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.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 osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Skinning +{ + public class LegacyMainCirclePiece : CompositeDrawable + { + public LegacyMainCirclePiece() + { + Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); + } + + private readonly IBindable state = new Bindable(); + + private readonly Bindable accentColour = new Bindable(); + + [BackgroundDependencyLoader] + private void load(DrawableHitObject drawableObject, ISkinSource skin) + { + Sprite hitCircleSprite; + + InternalChildren = new Drawable[] + { + hitCircleSprite = new Sprite + { + Texture = skin.GetTexture("hitcircle"), + Colour = drawableObject.AccentColour.Value, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new SkinnableSpriteText("Play/osu/number-text", _ => new OsuSpriteText + { + Font = OsuFont.Numeric.With(size: 40), + UseFullGlyphHeight = false, + }, confineMode: ConfineMode.NoScaling) + { + Text = (((IHasComboInformation)drawableObject.HitObject).IndexInCurrentCombo + 1).ToString() + }, + new Sprite + { + Texture = skin.GetTexture("hitcircleoverlay"), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }; + + state.BindTo(drawableObject.State); + state.BindValueChanged(updateState, true); + + accentColour.BindTo(drawableObject.AccentColour); + accentColour.BindValueChanged(colour => hitCircleSprite.Colour = colour.NewValue, true); + } + + private void updateState(ValueChangedEvent state) + { + const double legacy_fade_duration = 240; + + switch (state.NewValue) + { + case ArmedState.Hit: + this.FadeOut(legacy_fade_duration, Easing.Out); + this.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); + break; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs new file mode 100644 index 0000000000..ec838c596d --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs @@ -0,0 +1,44 @@ +// 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.Sprites; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Skinning; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Skinning +{ + public class LegacySliderBall : CompositeDrawable + { + private readonly Drawable animationContent; + + public LegacySliderBall(Drawable animationContent) + { + this.animationContent = animationContent; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin, DrawableHitObject drawableObject) + { + animationContent.Colour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? Color4.White; + + InternalChildren = new[] + { + new Sprite + { + Texture = skin.GetTexture("sliderb-nd"), + Colour = new Color4(5, 5, 5, 255), + }, + animationContent, + new Sprite + { + Texture = skin.GetTexture("sliderb-spec"), + Blending = BlendingParameters.Additive, + }, + }; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs new file mode 100644 index 0000000000..927cbc5d2f --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs @@ -0,0 +1,97 @@ +// 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.Audio.Sample; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Textures; +using osu.Game.Audio; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Skinning +{ + public class OsuLegacySkin : ISkin + { + private readonly ISkin source; + + private Lazy configuration; + + private Lazy hasHitCircle; + + /// + /// On osu-stable, hitcircles have 5 pixels of transparent padding on each side to allow for shadows etc. + /// Their hittable area is 128px, but the actual circle portion is 118px. + /// We must account for some gameplay elements such as slider bodies, where this padding is not present. + /// + private const float legacy_circle_radius = 64 - 5; + + public OsuLegacySkin(ISkinSource source) + { + this.source = source; + + source.SourceChanged += sourceChanged; + sourceChanged(); + } + + private void sourceChanged() + { + // these need to be lazy in order to ensure they aren't called before the dependencies have been loaded into our source. + configuration = new Lazy(() => + { + var config = new SkinConfiguration(); + if (hasHitCircle.Value) + config.SliderPathRadius = legacy_circle_radius; + + // defaults should only be applied for non-beatmap skins (which are parsed via this constructor). + config.CustomColours["SliderBall"] = + source.GetValue(s => s.CustomColours.TryGetValue("SliderBall", out var val) ? val : (Color4?)null) + ?? new Color4(2, 170, 255, 255); + + return config; + }); + + hasHitCircle = new Lazy(() => source.GetTexture("hitcircle") != null); + } + + public Drawable GetDrawableComponent(string componentName) + { + switch (componentName) + { + case "Play/osu/sliderball": + var sliderBallContent = this.GetAnimation("sliderb", true, true, ""); + + if (sliderBallContent != null) + { + var size = sliderBallContent.Size; + + sliderBallContent.RelativeSizeAxes = Axes.Both; + sliderBallContent.Size = Vector2.One; + + return new LegacySliderBall(sliderBallContent) + { + Size = size + }; + } + + return null; + + case "Play/osu/hitcircle": + if (hasHitCircle.Value) + return new LegacyMainCirclePiece(); + + return null; + } + + return null; + } + + public Texture GetTexture(string componentName) => null; + + public SampleChannel GetSample(ISampleInfo sample) => null; + + public TValue GetValue(Func query) where TConfiguration : SkinConfiguration + => configuration.Value is TConfiguration conf ? query.Invoke(conf) : default; + } +} diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index aea3751bb5..44071d9cc1 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -43,7 +43,7 @@ namespace osu.Game.Beatmaps /// /// Retrieves the which this provides. /// - Skin Skin { get; } + ISkin Skin { get; } /// /// Constructs a playable from using the applicable converters for a specific . diff --git a/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs b/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs index 45fca493a2..299059407c 100644 --- a/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Edit public Storyboard Storyboard => workingBeatmap.Storyboard; - public Skin Skin => workingBeatmap.Skin; + public ISkin Skin => workingBeatmap.Skin; public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods) => playableBeatmap; } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 8dfefcfa1b..d567e48d9a 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -10,7 +10,6 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Graphics; -using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; @@ -60,8 +59,6 @@ namespace osu.Game.Skinning Samples?.Dispose(); } - private const double default_frame_time = 1000 / 60d; - public override Drawable GetDrawableComponent(string componentName) { bool animatable = false; @@ -114,7 +111,7 @@ namespace osu.Game.Skinning }; } - return getAnimation(componentName, animatable, looping); + return this.GetAnimation(componentName, animatable, looping); } public override Texture GetTexture(string componentName) @@ -161,41 +158,6 @@ namespace osu.Game.Skinning return componentName.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece; } - private Drawable getAnimation(string componentName, bool animatable, bool looping, string animationSeparator = "-") - { - Texture texture; - - Texture getFrameTexture(int frame) => GetTexture($"{componentName}{animationSeparator}{frame}"); - - TextureAnimation animation = null; - - if (animatable) - { - for (int i = 0;; i++) - { - if ((texture = getFrameTexture(i)) == null) - break; - - if (animation == null) - animation = new TextureAnimation - { - DefaultFrameLength = default_frame_time, - Repeat = looping - }; - - animation.AddFrame(texture); - } - } - - if (animation != null) - return animation; - - if ((texture = GetTexture(componentName)) != null) - return new Sprite { Texture = texture }; - - return null; - } - protected class LegacySkinResourceStore : IResourceStore where T : INamedFileInfo { diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs new file mode 100644 index 0000000000..c5582af836 --- /dev/null +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -0,0 +1,53 @@ +// 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.Animations; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; + +namespace osu.Game.Skinning +{ + public static class LegacySkinExtensions + { + public static Drawable GetAnimation(this ISkin source, string componentName, bool animatable, bool looping, string animationSeparator = "-") + { + const double default_frame_time = 1000 / 60d; + + Texture texture; + + Texture getFrameTexture(int frame) => source.GetTexture($"{componentName}{animationSeparator}{frame}"); + + TextureAnimation animation = null; + + if (animatable) + { + for (int i = 0;; i++) + { + if ((texture = getFrameTexture(i)) == null) + break; + + if (animation == null) + animation = new TextureAnimation + { + DefaultFrameLength = default_frame_time, + Repeat = looping + }; + + animation.AddFrame(texture); + } + } + + if (animation != null) + return animation; + + if ((texture = source.GetTexture(componentName)) != null) + return new Sprite + { + Texture = texture + }; + + return null; + } + } +} From 7bba8ca14bb92bea3f1d6a707ba1c85cc8f8f9a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 13:04:11 +0900 Subject: [PATCH 0954/2815] Split out nested classes --- osu.Game/Skinning/LegacySkin.cs | 112 ------------------- osu.Game/Skinning/LegacySkinResourceStore.cs | 76 +++++++++++++ osu.Game/Skinning/LegacySpriteText.cs | 53 +++++++++ 3 files changed, 129 insertions(+), 112 deletions(-) create mode 100644 osu.Game/Skinning/LegacySkinResourceStore.cs create mode 100644 osu.Game/Skinning/LegacySpriteText.cs diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index d567e48d9a..0151a211d3 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.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 System; -using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -14,11 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; -using osu.Framework.Text; using osu.Game.Audio; -using osu.Game.Database; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.UI; using osuTK; using osuTK.Graphics; @@ -158,111 +151,6 @@ namespace osu.Game.Skinning return componentName.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece; } - protected class LegacySkinResourceStore : IResourceStore - where T : INamedFileInfo - { - private readonly IHasFiles source; - private readonly IResourceStore underlyingStore; - - private string getPathForFile(string filename) - { - bool hasExtension = filename.Contains('.'); - - var file = source.Files.Find(f => - string.Equals(hasExtension ? f.Filename : Path.ChangeExtension(f.Filename, null), filename, StringComparison.InvariantCultureIgnoreCase)); - return file?.FileInfo.StoragePath; - } - - public LegacySkinResourceStore(IHasFiles source, IResourceStore underlyingStore) - { - this.source = source; - this.underlyingStore = underlyingStore; - } - - public Stream GetStream(string name) - { - string path = getPathForFile(name); - return path == null ? null : underlyingStore.GetStream(path); - } - - public IEnumerable GetAvailableResources() => source.Files.Select(f => f.Filename); - - byte[] IResourceStore.Get(string name) => GetAsync(name).Result; - - public Task GetAsync(string name) - { - string path = getPathForFile(name); - return path == null ? Task.FromResult(null) : underlyingStore.GetAsync(path); - } - - #region IDisposable Support - - private bool isDisposed; - - protected virtual void Dispose(bool disposing) - { - if (!isDisposed) - { - isDisposed = true; - } - } - - ~LegacySkinResourceStore() - { - Dispose(false); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - #endregion - } - - private class LegacySpriteText : OsuSpriteText - { - private readonly LegacyGlyphStore glyphStore; - - public LegacySpriteText(ISkin skin, string font) - { - Shadow = false; - UseFullGlyphHeight = false; - - Font = new FontUsage(font, OsuFont.DEFAULT_FONT_SIZE); - glyphStore = new LegacyGlyphStore(skin); - } - - protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore); - - private class LegacyGlyphStore : ITexturedGlyphLookupStore - { - private readonly ISkin skin; - - public LegacyGlyphStore(ISkin skin) - { - this.skin = skin; - } - - public ITexturedCharacterGlyph Get(string fontName, char character) - { - var texture = skin.GetTexture($"{fontName}-{character}"); - - if (texture != null) - // Approximate value that brings character sizing roughly in-line with stable - texture.ScaleAdjust *= 18; - - if (texture == null) - return null; - - return new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, null), texture, 1f / texture.ScaleAdjust); - } - - public Task GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character)); - } - } - public class LegacyCursor : CompositeDrawable { public LegacyCursor() diff --git a/osu.Game/Skinning/LegacySkinResourceStore.cs b/osu.Game/Skinning/LegacySkinResourceStore.cs new file mode 100644 index 0000000000..c8912aeb1f --- /dev/null +++ b/osu.Game/Skinning/LegacySkinResourceStore.cs @@ -0,0 +1,76 @@ +// 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.IO; +using System.Linq; +using System.Threading.Tasks; +using osu.Framework.IO.Stores; +using osu.Game.Database; + +namespace osu.Game.Skinning +{ + public class LegacySkinResourceStore : IResourceStore + where T : INamedFileInfo + { + private readonly IHasFiles source; + private readonly IResourceStore underlyingStore; + + private string getPathForFile(string filename) + { + bool hasExtension = filename.Contains('.'); + + var file = source.Files.Find(f => + string.Equals(hasExtension ? f.Filename : Path.ChangeExtension(f.Filename, null), filename, StringComparison.InvariantCultureIgnoreCase)); + return file?.FileInfo.StoragePath; + } + + public LegacySkinResourceStore(IHasFiles source, IResourceStore underlyingStore) + { + this.source = source; + this.underlyingStore = underlyingStore; + } + + public Stream GetStream(string name) + { + string path = getPathForFile(name); + return path == null ? null : underlyingStore.GetStream(path); + } + + public IEnumerable GetAvailableResources() => source.Files.Select(f => f.Filename); + + byte[] IResourceStore.Get(string name) => GetAsync(name).Result; + + public Task GetAsync(string name) + { + string path = getPathForFile(name); + return path == null ? Task.FromResult(null) : underlyingStore.GetAsync(path); + } + + #region IDisposable Support + + private bool isDisposed; + + protected virtual void Dispose(bool disposing) + { + if (!isDisposed) + { + isDisposed = true; + } + } + + ~LegacySkinResourceStore() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + #endregion + } +} diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs new file mode 100644 index 0000000000..dbcec019d6 --- /dev/null +++ b/osu.Game/Skinning/LegacySpriteText.cs @@ -0,0 +1,53 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Threading.Tasks; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Text; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Skinning +{ + public class LegacySpriteText : OsuSpriteText + { + private readonly LegacyGlyphStore glyphStore; + + public LegacySpriteText(ISkin skin, string font) + { + Shadow = false; + UseFullGlyphHeight = false; + + Font = new FontUsage(font, OsuFont.DEFAULT_FONT_SIZE); + glyphStore = new LegacyGlyphStore(skin); + } + + protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore); + + private class LegacyGlyphStore : ITexturedGlyphLookupStore + { + private readonly ISkin skin; + + public LegacyGlyphStore(ISkin skin) + { + this.skin = skin; + } + + public ITexturedCharacterGlyph Get(string fontName, char character) + { + var texture = skin.GetTexture($"{fontName}-{character}"); + + if (texture != null) + // Approximate value that brings character sizing roughly in-line with stable + texture.ScaleAdjust *= 18; + + if (texture == null) + return null; + + return new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, null), texture, 1f / texture.ScaleAdjust); + } + + public Task GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character)); + } + } +} From c389a5c798974d33f1b8728b09b99ebdd7e23137 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 13:42:29 +0900 Subject: [PATCH 0955/2815] Move remaining osu-specific implementations to OsuLegacySkin --- .../Skinning/LegacyCursor.cs | 42 +++++++++++ .../Skinning/NonPlayfieldSprite.cs | 28 +++++++ .../Skinning/OsuLegacySkin.cs | 22 ++++++ osu.Game/Skinning/LegacySkin.cs | 73 ------------------- 4 files changed, 92 insertions(+), 73 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs create mode 100644 osu.Game.Rulesets.Osu/Skinning/NonPlayfieldSprite.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs new file mode 100644 index 0000000000..470ba3acae --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs @@ -0,0 +1,42 @@ +// 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.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Skinning +{ + public class LegacyCursor : CompositeDrawable + { + public LegacyCursor() + { + Size = new Vector2(50); + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + InternalChildren = new Drawable[] + { + new NonPlayfieldSprite + { + Texture = skin.GetTexture("cursormiddle"), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new NonPlayfieldSprite + { + Texture = skin.GetTexture("cursor"), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/NonPlayfieldSprite.cs b/osu.Game.Rulesets.Osu/Skinning/NonPlayfieldSprite.cs new file mode 100644 index 0000000000..55257106e2 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/NonPlayfieldSprite.cs @@ -0,0 +1,28 @@ +// 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.Framework.Graphics.Textures; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Osu.Skinning +{ + /// + /// A sprite which is displayed within the playfield, but historically was not considered part of the playfield. + /// Performs scale adjustment to undo the scale applied by (osu! ruleset specifically). + /// + public class NonPlayfieldSprite : Sprite + { + public override Texture Texture + { + get => base.Texture; + set + { + if (value != null) + // stable "magic ratio". see OsuPlayfieldAdjustmentContainer for full explanation. + value.ScaleAdjust *= 1.6f; + base.Texture = value; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs index 927cbc5d2f..27bfdc315b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs @@ -82,6 +82,26 @@ namespace osu.Game.Rulesets.Osu.Skinning return new LegacyMainCirclePiece(); return null; + + case "Play/osu/cursor": + if (GetTexture("cursor") != null) + return new LegacyCursor(); + + return null; + + case "Play/osu/number-text": + + string font = GetValue(config => config.HitCircleFont); + var overlap = GetValue(config => config.HitCircleOverlap); + + return !hasFont(font) + ? null + : new LegacySpriteText(this, font) + { + // Spacing value was reverse-engineered from the ratio of the rendered sprite size in the visual inspector vs the actual texture size + Scale = new Vector2(0.96f), + Spacing = new Vector2(-overlap * 0.89f, 0) + }; } return null; @@ -93,5 +113,7 @@ namespace osu.Game.Rulesets.Osu.Skinning public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => configuration.Value is TConfiguration conf ? query.Invoke(conf) : default; + + private bool hasFont(string fontName) => GetTexture($"{fontName}-0") != null; } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 0151a211d3..9a47e01f4e 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -3,17 +3,12 @@ using System.IO; using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Game.Audio; -using osu.Game.Rulesets.UI; -using osuTK; using osuTK.Graphics; namespace osu.Game.Skinning @@ -59,12 +54,6 @@ namespace osu.Game.Skinning switch (componentName) { - case "Play/osu/cursor": - if (GetTexture("cursor") != null) - return new LegacyCursor(); - - return null; - case "Play/osu/sliderfollowcircle": animatable = true; break; @@ -92,16 +81,6 @@ namespace osu.Game.Skinning animatable = true; looping = false; break; - - case "Play/osu/number-text": - return !hasFont(Configuration.HitCircleFont) - ? null - : new LegacySpriteText(this, Configuration.HitCircleFont) - { - Scale = new Vector2(0.96f), - // Spacing value was reverse-engineered from the ratio of the rendered sprite size in the visual inspector vs the actual texture size - Spacing = new Vector2(-Configuration.HitCircleOverlap * 0.89f, 0) - }; } return this.GetAnimation(componentName, animatable, looping); @@ -143,62 +122,10 @@ namespace osu.Game.Skinning return null; } - private bool hasFont(string fontName) => GetTexture($"{fontName}-0") != null; - private string getFallbackName(string componentName) { string lastPiece = componentName.Split('/').Last(); return componentName.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece; } - - public class LegacyCursor : CompositeDrawable - { - public LegacyCursor() - { - Size = new Vector2(50); - - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - } - - [BackgroundDependencyLoader] - private void load(ISkinSource skin) - { - InternalChildren = new Drawable[] - { - new NonPlayfieldSprite - { - Texture = skin.GetTexture("cursormiddle"), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new NonPlayfieldSprite - { - Texture = skin.GetTexture("cursor"), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - } - }; - } - } - - /// - /// A sprite which is displayed within the playfield, but historically was not considered part of the playfield. - /// Performs scale adjustment to undo the scale applied by (osu! ruleset specifically). - /// - private class NonPlayfieldSprite : Sprite - { - public override Texture Texture - { - get => base.Texture; - set - { - if (value != null) - // stable "magic ratio". see OsuPlayfieldAdjustmentContainer for full explanation. - value.ScaleAdjust *= 1.6f; - base.Texture = value; - } - } - } } } From 22e3ad8b9c388ee6b4ff43c9acb3f186a7070264 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 13:58:17 +0900 Subject: [PATCH 0956/2815] Add skinning support to cursor test --- .../Resources/default-skin/cursor@2x.png | Bin 0 -> 28063 bytes .../default-skin/cursormiddle@2x.png | Bin 0 -> 7676 bytes .../TestSceneGameplayCursor.cs | 18 ++++++------------ .../Skinning/OsuLegacySkin.cs | 2 +- 4 files changed, 7 insertions(+), 13 deletions(-) create mode 100755 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursor@2x.png create mode 100755 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursormiddle@2x.png diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursor@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursor@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..75f9ba5ea6637636912004f8a889f5575d830436 GIT binary patch literal 28063 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursormiddle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursormiddle@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..ebf59c18ba9f62390f49be4bed696a997a51f05c GIT binary patch literal 7676 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 q!@f`~Q0000 RequiredTypes => new[] { typeof(CursorTrail) }; - public CursorContainer Cursor => cursorContainer; - - public bool ProvidingUserCursor => true; - [BackgroundDependencyLoader] private void load() { - Add(cursorContainer = new OsuCursorContainer { RelativeSizeAxes = Axes.Both }); + SetContents(() => new OsuCursorContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + }); } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs index 27bfdc315b..0295a419ac 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Skinning return null; case "Play/osu/cursor": - if (GetTexture("cursor") != null) + if (source.GetTexture("cursor") != null) return new LegacyCursor(); return null; From 8f9fe9923746dfd62231d30dc198bf2458bfa36d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 14:40:36 +0900 Subject: [PATCH 0957/2815] Move SliderFollowCircle implementation --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs | 3 +++ osu.Game/Skinning/LegacySkin.cs | 4 ---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs index 0295a419ac..3c508f34e0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs @@ -59,6 +59,9 @@ namespace osu.Game.Rulesets.Osu.Skinning { switch (componentName) { + case "Play/osu/sliderfollowcircle": + return this.GetAnimation(componentName, true, true); + case "Play/osu/sliderball": var sliderBallContent = this.GetAnimation("sliderb", true, true, ""); diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 9a47e01f4e..1572c588e8 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -54,10 +54,6 @@ namespace osu.Game.Skinning switch (componentName) { - case "Play/osu/sliderfollowcircle": - animatable = true; - break; - case "Play/Miss": componentName = "hit0"; animatable = true; From 493fc5d400d616135e94291f6ec6f498cbd971da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 14:44:36 +0900 Subject: [PATCH 0958/2815] Bring back OsuPlayer test --- .../TestSceneOsuPlayer.cs | 148 +---------------- .../TestSceneSkinFallbacks.cs | 157 ++++++++++++++++++ 2 files changed, 161 insertions(+), 144 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs index d61378f4f8..1ba027ea4b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs @@ -1,157 +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 System; -using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Framework.Timing; -using osu.Game.Audio; -using osu.Game.Beatmaps; -using osu.Game.Configuration; -using osu.Game.Graphics; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Screens.Play; -using osu.Game.Skinning; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { - [TestFixture] - public class TestSceneOsuPlayer : PlayerTestScene - { - private readonly TestSource testUserSkin; - private readonly TestSource testBeatmapSkin; - - public TestSceneOsuPlayer() - : base(new OsuRuleset()) + [TestFixture] + public class TestSceneOsuPlayer : PlayerTestScene { - testUserSkin = new TestSource("user"); - testBeatmapSkin = new TestSource("beatmap"); - } - - [Test] - public void TestBeatmapSkinDefault() - { - AddStep("enable user provider", () => testUserSkin.Enabled = true); - - AddStep("enable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, true)); - checkNextHitObject("beatmap"); - - AddStep("disable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, false)); - checkNextHitObject("user"); - - AddStep("disable user provider", () => testUserSkin.Enabled = false); - checkNextHitObject(null); - } - - private void checkNextHitObject(string skin) => - AddUntilStep($"check skin from {skin}", () => + public TestSceneOsuPlayer() + : base(new OsuRuleset()) { - var firstObject = ((TestPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.OfType().FirstOrDefault(); - - if (firstObject == null) - return false; - - var skinnable = firstObject.ApproachCircle.Child as SkinnableDrawable; - - if (skin == null && skinnable?.Drawable is Sprite) - // check for default skin provider - return true; - - var text = skinnable?.Drawable as SpriteText; - - return text?.Text == skin; - }); - - [Resolved] - private AudioManager audio { get; set; } - - protected override Player CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(testUserSkin); - - protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) => new CustomSkinWorkingBeatmap(beatmap, Clock, audio, testBeatmapSkin); - - public class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap - { - private readonly ISkinSource skin; - - public CustomSkinWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock frameBasedClock, AudioManager audio, ISkinSource skin) - : base(beatmap, frameBasedClock, audio) - { - this.skin = skin; - } - - protected override ISkin GetSkin() => skin; - } - - public class SkinProvidingPlayer : TestPlayer - { - private readonly TestSource userSkin; - - public SkinProvidingPlayer(TestSource userSkin) - { - this.userSkin = userSkin; - } - - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - - dependencies.CacheAs(userSkin); - - return dependencies; } } - - public class TestSource : ISkinSource - { - private readonly string identifier; - - public TestSource(string identifier) - { - this.identifier = identifier; - } - - public Drawable GetDrawableComponent(string componentName) - { - if (!enabled) return null; - - return new SpriteText - { - Text = identifier, - Font = OsuFont.Default.With(size: 30), - }; - } - - public Texture GetTexture(string componentName) => null; - - public SampleChannel GetSample(ISampleInfo sampleInfo) => null; - - public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => default; - - public event Action SourceChanged; - - private bool enabled = true; - - public bool Enabled - { - get => enabled; - set - { - if (value == enabled) - return; - - enabled = value; - SourceChanged?.Invoke(); - } - } - } - } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs new file mode 100644 index 0000000000..24d0b39bed --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -0,0 +1,157 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Timing; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Screens.Play; +using osu.Game.Skinning; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestSceneSkinFallbacks : PlayerTestScene + { + private readonly TestSource testUserSkin; + private readonly TestSource testBeatmapSkin; + + public TestSceneSkinFallbacks() + : base(new OsuRuleset()) + { + testUserSkin = new TestSource("user"); + testBeatmapSkin = new TestSource("beatmap"); + } + + [Test] + public void TestBeatmapSkinDefault() + { + AddStep("enable user provider", () => testUserSkin.Enabled = true); + + AddStep("enable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, true)); + checkNextHitObject("beatmap"); + + AddStep("disable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, false)); + checkNextHitObject("user"); + + AddStep("disable user provider", () => testUserSkin.Enabled = false); + checkNextHitObject(null); + } + + private void checkNextHitObject(string skin) => + AddUntilStep($"check skin from {skin}", () => + { + var firstObject = ((TestPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.OfType().FirstOrDefault(); + + if (firstObject == null) + return false; + + var skinnable = firstObject.ApproachCircle.Child as SkinnableDrawable; + + if (skin == null && skinnable?.Drawable is Sprite) + // check for default skin provider + return true; + + var text = skinnable?.Drawable as SpriteText; + + return text?.Text == skin; + }); + + [Resolved] + private AudioManager audio { get; set; } + + protected override Player CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(testUserSkin); + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) => new CustomSkinWorkingBeatmap(beatmap, Clock, audio, testBeatmapSkin); + + public class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap + { + private readonly ISkinSource skin; + + public CustomSkinWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock frameBasedClock, AudioManager audio, ISkinSource skin) + : base(beatmap, frameBasedClock, audio) + { + this.skin = skin; + } + + protected override ISkin GetSkin() => skin; + } + + public class SkinProvidingPlayer : TestPlayer + { + private readonly TestSource userSkin; + + public SkinProvidingPlayer(TestSource userSkin) + { + this.userSkin = userSkin; + } + + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + dependencies.CacheAs(userSkin); + + return dependencies; + } + } + + public class TestSource : ISkinSource + { + private readonly string identifier; + + public TestSource(string identifier) + { + this.identifier = identifier; + } + + public Drawable GetDrawableComponent(string componentName) + { + if (!enabled) return null; + + return new SpriteText + { + Text = identifier, + Font = OsuFont.Default.With(size: 30), + }; + } + + public Texture GetTexture(string componentName) => null; + + public SampleChannel GetSample(ISampleInfo sampleInfo) => null; + + public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => default; + + public event Action SourceChanged; + + private bool enabled = true; + + public bool Enabled + { + get => enabled; + set + { + if (value == enabled) + return; + + enabled = value; + SourceChanged?.Invoke(); + } + } + } + } +} From c3fb4b9099c9f7dd7e0bd71f6ae14a61d66c6f3d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 14:51:47 +0900 Subject: [PATCH 0959/2815] Fix test failing --- osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index 24d0b39bed..731b0a84e9 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void checkNextHitObject(string skin) => AddUntilStep($"check skin from {skin}", () => { - var firstObject = ((TestPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.OfType().FirstOrDefault(); + var firstObject = ((TestPlayer)Player).DrawableRuleset.Playfield.HitObjectContainer.AliveObjects.OfType().FirstOrDefault(); if (firstObject == null) return false; From ae05faa6d2e96d49c0de9d2791d0bbb4d12c4e21 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 14:54:46 +0900 Subject: [PATCH 0960/2815] Fix indentation --- osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs index 1ba027ea4b..0a33b09ba8 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs @@ -6,12 +6,12 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { - [TestFixture] - public class TestSceneOsuPlayer : PlayerTestScene + [TestFixture] + public class TestSceneOsuPlayer : PlayerTestScene + { + public TestSceneOsuPlayer() + : base(new OsuRuleset()) { - public TestSceneOsuPlayer() - : base(new OsuRuleset()) - { - } } + } } From a15828ab25d6b727618fe4159fbadacb90fc9de5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 14:39:02 +0900 Subject: [PATCH 0961/2815] Introduce the concept of SkinComponents Removes reliance on string lookups and better defines elements for introduction into database --- .../TestSceneCatcher.cs | 6 ++-- osu.Game.Rulesets.Catch/CatchRuleset.cs | 4 ++- osu.Game.Rulesets.Catch/CatchSkinComponent.cs | 19 ++++++++++++ .../CatchSkinComponents.cs | 10 +++++++ osu.Game.Rulesets.Catch/UI/CatcherSprite.cs | 2 +- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 4 ++- osu.Game.Rulesets.Mania/ManiaSkinComponent.cs | 19 ++++++++++++ .../ManiaSkinComponents.cs | 9 ++++++ .../TestSceneSkinFallbacks.cs | 2 +- .../Drawables/Connections/FollowPoint.cs | 2 +- .../Objects/Drawables/DrawableHitCircle.cs | 2 +- .../Objects/Drawables/DrawableRepeatPoint.cs | 2 +- .../Objects/Drawables/DrawableSliderTick.cs | 2 +- .../Drawables/Pieces/ApproachCircle.cs | 6 ++-- .../Objects/Drawables/Pieces/ExplodePiece.cs | 5 ++-- .../Objects/Drawables/Pieces/FlashPiece.cs | 5 ++-- .../Objects/Drawables/Pieces/GlowPiece.cs | 7 ++--- .../Objects/Drawables/Pieces/NumberPiece.cs | 6 ++-- .../Objects/Drawables/Pieces/RingPiece.cs | 5 ++-- .../Objects/Drawables/Pieces/SliderBall.cs | 4 +-- osu.Game.Rulesets.Osu/OsuRuleset.cs | 4 ++- osu.Game.Rulesets.Osu/OsuSkinComponent.cs | 19 ++++++++++++ osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 18 +++++++++++ .../Skinning/LegacyMainCirclePiece.cs | 2 +- .../Skinning/OsuLegacySkin.cs | 22 +++++++------- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 2 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 4 ++- osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs | 19 ++++++++++++ .../TaikoSkinComponents.cs | 9 ++++++ .../Gameplay/TestSceneSkinnableDrawable.cs | 30 ++++++++++++++----- .../Rulesets/Judgements/DrawableJudgement.cs | 2 +- .../Skinning/BeatmapSkinProvidingContainer.cs | 2 +- osu.Game/Skinning/DefaultSkin.cs | 4 +-- osu.Game/Skinning/ISkin.cs | 2 +- osu.Game/Skinning/ISkinComponent.cs | 10 +++++++ osu.Game/Skinning/LegacySkin.cs | 29 +++++------------- osu.Game/Skinning/PlaySkinComponent.cs | 23 ++++++++++++++ osu.Game/Skinning/Skin.cs | 4 +-- osu.Game/Skinning/SkinManager.cs | 4 +-- osu.Game/Skinning/SkinProvidingContainer.cs | 8 ++--- osu.Game/Skinning/SkinnableDrawable.cs | 20 ++++++------- osu.Game/Skinning/SkinnableSprite.cs | 6 ++-- osu.Game/Skinning/SkinnableSpriteText.cs | 4 +-- 43 files changed, 264 insertions(+), 104 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/CatchSkinComponent.cs create mode 100644 osu.Game.Rulesets.Catch/CatchSkinComponents.cs create mode 100644 osu.Game.Rulesets.Mania/ManiaSkinComponent.cs create mode 100644 osu.Game.Rulesets.Mania/ManiaSkinComponents.cs create mode 100644 osu.Game.Rulesets.Osu/OsuSkinComponent.cs create mode 100644 osu.Game.Rulesets.Osu/OsuSkinComponents.cs create mode 100644 osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs create mode 100644 osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs create mode 100644 osu.Game/Skinning/ISkinComponent.cs create mode 100644 osu.Game/Skinning/PlaySkinComponent.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 406c0af28d..13286f4524 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.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 NUnit.Framework; @@ -82,9 +82,9 @@ namespace osu.Game.Rulesets.Catch.Tests remove { } } - public Drawable GetDrawableComponent(string componentName) + public Drawable GetDrawableComponent(ISkinComponent component) { - switch (componentName) + switch (component.LookupName) { case "Play/Catch/fruit-catcher-idle": return new CatcherCustomSkin(); diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 71e05083be..cd2f8d56af 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -27,6 +27,8 @@ namespace osu.Game.Rulesets.Catch public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap); + public const string SHORT_NAME = "catch"; + public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] { new KeyBinding(InputKey.Z, CatchAction.MoveLeft), @@ -117,7 +119,7 @@ namespace osu.Game.Rulesets.Catch public override string Description => "osu!catch"; - public override string ShortName => "fruits"; + public override string ShortName => SHORT_NAME; public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch }; diff --git a/osu.Game.Rulesets.Catch/CatchSkinComponent.cs b/osu.Game.Rulesets.Catch/CatchSkinComponent.cs new file mode 100644 index 0000000000..620720310f --- /dev/null +++ b/osu.Game.Rulesets.Catch/CatchSkinComponent.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.Catch +{ + public class CatchSkinComponent : PlaySkinComponent + { + public CatchSkinComponent(CatchSkinComponents component) + : base(component) + { + } + + protected override string RulesetPrefix => CatchRuleset.SHORT_NAME; + + protected override string ComponentName => Component.ToString().ToLower(); + } +} diff --git a/osu.Game.Rulesets.Catch/CatchSkinComponents.cs b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs new file mode 100644 index 0000000000..c03fe42af7 --- /dev/null +++ b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs @@ -0,0 +1,10 @@ +// 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.Catch +{ + public enum CatchSkinComponents + { + Catcher + } +} diff --git a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs index c0c1952064..1c2fe3517a 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.UI [BackgroundDependencyLoader] private void load() { - InternalChild = new SkinnableSprite(@"Play/Catch/fruit-catcher-idle") + InternalChild = new SkinnableSprite(new CatchSkinComponent(CatchSkinComponents.Catcher)) { RelativeSizeAxes = Axes.Both, Anchor = Anchor.TopCentre, diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 0de86c2149..0c4e7d4858 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -35,6 +35,8 @@ namespace osu.Game.Rulesets.Mania public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score); + public const string SHORT_NAME = "mania"; + public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this); public override IEnumerable ConvertLegacyMods(LegacyMods mods) @@ -163,7 +165,7 @@ namespace osu.Game.Rulesets.Mania public override string Description => "osu!mania"; - public override string ShortName => "mania"; + public override string ShortName => SHORT_NAME; public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetMania }; diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs new file mode 100644 index 0000000000..72a3ce7ad5 --- /dev/null +++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.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 +{ + public class ManiaSkinComponent : PlaySkinComponent + { + public ManiaSkinComponent(ManiaSkinComponents component) + : base(component) + { + } + + protected override string RulesetPrefix => ManiaRuleset.SHORT_NAME; + + protected override string ComponentName => Component.ToString().ToLower(); + } +} diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponents.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponents.cs new file mode 100644 index 0000000000..6d85816e5a --- /dev/null +++ b/osu.Game.Rulesets.Mania/ManiaSkinComponents.cs @@ -0,0 +1,9 @@ +// 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 + { + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index 731b0a84e9..fe73e7c861 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Tests this.identifier = identifier; } - public Drawable GetDrawableComponent(string componentName) + public Drawable GetDrawableComponent(ISkinComponent component) { if (!enabled) return null; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index 523e911434..89ffddf4cb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { Origin = Anchor.Centre; - Child = new SkinnableDrawable("Play/osu/followpoint", _ => new Container + Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.FollowPoint), _ => new Container { Masking = true, AutoSizeAxes = Axes.Both, diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 0af278f6a4..22b12be030 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return true; }, }, - mainContent = new SkinnableDrawable("Play/osu/hitcircle", _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)), + mainContent = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.HitCircle), _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)), ApproachCircle = new ApproachCircle { Alpha = 0, diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index 1db1eec33e..50187781f6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Blending = BlendingParameters.Additive; Origin = Anchor.Centre; - InternalChild = scaleContainer = new SkinnableDrawable("Play/osu/reversearrow", _ => new SpriteIcon + InternalChild = scaleContainer = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.ReverseArrow), _ => new SpriteIcon { RelativeSizeAxes = Axes.Both, Icon = FontAwesome.Solid.ChevronRight, diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index 653e73ac3f..c5fa5f0af5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); Origin = Anchor.Centre; - InternalChild = scaleContainer = new SkinnableDrawable("Play/osu/sliderscorepoint", _ => new CircularContainer + InternalChild = scaleContainer = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderScorePoint), _ => new CircularContainer { Masking = true, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs index 5813197336..c17c276205 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs @@ -31,13 +31,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private class SkinnableApproachCircle : SkinnableSprite { public SkinnableApproachCircle() - : base("Play/osu/approachcircle") + : base(new OsuSkinComponent(OsuSkinComponents.ApproachCircle)) { } - protected override Drawable CreateDefault(string name) + protected override Drawable CreateDefault(ISkinComponent component) { - var drawable = base.CreateDefault(name); + var drawable = base.CreateDefault(component); // account for the sprite being used for the default approach circle being taken from stable, // when hitcircles have 5px padding on each size. this should be removed if we update the sprite. diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs index 1d21347cba..6381ddca69 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs @@ -3,7 +3,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces @@ -20,12 +19,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Blending = BlendingParameters.Additive; Alpha = 0; - Child = new SkinnableDrawable("Play/osu/hitcircle-explode", _ => new TrianglesPiece + Child = new TrianglesPiece { Blending = BlendingParameters.Additive, RelativeSizeAxes = Axes.Both, Alpha = 0.2f, - }, s => s.GetTexture("Play/osu/hitcircle") == null); + }; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs index 1e3af567fe..038a2299e9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs @@ -5,7 +5,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osuTK; using osu.Framework.Graphics.Shapes; -using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { @@ -21,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Blending = BlendingParameters.Additive; Alpha = 0; - Child = new SkinnableDrawable("Play/osu/hitcircle-flash", name => new CircularContainer + Child = new CircularContainer { Masking = true, RelativeSizeAxes = Axes.Both, @@ -29,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { RelativeSizeAxes = Axes.Both } - }, s => s.GetTexture("Play/osu/hitcircle") == null); + }; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs index a36d9e96c8..00188689dd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { @@ -22,14 +21,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces [BackgroundDependencyLoader] private void load(TextureStore textures) { - Child = new SkinnableDrawable("Play/osu/ring-glow", name => new Sprite + Child = new Sprite { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Texture = textures.Get(name), + Texture = textures.Get("ring-glow"), Blending = BlendingParameters.Additive, Alpha = 0.5f - }, s => s.GetTexture("Play/osu/hitcircle") == null); + }; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs index e8dc63abca..62c4ba5ee3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Children = new Drawable[] { - new SkinnableDrawable("Play/osu/number-glow", name => new CircularContainer + new CircularContainer { Masking = true, Origin = Anchor.Centre, @@ -41,8 +41,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Colour = Color4.White.Opacity(0.5f), }, Child = new Box() - }, s => s.GetTexture("Play/osu/hitcircle") == null), - number = new SkinnableSpriteText("Play/osu/number-text", _ => new OsuSpriteText + }, + number = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText { Font = OsuFont.Numeric.With(size: 40), UseFullGlyphHeight = false, diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs index 575f2c92c5..c97b74756a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics.Containers; using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; -using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { @@ -19,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Anchor = Anchor.Centre; Origin = Anchor.Centre; - InternalChild = new SkinnableDrawable("Play/osu/hitcircleoverlay", _ => new Container + InternalChild = new Container { Masking = true, CornerRadius = Size.X / 2, @@ -35,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces RelativeSizeAxes = Axes.Both } } - }); + }; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index 329aed7b81..7c871c6ccd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Anchor = Anchor.Centre, RelativeSizeAxes = Axes.Both, Alpha = 0, - Child = new SkinnableDrawable("Play/osu/sliderfollowcircle", _ => new DefaultFollowCircle()), + Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle()), }, new CircularContainer { @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Child = new Container { RelativeSizeAxes = Axes.Both, - Child = new SkinnableDrawable("Play/osu/sliderball", _ => new DefaultSliderBall()), + Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBall), _ => new DefaultSliderBall()), } } }; diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 49676933e1..27899ab56e 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -35,6 +35,8 @@ namespace osu.Game.Rulesets.Osu public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap); + public const string SHORT_NAME = "osu"; + public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] { new KeyBinding(InputKey.Z, OsuAction.LeftButton), @@ -161,7 +163,7 @@ namespace osu.Game.Rulesets.Osu public override string Description => "osu!"; - public override string ShortName => "osu"; + public override string ShortName => SHORT_NAME; public override RulesetSettingsSubsection CreateSettings() => new OsuSettingsSubsection(this); diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponent.cs b/osu.Game.Rulesets.Osu/OsuSkinComponent.cs new file mode 100644 index 0000000000..ef0df6cbda --- /dev/null +++ b/osu.Game.Rulesets.Osu/OsuSkinComponent.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.Osu +{ + public class OsuSkinComponent : PlaySkinComponent + { + public OsuSkinComponent(OsuSkinComponents component) + : base(component) + { + } + + protected override string RulesetPrefix => OsuRuleset.SHORT_NAME; + + protected override string ComponentName => Component.ToString().ToLower(); + } +} diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs new file mode 100644 index 0000000000..5971f053c2 --- /dev/null +++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs @@ -0,0 +1,18 @@ +// 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.Osu +{ + public enum OsuSkinComponents + { + HitCircle, + FollowPoint, + Cursor, + SliderScorePoint, + ApproachCircle, + ReverseArrow, + HitCircleText, + SliderFollowCircle, + SliderBall + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index a7906ddd24..83d507f64b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Skinning Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - new SkinnableSpriteText("Play/osu/number-text", _ => new OsuSpriteText + new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText { Font = OsuFont.Numeric.With(size: 40), UseFullGlyphHeight = false, diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs index 3c508f34e0..002b3f8cda 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs @@ -55,14 +55,17 @@ namespace osu.Game.Rulesets.Osu.Skinning hasHitCircle = new Lazy(() => source.GetTexture("hitcircle") != null); } - public Drawable GetDrawableComponent(string componentName) + public Drawable GetDrawableComponent(ISkinComponent component) { - switch (componentName) - { - case "Play/osu/sliderfollowcircle": - return this.GetAnimation(componentName, true, true); + if (!(component is OsuSkinComponent osuComponent)) + return null; - case "Play/osu/sliderball": + switch (osuComponent.Component) + { + case OsuSkinComponents.SliderFollowCircle: + return this.GetAnimation("sliderfollowcircle", true, true); + + case OsuSkinComponents.SliderBall: var sliderBallContent = this.GetAnimation("sliderb", true, true, ""); if (sliderBallContent != null) @@ -80,20 +83,19 @@ namespace osu.Game.Rulesets.Osu.Skinning return null; - case "Play/osu/hitcircle": + case OsuSkinComponents.HitCircle: if (hasHitCircle.Value) return new LegacyMainCirclePiece(); return null; - case "Play/osu/cursor": + case OsuSkinComponents.Cursor: if (source.GetTexture("cursor") != null) return new LegacyCursor(); return null; - case "Play/osu/number-text": - + case OsuSkinComponents.HitCircleText: string font = GetValue(config => config.HitCircleFont); var overlap = GetValue(config => config.HitCircleOverlap); diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index eb1977a13d..869c27dcac 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, Anchor = Anchor.Centre, - Child = scaleTarget = new SkinnableDrawable("Play/osu/cursor", _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling) + Child = scaleTarget = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling) { Origin = Anchor.Centre, Anchor = Anchor.Centre, diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 6d0a5eb1e1..7fdb823388 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Taiko public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableTaikoRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap); + public const string SHORT_NAME = "taiko"; + public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] { new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre), @@ -116,7 +118,7 @@ namespace osu.Game.Rulesets.Taiko public override string Description => "osu!taiko"; - public override string ShortName => "taiko"; + public override string ShortName => SHORT_NAME; public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetTaiko }; diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs new file mode 100644 index 0000000000..474154279c --- /dev/null +++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponent.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.Taiko +{ + public class TaikoSkinComponent : PlaySkinComponent + { + public TaikoSkinComponent(TaikoSkinComponents component) + : base(component) + { + } + + protected override string RulesetPrefix => TaikoRuleset.SHORT_NAME; + + protected override string ComponentName => Component.ToString().ToLower(); + } +} diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs new file mode 100644 index 0000000000..04aca534c6 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs @@ -0,0 +1,9 @@ +// 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.Taiko +{ + public enum TaikoSkinComponents + { + } +} diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index 96dc864577..ee5552c6e0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -137,8 +137,8 @@ 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) - : base(name, defaultImplementation, allowFallback, confineMode) + public ExposedSkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) + : base(new TestSkinComponent(name), defaultImplementation, allowFallback, confineMode) { } } @@ -206,8 +206,8 @@ namespace osu.Game.Tests.Visual.Gameplay public new Drawable Drawable => base.Drawable; public int SkinChangedCount { get; private set; } - public SkinConsumer(string name, Func defaultImplementation, Func allowFallback = null) - : base(name, defaultImplementation, allowFallback) + public SkinConsumer(string name, Func defaultImplementation, Func allowFallback = null) + : base(new TestSkinComponent(name), defaultImplementation, allowFallback) { } @@ -243,8 +243,8 @@ namespace osu.Game.Tests.Visual.Gameplay this.size = size; } - public Drawable GetDrawableComponent(string componentName) => - componentName == "available" + public Drawable GetDrawableComponent(ISkinComponent componentName) => + componentName.LookupName == "available" ? new DrawWidthBox { Colour = Color4.Yellow, @@ -261,7 +261,7 @@ namespace osu.Game.Tests.Visual.Gameplay private class SecondarySource : ISkin { - public Drawable GetDrawableComponent(string componentName) => new SecondarySourceBox(); + public Drawable GetDrawableComponent(ISkinComponent componentName) => new SecondarySourceBox(); public Texture GetTexture(string componentName) => throw new NotImplementedException(); @@ -272,7 +272,7 @@ namespace osu.Game.Tests.Visual.Gameplay private class SkinSourceContainer : Container, ISkin { - public Drawable GetDrawableComponent(string componentName) => new BaseSourceBox(); + public Drawable GetDrawableComponent(ISkinComponent componentName) => new BaseSourceBox(); public Texture GetTexture(string componentName) => throw new NotImplementedException(); @@ -280,5 +280,19 @@ namespace osu.Game.Tests.Visual.Gameplay public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException(); } + + private class TestSkinComponent : ISkinComponent + { + private readonly string name; + + public TestSkinComponent(string name) + { + this.name = name; + } + + public string ComponentGroup => string.Empty; + + public string LookupName => name; + } } } diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 61c2644c6f..ecbdc53493 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Judgements Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Child = new SkinnableDrawable($"Play/{Result.Type}", _ => JudgementText = new OsuSpriteText + Child = new SkinnableDrawable(new PlaySkinComponent(Result.Type), _ => JudgementText = new OsuSpriteText { Text = Result.Type.GetDescription().ToUpperInvariant(), Font = OsuFont.Numeric.With(size: 12), diff --git a/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs b/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs index 345df35b12..40335db697 100644 --- a/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs +++ b/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs @@ -17,7 +17,7 @@ namespace osu.Game.Skinning private readonly Bindable beatmapHitsounds = new Bindable(); protected override bool AllowConfigurationLookup => beatmapSkins.Value; - protected override bool AllowDrawableLookup(string componentName) => beatmapSkins.Value; + protected override bool AllowDrawableLookup(ISkinComponent component) => beatmapSkins.Value; protected override bool AllowTextureLookup(string componentName) => beatmapSkins.Value; protected override bool AllowSampleLookup(ISampleInfo componentName) => beatmapHitsounds.Value; diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 6072bb64ed..f917514877 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.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 osu.Framework.Audio.Sample; @@ -16,7 +16,7 @@ namespace osu.Game.Skinning Configuration = new SkinConfiguration(); } - public override Drawable GetDrawableComponent(string componentName) => null; + public override Drawable GetDrawableComponent(ISkinComponent component) => null; public override Texture GetTexture(string componentName) => null; diff --git a/osu.Game/Skinning/ISkin.cs b/osu.Game/Skinning/ISkin.cs index 4867aba0a9..bc1ae634c9 100644 --- a/osu.Game/Skinning/ISkin.cs +++ b/osu.Game/Skinning/ISkin.cs @@ -14,7 +14,7 @@ namespace osu.Game.Skinning /// public interface ISkin { - Drawable GetDrawableComponent(string componentName); + Drawable GetDrawableComponent(ISkinComponent component); Texture GetTexture(string componentName); diff --git a/osu.Game/Skinning/ISkinComponent.cs b/osu.Game/Skinning/ISkinComponent.cs new file mode 100644 index 0000000000..4bd9f21b6b --- /dev/null +++ b/osu.Game/Skinning/ISkinComponent.cs @@ -0,0 +1,10 @@ +// 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 interface ISkinComponent + { + string LookupName { get; } + } +} diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 1572c588e8..179b93d405 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -47,39 +47,24 @@ namespace osu.Game.Skinning Samples?.Dispose(); } - public override Drawable GetDrawableComponent(string componentName) + public override Drawable GetDrawableComponent(ISkinComponent component) { - bool animatable = false; - bool looping = true; - - switch (componentName) + switch (component.LookupName) { case "Play/Miss": - componentName = "hit0"; - animatable = true; - looping = false; - break; + return this.GetAnimation("hit0", true, false); case "Play/Meh": - componentName = "hit50"; - animatable = true; - looping = false; - break; + return this.GetAnimation("hit50", true, false); case "Play/Good": - componentName = "hit100"; - animatable = true; - looping = false; - break; + return this.GetAnimation("hit100", true, false); case "Play/Great": - componentName = "hit300"; - animatable = true; - looping = false; - break; + return this.GetAnimation("hit300", true, false); } - return this.GetAnimation(componentName, animatable, looping); + return this.GetAnimation(component.LookupName, false, false); } public override Texture GetTexture(string componentName) diff --git a/osu.Game/Skinning/PlaySkinComponent.cs b/osu.Game/Skinning/PlaySkinComponent.cs new file mode 100644 index 0000000000..f228d5cf9c --- /dev/null +++ b/osu.Game/Skinning/PlaySkinComponent.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.Linq; + +namespace osu.Game.Skinning +{ + public class PlaySkinComponent : ISkinComponent where T : struct + { + public readonly T Component; + + public PlaySkinComponent(T component) + { + this.Component = component; + } + + protected virtual string RulesetPrefix => string.Empty; + protected virtual string ComponentName => Component.ToString(); + + public string LookupName => + string.Join("/", new[] { "Play", RulesetPrefix, ComponentName }.Where(s => !string.IsNullOrEmpty(s))); + } +} diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 027d9df8b8..299f257e57 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.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; @@ -15,7 +15,7 @@ namespace osu.Game.Skinning public virtual SkinConfiguration Configuration { get; protected set; } - public abstract Drawable GetDrawableComponent(string componentName); + public abstract Drawable GetDrawableComponent(ISkinComponent componentName); public abstract SampleChannel GetSample(ISampleInfo sampleInfo); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index a713933c6e..a55a128dff 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.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; @@ -125,7 +125,7 @@ namespace osu.Game.Skinning public event Action SourceChanged; - public Drawable GetDrawableComponent(string componentName) => CurrentSkin.Value.GetDrawableComponent(componentName); + public Drawable GetDrawableComponent(ISkinComponent component) => CurrentSkin.Value.GetDrawableComponent(component); public Texture GetTexture(string componentName) => CurrentSkin.Value.GetTexture(componentName); diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index 45b8baa0bb..85a80655ea 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -22,7 +22,7 @@ namespace osu.Game.Skinning private ISkinSource fallbackSource; - protected virtual bool AllowDrawableLookup(string componentName) => true; + protected virtual bool AllowDrawableLookup(ISkinComponent component) => true; protected virtual bool AllowTextureLookup(string componentName) => true; @@ -37,13 +37,13 @@ namespace osu.Game.Skinning RelativeSizeAxes = Axes.Both; } - public Drawable GetDrawableComponent(string componentName) + public Drawable GetDrawableComponent(ISkinComponent component) { Drawable sourceDrawable; - if (AllowDrawableLookup(componentName) && (sourceDrawable = skin?.GetDrawableComponent(componentName)) != null) + if (AllowDrawableLookup(component) && (sourceDrawable = skin?.GetDrawableComponent(component)) != null) return sourceDrawable; - return fallbackSource?.GetDrawableComponent(componentName); + return fallbackSource?.GetDrawableComponent(component); } public Texture GetTexture(string componentName) diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs index 07f802944b..9ca5d60cb0 100644 --- a/osu.Game/Skinning/SkinnableDrawable.cs +++ b/osu.Game/Skinning/SkinnableDrawable.cs @@ -18,39 +18,39 @@ namespace osu.Game.Skinning /// public Drawable Drawable { get; private set; } - private readonly string componentName; + private readonly ISkinComponent component; private readonly ConfineMode confineMode; /// /// Create a new skinnable drawable. /// - /// The namespace-complete resource name for this skinnable element. + /// The namespace-complete resource name for this skinnable element. /// A function to create the default skin implementation of this element. /// A conditional to decide whether to allow fallback to the default implementation if a skinned element is not present. /// How (if at all) the should be resize to fit within our own bounds. - public SkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) - : this(name, allowFallback, confineMode) + public SkinnableDrawable(ISkinComponent component, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) + : this(component, allowFallback, confineMode) { createDefault = defaultImplementation; } - protected SkinnableDrawable(string name, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) + protected SkinnableDrawable(ISkinComponent component, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) : base(allowFallback) { - componentName = name; + this.component = component; this.confineMode = confineMode; RelativeSizeAxes = Axes.Both; } - private readonly Func createDefault; + private readonly Func createDefault; private readonly Cached scaling = new Cached(); private bool isDefault; - protected virtual Drawable CreateDefault(string name) => createDefault(name); + protected virtual Drawable CreateDefault(ISkinComponent component) => createDefault(component); /// /// Whether to apply size restrictions (specified via ) to the default implementation. @@ -59,13 +59,13 @@ namespace osu.Game.Skinning protected override void SkinChanged(ISkinSource skin, bool allowFallback) { - Drawable = skin.GetDrawableComponent(componentName); + Drawable = skin.GetDrawableComponent(component); isDefault = false; if (Drawable == null && allowFallback) { - Drawable = CreateDefault(componentName); + Drawable = CreateDefault(component); isDefault = true; } diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs index 07ba48d6ae..0081aef520 100644 --- a/osu.Game/Skinning/SkinnableSprite.cs +++ b/osu.Game/Skinning/SkinnableSprite.cs @@ -19,11 +19,11 @@ namespace osu.Game.Skinning [Resolved] private TextureStore textures { get; set; } - public SkinnableSprite(string name, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) - : base(name, allowFallback, confineMode) + public SkinnableSprite(ISkinComponent component, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) + : base(component, allowFallback, confineMode) { } - protected override Drawable CreateDefault(string name) => new Sprite { Texture = textures.Get(name) }; + protected override Drawable CreateDefault(ISkinComponent component) => new Sprite { Texture = textures.Get(component.LookupName) }; } } diff --git a/osu.Game/Skinning/SkinnableSpriteText.cs b/osu.Game/Skinning/SkinnableSpriteText.cs index 5af6df15e1..e72f9c9811 100644 --- a/osu.Game/Skinning/SkinnableSpriteText.cs +++ b/osu.Game/Skinning/SkinnableSpriteText.cs @@ -8,8 +8,8 @@ namespace osu.Game.Skinning { public class SkinnableSpriteText : SkinnableDrawable, IHasText { - public SkinnableSpriteText(string name, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) - : base(name, defaultImplementation, allowFallback, confineMode) + public SkinnableSpriteText(ISkinComponent component, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit) + : base(component, defaultImplementation, allowFallback, confineMode) { } From a7c94c388307e02980a172ca357bb3da1fd78f63 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 15:12:03 +0900 Subject: [PATCH 0962/2815] Simplify hit result lookups --- osu.Game/Skinning/LegacySkin.cs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 179b93d405..e51bf8245c 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Game.Audio; +using osu.Game.Rulesets.Scoring; using osuTK.Graphics; namespace osu.Game.Skinning @@ -49,19 +50,25 @@ namespace osu.Game.Skinning public override Drawable GetDrawableComponent(ISkinComponent component) { - switch (component.LookupName) + switch (component) { - case "Play/Miss": - return this.GetAnimation("hit0", true, false); + case PlaySkinComponent resultComponent: + switch (resultComponent.Component) + { + case HitResult.Miss: + return this.GetAnimation("hit0", true, false); - case "Play/Meh": - return this.GetAnimation("hit50", true, false); + case HitResult.Meh: + return this.GetAnimation("hit50", true, false); - case "Play/Good": - return this.GetAnimation("hit100", true, false); + case HitResult.Good: + return this.GetAnimation("hit100", true, false); - case "Play/Great": - return this.GetAnimation("hit300", true, false); + case HitResult.Great: + return this.GetAnimation("hit300", true, false); + } + + break; } return this.GetAnimation(component.LookupName, false, false); From 70e417533f47009049075747a8bc87636b633b53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 15:10:11 +0900 Subject: [PATCH 0963/2815] Update naming --- osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs | 4 ++-- osu.Game.Rulesets.Catch/CatchSkinComponent.cs | 2 +- osu.Game.Rulesets.Mania/ManiaSkinComponent.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs | 2 +- .../Objects/Drawables/Pieces/CirclePiece.cs | 2 +- osu.Game.Rulesets.Osu/OsuSkinComponent.cs | 2 +- osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs | 2 +- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 8 ++++---- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 2 +- .../{PlaySkinComponent.cs => GameplaySkinComponent.cs} | 6 +++--- osu.Game/Skinning/LegacySkin.cs | 2 +- 11 files changed, 17 insertions(+), 17 deletions(-) rename osu.Game/Skinning/{PlaySkinComponent.cs => GameplaySkinComponent.cs} (66%) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 13286f4524..c89cd95f36 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.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 NUnit.Framework; @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Catch.Tests { switch (component.LookupName) { - case "Play/Catch/fruit-catcher-idle": + case "Gameplay/Catch/fruit-catcher-idle": return new CatcherCustomSkin(); } diff --git a/osu.Game.Rulesets.Catch/CatchSkinComponent.cs b/osu.Game.Rulesets.Catch/CatchSkinComponent.cs index 620720310f..0a3e43dcfc 100644 --- a/osu.Game.Rulesets.Catch/CatchSkinComponent.cs +++ b/osu.Game.Rulesets.Catch/CatchSkinComponent.cs @@ -5,7 +5,7 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Catch { - public class CatchSkinComponent : PlaySkinComponent + public class CatchSkinComponent : GameplaySkinComponent { public CatchSkinComponent(CatchSkinComponents component) : base(component) diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs index 72a3ce7ad5..69bd4b0ecf 100644 --- a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs +++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs @@ -5,7 +5,7 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania { - public class ManiaSkinComponent : PlaySkinComponent + public class ManiaSkinComponent : GameplaySkinComponent { public ManiaSkinComponent(ManiaSkinComponents component) : base(component) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index 445f81c6d4..1eb37f8119 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs @@ -188,7 +188,7 @@ namespace osu.Game.Rulesets.Osu.Mods [BackgroundDependencyLoader] private void load(TextureStore textures) { - Texture = textures.Get("Play/osu/blinds-panel"); + Texture = textures.Get("Gameplay/osu/blinds-panel"); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs index a59cfc1123..210d5ff839 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Texture = textures.Get(@"Play/osu/disc"), + Texture = textures.Get(@"Gameplay/osu/disc"), }, new TrianglesPiece { diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponent.cs b/osu.Game.Rulesets.Osu/OsuSkinComponent.cs index ef0df6cbda..1d223f231b 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponent.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponent.cs @@ -5,7 +5,7 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu { - public class OsuSkinComponent : PlaySkinComponent + public class OsuSkinComponent : GameplaySkinComponent { public OsuSkinComponent(OsuSkinComponents component) : base(component) diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs index 474154279c..e6e4bc0dd7 100644 --- a/osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs +++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs @@ -5,7 +5,7 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Taiko { - public class TaikoSkinComponent : PlaySkinComponent + public class TaikoSkinComponent : GameplaySkinComponent { public TaikoSkinComponent(TaikoSkinComponents component) : base(component) diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index d6866c7d25..9766da9a24 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -132,10 +132,10 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load(TextureStore textures, OsuColour colours) { - rim.Texture = textures.Get(@"Play/Taiko/taiko-drum-outer"); - rimHit.Texture = textures.Get(@"Play/Taiko/taiko-drum-outer-hit"); - centre.Texture = textures.Get(@"Play/Taiko/taiko-drum-inner"); - centreHit.Texture = textures.Get(@"Play/Taiko/taiko-drum-inner-hit"); + rim.Texture = textures.Get(@"Gameplay/Taiko/taiko-drum-outer"); + rimHit.Texture = textures.Get(@"Gameplay/Taiko/taiko-drum-outer-hit"); + centre.Texture = textures.Get(@"Gameplay/Taiko/taiko-drum-inner"); + centreHit.Texture = textures.Get(@"Gameplay/Taiko/taiko-drum-inner-hit"); rimHit.Colour = colours.Blue; centreHit.Colour = colours.Pink; diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index ecbdc53493..4f8cb7660b 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Judgements Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Child = new SkinnableDrawable(new PlaySkinComponent(Result.Type), _ => JudgementText = new OsuSpriteText + Child = new SkinnableDrawable(new GameplaySkinComponent(Result.Type), _ => JudgementText = new OsuSpriteText { Text = Result.Type.GetDescription().ToUpperInvariant(), Font = OsuFont.Numeric.With(size: 12), diff --git a/osu.Game/Skinning/PlaySkinComponent.cs b/osu.Game/Skinning/GameplaySkinComponent.cs similarity index 66% rename from osu.Game/Skinning/PlaySkinComponent.cs rename to osu.Game/Skinning/GameplaySkinComponent.cs index f228d5cf9c..da65084a1d 100644 --- a/osu.Game/Skinning/PlaySkinComponent.cs +++ b/osu.Game/Skinning/GameplaySkinComponent.cs @@ -5,11 +5,11 @@ using System.Linq; namespace osu.Game.Skinning { - public class PlaySkinComponent : ISkinComponent where T : struct + public class GameplaySkinComponent : ISkinComponent where T : struct { public readonly T Component; - public PlaySkinComponent(T component) + public GameplaySkinComponent(T component) { this.Component = component; } @@ -18,6 +18,6 @@ namespace osu.Game.Skinning protected virtual string ComponentName => Component.ToString(); public string LookupName => - string.Join("/", new[] { "Play", RulesetPrefix, ComponentName }.Where(s => !string.IsNullOrEmpty(s))); + string.Join("/", new[] { "Gameplay", RulesetPrefix, ComponentName }.Where(s => !string.IsNullOrEmpty(s))); } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index e51bf8245c..753b2ba41b 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -52,7 +52,7 @@ namespace osu.Game.Skinning { switch (component) { - case PlaySkinComponent resultComponent: + case GameplaySkinComponent resultComponent: switch (resultComponent.Component) { case HitResult.Miss: From 835ee0aa2fbd75ed38782a54750135131396e708 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 15:29:13 +0900 Subject: [PATCH 0964/2815] Code quality fixes --- .../Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs index fae1b68e4a..fbd6aedd45 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs @@ -18,10 +18,13 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private const int margin = 10; private readonly Bindable type = new Bindable(); + private readonly HitWindows hitWindows; + private readonly ScoreProcessor processor; private BarHitErrorDisplay leftDisplay; + private BarHitErrorDisplay rightDisplay; public HitErrorDisplayOverlay(ScoreProcessor processor) @@ -40,10 +43,10 @@ namespace osu.Game.Screens.Play.HitErrorDisplay protected override void LoadComplete() { base.LoadComplete(); - type.BindValueChanged(onTypeChanged, true); + type.BindValueChanged(typeChanged, true); } - private void onTypeChanged(ValueChangedEvent type) + private void typeChanged(ValueChangedEvent type) { switch (type.NewValue) { From fe90e194e37b977ebcd2232f0af0b5177efef571 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 15:29:44 +0900 Subject: [PATCH 0965/2815] Remove redundant qualifier --- osu.Game/Skinning/PlaySkinComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/PlaySkinComponent.cs b/osu.Game/Skinning/PlaySkinComponent.cs index f228d5cf9c..68b67ee0c5 100644 --- a/osu.Game/Skinning/PlaySkinComponent.cs +++ b/osu.Game/Skinning/PlaySkinComponent.cs @@ -11,7 +11,7 @@ namespace osu.Game.Skinning public PlaySkinComponent(T component) { - this.Component = component; + Component = component; } protected virtual string RulesetPrefix => string.Empty; From bdbfa7bd2f5a4afde5c21478144f76a49dc25b2d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 15:32:47 +0900 Subject: [PATCH 0966/2815] Fix class naming --- ...isplay.cs => TestSceneBarHitErrorMeter.cs} | 12 +- osu.Game/Screens/Play/HUDOverlay.cs | 7 +- ...HitErrorDisplay.cs => BarHitErrorMeter.cs} | 4 +- .../Play/HitErrorDisplay/HitErrorDisplay.cs | 120 +++++++++++++++- .../HitErrorDisplay/HitErrorDisplayOverlay.cs | 129 ------------------ .../Play/HitErrorDisplay/HitErrorMeter.cs | 21 +++ 6 files changed, 146 insertions(+), 147 deletions(-) rename osu.Game.Tests/Visual/Gameplay/{TestSceneHitErrorDisplay.cs => TestSceneBarHitErrorMeter.cs} (94%) rename osu.Game/Screens/Play/HitErrorDisplay/{BarHitErrorDisplay.cs => BarHitErrorMeter.cs} (98%) delete mode 100644 osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs create mode 100644 osu.Game/Screens/Play/HitErrorDisplay/HitErrorMeter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs similarity index 94% rename from osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs index a148bdad67..aac9e206c3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs @@ -19,16 +19,16 @@ using osu.Framework.Graphics.Sprites; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneHitErrorDisplay : OsuTestScene + public class TestSceneBarHitErrorMeter : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { - typeof(HitErrorDisplay), + typeof(HitErrorMeter), }; - private HitErrorDisplay display; + private HitErrorMeter meter; - public TestSceneHitErrorDisplay() + public TestSceneBarHitErrorMeter() { recreateDisplay(new OsuHitWindows(), 5); AddStep("New random judgement", () => newJudgement()); @@ -115,7 +115,7 @@ namespace osu.Game.Tests.Visual.Gameplay } }); - Add(display = new BarHitErrorDisplay(hitWindows) + Add(meter = new BarHitErrorMeter(hitWindows) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void newJudgement(float offset = 0) { - display?.OnNewJudgement(new JudgementResult(new Judgement()) + meter?.OnNewJudgement(new JudgementResult(new Judgement()) { TimeOffset = offset == 0 ? RNG.Next(-70, 70) : offset, Type = HitResult.Perfect, diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 02432cf64e..79392221e4 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -15,7 +15,6 @@ using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play.HitErrorDisplay; using osu.Game.Screens.Play.HUD; using osuTK; using osuTK.Input; @@ -34,7 +33,7 @@ namespace osu.Game.Screens.Play public readonly HealthDisplay HealthDisplay; public readonly SongProgress Progress; public readonly ModDisplay ModDisplay; - public readonly HitErrorDisplayOverlay HitErrorDisplayOverlay; + public readonly HitErrorDisplay.HitErrorDisplay HitErrorDisplay; public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; @@ -86,7 +85,7 @@ namespace osu.Game.Screens.Play HealthDisplay = CreateHealthDisplay(), Progress = CreateProgress(), ModDisplay = CreateModsContainer(), - HitErrorDisplayOverlay = CreateHitErrorDisplayOverlay(), + HitErrorDisplay = CreateHitErrorDisplayOverlay(), } }, PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), @@ -259,7 +258,7 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20, Right = 10 }, }; - protected virtual HitErrorDisplayOverlay CreateHitErrorDisplayOverlay() => new HitErrorDisplayOverlay(scoreProcessor) + protected virtual HitErrorDisplay.HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay.HitErrorDisplay(scoreProcessor) { RelativeSizeAxes = Axes.Both, }; diff --git a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs similarity index 98% rename from osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs rename to osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs index 85d017073a..3ec3740816 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs @@ -18,7 +18,7 @@ using System.Linq; namespace osu.Game.Screens.Play.HitErrorDisplay { - public class BarHitErrorDisplay : HitErrorDisplay + public class BarHitErrorMeter : HitErrorMeter { /// /// The amount of which will be stored to calculate arrow position. @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private readonly List judgementOffsets = new List(); private readonly double maxHitWindows; - public BarHitErrorDisplay(HitWindows hitWindows, bool reversed = false) + public BarHitErrorMeter(HitWindows hitWindows, bool reversed = false) : base(hitWindows) { maxHitWindows = HitWindows.Meh == 0 ? HitWindows.Good : HitWindows.Meh; diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs index 422e151d8a..5c884f3f53 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs @@ -2,20 +2,128 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Judgements; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Scoring; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Play.HitErrorDisplay { - public abstract class HitErrorDisplay : CompositeDrawable + public class HitErrorDisplay : Container { - protected readonly HitWindows HitWindows; + private const int fade_duration = 200; + private const int margin = 10; - protected HitErrorDisplay(HitWindows hitWindows) + private readonly Bindable type = new Bindable(); + + private readonly HitWindows hitWindows; + + private readonly ScoreProcessor processor; + + private BarHitErrorMeter leftMeter; + + private BarHitErrorMeter rightMeter; + + public HitErrorDisplay(ScoreProcessor processor) { - HitWindows = hitWindows; + this.processor = processor; + hitWindows = processor.CreateHitWindows(); } - public abstract void OnNewJudgement(JudgementResult newJudgement); + [BackgroundDependencyLoader] + private void load(OsuConfigManager config, Bindable workingBeatmap) + { + config.BindWith(OsuSetting.ScoreMeter, type); + hitWindows.SetDifficulty(workingBeatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + type.BindValueChanged(typeChanged, true); + } + + private void typeChanged(ValueChangedEvent type) + { + switch (type.NewValue) + { + case ScoreMeterType.None: + removeLeftDisplay(); + removeRightDisplay(); + break; + + case ScoreMeterType.HitErrorBoth: + addLeftDisplay(); + addRightDisplay(); + break; + + case ScoreMeterType.HitErrorLeft: + addLeftDisplay(); + removeRightDisplay(); + break; + + case ScoreMeterType.HitErrorRight: + addRightDisplay(); + removeLeftDisplay(); + break; + } + } + + private void addLeftDisplay() + { + if (leftMeter != null) + return; + + leftMeter = createNew(); + } + + private void addRightDisplay() + { + if (rightMeter != null) + return; + + rightMeter = createNew(true); + } + + private void removeRightDisplay() + { + if (rightMeter == null) + return; + + processor.NewJudgement -= rightMeter.OnNewJudgement; + + rightMeter.FadeOut(fade_duration, Easing.OutQuint).Expire(); + rightMeter = null; + } + + private void removeLeftDisplay() + { + if (leftMeter == null) + return; + + processor.NewJudgement -= leftMeter.OnNewJudgement; + + leftMeter.FadeOut(fade_duration, Easing.OutQuint).Expire(); + leftMeter = null; + } + + private BarHitErrorMeter createNew(bool reversed = false) + { + var display = new BarHitErrorMeter(hitWindows, reversed) + { + Margin = new MarginPadding(margin), + Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Alpha = 0, + }; + + processor.NewJudgement += display.OnNewJudgement; + Add(display); + display.FadeInFromZero(fade_duration, Easing.OutQuint); + return display; + } } } diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs deleted file mode 100644 index fbd6aedd45..0000000000 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs +++ /dev/null @@ -1,129 +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.Graphics.Containers; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Scoring; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Game.Beatmaps; -using osu.Game.Configuration; -using osu.Game.Rulesets.Objects; - -namespace osu.Game.Screens.Play.HitErrorDisplay -{ - public class HitErrorDisplayOverlay : Container - { - private const int fade_duration = 200; - private const int margin = 10; - - private readonly Bindable type = new Bindable(); - - private readonly HitWindows hitWindows; - - private readonly ScoreProcessor processor; - - private BarHitErrorDisplay leftDisplay; - - private BarHitErrorDisplay rightDisplay; - - public HitErrorDisplayOverlay(ScoreProcessor processor) - { - this.processor = processor; - hitWindows = processor.CreateHitWindows(); - } - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config, Bindable workingBeatmap) - { - config.BindWith(OsuSetting.ScoreMeter, type); - hitWindows.SetDifficulty(workingBeatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - type.BindValueChanged(typeChanged, true); - } - - private void typeChanged(ValueChangedEvent type) - { - switch (type.NewValue) - { - case ScoreMeterType.None: - removeLeftDisplay(); - removeRightDisplay(); - break; - - case ScoreMeterType.HitErrorBoth: - addLeftDisplay(); - addRightDisplay(); - break; - - case ScoreMeterType.HitErrorLeft: - addLeftDisplay(); - removeRightDisplay(); - break; - - case ScoreMeterType.HitErrorRight: - addRightDisplay(); - removeLeftDisplay(); - break; - } - } - - private void addLeftDisplay() - { - if (leftDisplay != null) - return; - - leftDisplay = createNew(); - } - - private void addRightDisplay() - { - if (rightDisplay != null) - return; - - rightDisplay = createNew(true); - } - - private void removeRightDisplay() - { - if (rightDisplay == null) - return; - - processor.NewJudgement -= rightDisplay.OnNewJudgement; - - rightDisplay.FadeOut(fade_duration, Easing.OutQuint).Expire(); - rightDisplay = null; - } - - private void removeLeftDisplay() - { - if (leftDisplay == null) - return; - - processor.NewJudgement -= leftDisplay.OnNewJudgement; - - leftDisplay.FadeOut(fade_duration, Easing.OutQuint).Expire(); - leftDisplay = null; - } - - private BarHitErrorDisplay createNew(bool reversed = false) - { - var display = new BarHitErrorDisplay(hitWindows, reversed) - { - Margin = new MarginPadding(margin), - Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - Alpha = 0, - }; - - processor.NewJudgement += display.OnNewJudgement; - Add(display); - display.FadeInFromZero(fade_duration, Easing.OutQuint); - return display; - } - } -} diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorMeter.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorMeter.cs new file mode 100644 index 0000000000..848e892eaa --- /dev/null +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorMeter.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.Framework.Graphics.Containers; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Screens.Play.HitErrorDisplay +{ + public abstract class HitErrorMeter : CompositeDrawable + { + protected readonly HitWindows HitWindows; + + protected HitErrorMeter(HitWindows hitWindows) + { + HitWindows = hitWindows; + } + + public abstract void OnNewJudgement(JudgementResult newJudgement); + } +} From 6640161bc1fde5e88fff7e1084c83468670660ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 15:51:36 +0900 Subject: [PATCH 0967/2815] Simplify event propagation --- .../Play/HitErrorDisplay/BarHitErrorMeter.cs | 16 ++-- .../Play/HitErrorDisplay/HitErrorDisplay.cs | 90 ++++++------------- 2 files changed, 36 insertions(+), 70 deletions(-) diff --git a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs index 3ec3740816..b00f3f6f5f 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private readonly List judgementOffsets = new List(); private readonly double maxHitWindows; - public BarHitErrorMeter(HitWindows hitWindows, bool reversed = false) + public BarHitErrorMeter(HitWindows hitWindows, bool rightAligned = false) : base(hitWindows) { maxHitWindows = HitWindows.Meh == 0 ? HitWindows.Good : HitWindows.Meh; @@ -54,23 +54,23 @@ namespace osu.Game.Screens.Play.HitErrorDisplay { judgementsContainer = new Container { - Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Anchor = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, Width = judgement_line_width, RelativeSizeAxes = Axes.Y, }, bar = new FillFlowContainer { - Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Anchor = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, Width = bar_width, RelativeSizeAxes = Axes.Y, Direction = FillDirection.Vertical, }, new Container { - Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Anchor = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, AutoSizeAxes = Axes.X, RelativeSizeAxes = Axes.Y, Child = arrow = new SpriteIcon @@ -78,7 +78,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativePositionAxes = Axes.Y, - Icon = reversed ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, + Icon = rightAligned ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, Size = new Vector2(8), } }, diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs index 5c884f3f53..eaaf8e810c 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs @@ -1,13 +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.Linq; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Game.Rulesets.Scoring; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Play.HitErrorDisplay @@ -19,25 +22,33 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private readonly Bindable type = new Bindable(); - private readonly HitWindows hitWindows; + private HitWindows hitWindows; private readonly ScoreProcessor processor; - private BarHitErrorMeter leftMeter; - - private BarHitErrorMeter rightMeter; - public HitErrorDisplay(ScoreProcessor processor) { this.processor = processor; - hitWindows = processor.CreateHitWindows(); + processor.NewJudgement += onNewJudgement; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + processor.NewJudgement -= onNewJudgement; + } + + private void onNewJudgement(JudgementResult result) + { + foreach (var c in Children) + c.OnNewJudgement(result); } [BackgroundDependencyLoader] private void load(OsuConfigManager config, Bindable workingBeatmap) { config.BindWith(OsuSetting.ScoreMeter, type); - hitWindows.SetDifficulty(workingBeatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty); + hitWindows = workingBeatmap.Value.Beatmap.HitObjects.First().HitWindows; } protected override void LoadComplete() @@ -48,82 +59,37 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private void typeChanged(ValueChangedEvent type) { + Children.ForEach(c => c.FadeOut(fade_duration, Easing.OutQuint)); + switch (type.NewValue) { - case ScoreMeterType.None: - removeLeftDisplay(); - removeRightDisplay(); - break; - case ScoreMeterType.HitErrorBoth: - addLeftDisplay(); - addRightDisplay(); + createBar(false); + createBar(true); break; case ScoreMeterType.HitErrorLeft: - addLeftDisplay(); - removeRightDisplay(); + createBar(false); break; case ScoreMeterType.HitErrorRight: - addRightDisplay(); - removeLeftDisplay(); + createBar(true); break; } } - private void addLeftDisplay() + private void createBar(bool rightAligned) { - if (leftMeter != null) - return; - - leftMeter = createNew(); - } - - private void addRightDisplay() - { - if (rightMeter != null) - return; - - rightMeter = createNew(true); - } - - private void removeRightDisplay() - { - if (rightMeter == null) - return; - - processor.NewJudgement -= rightMeter.OnNewJudgement; - - rightMeter.FadeOut(fade_duration, Easing.OutQuint).Expire(); - rightMeter = null; - } - - private void removeLeftDisplay() - { - if (leftMeter == null) - return; - - processor.NewJudgement -= leftMeter.OnNewJudgement; - - leftMeter.FadeOut(fade_duration, Easing.OutQuint).Expire(); - leftMeter = null; - } - - private BarHitErrorMeter createNew(bool reversed = false) - { - var display = new BarHitErrorMeter(hitWindows, reversed) + var display = new BarHitErrorMeter(hitWindows, rightAligned) { Margin = new MarginPadding(margin), - Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Anchor = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, Alpha = 0, }; - processor.NewJudgement += display.OnNewJudgement; Add(display); display.FadeInFromZero(fade_duration, Easing.OutQuint); - return display; } } } From b03b520818eb2e9474fa7f7ea7daf01c287263b5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 30 Aug 2019 10:13:21 +0300 Subject: [PATCH 0968/2815] Move Absing from the APIKudosuHistory --- osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs | 6 ------ .../Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs | 3 ++- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs index 19ce11aa13..67ff20e6c2 100644 --- a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs +++ b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs @@ -13,12 +13,6 @@ namespace osu.Game.Online.API.Requests.Responses public DateTimeOffset CreatedAt; [JsonProperty("amount")] - private int amount - { - //We can receive negative values. However "action" is enough to build needed items - set => Amount = Math.Abs(value); - } - public int Amount; [JsonProperty("post")] diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index fb7d597012..94733324ba 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -9,6 +9,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests; using osu.Game.Online.Chat; +using System; namespace osu.Game.Overlays.Profile.Sections.Kudosu { @@ -56,7 +57,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu if (!string.IsNullOrEmpty(prefix)) { linkFlowContainer.AddText(prefix); - linkFlowContainer.AddText($@" {historyItem.Amount} kudosu", t => + linkFlowContainer.AddText($@" {Math.Abs(historyItem.Amount)} kudosu", t => { t.Font = t.Font.With(italics: true); t.Colour = colours.Blue; From 71c844facdff1219fc5b0a6e1189b81e6058c8d5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 30 Aug 2019 10:22:49 +0300 Subject: [PATCH 0969/2815] Remove unwanted spacings --- .../Kudosu/DrawableKudosuHistoryItem.cs | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index 94733324ba..408468fa73 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -10,6 +10,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests; using osu.Game.Online.Chat; using System; +using osuTK; namespace osu.Game.Overlays.Profile.Sections.Kudosu { @@ -37,6 +38,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, + Spacing = new Vector2(3), }, date = new DrawableDate(historyItem.CreatedAt) { @@ -57,7 +59,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu if (!string.IsNullOrEmpty(prefix)) { linkFlowContainer.AddText(prefix); - linkFlowContainer.AddText($@" {Math.Abs(historyItem.Amount)} kudosu", t => + linkFlowContainer.AddText($@"{Math.Abs(historyItem.Amount)} kudosu", t => { t.Font = t.Font.With(italics: true); t.Colour = colours.Blue; @@ -75,40 +77,40 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu switch (historyItem.Action) { case KudosuAction.VoteGive: - return @" from obtaining votes in modding post of"; + return @"from obtaining votes in modding post of"; case KudosuAction.Give: - return $@" from {userLink()} for a post at"; + return $@"from {userLink()} for a post at"; case KudosuAction.Reset: return $@"Kudosu reset by {userLink()} for the post"; case KudosuAction.VoteReset: - return @" from losing votes in modding post of"; + return @"from losing votes in modding post of"; case KudosuAction.DenyKudosuReset: - return @" from modding post"; + return @"from modding post"; case KudosuAction.Revoke: return $@"Denied kudosu by {userLink()} for the post"; case KudosuAction.AllowKudosuGive: - return @" from kudosu deny repeal of modding post"; + return @"from kudosu deny repeal of modding post"; case KudosuAction.DeleteReset: - return @" from modding post deletion of"; + return @"from modding post deletion of"; case KudosuAction.RestoreGive: - return @" from modding post restoration of"; + return @"from modding post restoration of"; case KudosuAction.RecalculateGive: - return @" from votes recalculation in modding post of"; + return @"from votes recalculation in modding post of"; case KudosuAction.RecalculateReset: - return @" from votes recalculation in modding post of"; + return @"from votes recalculation in modding post of"; default: - return @" from unknown event "; + return @"from unknown event"; } } From 72dbeaec1632ce6a94c71bc82141d447a1f19d4f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 30 Aug 2019 10:26:11 +0300 Subject: [PATCH 0970/2815] Fix the comment --- osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs index 67ff20e6c2..f2297f7a10 100644 --- a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs +++ b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs @@ -44,7 +44,7 @@ namespace osu.Game.Online.API.Requests.Responses { set { - //We will receive something like "foo.bar" or just "foo" + //We will receive something like "event.action" or just "action" string parsed = value.Contains(".") ? value.Split('.')[0].Pascalize() + value.Split('.')[1].Pascalize() : value.Pascalize(); Action = (KudosuAction)Enum.Parse(typeof(KudosuAction), parsed); From 5f3e638499c1d8fde4362239bdbfef91b9add024 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 16:40:24 +0900 Subject: [PATCH 0971/2815] Make test useful --- .../Visual/Gameplay/TestSceneBarHitErrorMeter.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs index aac9e206c3..28c5f0ae08 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs @@ -30,8 +30,14 @@ namespace osu.Game.Tests.Visual.Gameplay public TestSceneBarHitErrorMeter() { - recreateDisplay(new OsuHitWindows(), 5); - AddStep("New random judgement", () => newJudgement()); + var hitWindows = new OsuHitWindows(); + + recreateDisplay(hitWindows, 5); + + AddRepeatStep("New random judgement", () => newJudgement(), 40); + + AddRepeatStep("New max negative", () => newJudgement(-hitWindows.Meh), 20); + AddRepeatStep("New max positive", () => newJudgement(hitWindows.Meh), 20); AddStep("New fixed judgement (50ms)", () => newJudgement(50)); } @@ -122,11 +128,11 @@ namespace osu.Game.Tests.Visual.Gameplay }); } - private void newJudgement(float offset = 0) + private void newJudgement(double offset = 0) { meter?.OnNewJudgement(new JudgementResult(new Judgement()) { - TimeOffset = offset == 0 ? RNG.Next(-70, 70) : offset, + TimeOffset = offset == 0 ? RNG.Next(-150, 150) : offset, Type = HitResult.Perfect, }); } From a73d672c2f3c0397d80400d8c3e96d4b22b55d2c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 16:40:39 +0900 Subject: [PATCH 0972/2815] Tidy up judgement line logic (and fix it displaying at the wrong place) --- .../Play/HitErrorDisplay/BarHitErrorMeter.cs | 119 +++++++++--------- .../Play/HitErrorDisplay/HitErrorMeter.cs | 2 +- 2 files changed, 63 insertions(+), 58 deletions(-) diff --git a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs index b00f3f6f5f..ffaada6ff4 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.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.Containers; using osu.Framework.Graphics.Shapes; @@ -8,42 +9,46 @@ using osu.Game.Rulesets.Judgements; using osuTK.Graphics; using osuTK; using osu.Framework.Graphics.Sprites; -using System.Collections.Generic; using osu.Game.Rulesets.Objects; using osu.Framework.Allocation; using osu.Framework.Graphics.Colour; using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; -using System.Linq; namespace osu.Game.Screens.Play.HitErrorDisplay { public class BarHitErrorMeter : HitErrorMeter { - /// - /// The amount of which will be stored to calculate arrow position. - /// - private const int stored_judgements_amount = 5; + private readonly bool rightAligned; private const int judgement_fade_duration = 10000; - private const int arrow_move_duration = 500; + + private const int arrow_move_duration = 400; + private const int judgement_line_width = 8; + private const int bar_height = 200; + private const int bar_width = 3; + private const int spacing = 3; private readonly SpriteIcon arrow; + private readonly FillFlowContainer bar; + private readonly Container judgementsContainer; - private readonly List judgementOffsets = new List(); - private readonly double maxHitWindows; + + private readonly double maxHitWindow; public BarHitErrorMeter(HitWindows hitWindows, bool rightAligned = false) : base(hitWindows) { - maxHitWindows = HitWindows.Meh == 0 ? HitWindows.Good : HitWindows.Meh; + this.rightAligned = rightAligned; + maxHitWindow = Math.Max(Math.Max(HitWindows.Meh, HitWindows.Ok), HitWindows.Good); AutoSizeAxes = Axes.Both; + AddInternal(new FillFlowContainer { AutoSizeAxes = Axes.X, @@ -75,9 +80,10 @@ namespace osu.Game.Screens.Play.HitErrorDisplay RelativeSizeAxes = Axes.Y, Child = arrow = new SpriteIcon { - Anchor = Anchor.Centre, + Anchor = Anchor.TopCentre, Origin = Anchor.Centre, RelativePositionAxes = Axes.Y, + Y = 0.5f, Icon = rightAligned ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, Size = new Vector2(8), } @@ -93,24 +99,20 @@ namespace osu.Game.Screens.Play.HitErrorDisplay { bar.AddRange(new[] { - createColoredPiece(ColourInfo.GradientVertical(colours.Yellow.Opacity(0), colours.Yellow), - (maxHitWindows - HitWindows.Good) / (maxHitWindows * 2)), - createColoredPiece(colours.Green, (HitWindows.Good - HitWindows.Great) / (maxHitWindows * 2)), - createColoredPiece(colours.BlueLight, HitWindows.Great / maxHitWindows), - createColoredPiece(colours.Green, (HitWindows.Good - HitWindows.Great) / (maxHitWindows * 2)), - createColoredPiece(ColourInfo.GradientVertical(colours.Yellow, colours.Yellow.Opacity(0)), - (maxHitWindows - HitWindows.Good) / (maxHitWindows * 2)) + createColoredPiece(ColourInfo.GradientVertical(colours.Yellow.Opacity(0), colours.Yellow), (maxHitWindow - HitWindows.Good) / (maxHitWindow * 2)), + createColoredPiece(colours.Green, (HitWindows.Good - HitWindows.Great) / (maxHitWindow * 2)), + createColoredPiece(colours.BlueLight, HitWindows.Great / maxHitWindow), + createColoredPiece(colours.Green, (HitWindows.Good - HitWindows.Great) / (maxHitWindow * 2)), + createColoredPiece(ColourInfo.GradientVertical(colours.Yellow, colours.Yellow.Opacity(0)), (maxHitWindow - HitWindows.Good) / (maxHitWindow * 2)) }); } else { bar.AddRange(new[] { - createColoredPiece(ColourInfo.GradientVertical(colours.Green.Opacity(0), colours.Green), - (HitWindows.Good - HitWindows.Great) / (maxHitWindows * 2)), - createColoredPiece(colours.BlueLight, HitWindows.Great / maxHitWindows), - createColoredPiece(ColourInfo.GradientVertical(colours.Green, colours.Green.Opacity(0)), - (HitWindows.Good - HitWindows.Great) / (maxHitWindows * 2)), + createColoredPiece(ColourInfo.GradientVertical(colours.Green.Opacity(0), colours.Green), (HitWindows.Good - HitWindows.Great) / (maxHitWindow * 2)), + createColoredPiece(colours.BlueLight, HitWindows.Great / maxHitWindow), + createColoredPiece(ColourInfo.GradientVertical(colours.Green, colours.Green.Opacity(0)), (HitWindows.Good - HitWindows.Great) / (maxHitWindow * 2)), }); } } @@ -122,51 +124,54 @@ namespace osu.Game.Screens.Play.HitErrorDisplay Height = (float)height }; - public override void OnNewJudgement(JudgementResult newJudgement) + private double floatingAverage; + + public override void OnNewJudgement(JudgementResult judgement) { - if (!newJudgement.IsHit) + if (!judgement.IsHit) return; - var judgementLine = CreateJudgementLine(newJudgement); + judgementsContainer.Add(new JudgementLine + { + Y = getRelativeJudgementPosition(judgement.TimeOffset), + Anchor = rightAligned ? Anchor.TopLeft : Anchor.TopRight, + Origin = rightAligned ? Anchor.TopLeft : Anchor.TopRight, + }); - judgementsContainer.Add(judgementLine); - - judgementLine.FadeOut(judgement_fade_duration, Easing.OutQuint).Expire(); - - arrow.MoveToY(calculateArrowPosition(newJudgement), arrow_move_duration, Easing.OutQuint); + arrow.MoveToY(getRelativeJudgementPosition(floatingAverage = floatingAverage * 0.9 + judgement.TimeOffset * 0.1) + , arrow_move_duration, Easing.Out); } - protected virtual Container CreateJudgementLine(JudgementResult judgement) => new CircularContainer + private float getRelativeJudgementPosition(double value) => (float)((value / maxHitWindow) + 1) / 2; + + public class JudgementLine : CompositeDrawable { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Masking = true, - RelativeSizeAxes = Axes.X, - Height = 2, - RelativePositionAxes = Axes.Y, - Y = getRelativeJudgementPosition(judgement.TimeOffset), - Child = new Box + public JudgementLine() { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, + RelativeSizeAxes = Axes.X; + RelativePositionAxes = Axes.Y; + Height = 2; + + InternalChild = new CircularContainer + { + Masking = true, + RelativeSizeAxes = Axes.Both, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + } + }; } - }; - private float getRelativeJudgementPosition(double value) => (float)(value / maxHitWindows); + protected override void LoadComplete() + { + base.LoadComplete(); - private float calculateArrowPosition(JudgementResult newJudgement) - { - judgementOffsets.Add(newJudgement.TimeOffset); - - if (judgementOffsets.Count < stored_judgements_amount) - return getRelativeJudgementPosition(judgementOffsets.Average()); - - double sum = 0; - - for (int i = judgementOffsets.Count - stored_judgements_amount; i < judgementOffsets.Count; i++) - sum += judgementOffsets[i]; - - return getRelativeJudgementPosition(sum / stored_judgements_amount); + Width = 0; + this.ResizeWidthTo(1, 150, Easing.OutElasticHalf); + this.FadeTo(0.8f, 150).Then().FadeOut(judgement_fade_duration, Easing.OutQuint).Expire(); + } } } } diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorMeter.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorMeter.cs index 848e892eaa..e4599eb2fc 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorMeter.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorMeter.cs @@ -16,6 +16,6 @@ namespace osu.Game.Screens.Play.HitErrorDisplay HitWindows = hitWindows; } - public abstract void OnNewJudgement(JudgementResult newJudgement); + public abstract void OnNewJudgement(JudgementResult judgement); } } From 54696eef3990a1a93f2f707653d2b1fef5071ddc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 17:06:23 +0900 Subject: [PATCH 0973/2815] Reverse display, add animation and reduce width --- .../Gameplay/TestSceneBarHitErrorMeter.cs | 24 +++++--- .../Play/HitErrorDisplay/BarHitErrorMeter.cs | 59 +++++++++++-------- 2 files changed, 52 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs index 28c5f0ae08..334e0d3b90 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs @@ -27,6 +27,7 @@ namespace osu.Game.Tests.Visual.Gameplay }; private HitErrorMeter meter; + private HitErrorMeter meter2; public TestSceneBarHitErrorMeter() { @@ -109,8 +110,8 @@ namespace osu.Game.Tests.Visual.Gameplay Add(new FillFlowContainer { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Both, Children = new[] @@ -121,20 +122,29 @@ namespace osu.Game.Tests.Visual.Gameplay } }); - Add(meter = new BarHitErrorMeter(hitWindows) + Add(meter = new BarHitErrorMeter(hitWindows, true) { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + }); + + Add(meter2 = new BarHitErrorMeter(hitWindows, false) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, }); } private void newJudgement(double offset = 0) { - meter?.OnNewJudgement(new JudgementResult(new Judgement()) + var judgement = new JudgementResult(new Judgement()) { TimeOffset = offset == 0 ? RNG.Next(-150, 150) : offset, Type = HitResult.Perfect, - }); + }; + + meter.OnNewJudgement(judgement); + meter2.OnNewJudgement(judgement); } } } diff --git a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs index ffaada6ff4..7fb7b3cf99 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs @@ -25,19 +25,19 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private const int arrow_move_duration = 400; - private const int judgement_line_width = 8; + private const int judgement_line_width = 6; private const int bar_height = 200; - private const int bar_width = 3; + private const int bar_width = 2; - private const int spacing = 3; + private const int spacing = 2; - private readonly SpriteIcon arrow; + private SpriteIcon arrow; - private readonly FillFlowContainer bar; + private FillFlowContainer colourBarFlow; - private readonly Container judgementsContainer; + private Container judgementsContainer; private readonly double maxHitWindow; @@ -48,34 +48,39 @@ namespace osu.Game.Screens.Play.HitErrorDisplay maxHitWindow = Math.Max(Math.Max(HitWindows.Meh, HitWindows.Ok), HitWindows.Good); AutoSizeAxes = Axes.Both; + } - AddInternal(new FillFlowContainer + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChild = new FillFlowContainer { AutoSizeAxes = Axes.X, Height = bar_height, Direction = FillDirection.Horizontal, Spacing = new Vector2(spacing, 0), + Margin = new MarginPadding(2), Children = new Drawable[] { judgementsContainer = new Container { - Anchor = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, + Anchor = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, Width = judgement_line_width, RelativeSizeAxes = Axes.Y, }, - bar = new FillFlowContainer + colourBarFlow = new FillFlowContainer { - Anchor = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, + Anchor = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, Width = bar_width, RelativeSizeAxes = Axes.Y, Direction = FillDirection.Vertical, }, new Container { - Anchor = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, + Anchor = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, AutoSizeAxes = Axes.X, RelativeSizeAxes = Axes.Y, Child = arrow = new SpriteIcon @@ -84,20 +89,16 @@ namespace osu.Game.Screens.Play.HitErrorDisplay Origin = Anchor.Centre, RelativePositionAxes = Axes.Y, Y = 0.5f, - Icon = rightAligned ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, + Icon = rightAligned ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight, Size = new Vector2(8), } }, } - }); - } + }; - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { if (HitWindows.Meh != 0) { - bar.AddRange(new[] + colourBarFlow.AddRange(new[] { createColoredPiece(ColourInfo.GradientVertical(colours.Yellow.Opacity(0), colours.Yellow), (maxHitWindow - HitWindows.Good) / (maxHitWindow * 2)), createColoredPiece(colours.Green, (HitWindows.Good - HitWindows.Great) / (maxHitWindow * 2)), @@ -108,7 +109,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay } else { - bar.AddRange(new[] + colourBarFlow.AddRange(new[] { createColoredPiece(ColourInfo.GradientVertical(colours.Green.Opacity(0), colours.Green), (HitWindows.Good - HitWindows.Great) / (maxHitWindow * 2)), createColoredPiece(colours.BlueLight, HitWindows.Great / maxHitWindow), @@ -117,6 +118,16 @@ namespace osu.Game.Screens.Play.HitErrorDisplay } } + protected override void LoadComplete() + { + base.LoadComplete(); + + colourBarFlow.Height = 0; + colourBarFlow.ResizeHeightTo(1, 400, Easing.OutQuint); + + arrow.FadeInFromZero(400); + } + private Box createColoredPiece(ColourInfo colour, double height) => new Box { RelativeSizeAxes = Axes.Both, @@ -134,8 +145,8 @@ namespace osu.Game.Screens.Play.HitErrorDisplay judgementsContainer.Add(new JudgementLine { Y = getRelativeJudgementPosition(judgement.TimeOffset), - Anchor = rightAligned ? Anchor.TopLeft : Anchor.TopRight, - Origin = rightAligned ? Anchor.TopLeft : Anchor.TopRight, + Anchor = rightAligned ? Anchor.TopRight : Anchor.TopLeft, + Origin = rightAligned ? Anchor.TopRight : Anchor.TopLeft, }); arrow.MoveToY(getRelativeJudgementPosition(floatingAverage = floatingAverage * 0.9 + judgement.TimeOffset * 0.1) From 40729356fa1d19849f15120851653c323c372388 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Aug 2019 17:34:02 +0900 Subject: [PATCH 0974/2815] Move beat divisor colour retrieval to BindableBeatDivisor --- osu.Game/Screens/Edit/BindableBeatDivisor.cs | 39 +++++++++++++++ .../Compose/Components/BeatDivisorControl.cs | 47 +++---------------- 2 files changed, 45 insertions(+), 41 deletions(-) diff --git a/osu.Game/Screens/Edit/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/BindableBeatDivisor.cs index ea3b68e3bd..055077cc4f 100644 --- a/osu.Game/Screens/Edit/BindableBeatDivisor.cs +++ b/osu.Game/Screens/Edit/BindableBeatDivisor.cs @@ -4,6 +4,9 @@ using System; using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Graphics.Colour; +using osu.Game.Graphics; +using osuTK.Graphics; namespace osu.Game.Screens.Edit { @@ -35,5 +38,41 @@ namespace osu.Game.Screens.Edit protected override int DefaultMinValue => VALID_DIVISORS.First(); protected override int DefaultMaxValue => VALID_DIVISORS.Last(); protected override int DefaultPrecision => 1; + + /// + /// Retrieves the appropriate colour for a beat divisor. + /// + /// The beat divisor. + /// The set of colours. + /// The applicable colour from for . + public ColourInfo GetColourFor(int beatDivisor, OsuColour colours) + { + switch (beatDivisor) + { + case 2: + return colours.BlueLight; + + case 4: + return colours.Blue; + + case 8: + return colours.BlueDarker; + + case 16: + return colours.PurpleDark; + + case 3: + return colours.YellowLight; + + case 6: + return colours.Yellow; + + case 12: + return colours.YellowDarker; + + default: + return Color4.White; + } + } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 0d16d8474b..ddcdfdaf80 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -188,6 +188,9 @@ namespace osu.Game.Screens.Edit.Compose.Components { private Marker marker; + [Resolved] + private OsuColour colours { get; set; } + private readonly BindableBeatDivisor beatDivisor; private readonly int[] availableDivisors; @@ -204,11 +207,12 @@ namespace osu.Game.Screens.Edit.Compose.Components { foreach (var t in availableDivisors) { - AddInternal(new Tick(t) + AddInternal(new Tick { Anchor = Anchor.TopLeft, Origin = Anchor.TopCentre, RelativePositionAxes = Axes.X, + Colour = beatDivisor.GetColourFor(t, colours), X = getMappedPosition(t) }); } @@ -284,11 +288,8 @@ namespace osu.Game.Screens.Edit.Compose.Components private class Tick : CompositeDrawable { - private readonly int divisor; - - public Tick(int divisor) + public Tick() { - this.divisor = divisor; Size = new Vector2(2.5f, 10); InternalChild = new Box { RelativeSizeAxes = Axes.Both }; @@ -296,42 +297,6 @@ namespace osu.Game.Screens.Edit.Compose.Components CornerRadius = 0.5f; Masking = true; } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Colour = getColourForDivisor(divisor, colours); - } - - private ColourInfo getColourForDivisor(int divisor, OsuColour colours) - { - switch (divisor) - { - case 2: - return colours.BlueLight; - - case 4: - return colours.Blue; - - case 8: - return colours.BlueDarker; - - case 16: - return colours.PurpleDark; - - case 3: - return colours.YellowLight; - - case 6: - return colours.Yellow; - - case 12: - return colours.YellowDarker; - - default: - return Color4.White; - } - } } private class Marker : CompositeDrawable From 741bd0a5cffd4a972291054188cc139d62a7358d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 18:35:06 +0900 Subject: [PATCH 0975/2815] Fix incorrect colour sizes and simplify alignment specification --- .../Gameplay/TestSceneBarHitErrorMeter.cs | 4 +- osu.Game/Rulesets/Objects/HitWindows.cs | 13 ++ .../Play/HitErrorDisplay/BarHitErrorMeter.cs | 172 +++++++++++++----- 3 files changed, 140 insertions(+), 49 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs index 334e0d3b90..8852a27f50 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs @@ -37,8 +37,8 @@ namespace osu.Game.Tests.Visual.Gameplay AddRepeatStep("New random judgement", () => newJudgement(), 40); - AddRepeatStep("New max negative", () => newJudgement(-hitWindows.Meh), 20); - AddRepeatStep("New max positive", () => newJudgement(hitWindows.Meh), 20); + AddRepeatStep("New max negative", () => newJudgement(-hitWindows.HalfWindowFor(HitResult.Meh)), 20); + AddRepeatStep("New max positive", () => newJudgement(hitWindows.HalfWindowFor(HitResult.Meh)), 20); AddStep("New fixed judgement (50ms)", () => newJudgement(50)); } diff --git a/osu.Game/Rulesets/Objects/HitWindows.cs b/osu.Game/Rulesets/Objects/HitWindows.cs index fe099aaee7..e88af67c7c 100644 --- a/osu.Game/Rulesets/Objects/HitWindows.cs +++ b/osu.Game/Rulesets/Objects/HitWindows.cs @@ -65,6 +65,19 @@ namespace osu.Game.Rulesets.Objects return HitResult.None; } + /// + /// Retrieves a mapping of s to their half window timing for all allowed s. + /// + /// + public IEnumerable<(HitResult result, double length)> GetAllAvailableHalfWindows() + { + for (var result = HitResult.Meh; result <= HitResult.Perfect; ++result) + { + if (IsHitResultAllowed(result)) + yield return (result, HalfWindowFor(result)); + } + } + /// /// Check whether it is possible to achieve the provided . /// diff --git a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs index 7fb7b3cf99..5a2d892d7f 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs @@ -1,7 +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.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -11,15 +11,16 @@ using osuTK; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Objects; using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.Colour; using osu.Game.Graphics; -using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Screens.Play.HitErrorDisplay { public class BarHitErrorMeter : HitErrorMeter { - private readonly bool rightAligned; + private readonly Anchor alignment; private const int judgement_fade_duration = 10000; @@ -35,17 +36,17 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private SpriteIcon arrow; - private FillFlowContainer colourBarFlow; + private Container colourBarsEarly; + private Container colourBarsLate; private Container judgementsContainer; - private readonly double maxHitWindow; + private double maxHitWindow; public BarHitErrorMeter(HitWindows hitWindows, bool rightAligned = false) : base(hitWindows) { - this.rightAligned = rightAligned; - maxHitWindow = Math.Max(Math.Max(HitWindows.Meh, HitWindows.Ok), HitWindows.Good); + alignment = rightAligned ? Anchor.x0 : Anchor.x2; AutoSizeAxes = Axes.Both; } @@ -64,23 +65,40 @@ namespace osu.Game.Screens.Play.HitErrorDisplay { judgementsContainer = new Container { - Anchor = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, + Anchor = Anchor.y1 | alignment, + Origin = Anchor.y1 | alignment, Width = judgement_line_width, RelativeSizeAxes = Axes.Y, }, - colourBarFlow = new FillFlowContainer + colourBars = new Container { - Anchor = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, Width = bar_width, RelativeSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, + Anchor = Anchor.y1 | alignment, + Origin = Anchor.y1 | alignment, + Children = new Drawable[] + { + colourBarsEarly = new Container + { + Anchor = Anchor.y1 | alignment, + Origin = alignment, + RelativeSizeAxes = Axes.Both, + Height = 0.5f, + Scale = new Vector2(1, -1), + }, + colourBarsLate = new Container + { + Anchor = Anchor.y1 | alignment, + Origin = alignment, + RelativeSizeAxes = Axes.Both, + Height = 0.5f, + }, + } }, new Container { - Anchor = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, + Anchor = Anchor.y1 | alignment, + Origin = Anchor.y1 | alignment, AutoSizeAxes = Axes.X, RelativeSizeAxes = Axes.Y, Child = arrow = new SpriteIcon @@ -89,53 +107,111 @@ namespace osu.Game.Screens.Play.HitErrorDisplay Origin = Anchor.Centre, RelativePositionAxes = Axes.Y, Y = 0.5f, - Icon = rightAligned ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight, + Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, Size = new Vector2(8), } }, } }; - if (HitWindows.Meh != 0) - { - colourBarFlow.AddRange(new[] - { - createColoredPiece(ColourInfo.GradientVertical(colours.Yellow.Opacity(0), colours.Yellow), (maxHitWindow - HitWindows.Good) / (maxHitWindow * 2)), - createColoredPiece(colours.Green, (HitWindows.Good - HitWindows.Great) / (maxHitWindow * 2)), - createColoredPiece(colours.BlueLight, HitWindows.Great / maxHitWindow), - createColoredPiece(colours.Green, (HitWindows.Good - HitWindows.Great) / (maxHitWindow * 2)), - createColoredPiece(ColourInfo.GradientVertical(colours.Yellow, colours.Yellow.Opacity(0)), (maxHitWindow - HitWindows.Good) / (maxHitWindow * 2)) - }); - } - else - { - colourBarFlow.AddRange(new[] - { - createColoredPiece(ColourInfo.GradientVertical(colours.Green.Opacity(0), colours.Green), (HitWindows.Good - HitWindows.Great) / (maxHitWindow * 2)), - createColoredPiece(colours.BlueLight, HitWindows.Great / maxHitWindow), - createColoredPiece(ColourInfo.GradientVertical(colours.Green, colours.Green.Opacity(0)), (HitWindows.Good - HitWindows.Great) / (maxHitWindow * 2)), - }); - } + createColourBars(colours); } protected override void LoadComplete() { base.LoadComplete(); - colourBarFlow.Height = 0; - colourBarFlow.ResizeHeightTo(1, 400, Easing.OutQuint); + colourBars.Height = 0; + colourBars.ResizeHeightTo(1, 800, Easing.OutQuint); - arrow.FadeInFromZero(400); + arrow.Alpha = 0.01f; + arrow.Delay(200).FadeInFromZero(600); } - private Box createColoredPiece(ColourInfo colour, double height) => new Box + private void createColourBars(OsuColour colours) { - RelativeSizeAxes = Axes.Both, - Colour = colour, - Height = (float)height - }; + var windows = HitWindows.GetAllAvailableHalfWindows().ToArray(); + + maxHitWindow = windows.First().length; + + for (var i = 0; i < windows.Length; i++) + { + var (result, length) = windows[i]; + + colourBarsEarly.Add(createColourBar(result, (float)(length / maxHitWindow), i == 0)); + colourBarsLate.Add(createColourBar(result, (float)(length / maxHitWindow), i == 0)); + } + + // a little nub to mark the centre point. + var centre = createColourBar(windows.Last().result, 0.01f); + centre.Anchor = centre.Origin = Anchor.y1 | alignment; + centre.Width = 1.5f; + colourBars.Add(centre); + + Color4 getColour(HitResult result) + { + switch (result) + { + case HitResult.Meh: + return colours.Yellow; + + case HitResult.Ok: + return colours.Green; + + case HitResult.Good: + return colours.GreenLight; + + case HitResult.Great: + return colours.Blue; + + default: + return colours.BlueLight; + } + } + + Drawable createColourBar(HitResult result, float height, bool first = false) + { + var colour = getColour(result); + + if (first) + { + // the first bar needs gradient rendering. + const float gradient_start = 0.8f; + + return new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = getColour(result), + Height = height * gradient_start + }, + new Box + { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(colour, colour.Opacity(0)), + Y = gradient_start, + Height = height * (1 - gradient_start) + }, + } + }; + } + + return new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colour, + Height = height + }; + } + } private double floatingAverage; + private Container colourBars; public override void OnNewJudgement(JudgementResult judgement) { @@ -145,11 +221,12 @@ namespace osu.Game.Screens.Play.HitErrorDisplay judgementsContainer.Add(new JudgementLine { Y = getRelativeJudgementPosition(judgement.TimeOffset), - Anchor = rightAligned ? Anchor.TopRight : Anchor.TopLeft, - Origin = rightAligned ? Anchor.TopRight : Anchor.TopLeft, + Anchor = alignment == Anchor.x2 ? Anchor.x0 : Anchor.x2, + Origin = alignment == Anchor.x2 ? Anchor.x0 : Anchor.x2, }); - arrow.MoveToY(getRelativeJudgementPosition(floatingAverage = floatingAverage * 0.9 + judgement.TimeOffset * 0.1) + arrow.MoveToY( + getRelativeJudgementPosition(floatingAverage = floatingAverage * 0.9 + judgement.TimeOffset * 0.1) , arrow_move_duration, Easing.Out); } @@ -180,6 +257,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay base.LoadComplete(); Width = 0; + this.ResizeWidthTo(1, 150, Easing.OutElasticHalf); this.FadeTo(0.8f, 150).Then().FadeOut(judgement_fade_duration, Easing.OutQuint).Expire(); } From 8fc177b743d8a1b3c5300379b597c5487650cb31 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 18:46:42 +0900 Subject: [PATCH 0976/2815] Fix namespacing and hitwindow source --- .../Gameplay/TestSceneBarHitErrorMeter.cs | 2 +- .../HitErrorDisplay.cs | 22 ++++++++++--------- .../HitErrorMeters}/BarHitErrorMeter.cs | 18 +++++++-------- .../HitErrorMeters}/HitErrorMeter.cs | 2 +- osu.Game/Screens/Play/HUDOverlay.cs | 8 +++---- 5 files changed, 26 insertions(+), 26 deletions(-) rename osu.Game/Screens/Play/{HitErrorDisplay => HUD}/HitErrorDisplay.cs (87%) rename osu.Game/Screens/Play/{HitErrorDisplay => HUD/HitErrorMeters}/BarHitErrorMeter.cs (99%) rename osu.Game/Screens/Play/{HitErrorDisplay => HUD/HitErrorMeters}/HitErrorMeter.cs (91%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs index 8852a27f50..98826331e1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs @@ -7,7 +7,6 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Screens.Play.HitErrorDisplay; using System; using System.Collections.Generic; using osu.Game.Rulesets.Judgements; @@ -16,6 +15,7 @@ using osu.Framework.MathUtils; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Game.Screens.Play.HUD.HitErrorMeters; namespace osu.Game.Tests.Visual.Gameplay { diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs similarity index 87% rename from osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs rename to osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index eaaf8e810c..0dcb1fee2b 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -1,19 +1,18 @@ // 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.Graphics.Containers; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Scoring; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Game.Beatmaps; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Configuration; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play.HUD.HitErrorMeters; -namespace osu.Game.Screens.Play.HitErrorDisplay +namespace osu.Game.Screens.Play.HUD { public class HitErrorDisplay : Container { @@ -22,13 +21,17 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private readonly Bindable type = new Bindable(); - private HitWindows hitWindows; + private readonly HitWindows hitWindows; private readonly ScoreProcessor processor; - public HitErrorDisplay(ScoreProcessor processor) + public HitErrorDisplay(ScoreProcessor processor, HitWindows hitWindows) { this.processor = processor; + this.hitWindows = hitWindows; + + RelativeSizeAxes = Axes.Both; + processor.NewJudgement += onNewJudgement; } @@ -45,10 +48,9 @@ namespace osu.Game.Screens.Play.HitErrorDisplay } [BackgroundDependencyLoader] - private void load(OsuConfigManager config, Bindable workingBeatmap) + private void load(OsuConfigManager config) { config.BindWith(OsuSetting.ScoreMeter, type); - hitWindows = workingBeatmap.Value.Beatmap.HitObjects.First().HitWindows; } protected override void LoadComplete() diff --git a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs similarity index 99% rename from osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs rename to osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 5a2d892d7f..22cccf30d7 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -2,21 +2,21 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Judgements; -using osuTK.Graphics; -using osuTK; -using osu.Framework.Graphics.Sprites; -using osu.Game.Rulesets.Objects; using osu.Framework.Allocation; 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.Graphics.Sprites; using osu.Game.Graphics; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; +using osuTK; +using osuTK.Graphics; -namespace osu.Game.Screens.Play.HitErrorDisplay +namespace osu.Game.Screens.Play.HUD.HitErrorMeters { public class BarHitErrorMeter : HitErrorMeter { diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs similarity index 91% rename from osu.Game/Screens/Play/HitErrorDisplay/HitErrorMeter.cs rename to osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs index e4599eb2fc..da1d9fff0d 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs @@ -5,7 +5,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; -namespace osu.Game.Screens.Play.HitErrorDisplay +namespace osu.Game.Screens.Play.HUD.HitErrorMeters { public abstract class HitErrorMeter : CompositeDrawable { diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 79392221e4..21d5ae557f 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -33,7 +34,7 @@ namespace osu.Game.Screens.Play public readonly HealthDisplay HealthDisplay; public readonly SongProgress Progress; public readonly ModDisplay ModDisplay; - public readonly HitErrorDisplay.HitErrorDisplay HitErrorDisplay; + public readonly HitErrorDisplay HitErrorDisplay; public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; @@ -258,10 +259,7 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20, Right = 10 }, }; - protected virtual HitErrorDisplay.HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay.HitErrorDisplay(scoreProcessor) - { - RelativeSizeAxes = Axes.Both, - }; + protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset.Objects.First().HitWindows); protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay(); From 171f88da409964efc0b56908fc30a0824ee20da9 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2019 09:47:18 +0000 Subject: [PATCH 0977/2815] Bump ppy.osu.Framework from 2019.830.0 to 2019.830.1 Bumps [ppy.osu.Framework](https://github.com/ppy/osu-framework) from 2019.830.0 to 2019.830.1. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.830.0...2019.830.1) 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 8e6ce2d1ba..330018d5cb 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -15,7 +15,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 47cc6ec97a..298fbc6704 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,7 +118,7 @@ - + From c3abf0ccb7112f654910ef867a658986e517fa54 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 18:50:38 +0900 Subject: [PATCH 0978/2815] Improve visuals --- .../Play/HUD/HitErrorMeters/BarHitErrorMeter.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 22cccf30d7..4e32be3cda 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -22,8 +22,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { private readonly Anchor alignment; - private const int judgement_fade_duration = 10000; - private const int arrow_move_duration = 400; private const int judgement_line_width = 6; @@ -34,6 +32,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters private const int spacing = 2; + private const float chevron_size = 8; + private SpriteIcon arrow; private Container colourBarsEarly; @@ -99,7 +99,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { Anchor = Anchor.y1 | alignment, Origin = Anchor.y1 | alignment, - AutoSizeAxes = Axes.X, + Width = chevron_size, RelativeSizeAxes = Axes.Y, Child = arrow = new SpriteIcon { @@ -108,7 +108,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters RelativePositionAxes = Axes.Y, Y = 0.5f, Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, - Size = new Vector2(8), + Size = new Vector2(chevron_size), } }, } @@ -124,7 +124,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters colourBars.Height = 0; colourBars.ResizeHeightTo(1, 800, Easing.OutQuint); - arrow.Alpha = 0.01f; + arrow.Alpha = 0; arrow.Delay(200).FadeInFromZero(600); } @@ -234,11 +234,13 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters public class JudgementLine : CompositeDrawable { + private const int judgement_fade_duration = 10000; + public JudgementLine() { RelativeSizeAxes = Axes.X; RelativePositionAxes = Axes.Y; - Height = 2; + Height = 3; InternalChild = new CircularContainer { @@ -258,7 +260,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters Width = 0; - this.ResizeWidthTo(1, 150, Easing.OutElasticHalf); + this.ResizeWidthTo(1, 200, Easing.OutElasticHalf); this.FadeTo(0.8f, 150).Then().FadeOut(judgement_fade_duration, Easing.OutQuint).Expire(); } } From b639ce8e5d237c845adec63f26f5993df4140be8 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2019 10:01:16 +0000 Subject: [PATCH 0979/2815] Bump ppy.osu.Framework.iOS from 2019.830.0 to 2019.830.1 Bumps [ppy.osu.Framework.iOS](https://github.com/ppy/osu-framework) from 2019.830.0 to 2019.830.1. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.830.0...2019.830.1) Signed-off-by: dependabot-preview[bot] --- osu.iOS.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.iOS.props b/osu.iOS.props index 47cc6ec97a..afb0b9217e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -119,7 +119,7 @@ - + From 815220de039ef25606591f5a0e0f7f1122952870 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2019 10:01:35 +0000 Subject: [PATCH 0980/2815] Bump ppy.osu.Framework.Android from 2019.830.0 to 2019.830.1 Bumps [ppy.osu.Framework.Android](https://github.com/ppy/osu-framework) from 2019.830.0 to 2019.830.1. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.830.0...2019.830.1) Signed-off-by: dependabot-preview[bot] --- osu.Android.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Android.props b/osu.Android.props index 2c3c8bcaad..93a9a073a4 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -61,6 +61,6 @@ - + From 80671cefd7db5e83b1059fdccaa3bc5a475bd603 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 19:14:07 +0900 Subject: [PATCH 0981/2815] Final visual polish --- .../HUD/HitErrorMeters/BarHitErrorMeter.cs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 4e32be3cda..51f9be4792 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -93,6 +93,22 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters RelativeSizeAxes = Axes.Both, Height = 0.5f, }, + new SpriteIcon + { + Y = -10, + Size = new Vector2(10), + Icon = FontAwesome.Solid.ShippingFast, + Anchor = Anchor.y0 | alignment, + Origin = Anchor.y0 | alignment, + }, + new SpriteIcon + { + Y = 10, + Size = new Vector2(10), + Icon = FontAwesome.Solid.Bicycle, + Anchor = Anchor.y2 | alignment, + Origin = Anchor.y2 | alignment, + } } }, new Container @@ -144,8 +160,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters // a little nub to mark the centre point. var centre = createColourBar(windows.Last().result, 0.01f); - centre.Anchor = centre.Origin = Anchor.y1 | alignment; - centre.Width = 1.5f; + centre.Anchor = centre.Origin = Anchor.y1 | (alignment == Anchor.x2 ? Anchor.x0 : Anchor.x2); + centre.Width = 2.5f; colourBars.Add(centre); Color4 getColour(HitResult result) From 665fc95d49c595b751d44ca5cf5bae91ae5bda58 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 19:37:20 +0900 Subject: [PATCH 0982/2815] Handle no hitobjects / no hitwindows (osu!catch) --- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 3 +++ osu.Game/Screens/Play/HUDOverlay.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 0dcb1fee2b..2e28d17b80 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -63,6 +63,9 @@ namespace osu.Game.Screens.Play.HUD { Children.ForEach(c => c.FadeOut(fade_duration, Easing.OutQuint)); + if (hitWindows == null) + return; + switch (type.NewValue) { case ScoreMeterType.HitErrorBoth: diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 21d5ae557f..285737f7a8 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -259,7 +259,7 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20, Right = 10 }, }; - protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset.Objects.First().HitWindows); + protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset.Objects.FirstOrDefault().HitWindows); protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay(); From fab12fa9cd00043a9b820d7511ca2fcddb729d8d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Aug 2019 19:34:28 +0900 Subject: [PATCH 0983/2815] Centre align the icons Seems to look better this way. --- .../Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 51f9be4792..7d3b0ae141 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -98,16 +98,16 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters Y = -10, Size = new Vector2(10), Icon = FontAwesome.Solid.ShippingFast, - Anchor = Anchor.y0 | alignment, - Origin = Anchor.y0 | alignment, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, }, new SpriteIcon { Y = 10, Size = new Vector2(10), Icon = FontAwesome.Solid.Bicycle, - Anchor = Anchor.y2 | alignment, - Origin = Anchor.y2 | alignment, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, } } }, From 8b4976ad92399c19acffa4275710eaffe3d9ff02 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Aug 2019 19:37:18 +0900 Subject: [PATCH 0984/2815] Remove unnecessary intermediate OD tests --- .../Gameplay/TestSceneBarHitErrorMeter.cs | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs index 98826331e1..d317c6551f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs @@ -46,14 +46,6 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestOsu() { AddStep("OD 1", () => recreateDisplay(new OsuHitWindows(), 1)); - AddStep("OD 2", () => recreateDisplay(new OsuHitWindows(), 2)); - AddStep("OD 3", () => recreateDisplay(new OsuHitWindows(), 3)); - AddStep("OD 4", () => recreateDisplay(new OsuHitWindows(), 4)); - AddStep("OD 5", () => recreateDisplay(new OsuHitWindows(), 5)); - AddStep("OD 6", () => recreateDisplay(new OsuHitWindows(), 6)); - AddStep("OD 7", () => recreateDisplay(new OsuHitWindows(), 7)); - AddStep("OD 8", () => recreateDisplay(new OsuHitWindows(), 8)); - AddStep("OD 9", () => recreateDisplay(new OsuHitWindows(), 9)); AddStep("OD 10", () => recreateDisplay(new OsuHitWindows(), 10)); } @@ -61,14 +53,6 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestTaiko() { AddStep("OD 1", () => recreateDisplay(new TaikoHitWindows(), 1)); - AddStep("OD 2", () => recreateDisplay(new TaikoHitWindows(), 2)); - AddStep("OD 3", () => recreateDisplay(new TaikoHitWindows(), 3)); - AddStep("OD 4", () => recreateDisplay(new TaikoHitWindows(), 4)); - AddStep("OD 5", () => recreateDisplay(new TaikoHitWindows(), 5)); - AddStep("OD 6", () => recreateDisplay(new TaikoHitWindows(), 6)); - AddStep("OD 7", () => recreateDisplay(new TaikoHitWindows(), 7)); - AddStep("OD 8", () => recreateDisplay(new TaikoHitWindows(), 8)); - AddStep("OD 9", () => recreateDisplay(new TaikoHitWindows(), 9)); AddStep("OD 10", () => recreateDisplay(new TaikoHitWindows(), 10)); } @@ -76,14 +60,6 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestMania() { AddStep("OD 1", () => recreateDisplay(new ManiaHitWindows(), 1)); - AddStep("OD 2", () => recreateDisplay(new ManiaHitWindows(), 2)); - AddStep("OD 3", () => recreateDisplay(new ManiaHitWindows(), 3)); - AddStep("OD 4", () => recreateDisplay(new ManiaHitWindows(), 4)); - AddStep("OD 5", () => recreateDisplay(new ManiaHitWindows(), 5)); - AddStep("OD 6", () => recreateDisplay(new ManiaHitWindows(), 6)); - AddStep("OD 7", () => recreateDisplay(new ManiaHitWindows(), 7)); - AddStep("OD 8", () => recreateDisplay(new ManiaHitWindows(), 8)); - AddStep("OD 9", () => recreateDisplay(new ManiaHitWindows(), 9)); AddStep("OD 10", () => recreateDisplay(new ManiaHitWindows(), 10)); } @@ -91,14 +67,6 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestCatch() { AddStep("OD 1", () => recreateDisplay(new CatchHitWindows(), 1)); - AddStep("OD 2", () => recreateDisplay(new CatchHitWindows(), 2)); - AddStep("OD 3", () => recreateDisplay(new CatchHitWindows(), 3)); - AddStep("OD 4", () => recreateDisplay(new CatchHitWindows(), 4)); - AddStep("OD 5", () => recreateDisplay(new CatchHitWindows(), 5)); - AddStep("OD 6", () => recreateDisplay(new CatchHitWindows(), 6)); - AddStep("OD 7", () => recreateDisplay(new CatchHitWindows(), 7)); - AddStep("OD 8", () => recreateDisplay(new CatchHitWindows(), 8)); - AddStep("OD 9", () => recreateDisplay(new CatchHitWindows(), 9)); AddStep("OD 10", () => recreateDisplay(new CatchHitWindows(), 10)); } From 6fb8a6cdbe894343c98a54e115c97a2451264717 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Aug 2019 19:47:13 +0900 Subject: [PATCH 0985/2815] Fix testcases not working for OD10 --- .../Visual/Gameplay/TestSceneBarHitErrorMeter.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs index d317c6551f..f20440249b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs @@ -28,12 +28,11 @@ namespace osu.Game.Tests.Visual.Gameplay private HitErrorMeter meter; private HitErrorMeter meter2; + private HitWindows hitWindows; public TestSceneBarHitErrorMeter() { - var hitWindows = new OsuHitWindows(); - - recreateDisplay(hitWindows, 5); + recreateDisplay(new OsuHitWindows(), 5); AddRepeatStep("New random judgement", () => newJudgement(), 40); @@ -72,7 +71,9 @@ namespace osu.Game.Tests.Visual.Gameplay private void recreateDisplay(HitWindows hitWindows, float overallDifficulty) { - hitWindows.SetDifficulty(overallDifficulty); + this.hitWindows = hitWindows; + + hitWindows?.SetDifficulty(overallDifficulty); Clear(); @@ -84,9 +85,9 @@ namespace osu.Game.Tests.Visual.Gameplay AutoSizeAxes = Axes.Both, Children = new[] { - new SpriteText { Text = $@"Great: {hitWindows.Great}" }, - new SpriteText { Text = $@"Good: {hitWindows.Good}" }, - new SpriteText { Text = $@"Meh: {hitWindows.Meh}" }, + new SpriteText { Text = $@"Great: {hitWindows?.Great}" }, + new SpriteText { Text = $@"Good: {hitWindows?.Good}" }, + new SpriteText { Text = $@"Meh: {hitWindows?.Meh}" }, } }); From dfccc6036109226ca4be14cd92ca38f6fd3e6d5c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Aug 2019 19:47:21 +0900 Subject: [PATCH 0986/2815] Reorder HitErrorDisplay --- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 2e28d17b80..cdfa0e993b 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -35,18 +35,6 @@ namespace osu.Game.Screens.Play.HUD processor.NewJudgement += onNewJudgement; } - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - processor.NewJudgement -= onNewJudgement; - } - - private void onNewJudgement(JudgementResult result) - { - foreach (var c in Children) - c.OnNewJudgement(result); - } - [BackgroundDependencyLoader] private void load(OsuConfigManager config) { @@ -59,6 +47,12 @@ namespace osu.Game.Screens.Play.HUD type.BindValueChanged(typeChanged, true); } + private void onNewJudgement(JudgementResult result) + { + foreach (var c in Children) + c.OnNewJudgement(result); + } + private void typeChanged(ValueChangedEvent type) { Children.ForEach(c => c.FadeOut(fade_duration, Easing.OutQuint)); @@ -96,5 +90,11 @@ namespace osu.Game.Screens.Play.HUD Add(display); display.FadeInFromZero(fade_duration, Easing.OutQuint); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + processor.NewJudgement -= onNewJudgement; + } } } From fc813347ac522fad92e2b88b022ab2561957b6ee Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Aug 2019 19:54:36 +0900 Subject: [PATCH 0987/2815] Make JudgementLine private --- osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 7d3b0ae141..d5d3cb528e 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -248,7 +248,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters private float getRelativeJudgementPosition(double value) => (float)((value / maxHitWindow) + 1) / 2; - public class JudgementLine : CompositeDrawable + private class JudgementLine : CompositeDrawable { private const int judgement_fade_duration = 10000; From f1db6c7039c1bbc037c36c0449a599f9a6694497 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 20:18:21 +0900 Subject: [PATCH 0988/2815] Fix likely nullref --- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 285737f7a8..8e642ea552 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -259,7 +259,7 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20, Right = 10 }, }; - protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset.Objects.FirstOrDefault().HitWindows); + protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset.Objects.FirstOrDefault()?.HitWindows); protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay(); From 2876588193897bc9128bb62041c0e6adbc13941b Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2019 13:36:31 +0000 Subject: [PATCH 0989/2815] Bump NUnit3TestAdapter from 3.15.0 to 3.15.1 Bumps [NUnit3TestAdapter](https://github.com/nunit/nunit3-vs-adapter) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/nunit/nunit3-vs-adapter/releases) - [Commits](https://github.com/nunit/nunit3-vs-adapter/compare/V3.15...V3.15.1) Signed-off-by: dependabot-preview[bot] --- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 7c282f449b..c527a81f51 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index 4dcfc1b81f..af10d5e06e 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index 197309c7c4..c331c811d2 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index a5db1625d9..d2a0a8fa6f 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 4a9d88f3a6..84f67c9319 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 2a8bd393da..bba3c92245 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -7,7 +7,7 @@ - + WinExe From 6e5cb8a318840fc1b1e156f8583641d4881cfd84 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 30 Aug 2019 23:19:34 +0300 Subject: [PATCH 0990/2815] implement video parsing --- osu.Game/Beatmaps/BeatmapManager.cs | 2 ++ .../Beatmaps/BeatmapManager_WorkingBeatmap.cs | 16 ++++++++++++++++ osu.Game/Beatmaps/BeatmapMetadata.cs | 4 +++- osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 3 +++ .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 9 +++++++-- osu.Game/Beatmaps/IWorkingBeatmap.cs | 6 ++++++ osu.Game/Beatmaps/WorkingBeatmap.cs | 9 +++++++++ .../20171019041408_InitialCreate.Designer.cs | 2 ++ .../Migrations/20171019041408_InitialCreate.cs | 1 + ...171025071459_AddMissingIndexRules.Designer.cs | 2 ++ ...ddBeatmapOnlineIDUniqueConstraint.Designer.cs | 2 ++ ...209034410_AddRulesetInfoShortName.Designer.cs | 2 ++ .../20180125143340_Settings.Designer.cs | 2 ++ .../20180219060912_AddSkins.Designer.cs | 2 ++ ...55154_RemoveUniqueHashConstraints.Designer.cs | 2 ++ ...044111_UpdateTaikoDefaultBindings.Designer.cs | 2 ++ ...180628011956_RemoveNegativeSetIDs.Designer.cs | 2 ++ .../20180913080842_AddRankStatus.Designer.cs | 2 ++ .../20181007180454_StandardizePaths.Designer.cs | 2 ++ .../20181007180454_StandardizePaths.cs | 1 + .../20181128100659_AddSkinInfoHash.Designer.cs | 2 ++ ...20181130113755_AddScoreInfoTables.Designer.cs | 2 ++ .../20190225062029_AddUserIDColumn.Designer.cs | 2 ++ .../20190525060824_SkinSettings.Designer.cs | 2 ++ ...46_AddDateAddedColumnToBeatmapSet.Designer.cs | 2 ++ ...0708070844_AddBPMAndLengthColumns.Designer.cs | 2 ++ osu.Game/Migrations/OsuDbContextModelSnapshot.cs | 2 ++ osu.Game/Screens/Edit/EditorWorkingBeatmap.cs | 3 +++ osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs | 3 +++ osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs | 3 +++ 30 files changed, 93 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 166ba5111c..b9ed3664ef 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -13,6 +13,7 @@ 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; @@ -340,6 +341,7 @@ 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 5bbffc2f77..1d00c94ef2 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -7,6 +7,7 @@ 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; @@ -64,6 +65,21 @@ namespace osu.Game.Beatmaps } } + protected override VideoSprite GetVideo() + { + if (Metadata?.VideoFile == null) + return null; + + try + { + return new VideoSprite(textureStore.GetStream(getPathForFile(Metadata.VideoFile))); + } + catch + { + return null; + } + } + protected override Track GetTrack() { try diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 001f319307..9267527d79 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 string VideoFile { get; set; } public override string ToString() => $"{Artist} - {Title} ({Author})"; @@ -81,7 +82,8 @@ namespace osu.Game.Beatmaps && Tags == other.Tags && PreviewTime == other.PreviewTime && AudioFile == other.AudioFile - && BackgroundFile == other.BackgroundFile; + && BackgroundFile == other.BackgroundFile + && VideoFile == other.VideoFile; } } } diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 29ade24328..a3ab01c886 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -7,6 +7,7 @@ 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; @@ -44,6 +45,8 @@ 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 02d969b571..0532790f0a 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -296,8 +296,13 @@ namespace osu.Game.Beatmaps.Formats switch (type) { case EventType.Background: - string filename = split[2].Trim('"'); - beatmap.BeatmapInfo.Metadata.BackgroundFile = FileSafety.PathStandardise(filename); + string bgFilename = split[2].Trim('"'); + beatmap.BeatmapInfo.Metadata.BackgroundFile = FileSafety.PathStandardise(bgFilename); + break; + + case EventType.Video: + string videoFilename = split[2].Trim('"'); + beatmap.BeatmapInfo.Metadata.VideoFile = FileSafety.PathStandardise(videoFilename); break; case EventType.Break: diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index 44071d9cc1..b932e67bae 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -4,6 +4,7 @@ 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; @@ -25,6 +26,11 @@ namespace osu.Game.Beatmaps /// Texture Background { get; } + /// + /// Retrieves the video 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 d8ab411beb..b489936556 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -19,6 +19,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.UI; using osu.Game.Skinning; +using osu.Framework.Graphics.Video; namespace osu.Game.Beatmaps { @@ -43,6 +44,7 @@ namespace osu.Game.Beatmaps track = new RecyclableLazy(() => GetTrack() ?? GetVirtualTrack()); background = new RecyclableLazy(GetBackground, BackgroundStillValid); + video = new RecyclableLazy(GetVideo); waveform = new RecyclableLazy(GetWaveform); storyboard = new RecyclableLazy(GetStoryboard); skin = new RecyclableLazy(GetSkin); @@ -183,9 +185,16 @@ namespace osu.Game.Beatmaps public bool BackgroundLoaded => background.IsResultAvailable; public Texture Background => background.Value; protected virtual bool BackgroundStillValid(Texture b) => b == null || b.Available; + protected abstract Texture GetBackground(); private readonly RecyclableLazy background; + public bool VideoLoaded => video.IsResultAvailable; + public VideoSprite Video => video.Value; + + protected abstract VideoSprite GetVideo(); + private readonly RecyclableLazy video; + public bool TrackLoaded => track.IsResultAvailable; public Track Track => track.Value; protected abstract Track GetTrack(); diff --git a/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs b/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs index c751530bf4..596d80557b 100644 --- a/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs +++ b/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs @@ -123,6 +123,8 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); + b.Property("VideoFile"); + b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20171019041408_InitialCreate.cs b/osu.Game/Migrations/20171019041408_InitialCreate.cs index 9b6881f98c..3349998873 100644 --- a/osu.Game/Migrations/20171019041408_InitialCreate.cs +++ b/osu.Game/Migrations/20171019041408_InitialCreate.cs @@ -35,6 +35,7 @@ namespace osu.Game.Migrations AudioFile = table.Column(type: "TEXT", nullable: true), Author = table.Column(type: "TEXT", nullable: true), BackgroundFile = table.Column(type: "TEXT", nullable: true), + VideoFile = table.Column(type: "TEXT", nullable: true), PreviewTime = table.Column(type: "INTEGER", nullable: false), Source = table.Column(type: "TEXT", nullable: true), Tags = table.Column(type: "TEXT", nullable: true), diff --git a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs index 4cd234f2ef..ab85aece9f 100644 --- a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs +++ b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs @@ -125,6 +125,8 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); + b.Property("VideoFile"); + b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs index 006acf12cd..d565e1cbf2 100644 --- a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs +++ b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs @@ -128,6 +128,8 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); + b.Property("VideoFile"); + b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs index fc2496bc24..3c37c59595 100644 --- a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs +++ b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs @@ -128,6 +128,8 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); + b.Property("VideoFile"); + b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20180125143340_Settings.Designer.cs b/osu.Game/Migrations/20180125143340_Settings.Designer.cs index 4bb599eec1..4c41d223c5 100644 --- a/osu.Game/Migrations/20180125143340_Settings.Designer.cs +++ b/osu.Game/Migrations/20180125143340_Settings.Designer.cs @@ -128,6 +128,8 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); + b.Property("VideoFile"); + b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs b/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs index cdc4ef2e66..124c61283f 100644 --- a/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs +++ b/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs @@ -128,6 +128,8 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); + b.Property("VideoFile"); + b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs index f28408bfb3..9cbd75ce2c 100644 --- a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs +++ b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs @@ -126,6 +126,8 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); + b.Property("VideoFile"); + b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs index aaa11e88b6..150bc2ecbc 100644 --- a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs +++ b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs @@ -125,6 +125,8 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); + b.Property("VideoFile"); + b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs index 7eeacd56d7..0a1db37c7f 100644 --- a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs +++ b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs @@ -125,6 +125,8 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); + b.Property("VideoFile"); + b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs b/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs index 5ab43da046..b04d36fac1 100644 --- a/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs +++ b/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs @@ -127,6 +127,8 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); + b.Property("VideoFile"); + b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs b/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs index b387a45ecf..aed946e577 100644 --- a/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs +++ b/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs @@ -127,6 +127,8 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); + b.Property("VideoFile"); + b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20181007180454_StandardizePaths.cs b/osu.Game/Migrations/20181007180454_StandardizePaths.cs index 274b8030a9..c106b839e2 100644 --- a/osu.Game/Migrations/20181007180454_StandardizePaths.cs +++ b/osu.Game/Migrations/20181007180454_StandardizePaths.cs @@ -15,6 +15,7 @@ namespace osu.Game.Migrations migrationBuilder.Sql($"UPDATE `BeatmapInfo` SET `Path` = REPLACE(`Path`, '{windowsStyle}', '{standardized}')"); migrationBuilder.Sql($"UPDATE `BeatmapMetadata` SET `AudioFile` = REPLACE(`AudioFile`, '{windowsStyle}', '{standardized}')"); migrationBuilder.Sql($"UPDATE `BeatmapMetadata` SET `BackgroundFile` = REPLACE(`BackgroundFile`, '{windowsStyle}', '{standardized}')"); + migrationBuilder.Sql($"UPDATE `BeatmapMetadata` SET `VideoFile` = REPLACE(`VideoFile`, '{windowsStyle}', '{standardized}')"); migrationBuilder.Sql($"UPDATE `BeatmapSetFileInfo` SET `Filename` = REPLACE(`Filename`, '{windowsStyle}', '{standardized}')"); migrationBuilder.Sql($"UPDATE `SkinFileInfo` SET `Filename` = REPLACE(`Filename`, '{windowsStyle}', '{standardized}')"); } diff --git a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs index 120674671a..fe0594e542 100644 --- a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs +++ b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs @@ -127,6 +127,8 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); + b.Property("VideoFile"); + b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs index eee53182ce..efa64f014f 100644 --- a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs +++ b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs @@ -127,6 +127,8 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); + b.Property("VideoFile"); + b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs b/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs index 8e1e3a59f3..a950a54e39 100644 --- a/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs +++ b/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs @@ -127,6 +127,8 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); + b.Property("VideoFile"); + b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs b/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs index 348c42adb9..df2c434b4e 100644 --- a/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs +++ b/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs @@ -127,6 +127,8 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); + b.Property("VideoFile"); + b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs index 9477369aa0..ea699edcc9 100644 --- a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs +++ b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs @@ -127,6 +127,8 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); + b.Property("VideoFile"); + b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs index c5fcc16f84..fb678178a2 100644 --- a/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs +++ b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs @@ -131,6 +131,8 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); + b.Property("VideoFile"); + b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index 761dca2801..1725812d33 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -129,6 +129,8 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); + b.Property("VideoFile"); + b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs b/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs index 299059407c..4b8720fe1c 100644 --- a/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; +using osu.Framework.Graphics.Video; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -33,6 +34,8 @@ namespace osu.Game.Screens.Edit public Texture Background => workingBeatmap.Background; + public VideoSprite Video => workingBeatmap.Video; + public Track Track => workingBeatmap.Track; public Waveform Waveform => workingBeatmap.Waveform; diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index a555a52e42..3fc9662b17 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -10,6 +10,7 @@ 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.Rulesets; @@ -201,6 +202,8 @@ 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 0ef35879e3..0d9f4f51be 100644 --- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs @@ -3,6 +3,7 @@ using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; +using osu.Framework.Graphics.Video; using osu.Game.Beatmaps; namespace osu.Game.Tests.Beatmaps @@ -25,6 +26,8 @@ namespace osu.Game.Tests.Beatmaps protected override Texture GetBackground() => null; + protected override VideoSprite GetVideo() => null; + protected override Track GetTrack() => null; } } From 58a0b4e19b2f5cfe29e9a98e3d33d314ffd9c277 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 30 Aug 2019 23:19:55 +0300 Subject: [PATCH 0991/2815] Add basic layout for player --- osu.Game/Screens/Play/Player.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b487f3e61b..9ff52d8444 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -10,6 +10,7 @@ using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Video; using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Framework.Screens; @@ -81,6 +82,8 @@ namespace osu.Game.Screens.Play protected DimmableStoryboard DimmableStoryboard { get; private set; } + protected VideoSprite Video { get; private set; } + [Cached] [Cached(Type = typeof(IBindable>))] protected new readonly Bindable> Mods = new Bindable>(Array.Empty()); @@ -143,6 +146,14 @@ namespace osu.Game.Screens.Play private void addUnderlayComponents(Container target) { target.Add(DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }); + + var video = Beatmap.Value.Video; + + if (video != null) + { + target.Add(Video = video); + Video.RelativeSizeAxes = Axes.Both; + } } private void addGameplayComponents(Container target, WorkingBeatmap working) From d55be4d59cd645e3dcde3d60c28c266235127115 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 30 Aug 2019 23:48:38 +0300 Subject: [PATCH 0992/2815] Implement DimmableVideo component --- osu.Game/Screens/Play/DimmableVideo.cs | 64 ++++++++++++++++++++++++++ osu.Game/Screens/Play/Player.cs | 10 +--- 2 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 osu.Game/Screens/Play/DimmableVideo.cs diff --git a/osu.Game/Screens/Play/DimmableVideo.cs b/osu.Game/Screens/Play/DimmableVideo.cs new file mode 100644 index 0000000000..68ce5fcd40 --- /dev/null +++ b/osu.Game/Screens/Play/DimmableVideo.cs @@ -0,0 +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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Video; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Screens.Play +{ + public class DimmableVideo : UserDimContainer + { + private readonly VideoSprite video; + private DrawableVideo drawableVideo; + + public DimmableVideo(VideoSprite video) + { + this.video = video; + } + + [BackgroundDependencyLoader] + private void load() + { + initializeVideo(false); + } + + protected override void LoadComplete() + { + ShowStoryboard.BindValueChanged(_ => initializeVideo(true), true); + base.LoadComplete(); + } + + protected override bool ShowDimContent => ShowStoryboard.Value && DimLevel < 1; + + private void initializeVideo(bool async) + { + if (drawableVideo != null) + return; + + if (!ShowStoryboard.Value) + return; + + drawableVideo = new DrawableVideo(video); + + if (async) + LoadComponentAsync(drawableVideo, Add); + else + Add(drawableVideo); + } + + private class DrawableVideo : Container + { + public DrawableVideo(VideoSprite video) + { + RelativeSizeAxes = Axes.Both; + Masking = true; + + AddInternal(video); + video.RelativeSizeAxes = Axes.Both; + } + } + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 9ff52d8444..968b78ad8e 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -81,6 +81,7 @@ namespace osu.Game.Screens.Play protected GameplayClockContainer GameplayClockContainer { get; private set; } protected DimmableStoryboard DimmableStoryboard { get; private set; } + protected DimmableVideo DimmableVideo { get; private set; } protected VideoSprite Video { get; private set; } @@ -146,14 +147,7 @@ namespace osu.Game.Screens.Play private void addUnderlayComponents(Container target) { target.Add(DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }); - - var video = Beatmap.Value.Video; - - if (video != null) - { - target.Add(Video = video); - Video.RelativeSizeAxes = Axes.Both; - } + target.Add(DimmableVideo = new DimmableVideo(Beatmap.Value.Video) { RelativeSizeAxes = Axes.Both }); } private void addGameplayComponents(Container target, WorkingBeatmap working) From 5dd688a51b841f534070fe3066905817a9dec8c4 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 31 Aug 2019 00:09:23 +0300 Subject: [PATCH 0993/2815] Fix video doesn't use gameplay clock --- osu.Game/Screens/Play/DimmableVideo.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Play/DimmableVideo.cs b/osu.Game/Screens/Play/DimmableVideo.cs index 68ce5fcd40..bc4105c3b9 100644 --- a/osu.Game/Screens/Play/DimmableVideo.cs +++ b/osu.Game/Screens/Play/DimmableVideo.cs @@ -59,6 +59,13 @@ namespace osu.Game.Screens.Play AddInternal(video); video.RelativeSizeAxes = Axes.Both; } + + [BackgroundDependencyLoader] + private void load(GameplayClock clock) + { + if (clock != null) + Clock = clock; + } } } } From fa3591e5ec35b80115b3ed3005067ef1ae05231a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 31 Aug 2019 00:42:20 +0300 Subject: [PATCH 0994/2815] Add setting to turn on/off the video --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ osu.Game/Graphics/Containers/UserDimContainer.cs | 4 ++++ .../Overlays/Settings/Sections/Graphics/DetailSettings.cs | 5 +++++ osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs | 2 +- osu.Game/Screens/Play/DimmableVideo.cs | 6 +++--- osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs | 3 +++ 6 files changed, 18 insertions(+), 4 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 0cecbb225f..357883da45 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -69,6 +69,7 @@ namespace osu.Game.Configuration Set(OsuSetting.ShowFpsDisplay, false); Set(OsuSetting.ShowStoryboard, true); + Set(OsuSetting.ShowVideo, true); Set(OsuSetting.BeatmapSkins, true); Set(OsuSetting.BeatmapHitsounds, true); @@ -136,6 +137,7 @@ namespace osu.Game.Configuration DimLevel, BlurLevel, ShowStoryboard, + ShowVideo, KeyOverlay, ScoreMeter, FloatingComments, diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 2b7635cc88..d0e932fac0 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -35,6 +35,8 @@ namespace osu.Game.Graphics.Containers protected Bindable ShowStoryboard { get; private set; } + protected Bindable ShowVideo { get; private set; } + protected double DimLevel => EnableUserDim.Value ? UserDimLevel.Value : 0; protected override Container Content => dimContent; @@ -54,10 +56,12 @@ namespace osu.Game.Graphics.Containers { UserDimLevel = config.GetBindable(OsuSetting.DimLevel); ShowStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); + ShowVideo = config.GetBindable(OsuSetting.ShowVideo); EnableUserDim.ValueChanged += _ => UpdateVisuals(); UserDimLevel.ValueChanged += _ => UpdateVisuals(); ShowStoryboard.ValueChanged += _ => UpdateVisuals(); + ShowVideo.ValueChanged += _ => UpdateVisuals(); StoryboardReplacesBackground.ValueChanged += _ => UpdateVisuals(); } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs index 01cdc9aa32..6d9870598f 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs @@ -22,6 +22,11 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics Bindable = config.GetBindable(OsuSetting.ShowStoryboard) }, new SettingsCheckbox + { + LabelText = "Video", + Bindable = config.GetBindable(OsuSetting.ShowVideo) + }, + new SettingsCheckbox { LabelText = "Rotate cursor when dragging", Bindable = config.GetBindable(OsuSetting.CursorRotation) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 5225740d0b..2730b0b90d 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -178,7 +178,7 @@ namespace osu.Game.Screens.Backgrounds BlurAmount.ValueChanged += _ => UpdateVisuals(); } - 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 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 void UpdateVisuals() { diff --git a/osu.Game/Screens/Play/DimmableVideo.cs b/osu.Game/Screens/Play/DimmableVideo.cs index bc4105c3b9..0452af8419 100644 --- a/osu.Game/Screens/Play/DimmableVideo.cs +++ b/osu.Game/Screens/Play/DimmableVideo.cs @@ -27,18 +27,18 @@ namespace osu.Game.Screens.Play protected override void LoadComplete() { - ShowStoryboard.BindValueChanged(_ => initializeVideo(true), true); + ShowVideo.BindValueChanged(_ => initializeVideo(true), true); base.LoadComplete(); } - protected override bool ShowDimContent => ShowStoryboard.Value && DimLevel < 1; + protected override bool ShowDimContent => ShowVideo.Value && DimLevel < 1; private void initializeVideo(bool async) { if (drawableVideo != null) return; - if (!ShowStoryboard.Value) + if (!ShowVideo.Value) return; drawableVideo = new DrawableVideo(video); diff --git a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs index 1c8628f704..5e47b730ad 100644 --- a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs @@ -15,6 +15,7 @@ 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; @@ -37,6 +38,7 @@ 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" } }; @@ -48,6 +50,7 @@ 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.ShowVideo); beatmapSkinsToggle.Current = config.GetBindable(OsuSetting.BeatmapSkins); beatmapHitsoundsToggle.Current = config.GetBindable(OsuSetting.BeatmapHitsounds); } From 264441d90c0662edfdcf722e7806f9997551c0de Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 31 Aug 2019 02:42:26 +0300 Subject: [PATCH 0995/2815] Fix broken test --- osu.Game.Tests/WaveformTestBeatmap.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs index 3e0df8d45e..db9576b5fa 100644 --- a/osu.Game.Tests/WaveformTestBeatmap.cs +++ b/osu.Game.Tests/WaveformTestBeatmap.cs @@ -6,6 +6,7 @@ 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.Archives; @@ -42,6 +43,8 @@ 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); From fd958ec1abb31f182b673dc2b13223df9a854560 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 31 Aug 2019 02:56:41 +0300 Subject: [PATCH 0996/2815] Remove unused property accessor --- osu.Game/Screens/Play/Player.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 968b78ad8e..b5a378506a 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -83,8 +83,6 @@ namespace osu.Game.Screens.Play protected DimmableStoryboard DimmableStoryboard { get; private set; } protected DimmableVideo DimmableVideo { get; private set; } - protected VideoSprite Video { get; private set; } - [Cached] [Cached(Type = typeof(IBindable>))] protected new readonly Bindable> Mods = new Bindable>(Array.Empty()); From d4291556eef6a67926b0dffd855c933abfc1ab82 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 31 Aug 2019 02:57:14 +0300 Subject: [PATCH 0997/2815] Remove unused using --- osu.Game/Screens/Play/Player.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b5a378506a..274107dfa5 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -10,7 +10,6 @@ using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Video; using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Framework.Screens; From 1ddf292ad687878c0acf8ef247a0060ba08a5d5c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 31 Aug 2019 12:20:50 +0900 Subject: [PATCH 0998/2815] Fix vertical alignment of hit error display ticks Wasn't correctly centered before. --- osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index d5d3cb528e..594dd64e52 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -238,7 +238,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { Y = getRelativeJudgementPosition(judgement.TimeOffset), Anchor = alignment == Anchor.x2 ? Anchor.x0 : Anchor.x2, - Origin = alignment == Anchor.x2 ? Anchor.x0 : Anchor.x2, + Origin = Anchor.y1 | (alignment == Anchor.x2 ? Anchor.x0 : Anchor.x2), }); arrow.MoveToY( From f89981e1a330c3105e7fe87481f7a0df6721fa32 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 31 Aug 2019 12:23:15 +0900 Subject: [PATCH 0999/2815] Fix legacy skin text reading from the wrong source Regressed with ruleset legacy skin implementation. --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs index 3c508f34e0..d1bd0f0a88 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Skinning return !hasFont(font) ? null - : new LegacySpriteText(this, font) + : new LegacySpriteText(source, font) { // Spacing value was reverse-engineered from the ratio of the rendered sprite size in the visual inspector vs the actual texture size Scale = new Vector2(0.96f), @@ -117,6 +117,6 @@ namespace osu.Game.Rulesets.Osu.Skinning public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => configuration.Value is TConfiguration conf ? query.Invoke(conf) : default; - private bool hasFont(string fontName) => GetTexture($"{fontName}-0") != null; + private bool hasFont(string fontName) => source.GetTexture($"{fontName}-0") != null; } } From 12eeec36fca2dccda14043caab9802aa9cdc9754 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 31 Aug 2019 12:33:29 +0900 Subject: [PATCH 1000/2815] Fix ruleset skins incorrectly providing configuration defaults --- osu.Game/Skinning/DefaultSkin.cs | 2 +- osu.Game/Skinning/DefaultSkinConfiguration.cs | 28 +++++++++++++++++++ osu.Game/Skinning/LegacySkin.cs | 2 +- osu.Game/Skinning/SkinConfiguration.cs | 15 ++++------ 4 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 osu.Game/Skinning/DefaultSkinConfiguration.cs diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 6072bb64ed..ec957566cb 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -13,7 +13,7 @@ namespace osu.Game.Skinning public DefaultSkin() : base(SkinInfo.Default) { - Configuration = new SkinConfiguration(); + Configuration = new DefaultSkinConfiguration(); } public override Drawable GetDrawableComponent(string componentName) => null; diff --git a/osu.Game/Skinning/DefaultSkinConfiguration.cs b/osu.Game/Skinning/DefaultSkinConfiguration.cs new file mode 100644 index 0000000000..722b35f102 --- /dev/null +++ b/osu.Game/Skinning/DefaultSkinConfiguration.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osuTK.Graphics; + +namespace osu.Game.Skinning +{ + /// + /// A skin configuration pre-populated with sane defaults. + /// + public class DefaultSkinConfiguration : SkinConfiguration + { + public DefaultSkinConfiguration() + { + HitCircleFont = "default"; + + ComboColours.AddRange(new[] + { + new Color4(17, 136, 170, 255), + new Color4(102, 136, 0, 255), + new Color4(204, 102, 0, 255), + new Color4(121, 9, 13, 255) + }); + + CursorExpand = true; + } + } +} diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 1572c588e8..56b8aa19c2 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -34,7 +34,7 @@ namespace osu.Game.Skinning using (StreamReader reader = new StreamReader(stream)) Configuration = new LegacySkinDecoder().Decode(reader); else - Configuration = new SkinConfiguration(); + Configuration = new DefaultSkinConfiguration(); Samples = audioManager.GetSampleStore(storage); Textures = new TextureStore(new TextureLoaderStore(storage)); diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs index 93b599f9f6..d585c58ef1 100644 --- a/osu.Game/Skinning/SkinConfiguration.cs +++ b/osu.Game/Skinning/SkinConfiguration.cs @@ -7,21 +7,18 @@ using osuTK.Graphics; namespace osu.Game.Skinning { + /// + /// An empty skin configuration. + /// public class SkinConfiguration : IHasComboColours, IHasCustomColours { public readonly SkinInfo SkinInfo = new SkinInfo(); - public List ComboColours { get; set; } = new List - { - new Color4(17, 136, 170, 255), - new Color4(102, 136, 0, 255), - new Color4(204, 102, 0, 255), - new Color4(121, 9, 13, 255) - }; + public List ComboColours { get; set; } = new List(); public Dictionary CustomColours { get; set; } = new Dictionary(); - public string HitCircleFont { get; set; } = "default"; + public string HitCircleFont { get; set; } public int HitCircleOverlap { get; set; } @@ -29,6 +26,6 @@ namespace osu.Game.Skinning public float? SliderPathRadius { get; set; } - public bool? CursorExpand { get; set; } = true; + public bool? CursorExpand { get; set; } } } From d2a3e0581b750c030d197573e6923815ca2e60f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 31 Aug 2019 13:27:03 +0900 Subject: [PATCH 1001/2815] Fix legacy decoder using wrong configuration --- osu.Game/Skinning/LegacySkinDecoder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs index ecb112955c..0160755eed 100644 --- a/osu.Game/Skinning/LegacySkinDecoder.cs +++ b/osu.Game/Skinning/LegacySkinDecoder.cs @@ -5,14 +5,14 @@ using osu.Game.Beatmaps.Formats; namespace osu.Game.Skinning { - public class LegacySkinDecoder : LegacyDecoder + public class LegacySkinDecoder : LegacyDecoder { public LegacySkinDecoder() : base(1) { } - protected override void ParseLine(SkinConfiguration skin, Section section, string line) + protected override void ParseLine(DefaultSkinConfiguration skin, Section section, string line) { line = StripComments(line); From 2988624f1fd66896c72cb286984a733ebd4bfb02 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 31 Aug 2019 16:52:41 +0900 Subject: [PATCH 1002/2815] Add fallback for safety --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs index 3c508f34e0..97db2d7c8e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs @@ -115,7 +115,7 @@ namespace osu.Game.Rulesets.Osu.Skinning public SampleChannel GetSample(ISampleInfo sample) => null; public TValue GetValue(Func query) where TConfiguration : SkinConfiguration - => configuration.Value is TConfiguration conf ? query.Invoke(conf) : default; + => configuration.Value is TConfiguration conf ? query.Invoke(conf) : source.GetValue(query); private bool hasFont(string fontName) => GetTexture($"{fontName}-0") != null; } From 3da5eb6c8b3642980a8f2e0197d0a644122bae13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 31 Aug 2019 16:56:32 +0900 Subject: [PATCH 1003/2815] Add source lookups for safety --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs index d1bd0f0a88..f9fbdcd0e9 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs @@ -110,9 +110,9 @@ namespace osu.Game.Rulesets.Osu.Skinning return null; } - public Texture GetTexture(string componentName) => null; + public Texture GetTexture(string componentName) => source.GetTexture(componentName); - public SampleChannel GetSample(ISampleInfo sample) => null; + public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample); public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => configuration.Value is TConfiguration conf ? query.Invoke(conf) : default; From cbbc6aad6eefdf4bcac3b1fba363dce0e66879d5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 31 Aug 2019 21:32:02 +0900 Subject: [PATCH 1004/2815] Make method static --- osu.Game/Screens/Edit/BindableBeatDivisor.cs | 2 +- osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/BindableBeatDivisor.cs index 055077cc4f..2aeb1ef04b 100644 --- a/osu.Game/Screens/Edit/BindableBeatDivisor.cs +++ b/osu.Game/Screens/Edit/BindableBeatDivisor.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Edit /// The beat divisor. /// The set of colours. /// The applicable colour from for . - public ColourInfo GetColourFor(int beatDivisor, OsuColour colours) + public static ColourInfo GetColourFor(int beatDivisor, OsuColour colours) { switch (beatDivisor) { diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index ddcdfdaf80..4d89e43ee5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -212,7 +212,7 @@ namespace osu.Game.Screens.Edit.Compose.Components Anchor = Anchor.TopLeft, Origin = Anchor.TopCentre, RelativePositionAxes = Axes.X, - Colour = beatDivisor.GetColourFor(t, colours), + Colour = BindableBeatDivisor.GetColourFor(t, colours), X = getMappedPosition(t) }); } From c10d2302dc6d276a3f50601bd59db44a1b4ec443 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 31 Aug 2019 15:49:57 +0300 Subject: [PATCH 1005/2815] Remove video property from migrations --- osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs | 2 -- .../Migrations/20171025071459_AddMissingIndexRules.Designer.cs | 2 -- ...0171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs | 2 -- .../20171209034410_AddRulesetInfoShortName.Designer.cs | 2 -- osu.Game/Migrations/20180125143340_Settings.Designer.cs | 2 -- osu.Game/Migrations/20180219060912_AddSkins.Designer.cs | 2 -- .../20180529055154_RemoveUniqueHashConstraints.Designer.cs | 2 -- .../20180621044111_UpdateTaikoDefaultBindings.Designer.cs | 2 -- .../Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs | 2 -- osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs | 2 -- osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs | 2 -- osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs | 2 -- .../Migrations/20181130113755_AddScoreInfoTables.Designer.cs | 2 -- osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs | 2 -- osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs | 2 -- .../20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs | 2 -- .../20190708070844_AddBPMAndLengthColumns.Designer.cs | 2 -- osu.Game/Migrations/OsuDbContextModelSnapshot.cs | 2 -- 18 files changed, 36 deletions(-) diff --git a/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs b/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs index 596d80557b..c751530bf4 100644 --- a/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs +++ b/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs @@ -123,8 +123,6 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); - b.Property("VideoFile"); - b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs index ab85aece9f..4cd234f2ef 100644 --- a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs +++ b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs @@ -125,8 +125,6 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); - b.Property("VideoFile"); - b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs index d565e1cbf2..006acf12cd 100644 --- a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs +++ b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs @@ -128,8 +128,6 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); - b.Property("VideoFile"); - b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs index 3c37c59595..fc2496bc24 100644 --- a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs +++ b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs @@ -128,8 +128,6 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); - b.Property("VideoFile"); - b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20180125143340_Settings.Designer.cs b/osu.Game/Migrations/20180125143340_Settings.Designer.cs index 4c41d223c5..4bb599eec1 100644 --- a/osu.Game/Migrations/20180125143340_Settings.Designer.cs +++ b/osu.Game/Migrations/20180125143340_Settings.Designer.cs @@ -128,8 +128,6 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); - b.Property("VideoFile"); - b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs b/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs index 124c61283f..cdc4ef2e66 100644 --- a/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs +++ b/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs @@ -128,8 +128,6 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); - b.Property("VideoFile"); - b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs index 9cbd75ce2c..f28408bfb3 100644 --- a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs +++ b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs @@ -126,8 +126,6 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); - b.Property("VideoFile"); - b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs index 150bc2ecbc..aaa11e88b6 100644 --- a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs +++ b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs @@ -125,8 +125,6 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); - b.Property("VideoFile"); - b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs index 0a1db37c7f..7eeacd56d7 100644 --- a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs +++ b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs @@ -125,8 +125,6 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); - b.Property("VideoFile"); - b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs b/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs index b04d36fac1..5ab43da046 100644 --- a/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs +++ b/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs @@ -127,8 +127,6 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); - b.Property("VideoFile"); - b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs b/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs index aed946e577..b387a45ecf 100644 --- a/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs +++ b/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs @@ -127,8 +127,6 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); - b.Property("VideoFile"); - b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs index fe0594e542..120674671a 100644 --- a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs +++ b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs @@ -127,8 +127,6 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); - b.Property("VideoFile"); - b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs index efa64f014f..eee53182ce 100644 --- a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs +++ b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs @@ -127,8 +127,6 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); - b.Property("VideoFile"); - b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs b/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs index a950a54e39..8e1e3a59f3 100644 --- a/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs +++ b/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs @@ -127,8 +127,6 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); - b.Property("VideoFile"); - b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs b/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs index df2c434b4e..348c42adb9 100644 --- a/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs +++ b/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs @@ -127,8 +127,6 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); - b.Property("VideoFile"); - b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs index ea699edcc9..9477369aa0 100644 --- a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs +++ b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs @@ -127,8 +127,6 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); - b.Property("VideoFile"); - b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs index fb678178a2..c5fcc16f84 100644 --- a/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs +++ b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs @@ -131,8 +131,6 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); - b.Property("VideoFile"); - b.Property("PreviewTime"); b.Property("Source"); diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index 1725812d33..761dca2801 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -129,8 +129,6 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); - b.Property("VideoFile"); - b.Property("PreviewTime"); b.Property("Source"); From d2f7a653a8aecc198fb7cadaddbbe75e2888b855 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 31 Aug 2019 16:10:07 +0300 Subject: [PATCH 1006/2815] Fix nullref --- osu.Game/Screens/Play/DimmableVideo.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/DimmableVideo.cs b/osu.Game/Screens/Play/DimmableVideo.cs index 0452af8419..3e6b95d2cc 100644 --- a/osu.Game/Screens/Play/DimmableVideo.cs +++ b/osu.Game/Screens/Play/DimmableVideo.cs @@ -35,6 +35,9 @@ namespace osu.Game.Screens.Play private void initializeVideo(bool async) { + if (video == null) + return; + if (drawableVideo != null) return; From 94512fea8e82504f920a3e56a0a195029b831cdf Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 31 Aug 2019 16:20:33 +0300 Subject: [PATCH 1007/2815] Apply naming suggestions --- osu.Game/Beatmaps/IWorkingBeatmap.cs | 2 +- osu.Game/Beatmaps/WorkingBeatmap.cs | 1 - osu.Game/Configuration/OsuConfigManager.cs | 4 ++-- osu.Game/Graphics/Containers/UserDimContainer.cs | 2 +- .../Overlays/Settings/Sections/Graphics/DetailSettings.cs | 2 +- osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs | 2 +- 6 files changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index b932e67bae..a087a52ada 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -27,7 +27,7 @@ namespace osu.Game.Beatmaps Texture Background { get; } /// - /// Retrieves the video file for this . + /// Retrieves the video background file for this . /// VideoSprite Video { get; } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index b489936556..bf3fa90d7b 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -185,7 +185,6 @@ namespace osu.Game.Beatmaps public bool BackgroundLoaded => background.IsResultAvailable; public Texture Background => background.Value; protected virtual bool BackgroundStillValid(Texture b) => b == null || b.Available; - protected abstract Texture GetBackground(); private readonly RecyclableLazy background; diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 357883da45..71a74a5558 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -69,7 +69,7 @@ namespace osu.Game.Configuration Set(OsuSetting.ShowFpsDisplay, false); Set(OsuSetting.ShowStoryboard, true); - Set(OsuSetting.ShowVideo, true); + Set(OsuSetting.ShowVideoBackground, true); Set(OsuSetting.BeatmapSkins, true); Set(OsuSetting.BeatmapHitsounds, true); @@ -137,7 +137,7 @@ namespace osu.Game.Configuration DimLevel, BlurLevel, ShowStoryboard, - ShowVideo, + ShowVideoBackground, KeyOverlay, ScoreMeter, FloatingComments, diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index d0e932fac0..7683bbcd63 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -56,7 +56,7 @@ namespace osu.Game.Graphics.Containers { UserDimLevel = config.GetBindable(OsuSetting.DimLevel); ShowStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); - ShowVideo = config.GetBindable(OsuSetting.ShowVideo); + ShowVideo = config.GetBindable(OsuSetting.ShowVideoBackground); EnableUserDim.ValueChanged += _ => UpdateVisuals(); UserDimLevel.ValueChanged += _ => UpdateVisuals(); diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs index 6d9870598f..56e56f6ca8 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics new SettingsCheckbox { LabelText = "Video", - Bindable = config.GetBindable(OsuSetting.ShowVideo) + Bindable = config.GetBindable(OsuSetting.ShowVideoBackground) }, new SettingsCheckbox { diff --git a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs index 5e47b730ad..ff64f35a18 100644 --- a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs @@ -50,7 +50,7 @@ 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.ShowVideo); + showVideoToggle.Current = config.GetBindable(OsuSetting.ShowVideoBackground); beatmapSkinsToggle.Current = config.GetBindable(OsuSetting.BeatmapSkins); beatmapHitsoundsToggle.Current = config.GetBindable(OsuSetting.BeatmapHitsounds); } From 1b4ae5a4a495917257be26a88a0b48a57cc231e4 Mon Sep 17 00:00:00 2001 From: pi1024e Date: Thu, 22 Aug 2019 22:37:01 -0400 Subject: [PATCH 1008/2815] Spelling fixes --- osu.Game/Online/API/APIAccess.cs | 2 +- osu.Game/Screens/Menu/IntroCircles.cs | 2 +- osu.Game/Screens/Menu/LogoVisualisation.cs | 4 ++-- osu.Game/Screens/Menu/OsuLogo.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index d722c7a98a..0303293c41 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -165,7 +165,7 @@ namespace osu.Game.Online.API } // The Success callback event is fired on the main thread, so we should wait for that to run before proceeding. - // Without this, we will end up circulating this Connecting loop multiple times and queueing up many web requests + // Without this, we will end up circulating this Connecting loop multiple times and queuing up many web requests // before actually going online. while (State > APIState.Offline && State < APIState.Online) Thread.Sleep(500); diff --git a/osu.Game/Screens/Menu/IntroCircles.cs b/osu.Game/Screens/Menu/IntroCircles.cs index 4fa1a81123..c069f82134 100644 --- a/osu.Game/Screens/Menu/IntroCircles.cs +++ b/osu.Game/Screens/Menu/IntroCircles.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Menu Scheduler.AddDelayed(delegate { - // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Manu. + // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu. if (menuMusic.Value) { track.Restart(); diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 9d0a5cd05b..6984959e9c 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -47,7 +47,7 @@ namespace osu.Game.Screens.Menu private const float visualiser_rounds = 5; /// - /// How much should each bar go down each milisecond (based on a full bar). + /// How much should each bar go down each millisecond (based on a full bar). /// private const float decay_per_milisecond = 0.0024f; @@ -161,7 +161,7 @@ namespace osu.Game.Screens.Menu private IShader shader; private Texture texture; - //Asuming the logo is a circle, we don't need a second dimension. + //Assuming the logo is a circle, we don't need a second dimension. private float size; private Color4 colour; diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 0c5bf12bdb..d37cfe32db 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -229,7 +229,7 @@ namespace osu.Game.Screens.Menu } /// - /// Schedule a new extenral animation. Handled queueing and finishing previous animations in a sane way. + /// Schedule a new external animation. Handled queuing and finishing previous animations in a sane way. /// /// The animation to be performed /// If true, the new animation is delayed until all previous transforms finish. If false, existing transformed are cleared. From 5695bb670e62db544b536942bfd52db23554b461 Mon Sep 17 00:00:00 2001 From: pi1024e Date: Fri, 30 Aug 2019 13:48:45 -0400 Subject: [PATCH 1009/2815] change back to queuing --- osu.Game/Online/API/APIAccess.cs | 2 +- osu.Game/Screens/Menu/OsuLogo.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 0303293c41..d722c7a98a 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -165,7 +165,7 @@ namespace osu.Game.Online.API } // The Success callback event is fired on the main thread, so we should wait for that to run before proceeding. - // Without this, we will end up circulating this Connecting loop multiple times and queuing up many web requests + // Without this, we will end up circulating this Connecting loop multiple times and queueing up many web requests // before actually going online. while (State > APIState.Offline && State < APIState.Online) Thread.Sleep(500); diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index d37cfe32db..534400e720 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -229,7 +229,7 @@ namespace osu.Game.Screens.Menu } /// - /// Schedule a new external animation. Handled queuing and finishing previous animations in a sane way. + /// Schedule a new external animation. Handled queueing and finishing previous animations in a sane way. /// /// The animation to be performed /// If true, the new animation is delayed until all previous transforms finish. If false, existing transformed are cleared. From a1c72db5f62001aa62f994f5f24c463032a8ddce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 31 Aug 2019 17:01:12 +0200 Subject: [PATCH 1010/2815] Fix inconsistent sound effects on mod buttons Because HoverClickSounds.OnClick() does not fire upon right clicking on mod buttons, the sound effects that play on left and right click were inconsistent. Introduce HoverMouseUpSounds drawable that allows to play the click sound effect upon mouse up events for an arbitrary set of mouse buttons and use it on mod buttons. --- .../UserInterface/HoverMouseUpSounds.cs | 42 +++++++++++++++++++ osu.Game/Overlays/Mods/ModButton.cs | 3 +- 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Graphics/UserInterface/HoverMouseUpSounds.cs diff --git a/osu.Game/Graphics/UserInterface/HoverMouseUpSounds.cs b/osu.Game/Graphics/UserInterface/HoverMouseUpSounds.cs new file mode 100644 index 0000000000..dcb443d5aa --- /dev/null +++ b/osu.Game/Graphics/UserInterface/HoverMouseUpSounds.cs @@ -0,0 +1,42 @@ +// 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.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Extensions; +using osu.Framework.Input.Events; +using osuTK.Input; + +namespace osu.Game.Graphics.UserInterface +{ + /// + /// Adds hover sounds to a drawable, as well as click sounds upon MouseUp events for selected mouse buttons. + /// Intended to be used for controls that can respond to clicks of buttons other than the left mouse button in place of . + /// + public class HoverMouseUpSounds : HoverSounds + { + private SampleChannel sampleClick; + private readonly List buttons; + + public HoverMouseUpSounds(List buttons, HoverSampleSet sampleSet = HoverSampleSet.Normal) + : base(sampleSet) + { + this.buttons = buttons; + } + + protected override bool OnMouseUp(MouseUpEvent e) + { + if (Contains(e.ScreenSpaceMousePosition) && buttons.Contains(e.Button)) + sampleClick?.Play(); + return base.OnMouseUp(e); + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + sampleClick = audio.Samples.Get($@"UI/generic-select{SampleSet.GetDescription()}"); + } + } +} diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 7b8745cf42..ba39360102 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -11,6 +11,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics.Cursor; using osu.Framework.Input.Events; @@ -283,7 +284,7 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.TopCentre, Font = OsuFont.GetFont(size: 18) }, - new HoverClickSounds() + new HoverMouseUpSounds(new List { MouseButton.Left, MouseButton.Right }) }; Mod = mod; From 658e0edc3e7df2510953bc9acc11aac35b081590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 31 Aug 2019 20:16:16 +0200 Subject: [PATCH 1011/2815] Handle other button clicks in HoverClickSounds As suggested in review, remove previously introduced HoverMouseUpSounds and instead change effect playing logic in HoverClickSounds by moving it out of OnClick() to OnMouseUp(). Users of the class can either use the existing constructor to play the effect only on left click or use the newly introduced constructor with the MouseButton[] parameter to specify which button clicks should trigger the sound. --- .../UserInterface/HoverClickSounds.cs | 32 ++++++++++++-- .../UserInterface/HoverMouseUpSounds.cs | 42 ------------------- osu.Game/Overlays/Mods/ModButton.cs | 3 +- 3 files changed, 29 insertions(+), 48 deletions(-) delete mode 100644 osu.Game/Graphics/UserInterface/HoverMouseUpSounds.cs diff --git a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs index 70d988f60e..8fe20e3566 100644 --- a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs @@ -1,11 +1,13 @@ // 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.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Extensions; using osu.Framework.Input.Events; +using osuTK.Input; namespace osu.Game.Graphics.UserInterface { @@ -16,16 +18,38 @@ namespace osu.Game.Graphics.UserInterface public class HoverClickSounds : HoverSounds { private SampleChannel sampleClick; + private readonly MouseButton[] buttons; + /// + /// Creates an instance that adds sounds on hover and left click only. + /// + /// Set of click samples to play. public HoverClickSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal) - : base(sampleSet) + : this(new[] { MouseButton.Left }, sampleSet) { } - protected override bool OnClick(ClickEvent e) + /// + /// Creates an instance that adds sounds on hover and on click for any of the buttons specified. + /// + /// Array of button codes which should trigger the click sound. + /// Set of click samples to play. + public HoverClickSounds(MouseButton[] buttons, HoverSampleSet sampleSet = HoverSampleSet.Normal) + : base(sampleSet) { - sampleClick?.Play(); - return base.OnClick(e); + this.buttons = buttons; + } + + protected override bool OnMouseUp(MouseUpEvent e) + { + var index = Array.IndexOf(buttons, e.Button); + bool shouldPlayEffect = index > -1 && index < buttons.Length; + + // examine the button pressed first for short-circuiting + // in most usages it is more likely that another button was pressed than that the cursor left the drawable bounds + if (shouldPlayEffect && Contains(e.ScreenSpaceMousePosition)) + sampleClick?.Play(); + return base.OnMouseUp(e); } [BackgroundDependencyLoader] diff --git a/osu.Game/Graphics/UserInterface/HoverMouseUpSounds.cs b/osu.Game/Graphics/UserInterface/HoverMouseUpSounds.cs deleted file mode 100644 index dcb443d5aa..0000000000 --- a/osu.Game/Graphics/UserInterface/HoverMouseUpSounds.cs +++ /dev/null @@ -1,42 +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 System.Collections.Generic; -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; -using osu.Framework.Extensions; -using osu.Framework.Input.Events; -using osuTK.Input; - -namespace osu.Game.Graphics.UserInterface -{ - /// - /// Adds hover sounds to a drawable, as well as click sounds upon MouseUp events for selected mouse buttons. - /// Intended to be used for controls that can respond to clicks of buttons other than the left mouse button in place of . - /// - public class HoverMouseUpSounds : HoverSounds - { - private SampleChannel sampleClick; - private readonly List buttons; - - public HoverMouseUpSounds(List buttons, HoverSampleSet sampleSet = HoverSampleSet.Normal) - : base(sampleSet) - { - this.buttons = buttons; - } - - protected override bool OnMouseUp(MouseUpEvent e) - { - if (Contains(e.ScreenSpaceMousePosition) && buttons.Contains(e.Button)) - sampleClick?.Play(); - return base.OnMouseUp(e); - } - - [BackgroundDependencyLoader] - private void load(AudioManager audio) - { - sampleClick = audio.Samples.Get($@"UI/generic-select{SampleSet.GetDescription()}"); - } - } -} diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index ba39360102..f46555dc4b 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -11,7 +11,6 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using System; -using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics.Cursor; using osu.Framework.Input.Events; @@ -284,7 +283,7 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.TopCentre, Font = OsuFont.GetFont(size: 18) }, - new HoverMouseUpSounds(new List { MouseButton.Left, MouseButton.Right }) + new HoverClickSounds(new[] { MouseButton.Left, MouseButton.Right }) }; Mod = mod; From aff4dab9aa342955a44e53335c6899fed8fbe3b3 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 1 Sep 2019 03:17:55 +0300 Subject: [PATCH 1012/2815] Ensure playing track of beatmap selected only if a track change occurred --- osu.Game/Screens/Select/SongSelect.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index edb0e6deb8..d0cb5986a8 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -414,7 +414,11 @@ namespace osu.Game.Screens.Select { Logger.Log($"beatmap changed from \"{Beatmap.Value.BeatmapInfo}\" to \"{beatmap}\""); - Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, Beatmap.Value); + WorkingBeatmap previous = Beatmap.Value; + Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, previous); + + if (this.IsCurrentScreen() && Beatmap.Value?.Track != previous?.Track) + ensurePlayingSelected(); if (beatmap != null) { @@ -425,8 +429,6 @@ namespace osu.Game.Screens.Select } } - if (this.IsCurrentScreen()) - ensurePlayingSelected(); UpdateBeatmap(Beatmap.Value); } } From 7d955839be8fc02b466ab53000598e98c36afb9c Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 1 Sep 2019 04:22:24 +0300 Subject: [PATCH 1013/2815] Instantly move rank graph tooltip --- .../Overlays/Profile/Header/Components/RankGraph.cs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index 24ed0cc022..56405483af 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -283,18 +283,7 @@ namespace osu.Game.Overlays.Profile.Header.Components return true; } - private bool instantMove = true; - - public void Move(Vector2 pos) - { - if (instantMove) - { - Position = pos; - instantMove = false; - } - else - this.MoveTo(pos, 200, Easing.OutQuint); - } + public void Move(Vector2 pos) => Position = pos; protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); From a155814bc48b5547b63f50ea60a4ae67bc377bd8 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 1 Sep 2019 06:07:25 +0300 Subject: [PATCH 1014/2815] Implement instant movement properly --- .../Profile/Header/Components/RankGraph.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index 56405483af..c6d96c5917 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -283,9 +283,24 @@ namespace osu.Game.Overlays.Profile.Header.Components return true; } - public void Move(Vector2 pos) => Position = pos; + private bool instantMove = true; - protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); + public void Move(Vector2 pos) + { + if (instantMove) + { + Position = pos; + instantMove = false; + } + else + this.MoveTo(pos, 200, Easing.OutQuint); + } + + protected override void PopIn() + { + instantMove |= !IsPresent; + this.FadeIn(200, Easing.OutQuint); + } protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); } From d1eafafa51b6632c5facea6e9b952e29fdef3441 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 1 Sep 2019 19:57:12 +0900 Subject: [PATCH 1015/2815] Allow searching channels by topics Closes #5939 --- osu.Game/Overlays/Chat/Selection/ChannelListItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs b/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs index 4d77e5f93d..cb0639d85d 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Chat.Selection private Color4 topicColour; private Color4 hoverColour; - public IEnumerable FilterTerms => new[] { channel.Name }; + public IEnumerable FilterTerms => new[] { channel.Name, channel.Topic }; public bool MatchingFilter { From c4dc34eefde8740e12bd2ed974f81841d862c878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 1 Sep 2019 13:10:11 +0200 Subject: [PATCH 1016/2815] Consolidate HoverClickSounds constructors As suggested in review, merge both HoverClickSounds constructors into one accepting optional arguments. Due to existing usages the parameter is added as second and supplied by name in ModButton. --- .../Graphics/UserInterface/HoverClickSounds.cs | 18 ++++++------------ osu.Game/Overlays/Mods/ModButton.cs | 2 +- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs index 8fe20e3566..7e6c0a0974 100644 --- a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs @@ -20,24 +20,18 @@ namespace osu.Game.Graphics.UserInterface private SampleChannel sampleClick; private readonly MouseButton[] buttons; - /// - /// Creates an instance that adds sounds on hover and left click only. - /// - /// Set of click samples to play. - public HoverClickSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal) - : this(new[] { MouseButton.Left }, sampleSet) - { - } - /// /// Creates an instance that adds sounds on hover and on click for any of the buttons specified. /// - /// Array of button codes which should trigger the click sound. /// Set of click samples to play. - public HoverClickSounds(MouseButton[] buttons, HoverSampleSet sampleSet = HoverSampleSet.Normal) + /// + /// Array of button codes which should trigger the click sound. + /// If this optional parameter is omitted or set to null, the click sound will also be added on left click. + /// + public HoverClickSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal, MouseButton[] buttons = null) : base(sampleSet) { - this.buttons = buttons; + this.buttons = buttons ?? new[] { MouseButton.Left }; } protected override bool OnMouseUp(MouseUpEvent e) diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index f46555dc4b..58892cd0dd 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -283,7 +283,7 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.TopCentre, Font = OsuFont.GetFont(size: 18) }, - new HoverClickSounds(new[] { MouseButton.Left, MouseButton.Right }) + new HoverClickSounds(buttons: new[] { MouseButton.Left, MouseButton.Right }) }; Mod = mod; From fc48b190fedb15f243e6087315567907cc80c166 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 1 Sep 2019 13:32:53 +0200 Subject: [PATCH 1017/2815] Fix inaccurate xmldoc --- osu.Game/Graphics/UserInterface/HoverClickSounds.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs index 7e6c0a0974..050e5a2835 100644 --- a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs @@ -26,7 +26,7 @@ namespace osu.Game.Graphics.UserInterface /// Set of click samples to play. /// /// Array of button codes which should trigger the click sound. - /// If this optional parameter is omitted or set to null, the click sound will also be added on left click. + /// If this optional parameter is omitted or set to null, the click sound will only be added on left click. /// public HoverClickSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal, MouseButton[] buttons = null) : base(sampleSet) From 7ca51d3866657302a2c30f90b5e7a757bdb3fe93 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2019 11:20:50 +0900 Subject: [PATCH 1018/2815] Fix resume overlay being drawn below cursor Closes #5905. --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 11 +++++++---- osu.Game/Screens/Play/Player.cs | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 021bd515b5..0ee9196fb8 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -215,10 +215,6 @@ namespace osu.Game.Rulesets.UI continueResume(); } - public ResumeOverlay ResumeOverlay { get; private set; } - - protected virtual ResumeOverlay CreateResumeOverlay() => null; - /// /// Creates and adds the visual representation of a to this . /// @@ -389,6 +385,13 @@ namespace osu.Game.Rulesets.UI /// public abstract GameplayCursorContainer Cursor { get; } + /// + /// An optional overlay used when resuming gameplay from a paused state. + /// + public ResumeOverlay ResumeOverlay { get; protected set; } + + protected virtual ResumeOverlay CreateResumeOverlay() => null; + /// /// Sets a replay to be used, overriding local input. /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b487f3e61b..3f1603eabe 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -178,6 +178,7 @@ namespace osu.Game.Screens.Play }, // display the cursor above some HUD elements. DrawableRuleset.Cursor?.CreateProxy() ?? new Container(), + DrawableRuleset.ResumeOverlay?.CreateProxy() ?? new Container(), HUDOverlay = new HUDOverlay(ScoreProcessor, DrawableRuleset, Mods.Value) { HoldToQuit = From 2bd074883a834305efbf5a89610e2c442a08dc64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2019 12:36:08 +0900 Subject: [PATCH 1019/2815] Fix OsuLegacySkin fallback logic being incorrect Fixes skin fonts not being applied. This is a temporary fix as configuration retrieval from skins will see a complete rewrite over the coming days. --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs index 904064e2f0..ea7257d258 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs @@ -115,7 +115,13 @@ namespace osu.Game.Rulesets.Osu.Skinning public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample); public TValue GetValue(Func query) where TConfiguration : SkinConfiguration - => configuration.Value is TConfiguration conf ? query.Invoke(conf) : source.GetValue(query); + { + TValue val; + if (configuration.Value is TConfiguration conf && (val = query.Invoke(conf)) != null) + return val; + + return source.GetValue(query); + } private bool hasFont(string fontName) => source.GetTexture($"{fontName}-0") != null; } From cad68bb82fce1bfdaed3ca85b092b39f484cb222 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Sep 2019 12:54:59 +0900 Subject: [PATCH 1020/2815] Add autoplay helper property --- osu.Game/Tests/Visual/PlayerTestScene.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 1ab20ecd48..599cb060b1 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -3,6 +3,7 @@ using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Rulesets; @@ -40,6 +41,8 @@ namespace osu.Game.Tests.Visual protected virtual bool AllowFail => false; + protected virtual bool Autoplay => false; + private void loadPlayer() { var beatmap = CreateBeatmap(ruleset.RulesetInfo); @@ -53,6 +56,16 @@ namespace osu.Game.Tests.Visual LoadScreen(Player); } - protected virtual Player CreatePlayer(Ruleset ruleset) => new TestPlayer(false, false); + protected virtual Player CreatePlayer(Ruleset ruleset) + { + if (Autoplay) + { + var mod = ruleset.GetAutoplayMod(); + if (mod != null) + Mods.Value = Mods.Value.Concat(mod.Yield()).ToArray(); + } + + return new TestPlayer(false, false); + } } } From 2945fef62d5afbd53ad3022bd9304d34d108fe0d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Sep 2019 13:04:30 +0900 Subject: [PATCH 1021/2815] Expose HasCompleted from ScoreProcessor --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index e47df6b473..3b7e457990 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Scoring /// /// Whether all s have been processed. /// - protected virtual bool HasCompleted => false; + public virtual bool HasCompleted => false; /// /// Whether this ScoreProcessor has already triggered the failed state. @@ -205,7 +205,7 @@ namespace osu.Game.Rulesets.Scoring private const double combo_portion = 0.7; private const double max_score = 1000000; - protected sealed override bool HasCompleted => JudgedHits == MaxHits; + public sealed override bool HasCompleted => JudgedHits == MaxHits; protected int MaxHits { get; private set; } protected int JudgedHits { get; private set; } From fc668d8a74898586c8a774fd1bd50f36032c7746 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Sep 2019 13:24:39 +0900 Subject: [PATCH 1022/2815] Move autoplay mod to a less overridable location --- osu.Game/Tests/Visual/PlayerTestScene.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 599cb060b1..ccd996098c 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -52,12 +52,6 @@ namespace osu.Game.Tests.Visual if (!AllowFail) Mods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; - Player = CreatePlayer(ruleset); - LoadScreen(Player); - } - - protected virtual Player CreatePlayer(Ruleset ruleset) - { if (Autoplay) { var mod = ruleset.GetAutoplayMod(); @@ -65,7 +59,10 @@ namespace osu.Game.Tests.Visual Mods.Value = Mods.Value.Concat(mod.Yield()).ToArray(); } - return new TestPlayer(false, false); + Player = CreatePlayer(ruleset); + LoadScreen(Player); } + + protected virtual Player CreatePlayer(Ruleset ruleset) => new TestPlayer(false, false); } } From 5b685c4cd2929f759be82a973f3f187f1642d50f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Sep 2019 13:25:39 +0900 Subject: [PATCH 1023/2815] Fix swell ticks having non-zero time offsets --- .../TestSceneSwellJudgements.cs | 74 +++++++++++++++++++ .../Objects/Drawables/DrawableSwellTick.cs | 6 +- 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs new file mode 100644 index 0000000000..f27e329e8e --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs @@ -0,0 +1,74 @@ +// 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.Game.Beatmaps; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Judgements; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public class TestSceneSwellJudgements : PlayerTestScene + { + protected new TestPlayer Player => (TestPlayer)base.Player; + + public TestSceneSwellJudgements() + : base(new TaikoRuleset()) + { + } + + [Test] + public void TestZeroTickTimeOffsets() + { + AddUntilStep("gameplay finished", () => Player.ScoreProcessor.HasCompleted); + AddAssert("all tick offsets are 0", () => Player.Results.Where(r => r.Judgement is TaikoSwellTickJudgement).All(r => r.TimeOffset == 0)); + } + + protected override bool Autoplay => true; + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + var beatmap = new Beatmap + { + BeatmapInfo = { Ruleset = new TaikoRuleset().RulesetInfo }, + HitObjects = + { + new Swell + { + StartTime = 1000, + Duration = 1000, + } + } + }; + + return beatmap; + } + + protected override Player CreatePlayer(Ruleset ruleset) => new TestPlayer(); + + protected class TestPlayer : Player + { + public readonly List Results = new List(); + + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + public TestPlayer() + : base(false, false) + { + } + + [BackgroundDependencyLoader] + private void load() + { + ScoreProcessor.NewJudgement += r => Results.Add(r); + } + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs index 8b27d78101..4833d420f7 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs @@ -14,7 +14,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { } - public void TriggerResult(HitResult type) => ApplyResult(r => r.Type = type); + public void TriggerResult(HitResult type) + { + HitObject.StartTime = Time.Current; + ApplyResult(r => r.Type = type); + } protected override void CheckForResult(bool userTriggered, double timeOffset) { From 1df422e59135964b243ac168ba15bf8fd843a722 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Sep 2019 13:30:08 +0900 Subject: [PATCH 1024/2815] Hide taiko swell ticks --- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs index 8b27d78101..c2e8c08e7e 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.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.Graphics; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Taiko.Objects.Drawables @@ -14,6 +15,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { } + protected override void UpdateInitialTransforms() => this.FadeOut(); + public void TriggerResult(HitResult type) => ApplyResult(r => r.Type = type); protected override void CheckForResult(bool userTriggered, double timeOffset) From 6603cbd74d2a5d4d0450af0953ea39452844ec89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2019 13:41:14 +0900 Subject: [PATCH 1025/2815] No language doesn't mean "Other" --- osu.Game/Overlays/BeatmapSet/Info.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 72db03a5a6..df077a65a9 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -133,7 +133,7 @@ namespace osu.Game.Overlays.BeatmapSet source.Text = b.NewValue?.Metadata.Source ?? string.Empty; tags.Text = b.NewValue?.Metadata.Tags ?? string.Empty; genre.Text = b.NewValue?.OnlineInfo?.Genre?.Name ?? "Unspecified"; - language.Text = b.NewValue?.OnlineInfo?.Language?.Name ?? "Other"; + language.Text = b.NewValue?.OnlineInfo?.Language?.Name ?? "Unspecified"; }; } From d4c12881f5fd4417d6d108da0719001619fd0893 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2019 13:45:13 +0900 Subject: [PATCH 1026/2815] Remove unnecessary over-complication and fix transitions --- osu.Game/Overlays/BeatmapSet/Info.cs | 30 ++++++---------------------- 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index df077a65a9..16d6236051 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -83,24 +83,12 @@ namespace osu.Game.Overlays.BeatmapSet { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - LayoutDuration = transition_duration, + Direction = FillDirection.Full, Children = new[] { source = new MetadataSection("Source"), - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Width = 0.5f, - FillMode = FillMode.Fit, - Children = new Drawable[] - { - genre = new MetadataSection("Genre"), - language = new MetadataSection("Language"), - } - }, + genre = new MetadataSection("Genre") { Width = 0.5f }, + language = new MetadataSection("Language") { Width = 0.5f }, tags = new MetadataSection("Tags"), }, }, @@ -132,8 +120,8 @@ namespace osu.Game.Overlays.BeatmapSet { source.Text = b.NewValue?.Metadata.Source ?? string.Empty; tags.Text = b.NewValue?.Metadata.Tags ?? string.Empty; - genre.Text = b.NewValue?.OnlineInfo?.Genre?.Name ?? "Unspecified"; - language.Text = b.NewValue?.OnlineInfo?.Language?.Name ?? "Unspecified"; + genre.Text = b.NewValue?.OnlineInfo?.Genre?.Name ?? string.Empty; + language.Text = b.NewValue?.OnlineInfo?.Language?.Name ?? string.Empty; }; } @@ -154,7 +142,7 @@ namespace osu.Game.Overlays.BeatmapSet { if (string.IsNullOrEmpty(value)) { - this.FadeOut(transition_duration); + Hide(); return; } @@ -164,12 +152,6 @@ namespace osu.Game.Overlays.BeatmapSet } } - public Color4 TextColour - { - get => textFlow.Colour; - set => textFlow.Colour = value; - } - public MetadataSection(string title) { RelativeSizeAxes = Axes.X; From 0f04357f1f0759c0560ed555f6e1071c1d4caef0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2019 14:41:14 +0900 Subject: [PATCH 1027/2815] Revert short name change for now --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index cd2f8d56af..5428b4eeb8 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Catch public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap); - public const string SHORT_NAME = "catch"; + public const string SHORT_NAME = "fruits"; public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] { From 8f8d35bd15f27f5613bfb9506c96266288081dee Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Sep 2019 15:02:16 +0900 Subject: [PATCH 1028/2815] Delay initial hitobject state computation --- .../Objects/Drawables/DrawableHitObject.cs | 9 ++++++ .../Scrolling/ScrollingHitObjectContainer.cs | 29 +++++++++++++++++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 4a6f261905..a24476418c 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; +using osu.Framework.Threading; using osu.Game.Audio; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; @@ -278,6 +279,14 @@ namespace osu.Game.Rulesets.Objects.Drawables UpdateResult(false); } + /// + /// Schedules an to this . + /// + /// + /// Only provided temporarily until hitobject pooling is implemented. + /// + protected internal new ScheduledDelegate Schedule(Action action) => base.Schedule(action); + private double? lifetimeStart; public override double LifetimeStart diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 1df8c8218f..107d55ff0d 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.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.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; @@ -86,13 +87,34 @@ namespace osu.Game.Rulesets.UI.Scrolling scrollingInfo.Algorithm.Reset(); foreach (var obj in Objects) + { + computeLifetimeStartRecursive(obj); computeInitialStateRecursive(obj); + } + initialStateCache.Validate(); } } - private void computeInitialStateRecursive(DrawableHitObject hitObject) + private void computeLifetimeStartRecursive(DrawableHitObject hitObject) { + hitObject.LifetimeStart = scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, timeRange.Value); + + foreach (var obj in hitObject.NestedHitObjects) + computeLifetimeStartRecursive(obj); + } + + private readonly Dictionary hitObjectInitialStateCache = new Dictionary(); + + // Cant use AddOnce() since the delegate is re-constructed every invocation + private void computeInitialStateRecursive(DrawableHitObject hitObject) => hitObject.Schedule(() => + { + if (!hitObjectInitialStateCache.TryGetValue(hitObject, out var cached)) + cached = hitObjectInitialStateCache[hitObject] = new Cached(); + + if (cached.IsValid) + return; + double endTime = hitObject.HitObject.StartTime; if (hitObject.HitObject is IHasEndTime e) @@ -113,7 +135,6 @@ namespace osu.Game.Rulesets.UI.Scrolling } } - hitObject.LifetimeStart = scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, timeRange.Value); hitObject.LifetimeEnd = scrollingInfo.Algorithm.TimeAt(scrollLength * safe_lifetime_end_multiplier, endTime, timeRange.Value, scrollLength); foreach (var obj in hitObject.NestedHitObjects) @@ -123,7 +144,9 @@ namespace osu.Game.Rulesets.UI.Scrolling // Nested hitobjects don't need to scroll, but they do need accurate positions updatePosition(obj, hitObject.HitObject.StartTime); } - } + + cached.Validate(); + }); protected override void UpdateAfterChildrenLife() { From d74e1b9b6473613784be40adcf3202ae3f3908f1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Sep 2019 15:06:43 +0900 Subject: [PATCH 1029/2815] Remove from dictionary on Remove() --- .../Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 107d55ff0d..bd1f496dfa 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -51,8 +51,13 @@ namespace osu.Game.Rulesets.UI.Scrolling public override bool Remove(DrawableHitObject hitObject) { var result = base.Remove(hitObject); + if (result) + { initialStateCache.Invalidate(); + hitObjectInitialStateCache.Remove(hitObject); + } + return result; } From c06908adf7c653f98fddda7d08f55e50a290b642 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2019 15:37:38 +0900 Subject: [PATCH 1030/2815] Fix spacing specifications --- .../Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index 408468fa73..9b81e8c573 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, - Spacing = new Vector2(3), + Spacing = new Vector2(0, 3), }, date = new DrawableDate(historyItem.CreatedAt) { @@ -59,14 +59,15 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu if (!string.IsNullOrEmpty(prefix)) { linkFlowContainer.AddText(prefix); - linkFlowContainer.AddText($@"{Math.Abs(historyItem.Amount)} kudosu", t => + linkFlowContainer.AddText($@" {Math.Abs(historyItem.Amount)} kudosu ", t => { t.Font = t.Font.With(italics: true); t.Colour = colours.Blue; }); } - linkFlowContainer.AddLinks(formattedSource.Text + " ", formattedSource.Links); + linkFlowContainer.AddLinks(formattedSource.Text, formattedSource.Links); + linkFlowContainer.AddText(" "); linkFlowContainer.AddLink(historyItem.Post.Title, historyItem.Post.Url); } From da4507037333adcda31a9512f101b667cddb77ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2019 15:44:21 +0900 Subject: [PATCH 1031/2815] Group common prefixes together --- .../Kudosu/DrawableKudosuHistoryItem.cs | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index 9b81e8c573..ff64ea5648 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -120,29 +120,17 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu switch (historyItem.Action) { case KudosuAction.VoteGive: - return @"Received"; - case KudosuAction.Give: + case KudosuAction.AllowKudosuGive: + case KudosuAction.RestoreGive: + case KudosuAction.RecalculateGive: return @"Received"; - case KudosuAction.VoteReset: - return @"Lost"; - case KudosuAction.DenyKudosuReset: return @"Denied"; - case KudosuAction.AllowKudosuGive: - return @"Received"; - case KudosuAction.DeleteReset: - return @"Lost"; - - case KudosuAction.RestoreGive: - return @"Received"; - - case KudosuAction.RecalculateGive: - return @"Received"; - + case KudosuAction.VoteReset: case KudosuAction.RecalculateReset: return @"Lost"; From 3d551b08a96a1d9be3130a9b5bf27613fced44bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2019 15:57:23 +0900 Subject: [PATCH 1032/2815] Rename legacy actions --- .../Visual/Online/TestSceneKudosuHistory.cs | 6 +++--- .../API/Requests/GetUserKudosuHistoryRequest.cs | 16 ++++++++-------- .../Sections/Kudosu/DrawableKudosuHistoryItem.cs | 8 ++++---- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs index 8badfeaa23..a4f3bf65e6 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual.Online { Amount = 5, CreatedAt = new DateTimeOffset(new DateTime(2012, 10, 11)), - Action = KudosuAction.Give, + Action = KudosuAction.ForumGive, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 2", @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.Online { Amount = 8, CreatedAt = new DateTimeOffset(new DateTime(2013, 9, 11)), - Action = KudosuAction.Reset, + Action = KudosuAction.ForumReset, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 3", @@ -107,7 +107,7 @@ namespace osu.Game.Tests.Visual.Online { Amount = 7, CreatedAt = new DateTimeOffset(new DateTime(2014, 8, 11)), - Action = KudosuAction.Revoke, + Action = KudosuAction.ForumRevoke, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 4", diff --git a/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs b/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs index dd6f2ccf22..32aa0c15fa 100644 --- a/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs @@ -21,16 +21,16 @@ namespace osu.Game.Online.API.Requests public enum KudosuAction { - Give, - VoteGive, - Reset, - VoteReset, - DenyKudosuReset, - Revoke, AllowKudosuGive, DeleteReset, - RestoreGive, + DenyKudosuReset, + ForumGive, + ForumReset, + ForumRevoke, RecalculateGive, - RecalculateReset + RecalculateReset, + RestoreGive, + VoteGive, + VoteReset, } } diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index ff64ea5648..5229269def 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -80,10 +80,10 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu case KudosuAction.VoteGive: return @"from obtaining votes in modding post of"; - case KudosuAction.Give: + case KudosuAction.ForumGive: return $@"from {userLink()} for a post at"; - case KudosuAction.Reset: + case KudosuAction.ForumReset: return $@"Kudosu reset by {userLink()} for the post"; case KudosuAction.VoteReset: @@ -92,7 +92,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu case KudosuAction.DenyKudosuReset: return @"from modding post"; - case KudosuAction.Revoke: + case KudosuAction.ForumRevoke: return $@"Denied kudosu by {userLink()} for the post"; case KudosuAction.AllowKudosuGive: @@ -120,7 +120,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu switch (historyItem.Action) { case KudosuAction.VoteGive: - case KudosuAction.Give: + case KudosuAction.ForumGive: case KudosuAction.AllowKudosuGive: case KudosuAction.RestoreGive: case KudosuAction.RecalculateGive: From 0985b1679f86de6a4a96d34edb0e84cb30a55dac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2019 15:57:55 +0900 Subject: [PATCH 1033/2815] Move enum to response class --- .../Visual/Online/TestSceneKudosuHistory.cs | 1 - .../API/Requests/GetUserKudosuHistoryRequest.cs | 15 --------------- .../API/Requests/Responses/APIKudosuHistory.cs | 15 +++++++++++++++ .../Sections/Kudosu/DrawableKudosuHistoryItem.cs | 1 - 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs index a4f3bf65e6..84152e40fa 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs @@ -10,7 +10,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Online.API.Requests; using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Tests.Visual.Online diff --git a/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs b/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs index 32aa0c15fa..e90e297672 100644 --- a/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs @@ -18,19 +18,4 @@ namespace osu.Game.Online.API.Requests protected override string Target => $"users/{userId}/kudosu"; } - - public enum KudosuAction - { - AllowKudosuGive, - DeleteReset, - DenyKudosuReset, - ForumGive, - ForumReset, - ForumRevoke, - RecalculateGive, - RecalculateReset, - RestoreGive, - VoteGive, - VoteReset, - } } diff --git a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs index f2297f7a10..25b11a6cf9 100644 --- a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs +++ b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs @@ -53,4 +53,19 @@ namespace osu.Game.Online.API.Requests.Responses public KudosuAction Action; } + + public enum KudosuAction + { + AllowKudosuGive, + DeleteReset, + DenyKudosuReset, + ForumGive, + ForumReset, + ForumRevoke, + RecalculateGive, + RecalculateReset, + RestoreGive, + VoteGive, + VoteReset, + } } diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index 5229269def..6efe9825ec 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Online.API.Requests; using osu.Game.Online.Chat; using System; using osuTK; From 0a89603e79871ffa10c7858759ce231a6113f2b5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Sep 2019 16:07:16 +0900 Subject: [PATCH 1034/2815] Fix hit error potentially not displaying with null hitwindows --- osu.Game/Screens/Play/HUDOverlay.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 8e642ea552..eac45f9214 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -259,7 +259,9 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20, Right = 10 }, }; - protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset.Objects.FirstOrDefault()?.HitWindows); + protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay( + scoreProcessor, + drawableRuleset.Objects.Concat(drawableRuleset.Objects.SelectMany(h => h.NestedHitObjects)).FirstOrDefault(h => h.HitWindows != null)?.HitWindows); protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay(); From f3656475de102de8379409868d63aecc321ebe9f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Sep 2019 16:10:30 +0900 Subject: [PATCH 1035/2815] Return null hitwindows for non-time-based objects --- osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 3 +++ osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs | 3 +++ osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 ++ osu.Game.Rulesets.Osu/Objects/SliderTick.cs | 3 +++ osu.Game.Rulesets.Osu/Objects/Spinner.cs | 3 +++ osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 3 +++ osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs | 3 +++ osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs | 3 +++ osu.Game.Rulesets.Taiko/Objects/Swell.cs | 3 +++ osu.Game.Rulesets.Taiko/Objects/SwellTick.cs | 3 +++ 10 files changed, 29 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index 5e9f46d9c7..d28d04b3c1 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -5,6 +5,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Mania.Objects @@ -99,5 +100,7 @@ namespace osu.Game.Rulesets.Mania.Objects } public override Judgement CreateJudgement() => new HoldNoteJudgement(); + + protected override HitWindows CreateHitWindows() => null; } } diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs index c133ee73b1..6bb21633b6 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs @@ -3,6 +3,7 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Judgements; +using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mania.Objects { @@ -12,5 +13,7 @@ namespace osu.Game.Rulesets.Mania.Objects public class HoldNoteTick : ManiaHitObject { public override Judgement CreateJudgement() => new HoldNoteTickJudgement(); + + protected override HitWindows CreateHitWindows() => null; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index d3279652c7..93231844bb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -229,5 +229,7 @@ namespace osu.Game.Rulesets.Osu.Objects nodeIndex < NodeSamples.Count ? NodeSamples[nodeIndex] : Samples; public override Judgement CreateJudgement() => new OsuJudgement(); + + protected override HitWindows CreateHitWindows() => null; } } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs index 85439699dd..60e9084ed3 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs @@ -4,6 +4,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; namespace osu.Game.Rulesets.Osu.Objects @@ -30,5 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects } public override Judgement CreateJudgement() => new OsuJudgement(); + + protected override HitWindows CreateHitWindows() => null; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 8a2fd3b7aa..69c779a182 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -6,6 +6,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Types; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; namespace osu.Game.Rulesets.Osu.Objects @@ -31,5 +32,7 @@ namespace osu.Game.Rulesets.Osu.Objects } public override Judgement CreateJudgement() => new OsuJudgement(); + + protected override HitWindows CreateHitWindows() => null; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 1d25735fe3..3ed52f21f0 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -6,6 +6,7 @@ using System; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Taiko.Judgements; namespace osu.Game.Rulesets.Taiko.Objects @@ -86,5 +87,7 @@ namespace osu.Game.Rulesets.Taiko.Objects } public override Judgement CreateJudgement() => new TaikoDrumRollJudgement(); + + protected override HitWindows CreateHitWindows() => null; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs index 8448036f76..39e2b45e24 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Taiko.Judgements; namespace osu.Game.Rulesets.Taiko.Objects @@ -25,5 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Objects public double HitWindow => TickSpacing / 2; public override Judgement CreateJudgement() => new TaikoDrumRollTickJudgement(); + + protected override HitWindows CreateHitWindows() => null; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs index 2a03c23934..830e640242 100644 --- a/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Taiko.Judgements; namespace osu.Game.Rulesets.Taiko.Objects @@ -9,5 +10,7 @@ namespace osu.Game.Rulesets.Taiko.Objects public class StrongHitObject : TaikoHitObject { public override Judgement CreateJudgement() => new TaikoStrongJudgement(); + + protected override HitWindows CreateHitWindows() => null; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs index befa728570..e7812841bf 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs @@ -4,6 +4,7 @@ using System; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Taiko.Judgements; namespace osu.Game.Rulesets.Taiko.Objects @@ -33,5 +34,7 @@ namespace osu.Game.Rulesets.Taiko.Objects } public override Judgement CreateJudgement() => new TaikoSwellJudgement(); + + protected override HitWindows CreateHitWindows() => null; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs index c2ae784b2a..049fa7de5f 100644 --- a/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Taiko.Judgements; namespace osu.Game.Rulesets.Taiko.Objects @@ -9,5 +10,7 @@ namespace osu.Game.Rulesets.Taiko.Objects public class SwellTick : TaikoHitObject { public override Judgement CreateJudgement() => new TaikoSwellTickJudgement(); + + protected override HitWindows CreateHitWindows() => null; } } From f2bdf94a1dda65e33a45ce3007b03c4d95f649ed Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Sep 2019 16:28:14 +0900 Subject: [PATCH 1036/2815] Add HitWindows to JudgementResult to indicate timing errors --- .../TestSceneDrawableJudgement.cs | 3 ++- .../Judgements/OsuJudgementResult.cs | 5 +++-- .../Objects/Drawables/DrawableOsuHitObject.cs | 3 ++- osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs | 2 +- .../TestSceneTaikoPlayfield.cs | 8 ++++---- .../Visual/Gameplay/TestSceneBarHitErrorMeter.cs | 2 +- osu.Game/Rulesets/Judgements/JudgementResult.cs | 14 +++++++++++++- .../Objects/Drawables/DrawableHitObject.cs | 6 ++++-- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 +- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 3 +++ 10 files changed, 34 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs index 82a8d0e5e6..6d240ee009 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Scoring; @@ -24,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Tests { foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType().Skip(1)) AddStep("Show " + result.GetDescription(), () => SetContents(() => - new DrawableOsuJudgement(new JudgementResult(null) { Type = result }, null) + new DrawableOsuJudgement(new JudgementResult(null, new HitWindows()) { Type = result }, null) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuJudgementResult.cs b/osu.Game.Rulesets.Osu/Judgements/OsuJudgementResult.cs index c7661bddb1..367c2c8f14 100644 --- a/osu.Game.Rulesets.Osu/Judgements/OsuJudgementResult.cs +++ b/osu.Game.Rulesets.Osu/Judgements/OsuJudgementResult.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Osu.Judgements { @@ -9,8 +10,8 @@ namespace osu.Game.Rulesets.Osu.Judgements { public ComboResult ComboType; - public OsuJudgementResult(Judgement judgement) - : base(judgement) + public OsuJudgementResult(Judgement judgement, HitWindows hitWindows) + : base(judgement, hitWindows) { } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index b4f5642f45..02b6a932d3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Graphics.Containers; +using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -41,6 +42,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength); - protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(judgement); + protected override JudgementResult CreateResult(Judgement judgement, HitWindows hitWindows) => new OsuJudgementResult(judgement, hitWindows); } } diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index cf0565c6da..08dc355bcf 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Osu.Scoring } } - protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(judgement); + protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(judgement, null); public override HitWindows CreateHitWindows() => new OsuHitWindows(); } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs index 3c84d900a6..2db1e3e70a 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs @@ -144,7 +144,7 @@ namespace osu.Game.Rulesets.Taiko.Tests var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; - ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement(), new TaikoHitWindows()) { Type = hitResult }); } private void addStrongHitJudgement(bool kiai) @@ -159,13 +159,13 @@ namespace osu.Game.Rulesets.Taiko.Tests var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; - ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult }); - ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new TaikoStrongJudgement()) { Type = HitResult.Great }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement(), new TaikoHitWindows()) { Type = hitResult }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new TaikoStrongJudgement(), new TaikoHitWindows()) { Type = HitResult.Great }); } private void addMissJudgement() { - ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new TaikoJudgement()) { Type = HitResult.Miss }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new TaikoJudgement(), new TaikoHitWindows()) { Type = HitResult.Miss }); } private void addBarLine(bool major, double delay = scroll_time) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs index f20440249b..0376e775bc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs @@ -106,7 +106,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void newJudgement(double offset = 0) { - var judgement = new JudgementResult(new Judgement()) + var judgement = new JudgementResult(new Judgement(), hitWindows) { TimeOffset = offset == 0 ? RNG.Next(-150, 150) : offset, Type = HitResult.Perfect, diff --git a/osu.Game/Rulesets/Judgements/JudgementResult.cs b/osu.Game/Rulesets/Judgements/JudgementResult.cs index 195fe316ac..aea3c9085b 100644 --- a/osu.Game/Rulesets/Judgements/JudgementResult.cs +++ b/osu.Game/Rulesets/Judgements/JudgementResult.cs @@ -1,6 +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.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; @@ -21,6 +23,13 @@ namespace osu.Game.Rulesets.Judgements /// public readonly Judgement Judgement; + /// + /// The which the was judged against. + /// May be null to indicate that the timing error should not be displayed to the user. + /// + [CanBeNull] + public readonly HitWindows HitWindows; + /// /// The offset from a perfect hit at which this occurred. /// Populated when this is applied via . @@ -56,9 +65,12 @@ namespace osu.Game.Rulesets.Judgements /// Creates a new . /// /// The to refer to for scoring information. - public JudgementResult(Judgement judgement) + /// The which the was judged against. + /// May be null to indicate that the timing error should not be displayed to the user. + public JudgementResult([NotNull] Judgement judgement, [CanBeNull] HitWindows hitWindows) { Judgement = judgement; + HitWindows = hitWindows; } public override string ToString() => $"{Type} (Score:{Judgement.NumericResultFor(this)} HP:{Judgement.HealthIncreaseFor(this)} {Judgement})"; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 4a6f261905..4106f8320f 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (judgement != null) { - Result = CreateResult(judgement); + Result = CreateResult(judgement, HitObject.HitWindows); if (Result == null) throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); } @@ -401,7 +401,9 @@ namespace osu.Game.Rulesets.Objects.Drawables /// Creates the that represents the scoring result for this . /// /// The that provides the scoring information. - protected virtual JudgementResult CreateResult(Judgement judgement) => new JudgementResult(judgement); + /// The which the was judged against. + /// May be null to indicate that the timing error should not be displayed to the user. + protected virtual JudgementResult CreateResult(Judgement judgement, HitWindows hitWindows) => new JudgementResult(judgement, hitWindows); } public abstract class DrawableHitObject : DrawableHitObject diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 3b7e457990..3230551386 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -442,7 +442,7 @@ namespace osu.Game.Rulesets.Scoring /// Creates the that represents the scoring result for a . /// /// The that provides the scoring information. - protected virtual JudgementResult CreateResult(Judgement judgement) => new JudgementResult(judgement); + protected virtual JudgementResult CreateResult(Judgement judgement) => new JudgementResult(judgement, null); } public enum ScoringMode diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index cdfa0e993b..3e925a5a50 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -49,6 +49,9 @@ namespace osu.Game.Screens.Play.HUD private void onNewJudgement(JudgementResult result) { + if (result.HitWindows == null) + return; + foreach (var c in Children) c.OnNewJudgement(result); } From d21d68b36c4271d611db888c1917e09ee93b95ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2019 16:34:11 +0900 Subject: [PATCH 1037/2815] Refactor to match web implementation 1:1 --- .../Visual/Online/TestSceneKudosuHistory.cs | 33 +++-- .../Requests/Responses/APIKudosuHistory.cs | 44 ++++-- .../Kudosu/DrawableKudosuHistoryItem.cs | 138 +++++++++--------- 3 files changed, 122 insertions(+), 93 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs index 84152e40fa..325d657f0e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs @@ -58,7 +58,8 @@ namespace osu.Game.Tests.Visual.Online { Amount = 10, CreatedAt = new DateTimeOffset(new DateTime(2011, 11, 11)), - Action = KudosuAction.DenyKudosuReset, + Source = KudosuSource.DenyKudosu, + Action = KudosuAction.Reset, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 1", @@ -74,7 +75,8 @@ namespace osu.Game.Tests.Visual.Online { Amount = 5, CreatedAt = new DateTimeOffset(new DateTime(2012, 10, 11)), - Action = KudosuAction.ForumGive, + Source = KudosuSource.Forum, + Action = KudosuAction.Give, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 2", @@ -90,7 +92,8 @@ namespace osu.Game.Tests.Visual.Online { Amount = 8, CreatedAt = new DateTimeOffset(new DateTime(2013, 9, 11)), - Action = KudosuAction.ForumReset, + Source = KudosuSource.Forum, + Action = KudosuAction.Reset, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 3", @@ -106,7 +109,8 @@ namespace osu.Game.Tests.Visual.Online { Amount = 7, CreatedAt = new DateTimeOffset(new DateTime(2014, 8, 11)), - Action = KudosuAction.ForumRevoke, + Source = KudosuSource.Forum, + Action = KudosuAction.Revoke, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 4", @@ -122,7 +126,8 @@ namespace osu.Game.Tests.Visual.Online { Amount = 100, CreatedAt = new DateTimeOffset(new DateTime(2015, 7, 11)), - Action = KudosuAction.VoteGive, + Source = KudosuSource.Vote, + Action = KudosuAction.Give, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 5", @@ -138,7 +143,8 @@ namespace osu.Game.Tests.Visual.Online { Amount = 20, CreatedAt = new DateTimeOffset(new DateTime(2016, 6, 11)), - Action = KudosuAction.VoteReset, + Source = KudosuSource.Vote, + Action = KudosuAction.Reset, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 6", @@ -154,7 +160,8 @@ namespace osu.Game.Tests.Visual.Online { Amount = 11, CreatedAt = new DateTimeOffset(new DateTime(2016, 6, 11)), - Action = KudosuAction.AllowKudosuGive, + Source = KudosuSource.AllowKudosu, + Action = KudosuAction.Give, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 7", @@ -170,7 +177,8 @@ namespace osu.Game.Tests.Visual.Online { Amount = 24, CreatedAt = new DateTimeOffset(new DateTime(2014, 6, 11)), - Action = KudosuAction.DeleteReset, + Source = KudosuSource.Delete, + Action = KudosuAction.Reset, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 8", @@ -186,7 +194,8 @@ namespace osu.Game.Tests.Visual.Online { Amount = 12, CreatedAt = new DateTimeOffset(new DateTime(2016, 6, 11)), - Action = KudosuAction.RestoreGive, + Source = KudosuSource.Restore, + Action = KudosuAction.Give, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 9", @@ -202,7 +211,8 @@ namespace osu.Game.Tests.Visual.Online { Amount = 2, CreatedAt = new DateTimeOffset(new DateTime(2012, 6, 11)), - Action = KudosuAction.RecalculateGive, + Source = KudosuSource.Recalculate, + Action = KudosuAction.Give, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 10", @@ -218,7 +228,8 @@ namespace osu.Game.Tests.Visual.Online { Amount = 32, CreatedAt = new DateTimeOffset(new DateTime(2019, 8, 11)), - Action = KudosuAction.RecalculateReset, + Source = KudosuSource.Recalculate, + Action = KudosuAction.Reset, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 11", diff --git a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs index 25b11a6cf9..d596ddc560 100644 --- a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs +++ b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using Humanizer; +using System.Linq; using Newtonsoft.Json; namespace osu.Game.Online.API.Requests.Responses @@ -39,33 +39,45 @@ namespace osu.Game.Online.API.Requests.Responses public string Username; } + public KudosuSource Source; + + public KudosuAction Action; + [JsonProperty("action")] private string action { set { - //We will receive something like "event.action" or just "action" - string parsed = value.Contains(".") ? value.Split('.')[0].Pascalize() + value.Split('.')[1].Pascalize() : value.Pascalize(); + // incoming action may contain a prefix. if it doesn't, it's a legacy forum event. - Action = (KudosuAction)Enum.Parse(typeof(KudosuAction), parsed); + string[] split = value.Split('.'); + + if (split.Length > 1) + Enum.TryParse(split.First().Replace("_", ""), true, out Source); + else + Source = KudosuSource.Forum; + + Enum.TryParse(split.Last(), true, out Action); } } + } - public KudosuAction Action; + public enum KudosuSource + { + Unknown, + AllowKudosu, + Delete, + DenyKudosu, + Forum, + Recalculate, + Restore, + Vote } public enum KudosuAction { - AllowKudosuGive, - DeleteReset, - DenyKudosuReset, - ForumGive, - ForumReset, - ForumRevoke, - RecalculateGive, - RecalculateReset, - RestoreGive, - VoteGive, - VoteReset, + Give, + Reset, + Revoke, } } diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index 6efe9825ec..d0cfe9fa54 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -51,91 +51,97 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu private void load() { date.Colour = colours.GreySeafoamLighter; - - string prefix = getPrefix(historyItem); - var formattedSource = MessageFormatter.FormatText(getSource(historyItem)); - - if (!string.IsNullOrEmpty(prefix)) - { - linkFlowContainer.AddText(prefix); - linkFlowContainer.AddText($@" {Math.Abs(historyItem.Amount)} kudosu ", t => - { - t.Font = t.Font.With(italics: true); - t.Colour = colours.Blue; - }); - } - + var formattedSource = MessageFormatter.FormatText(getString(historyItem)); linkFlowContainer.AddLinks(formattedSource.Text, formattedSource.Links); - linkFlowContainer.AddText(" "); - linkFlowContainer.AddLink(historyItem.Post.Title, historyItem.Post.Url); } - private string getSource(APIKudosuHistory historyItem) + private string getString(APIKudosuHistory item) { - string userLink() => $"[{historyItem.Giver?.Url} {historyItem.Giver?.Username}]"; + string amount = $"{Math.Abs(item.Amount)} kudosu"; + string post = $"[{item.Post.Title}]({item.Post.Url})"; - switch (historyItem.Action) + switch (item.Source) { - case KudosuAction.VoteGive: - return @"from obtaining votes in modding post of"; + case KudosuSource.AllowKudosu: + switch (item.Action) + { + case KudosuAction.Give: + return $"Received {amount} from kudosu deny repeal of modding post {post}"; + } - case KudosuAction.ForumGive: - return $@"from {userLink()} for a post at"; + break; - case KudosuAction.ForumReset: - return $@"Kudosu reset by {userLink()} for the post"; + case KudosuSource.DenyKudosu: + switch (item.Action) + { + case KudosuAction.Reset: + return $"Denied {amount} from modding post {post}"; + } - case KudosuAction.VoteReset: - return @"from losing votes in modding post of"; + break; - case KudosuAction.DenyKudosuReset: - return @"from modding post"; + case KudosuSource.Delete: + switch (item.Action) + { + case KudosuAction.Reset: + return $"Lost {amount} from modding post deletion of {post}"; + } - case KudosuAction.ForumRevoke: - return $@"Denied kudosu by {userLink()} for the post"; + break; - case KudosuAction.AllowKudosuGive: - return @"from kudosu deny repeal of modding post"; + case KudosuSource.Restore: + switch (item.Action) + { + case KudosuAction.Give: + return $"Received {amount} from modding post restoration of {post}"; + } - case KudosuAction.DeleteReset: - return @"from modding post deletion of"; + break; - case KudosuAction.RestoreGive: - return @"from modding post restoration of"; + case KudosuSource.Vote: + switch (item.Action) + { + case KudosuAction.Give: + return $"Received {amount} from obtaining votes in modding post of {post}"; - case KudosuAction.RecalculateGive: - return @"from votes recalculation in modding post of"; + case KudosuAction.Reset: + return $"Lost {amount} from losing votes in modding post of {post}"; + } - case KudosuAction.RecalculateReset: - return @"from votes recalculation in modding post of"; + break; - default: - return @"from unknown event"; + case KudosuSource.Recalculate: + switch (item.Action) + { + case KudosuAction.Give: + return $"Received {amount} from votes recalculation in modding post of {post}"; + + case KudosuAction.Reset: + return $"Lost {amount} from votes recalculation in modding post of {post}"; + } + + break; + + case KudosuSource.Forum: + + string giver = $"[{item.Giver?.Username}]({item.Giver?.Url})"; + + switch (historyItem.Action) + { + case KudosuAction.Give: + return $"Received {amount} from {giver} for a post at {post}"; + + case KudosuAction.Reset: + return $"Kudosu reset by {giver} for the post {post}"; + + case KudosuAction.Revoke: + return $"Denied kudosu by {giver} for the post {post}"; + } + + break; } - } - private string getPrefix(APIKudosuHistory historyItem) - { - switch (historyItem.Action) - { - case KudosuAction.VoteGive: - case KudosuAction.ForumGive: - case KudosuAction.AllowKudosuGive: - case KudosuAction.RestoreGive: - case KudosuAction.RecalculateGive: - return @"Received"; - - case KudosuAction.DenyKudosuReset: - return @"Denied"; - - case KudosuAction.DeleteReset: - case KudosuAction.VoteReset: - case KudosuAction.RecalculateReset: - return @"Lost"; - - default: - return null; - } + return $"Unknown event ({amount} change)"; } } } From 841da7d69147ce0c1490eae4c500cbd802d1805b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2019 17:12:32 +0900 Subject: [PATCH 1038/2815] Fix potential null reference on DirectPanel unbind --- osu.Game/Overlays/Direct/DirectPanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index 641423f21f..a9b6633864 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -35,7 +35,7 @@ namespace osu.Game.Overlays.Direct private BeatmapSetOverlay beatmapSetOverlay; public PreviewTrack Preview => PlayButton.Preview; - public Bindable PreviewPlaying => PlayButton.Playing; + public Bindable PreviewPlaying => PlayButton?.Playing; protected abstract PlayButton PlayButton { get; } protected abstract Box PreviewBar { get; } From 0c73c5acf3b104ebfe5b13832946be6752c5de1c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Sep 2019 17:14:40 +0900 Subject: [PATCH 1039/2815] Expose full hitobject rather than hit windows --- .../TestSceneDrawableJudgement.cs | 2 +- .../Judgements/OsuJudgementResult.cs | 4 ++-- .../Objects/Drawables/DrawableOsuHitObject.cs | 3 +-- .../Scoring/OsuScoreProcessor.cs | 2 +- .../TestSceneTaikoPlayfield.cs | 8 ++++---- .../Gameplay/TestSceneBarHitErrorMeter.cs | 2 +- .../Rulesets/Judgements/JudgementResult.cs | 19 +++++++++---------- .../Objects/Drawables/DrawableHitObject.cs | 6 ++---- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 5 +++-- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 2 +- 10 files changed, 25 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs index 6d240ee009..433ec6bd25 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Tests { foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType().Skip(1)) AddStep("Show " + result.GetDescription(), () => SetContents(() => - new DrawableOsuJudgement(new JudgementResult(null, new HitWindows()) { Type = result }, null) + new DrawableOsuJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuJudgementResult.cs b/osu.Game.Rulesets.Osu/Judgements/OsuJudgementResult.cs index 367c2c8f14..15444b847b 100644 --- a/osu.Game.Rulesets.Osu/Judgements/OsuJudgementResult.cs +++ b/osu.Game.Rulesets.Osu/Judgements/OsuJudgementResult.cs @@ -10,8 +10,8 @@ namespace osu.Game.Rulesets.Osu.Judgements { public ComboResult ComboType; - public OsuJudgementResult(Judgement judgement, HitWindows hitWindows) - : base(judgement, hitWindows) + public OsuJudgementResult(HitObject hitObject, Judgement judgement) + : base(hitObject, judgement) { } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 02b6a932d3..fcd42314fc 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Graphics.Containers; -using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -42,6 +41,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength); - protected override JudgementResult CreateResult(Judgement judgement, HitWindows hitWindows) => new OsuJudgementResult(judgement, hitWindows); + protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(HitObject, judgement); } } diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 08dc355bcf..66ef020d09 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Osu.Scoring } } - protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(judgement, null); + protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement) => new OsuJudgementResult(hitObject, judgement); public override HitWindows CreateHitWindows() => new OsuHitWindows(); } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs index 2db1e3e70a..6fd16c213b 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs @@ -144,7 +144,7 @@ namespace osu.Game.Rulesets.Taiko.Tests var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; - ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement(), new TaikoHitWindows()) { Type = hitResult }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult }); } private void addStrongHitJudgement(bool kiai) @@ -159,13 +159,13 @@ namespace osu.Game.Rulesets.Taiko.Tests var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; - ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement(), new TaikoHitWindows()) { Type = hitResult }); - ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new TaikoStrongJudgement(), new TaikoHitWindows()) { Type = HitResult.Great }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new HitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great }); } private void addMissJudgement() { - ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new TaikoJudgement(), new TaikoHitWindows()) { Type = HitResult.Miss }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = HitResult.Miss }); } private void addBarLine(bool major, double delay = scroll_time) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs index 0376e775bc..e9c15dab9b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs @@ -106,7 +106,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void newJudgement(double offset = 0) { - var judgement = new JudgementResult(new Judgement(), hitWindows) + var judgement = new JudgementResult(new HitObject(), new Judgement()) { TimeOffset = offset == 0 ? RNG.Next(-150, 150) : offset, Type = HitResult.Perfect, diff --git a/osu.Game/Rulesets/Judgements/JudgementResult.cs b/osu.Game/Rulesets/Judgements/JudgementResult.cs index aea3c9085b..56dc121b17 100644 --- a/osu.Game/Rulesets/Judgements/JudgementResult.cs +++ b/osu.Game/Rulesets/Judgements/JudgementResult.cs @@ -19,16 +19,16 @@ namespace osu.Game.Rulesets.Judgements public HitResult Type; /// - /// The which this applies for. + /// The which was judged. /// - public readonly Judgement Judgement; + [NotNull] + public readonly HitObject HitObject; /// - /// The which the was judged against. - /// May be null to indicate that the timing error should not be displayed to the user. + /// The which this applies for. /// - [CanBeNull] - public readonly HitWindows HitWindows; + [NotNull] + public readonly Judgement Judgement; /// /// The offset from a perfect hit at which this occurred. @@ -64,13 +64,12 @@ namespace osu.Game.Rulesets.Judgements /// /// Creates a new . /// + /// The which was judged. /// The to refer to for scoring information. - /// The which the was judged against. - /// May be null to indicate that the timing error should not be displayed to the user. - public JudgementResult([NotNull] Judgement judgement, [CanBeNull] HitWindows hitWindows) + public JudgementResult([NotNull] HitObject hitObject, [NotNull] Judgement judgement) { + HitObject = hitObject; Judgement = judgement; - HitWindows = hitWindows; } public override string ToString() => $"{Type} (Score:{Judgement.NumericResultFor(this)} HP:{Judgement.HealthIncreaseFor(this)} {Judgement})"; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 4106f8320f..4073bd53ab 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (judgement != null) { - Result = CreateResult(judgement, HitObject.HitWindows); + Result = CreateResult(judgement); if (Result == null) throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); } @@ -401,9 +401,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// Creates the that represents the scoring result for this . /// /// The that provides the scoring information. - /// The which the was judged against. - /// May be null to indicate that the timing error should not be displayed to the user. - protected virtual JudgementResult CreateResult(Judgement judgement, HitWindows hitWindows) => new JudgementResult(judgement, hitWindows); + protected virtual JudgementResult CreateResult(Judgement judgement) => new JudgementResult(HitObject, judgement); } public abstract class DrawableHitObject : DrawableHitObject diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 3230551386..86c2c07f2a 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -275,7 +275,7 @@ namespace osu.Game.Rulesets.Scoring if (judgement == null) return; - var result = CreateResult(judgement); + var result = CreateResult(obj, judgement); if (result == null) throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); @@ -441,8 +441,9 @@ namespace osu.Game.Rulesets.Scoring /// /// Creates the that represents the scoring result for a . /// + /// The which was judged. /// The that provides the scoring information. - protected virtual JudgementResult CreateResult(Judgement judgement) => new JudgementResult(judgement, null); + protected virtual JudgementResult CreateResult(HitObject hitObject, Judgement judgement) => new JudgementResult(hitObject, judgement); } public enum ScoringMode diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 3e925a5a50..adda94d629 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -49,7 +49,7 @@ namespace osu.Game.Screens.Play.HUD private void onNewJudgement(JudgementResult result) { - if (result.HitWindows == null) + if (result.HitObject.HitWindows == null) return; foreach (var c in Children) From f6102b4d920ca15ade9af87434ceac6e856d7f9c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Sep 2019 17:15:36 +0900 Subject: [PATCH 1040/2815] Adjust xmldoc --- osu.Game/Rulesets/Objects/HitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index bf04963b76..f0547550e0 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Objects /// /// Creates the for this . - /// This can be null to indicate that the has no . + /// This can be null to indicate that the has no and timing errors should not be displayed to the user. /// /// This will only be invoked if hasn't been set externally (e.g. from a . /// From 4c150839c0de41487da978017ec4d496e1663417 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Sep 2019 17:38:52 +0900 Subject: [PATCH 1041/2815] Fix potential diffcalc hitwindow nullref --- .../Difficulty/ManiaDifficultyCalculator.cs | 7 ++++++- .../Difficulty/OsuDifficultyCalculator.cs | 6 +++++- .../Difficulty/TaikoDifficultyCalculator.cs | 6 +++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 4a9c22d339..d945abdb04 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -11,7 +11,9 @@ using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Difficulty.Preprocessing; using osu.Game.Rulesets.Mania.Difficulty.Skills; using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mania.Difficulty { @@ -32,12 +34,15 @@ namespace osu.Game.Rulesets.Mania.Difficulty if (beatmap.HitObjects.Count == 0) return new ManiaDifficultyAttributes { Mods = mods, Skills = skills }; + HitWindows hitWindows = new ManiaHitWindows(); + hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); + return new ManiaDifficultyAttributes { StarRating = difficultyValue(skills) * star_scaling_factor, Mods = mods, // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future - GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate, + GreatHitWindow = (int)(hitWindows.Great / 2) / clockRate, Skills = skills }; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index c197933233..61e9f60cdd 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -9,6 +9,7 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Skills; using osu.Game.Rulesets.Osu.Mods; @@ -34,8 +35,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2; + HitWindows hitWindows = new OsuHitWindows(); + hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); + // Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future - double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate; + double hitWindowGreat = (int)(hitWindows.Great / 2) / clockRate; double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate; int maxCombo = beatmap.HitObjects.Count; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index c8f3e18911..fc93bccb94 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -8,6 +8,7 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; using osu.Game.Rulesets.Taiko.Difficulty.Skills; using osu.Game.Rulesets.Taiko.Mods; @@ -29,12 +30,15 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (beatmap.HitObjects.Count == 0) return new TaikoDifficultyAttributes { Mods = mods, Skills = skills }; + HitWindows hitWindows = new TaikoHitWindows(); + hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); + return new TaikoDifficultyAttributes { StarRating = skills.Single().DifficultyValue() * star_scaling_factor, Mods = mods, // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future - GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate, + GreatHitWindow = (int)(hitWindows.Great / 2) / clockRate, MaxCombo = beatmap.HitObjects.Count(h => h is Hit), Skills = skills }; From f20e07136a842f2b23bd52f9a45039757fbcb084 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Sep 2019 17:48:41 +0900 Subject: [PATCH 1042/2815] Add attribute to catch potential future nullrefs --- osu.Game/Rulesets/Objects/HitObject.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index f0547550e0..5e029139d9 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using JetBrains.Annotations; using Newtonsoft.Json; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -56,6 +57,7 @@ namespace osu.Game.Rulesets.Objects /// /// The hit windows for this . /// + [CanBeNull] public HitWindows HitWindows { get; set; } private readonly List nestedHitObjects = new List(); @@ -116,6 +118,7 @@ namespace osu.Game.Rulesets.Objects /// This will only be invoked if hasn't been set externally (e.g. from a . /// /// + [CanBeNull] protected virtual HitWindows CreateHitWindows() => new HitWindows(); } } From 9c53430a0f7ac80f7d9a4786a7b58c0000f4803a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2019 18:18:59 +0900 Subject: [PATCH 1043/2815] Only initialise when required --- osu.Game/Skinning/SkinnableSound.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index bf647baeec..c402df1bde 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -16,7 +16,7 @@ namespace osu.Game.Skinning { private readonly ISampleInfo[] hitSamples; - private readonly List<(AdjustableProperty, BindableDouble)> adjustments = new List<(AdjustableProperty, BindableDouble)>(); + private List<(AdjustableProperty, BindableDouble)> adjustments; private SampleChannel[] channels; @@ -56,13 +56,15 @@ namespace osu.Game.Skinning public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable) { + if (adjustments == null) adjustments = new List<(AdjustableProperty, BindableDouble)>(); + adjustments.Add((type, adjustBindable)); channels?.ForEach(c => c.AddAdjustment(type, adjustBindable)); } public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable) { - adjustments.Remove((type, adjustBindable)); + adjustments?.Remove((type, adjustBindable)); channels?.ForEach(c => c.RemoveAdjustment(type, adjustBindable)); } @@ -84,8 +86,9 @@ namespace osu.Game.Skinning ch.Looping = looping; ch.Volume.Value = s.Volume / 100.0; - foreach (var adjustment in adjustments) - ch.AddAdjustment(adjustment.Item1, adjustment.Item2); + if (adjustments != null) + foreach (var adjustment in adjustments) + ch.AddAdjustment(adjustment.Item1, adjustment.Item2); } return ch; From f08b523abf9b9296f241dd32b841da15549eb3bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2019 18:20:30 +0900 Subject: [PATCH 1044/2815] Name tuple items --- osu.Game/Skinning/SkinnableSound.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index c402df1bde..07ffbb8a51 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -16,7 +16,7 @@ namespace osu.Game.Skinning { private readonly ISampleInfo[] hitSamples; - private List<(AdjustableProperty, BindableDouble)> adjustments; + private List<(AdjustableProperty property, BindableDouble bindable)> adjustments; private SampleChannel[] channels; @@ -88,7 +88,7 @@ namespace osu.Game.Skinning if (adjustments != null) foreach (var adjustment in adjustments) - ch.AddAdjustment(adjustment.Item1, adjustment.Item2); + ch.AddAdjustment(adjustment.property, adjustment.bindable); } return ch; From 8302658186b7ff64f63a9c72190d0fa1603d14ad Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Sep 2019 18:31:33 +0900 Subject: [PATCH 1045/2815] Fix other potential nullref cases that rider missed --- .../Objects/Drawables/DrawableHoldNote.cs | 3 +++ .../Objects/Drawables/DrawableNote.cs | 3 +++ .../TestSceneShaking.cs | 7 +++++-- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 8 +++++++- .../Objects/Drawables/DrawableHitCircle.cs | 5 +++++ .../Replays/OsuAutoGenerator.cs | 18 +++++++++--------- .../Replays/OsuAutoGeneratorBase.cs | 10 ++++++++++ .../Objects/Drawables/DrawableHit.cs | 5 +++++ 8 files changed, 47 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index fc3b6885d7..c5c157608f 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.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.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; @@ -209,6 +210,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables protected override void CheckForResult(bool userTriggered, double timeOffset) { + Debug.Assert(HitObject.HitWindows != null); + // Factor in the release lenience timeOffset /= release_window_lenience; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index dccff7f6ac..2cd81104a3 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.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.Diagnostics; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -52,6 +53,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables protected override void CheckForResult(bool userTriggered, double timeOffset) { + Debug.Assert(HitObject.HitWindows != null); + if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs index 84a73c7cfc..585fdb9cb4 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.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.Diagnostics; using osu.Framework.MathUtils; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; @@ -13,8 +14,10 @@ namespace osu.Game.Rulesets.Osu.Tests { var drawableHitObject = base.CreateDrawableHitCircle(circle, auto); - Scheduler.AddDelayed(() => drawableHitObject.TriggerJudgement(), - drawableHitObject.HitObject.StartTime - (drawableHitObject.HitObject.HitWindows.HalfWindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current); + Debug.Assert(drawableHitObject.HitObject.HitWindows != null); + + double delay = drawableHitObject.HitObject.StartTime - (drawableHitObject.HitObject.HitWindows.HalfWindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current; + Scheduler.AddDelayed(() => drawableHitObject.TriggerJudgement(), delay); return drawableHitObject; } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 5625028707..649b01c132 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Types; @@ -38,7 +39,12 @@ namespace osu.Game.Rulesets.Osu.Mods if ((osuHit.HitObject is IHasEndTime hasEnd && time > hasEnd.EndTime) || osuHit.IsHit) continue; - requiresHit |= osuHit is DrawableHitCircle && osuHit.IsHovered && osuHit.HitObject.HitWindows.CanBeHit(relativetime); + if (osuHit is DrawableHitCircle && osuHit.IsHovered) + { + Debug.Assert(osuHit.HitObject.HitWindows != null); + requiresHit |= osuHit.HitObject.HitWindows.CanBeHit(relativetime); + } + requiresHold |= (osuHit is DrawableSlider slider && (slider.Ball.IsHovered || osuHit.IsHovered)) || osuHit is DrawableSpinner; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 0af278f6a4..1c40e37262 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -87,6 +88,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void CheckForResult(bool userTriggered, double timeOffset) { + Debug.Assert(HitObject.HitWindows != null); + if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) @@ -119,6 +122,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void UpdateStateTransforms(ArmedState state) { + Debug.Assert(HitObject.HitWindows != null); + switch (state) { case ArmedState.Idle: diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 690263c6a0..e1614984de 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -92,20 +92,20 @@ namespace osu.Game.Rulesets.Osu.Replays double endTime = (prev as IHasEndTime)?.EndTime ?? prev.StartTime; // Make the cursor stay at a hitObject as long as possible (mainly for autopilot). - if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Miss) > endTime + h.HitWindows.HalfWindowFor(HitResult.Meh) + 50) + if (h.StartTime - HitWindows.HalfWindowFor(HitResult.Miss) > endTime + HitWindows.HalfWindowFor(HitResult.Meh) + 50) { - if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); - if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Miss), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); + if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); + if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - HitWindows.HalfWindowFor(HitResult.Miss), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); } - else if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh) > endTime + h.HitWindows.HalfWindowFor(HitResult.Meh) + 50) + else if (h.StartTime - HitWindows.HalfWindowFor(HitResult.Meh) > endTime + HitWindows.HalfWindowFor(HitResult.Meh) + 50) { - if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); - if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); + if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); + if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); } - else if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Good) > endTime + h.HitWindows.HalfWindowFor(HitResult.Good) + 50) + else if (h.StartTime - HitWindows.HalfWindowFor(HitResult.Good) > endTime + HitWindows.HalfWindowFor(HitResult.Good) + 50) { - if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Good), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); - if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Good), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); + if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + HitWindows.HalfWindowFor(HitResult.Good), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); + if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - HitWindows.HalfWindowFor(HitResult.Good), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); } } diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs index 9ab358ee12..7c94027c28 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs @@ -6,6 +6,8 @@ using osu.Game.Beatmaps; using System; using System.Collections.Generic; using osu.Game.Replays; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Replays; @@ -27,6 +29,11 @@ namespace osu.Game.Rulesets.Osu.Replays /// protected readonly double FrameDelay; + /// + /// The hit windows. + /// + protected readonly HitWindows HitWindows; + #endregion #region Construction / Initialisation @@ -41,6 +48,9 @@ namespace osu.Game.Rulesets.Osu.Replays // We are using ApplyModsToRate and not ApplyModsToTime to counteract the speed up / slow down from HalfTime / DoubleTime so that we remain at a constant framerate of 60 fps. FrameDelay = ApplyModsToRate(1000.0 / 60.0); + + HitWindows = new OsuHitWindows(); + HitWindows.SetDifficulty(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); } #endregion diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index fa45067210..0942b37f58 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.Linq; using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; @@ -34,6 +35,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected override void CheckForResult(bool userTriggered, double timeOffset) { + Debug.Assert(HitObject.HitWindows != null); + if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) @@ -94,6 +97,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected override void UpdateStateTransforms(ArmedState state) { + Debug.Assert(HitObject.HitWindows != null); + switch (state) { case ArmedState.Idle: From 90671e061715b3c7458e3d2d1f8d32e2bb033ec5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Sep 2019 18:58:13 +0900 Subject: [PATCH 1046/2815] Attempt to not break per-hitobject hitwindows --- .../Replays/OsuAutoGenerator.cs | 53 +++++++++++++++---- .../Replays/OsuAutoGeneratorBase.cs | 8 --- 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index e1614984de..e5fa571d4d 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -6,9 +6,11 @@ using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using System; +using System.Diagnostics; using System.Linq; using osu.Framework.Graphics; using osu.Game.Replays; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Scoring; @@ -36,6 +38,8 @@ namespace osu.Game.Rulesets.Osu.Replays /// private readonly double reactionTime; + private readonly HitWindows defaultHitWindows; + /// /// What easing to use when moving between hitobjects /// @@ -50,6 +54,9 @@ namespace osu.Game.Rulesets.Osu.Replays { // Already superhuman, but still somewhat realistic reactionTime = ApplyModsToRate(100); + + defaultHitWindows = new OsuHitWindows(); + defaultHitWindows.SetDifficulty(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); } #endregion @@ -91,21 +98,49 @@ namespace osu.Game.Rulesets.Osu.Replays { double endTime = (prev as IHasEndTime)?.EndTime ?? prev.StartTime; + HitWindows hitWindows = null; + + switch (h) + { + case HitCircle hitCircle: + hitWindows = hitCircle.HitWindows; + break; + + case Slider slider: + hitWindows = slider.TailCircle.HitWindows; + break; + + case Spinner _: + hitWindows = defaultHitWindows; + break; + } + + Debug.Assert(hitWindows != null); + // Make the cursor stay at a hitObject as long as possible (mainly for autopilot). - if (h.StartTime - HitWindows.HalfWindowFor(HitResult.Miss) > endTime + HitWindows.HalfWindowFor(HitResult.Meh) + 50) + if (h.StartTime - hitWindows.HalfWindowFor(HitResult.Miss) > endTime + hitWindows.HalfWindowFor(HitResult.Meh) + 50) { - if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); - if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - HitWindows.HalfWindowFor(HitResult.Miss), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); + if (!(prev is Spinner) && h.StartTime - endTime < 1000) + AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); + + if (!(h is Spinner)) + AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.HalfWindowFor(HitResult.Miss), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); } - else if (h.StartTime - HitWindows.HalfWindowFor(HitResult.Meh) > endTime + HitWindows.HalfWindowFor(HitResult.Meh) + 50) + else if (h.StartTime - hitWindows.HalfWindowFor(HitResult.Meh) > endTime + hitWindows.HalfWindowFor(HitResult.Meh) + 50) { - if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); - if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); + if (!(prev is Spinner) && h.StartTime - endTime < 1000) + AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); + + if (!(h is Spinner)) + AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.HalfWindowFor(HitResult.Meh), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); } - else if (h.StartTime - HitWindows.HalfWindowFor(HitResult.Good) > endTime + HitWindows.HalfWindowFor(HitResult.Good) + 50) + else if (h.StartTime - hitWindows.HalfWindowFor(HitResult.Good) > endTime + hitWindows.HalfWindowFor(HitResult.Good) + 50) { - if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + HitWindows.HalfWindowFor(HitResult.Good), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); - if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - HitWindows.HalfWindowFor(HitResult.Good), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); + if (!(prev is Spinner) && h.StartTime - endTime < 1000) + AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.HalfWindowFor(HitResult.Good), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); + + if (!(h is Spinner)) + AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.HalfWindowFor(HitResult.Good), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); } } diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs index 7c94027c28..3c889d1f52 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs @@ -29,11 +29,6 @@ namespace osu.Game.Rulesets.Osu.Replays ///
protected readonly double FrameDelay; - /// - /// The hit windows. - /// - protected readonly HitWindows HitWindows; - #endregion #region Construction / Initialisation @@ -48,9 +43,6 @@ namespace osu.Game.Rulesets.Osu.Replays // We are using ApplyModsToRate and not ApplyModsToTime to counteract the speed up / slow down from HalfTime / DoubleTime so that we remain at a constant framerate of 60 fps. FrameDelay = ApplyModsToRate(1000.0 / 60.0); - - HitWindows = new OsuHitWindows(); - HitWindows.SetDifficulty(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); } #endregion From 0bfe4650c3da9796c112de5abbca281817ddbccf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2019 19:01:17 +0900 Subject: [PATCH 1047/2815] Early return if no change occurred in looping value --- osu.Game/Skinning/SkinnableSound.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 07ffbb8a51..8012500280 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -45,6 +45,8 @@ namespace osu.Game.Skinning get => looping; set { + if (value == looping) return; + looping = value; channels?.ForEach(c => c.Looping = looping); From b460f76fa6ad54a9098babe7bef500caa938923e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2019 19:01:43 +0900 Subject: [PATCH 1048/2815] Adjust file spacing slightly --- osu.Game/Skinning/SkinnableSound.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 8012500280..3d0219ed93 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -54,6 +54,7 @@ namespace osu.Game.Skinning } public void Play() => channels?.ForEach(c => c.Play()); + public void Stop() => channels?.ForEach(c => c.Stop()); public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable) From 0ee0184e01c5fd055d20744b998426e8f4d44ecb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2019 19:13:06 +0900 Subject: [PATCH 1049/2815] Remove unnecessary usings --- osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs index 3c889d1f52..9ab358ee12 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs @@ -6,8 +6,6 @@ using osu.Game.Beatmaps; using System; using System.Collections.Generic; using osu.Game.Replays; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Replays; From 53c254c6a5ca0b59f1471a670beaf81d083f0450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 2 Sep 2019 19:01:36 +0200 Subject: [PATCH 1050/2815] Replace Array.IndexOf() with Contains() --- osu.Game/Graphics/UserInterface/HoverClickSounds.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs index 050e5a2835..e64b9259f1 100644 --- a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs @@ -1,7 +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.Audio; using osu.Framework.Audio.Sample; @@ -36,9 +36,7 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnMouseUp(MouseUpEvent e) { - var index = Array.IndexOf(buttons, e.Button); - bool shouldPlayEffect = index > -1 && index < buttons.Length; - + bool shouldPlayEffect = buttons.Contains(e.Button); // examine the button pressed first for short-circuiting // in most usages it is more likely that another button was pressed than that the cursor left the drawable bounds if (shouldPlayEffect && Contains(e.ScreenSpaceMousePosition)) From 6ca17bdfd5106815ae2bbdd544302bf821679fed Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 2 Sep 2019 10:42:21 -0700 Subject: [PATCH 1051/2815] Center icon and text using anchor and origin instead of margin --- osu.Game/Overlays/Chat/Selection/ChannelListItem.cs | 3 ++- osu.Game/Overlays/Direct/DirectPanel.cs | 3 ++- osu.Game/Overlays/Music/PlaylistItem.cs | 6 +++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs b/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs index cb0639d85d..31c48deee0 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs @@ -121,10 +121,11 @@ namespace osu.Game.Overlays.Chat.Selection { new SpriteIcon { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Icon = FontAwesome.Solid.User, Size = new Vector2(text_size - 2), Shadow = false, - Margin = new MarginPadding { Top = 1 }, }, new OsuSpriteText { diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index a9b6633864..6074aa16a5 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -190,10 +190,11 @@ namespace osu.Game.Overlays.Direct text = new OsuSpriteText { Font = OsuFont.GetFont(weight: FontWeight.SemiBold, italics: true) }, new SpriteIcon { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Icon = icon, Shadow = true, Size = new Vector2(14), - Margin = new MarginPadding { Top = 1 }, }, }; diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index df37a1b2c7..29b6ae00f3 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -161,12 +161,12 @@ namespace osu.Game.Overlays.Music { public PlaylistItemHandle() { - Anchor = Anchor.TopLeft; - Origin = Anchor.TopLeft; + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; Size = new Vector2(12); Icon = FontAwesome.Solid.Bars; Alpha = 0f; - Margin = new MarginPadding { Left = 5, Top = 2 }; + Margin = new MarginPadding { Left = 5 }; } public override bool HandlePositionalInput => IsPresent; From 5d3f3b7cc2376d925e4269de7f982ba7a58d8897 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 2 Sep 2019 10:59:43 -0700 Subject: [PATCH 1052/2815] Add spacing to diff icons on direct panels --- osu.Game/Overlays/Direct/DirectGridPanel.cs | 1 + osu.Game/Overlays/Direct/DirectListPanel.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs index 7bf94c1483..2528ccec41 100644 --- a/osu.Game/Overlays/Direct/DirectGridPanel.cs +++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs @@ -151,6 +151,7 @@ namespace osu.Game.Overlays.Direct AutoSizeAxes = Axes.X, Height = 20, Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding }, + Spacing = new Vector2(3), Children = GetDifficultyIcons(colours), }, }, diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs index 158ff648dd..b64142dfe7 100644 --- a/osu.Game/Overlays/Direct/DirectListPanel.cs +++ b/osu.Game/Overlays/Direct/DirectListPanel.cs @@ -129,6 +129,7 @@ namespace osu.Game.Overlays.Direct AutoSizeAxes = Axes.X, Height = 20, Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding }, + Spacing = new Vector2(3), Children = GetDifficultyIcons(colours), }, }, From ce446826e8f9c87e63b8c9483c2fc057114ab345 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 2 Sep 2019 11:00:12 -0700 Subject: [PATCH 1053/2815] Match web's max diff icon number --- osu.Game/Overlays/Direct/DirectPanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index a9b6633864..c911a521b9 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Direct public readonly BeatmapSetInfo SetInfo; private const double hover_transition_time = 400; - private const int maximum_difficulty_icons = 15; + private const int maximum_difficulty_icons = 10; private Container content; From 3d1f051437650be239c1af83f253ea942248fbd1 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 2 Sep 2019 11:13:34 -0700 Subject: [PATCH 1054/2815] Move hover tests after key tests --- .../Gameplay/TestSceneGameplayMenuOverlay.cs | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs index 4727140d99..0d8a84fa51 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs @@ -56,17 +56,14 @@ namespace osu.Game.Tests.Visual.Gameplay pauseOverlay.Retries = failOverlay.Retries = retryCount; }); - AddToggleStep("Toggle pause overlay", t => pauseOverlay.ToggleVisibility()); - AddToggleStep("Toggle fail overlay", t => failOverlay.ToggleVisibility()); - - testHideResets(); - testEnterWithoutSelection(); testKeyUpFromInitial(); testKeyDownFromInitial(); testKeyUpWrapping(); testKeyDownWrapping(); + testHideResets(); + testMouseSelectionAfterKeySelection(); testKeySelectionAfterMouseSelection(); @@ -76,19 +73,6 @@ namespace osu.Game.Tests.Visual.Gameplay testEnterKeySelection(); } - /// - /// Test that hiding the overlay after hovering a button will reset the overlay to the initial state with no buttons selected. - /// - private void testHideResets() - { - AddStep("Show overlay", () => failOverlay.Show()); - - AddStep("Hover first button", () => InputManager.MoveMouseTo(failOverlay.Buttons.First())); - AddStep("Hide overlay", () => failOverlay.Hide()); - - AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected.Value)); - } - /// /// Tests that pressing enter after an overlay shows doesn't trigger an event because a selection hasn't occurred. /// @@ -162,6 +146,19 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Hide overlay", () => failOverlay.Hide()); } + /// + /// Test that hiding the overlay after hovering a button will reset the overlay to the initial state with no buttons selected. + /// + private void testHideResets() + { + AddStep("Show overlay", () => failOverlay.Show()); + + AddStep("Hover first button", () => InputManager.MoveMouseTo(failOverlay.Buttons.First())); + AddStep("Hide overlay", () => failOverlay.Hide()); + + AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected.Value)); + } + /// /// Tests that hovering a button that was previously selected with the keyboard correctly selects the new button and deselects the previous button. /// From cb55159b280d19c9376718093e34491c76852d02 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 3 Sep 2019 01:28:51 +0300 Subject: [PATCH 1055/2815] Use float types for cursor sizes --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 8 ++++---- osu.Game/Configuration/OsuConfigManager.cs | 4 ++-- osu.Game/Graphics/Cursor/MenuCursor.cs | 7 +++---- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 10 +++++----- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index eb1977a13d..f384b1150e 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private bool cursorExpand; - private Bindable cursorScale; + private Bindable cursorScale; private Bindable autoCursorScale; private readonly IBindable beatmap = new Bindable(); @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor this.beatmap.BindTo(beatmap); this.beatmap.ValueChanged += _ => calculateScale(); - cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize); + cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize); cursorScale.ValueChanged += _ => calculateScale(); autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize); @@ -70,12 +70,12 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private void calculateScale() { - float scale = (float)cursorScale.Value; + float scale = cursorScale.Value; if (autoCursorScale.Value && beatmap.Value != null) { // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier. - scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY); + scale *= 1f - 0.7f * (1f + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY; } scaleTarget.Scale = new Vector2(scale); diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 0cecbb225f..c901a28a59 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -58,8 +58,8 @@ namespace osu.Game.Configuration Set(OsuSetting.AudioOffset, 0, -500.0, 500.0, 1); // Input - Set(OsuSetting.MenuCursorSize, 1.0, 0.5f, 2, 0.01); - Set(OsuSetting.GameplayCursorSize, 1.0, 0.1f, 2, 0.01); + Set(OsuSetting.MenuCursorSize, 1.0f, 0.5f, 2f, 0.01f); + Set(OsuSetting.GameplayCursorSize, 1.0f, 0.1f, 2f, 0.01f); Set(OsuSetting.AutoCursorSize, false); Set(OsuSetting.MouseDisableButtons, false); diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index e103798355..5a83d8e4ce 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -124,7 +124,7 @@ namespace osu.Game.Graphics.Cursor public class Cursor : Container { private Container cursorContainer; - private Bindable cursorScale; + private Bindable cursorScale; private const float base_scale = 0.15f; public Sprite AdditiveLayer; @@ -159,9 +159,8 @@ namespace osu.Game.Graphics.Cursor } }; - cursorScale = config.GetBindable(OsuSetting.MenuCursorSize); - cursorScale.ValueChanged += scale => cursorContainer.Scale = new Vector2((float)scale.NewValue * base_scale); - cursorScale.TriggerChange(); + cursorScale = config.GetBindable(OsuSetting.MenuCursorSize); + cursorScale.BindValueChanged(scale => cursorContainer.Scale = new Vector2(scale.NewValue * base_scale), true); } } diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 35be930a2e..d3029d8ab9 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -35,16 +35,16 @@ namespace osu.Game.Overlays.Settings.Sections Children = new Drawable[] { skinDropdown = new SkinSettingsDropdown(), - new SettingsSlider + new SettingsSlider { LabelText = "Menu cursor size", - Bindable = config.GetBindable(OsuSetting.MenuCursorSize), + Bindable = config.GetBindable(OsuSetting.MenuCursorSize), KeyboardStep = 0.01f }, - new SettingsSlider + new SettingsSlider { LabelText = "Gameplay cursor size", - Bindable = config.GetBindable(OsuSetting.GameplayCursorSize), + Bindable = config.GetBindable(OsuSetting.GameplayCursorSize), KeyboardStep = 0.01f }, new SettingsCheckbox @@ -95,7 +95,7 @@ namespace osu.Game.Overlays.Settings.Sections } } - private class SizeSlider : OsuSliderBar + private class SizeSlider : OsuSliderBar { public override string TooltipText => Current.Value.ToString(@"0.##x"); } From de6dba9716ba720f8756b38662ecc48aa32d148d Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 3 Sep 2019 01:50:52 +0300 Subject: [PATCH 1056/2815] Use float type for chat overlay height --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- osu.Game/Overlays/ChatOverlay.cs | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 0cecbb225f..9246acb00b 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -31,7 +31,7 @@ namespace osu.Game.Configuration Set(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation); - Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2, 1); + Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f); // Online settings Set(OsuSetting.Username, string.Empty); diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 53a05656b1..6f848c7627 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -54,7 +54,7 @@ namespace osu.Game.Overlays private Box chatBackground; private Box tabBackground; - public Bindable ChatHeight { get; set; } + public Bindable ChatHeight { get; set; } private Container channelSelectionContainer; protected ChannelSelectionOverlay ChannelSelectionOverlay; @@ -190,14 +190,13 @@ namespace osu.Game.Overlays ChannelSelectionOverlay.OnRequestJoin = channel => channelManager.JoinChannel(channel); ChannelSelectionOverlay.OnRequestLeave = channelManager.LeaveChannel; - ChatHeight = config.GetBindable(OsuSetting.ChatDisplayHeight); - ChatHeight.ValueChanged += height => + ChatHeight = config.GetBindable(OsuSetting.ChatDisplayHeight); + ChatHeight.BindValueChanged(height => { - chatContainer.Height = (float)height.NewValue; - channelSelectionContainer.Height = 1f - (float)height.NewValue; - tabBackground.FadeTo(height.NewValue == 1 ? 1 : 0.8f, 200); - }; - ChatHeight.TriggerChange(); + chatContainer.Height = height.NewValue; + channelSelectionContainer.Height = 1f - height.NewValue; + tabBackground.FadeTo(height.NewValue == 1f ? 1f : 0.8f, 200); + }, true); chatBackground.Colour = colours.ChatBlue; @@ -273,7 +272,7 @@ namespace osu.Game.Overlays } } - private double startDragChatHeight; + private float startDragChatHeight; private bool isDragging; protected override bool OnDragStart(DragStartEvent e) @@ -291,7 +290,7 @@ namespace osu.Game.Overlays { if (isDragging) { - double targetChatHeight = startDragChatHeight - (e.MousePosition.Y - e.MouseDownPosition.Y) / Parent.DrawSize.Y; + float targetChatHeight = startDragChatHeight - (e.MousePosition.Y - e.MouseDownPosition.Y) / Parent.DrawSize.Y; // If the channel selection screen is shown, mind its minimum height if (ChannelSelectionOverlay.State.Value == Visibility.Visible && targetChatHeight > 1f - channel_selection_min_height) From 3b769128a82c8f9cb0e588208a7d5f7eee3938d6 Mon Sep 17 00:00:00 2001 From: jorolf Date: Tue, 3 Sep 2019 00:57:29 +0200 Subject: [PATCH 1057/2815] Add a 60bpm beat when no beatmap is playing --- .../TestSceneBeatSyncedContainer.cs | 10 ++- .../Containers/BeatSyncedContainer.cs | 64 +++++++++++++++---- 2 files changed, 62 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index 94228e22f0..d84ffa0d93 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -2,6 +2,7 @@ // 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.Audio.Track; @@ -25,6 +26,11 @@ namespace osu.Game.Tests.Visual.UserInterface { private readonly NowPlayingOverlay np; + public override IReadOnlyList RequiredTypes => new[] + { + typeof(BeatSyncedContainer) + }; + [Cached] private MusicController musicController = new MusicController(); @@ -154,7 +160,9 @@ namespace osu.Game.Tests.Visual.UserInterface if (timingPoints[timingPoints.Count - 1] == current) return current; - return timingPoints[timingPoints.IndexOf(current) + 1]; + int index = timingPoints.IndexOf(current); // -1 means that this is a "default beat" + + return index == -1 ? current : timingPoints[index + 1]; } private int calculateBeatCount(TimingControlPoint current) diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 621eeea2b7..f6d950c414 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -33,23 +33,46 @@ namespace osu.Game.Graphics.Containers ///
public double TimeSinceLastBeat { get; private set; } + /// + /// Default length of a beat in milliseconds. Used whenever there is no beatmap or track playing. + /// + private const double default_beat_length = 60000.0 / 60.0; + + private TimingControlPoint defaultTiming; + private EffectControlPoint defaultEffect; + private TrackAmplitudes defaultAmplitudes; + protected override void Update() { - if (!Beatmap.Value.TrackLoaded || !Beatmap.Value.BeatmapLoaded) return; + Track track = null; + IBeatmap beatmap = null; - var track = Beatmap.Value.Track; - var beatmap = Beatmap.Value.Beatmap; + double currentTrackTime; + TimingControlPoint timingPoint; + EffectControlPoint effectPoint; - if (track == null || beatmap == null) - return; + if (Beatmap.Value.TrackLoaded && Beatmap.Value.BeatmapLoaded) + { + track = Beatmap.Value.Track; + beatmap = Beatmap.Value.Beatmap; + } - double currentTrackTime = track.Length > 0 ? track.CurrentTime + EarlyActivationMilliseconds : Clock.CurrentTime; + if (track != null && beatmap != null && track.IsRunning) + { + currentTrackTime = track.Length > 0 ? track.CurrentTime + EarlyActivationMilliseconds : Clock.CurrentTime; - TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(currentTrackTime); - EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(currentTrackTime); + timingPoint = beatmap.ControlPointInfo.TimingPointAt(currentTrackTime); + effectPoint = beatmap.ControlPointInfo.EffectPointAt(currentTrackTime); - if (timingPoint.BeatLength == 0) - return; + if (timingPoint.BeatLength == 0) + return; + } + else + { + currentTrackTime = Clock.CurrentTime; + timingPoint = defaultTiming; + effectPoint = defaultEffect; + } int beatIndex = (int)((currentTrackTime - timingPoint.Time) / timingPoint.BeatLength); @@ -67,7 +90,7 @@ namespace osu.Game.Graphics.Containers return; using (BeginDelayedSequence(-TimeSinceLastBeat, true)) - OnNewBeat(beatIndex, timingPoint, effectPoint, track.CurrentAmplitudes); + OnNewBeat(beatIndex, timingPoint, effectPoint, track?.CurrentAmplitudes ?? defaultAmplitudes); lastBeat = beatIndex; lastTimingPoint = timingPoint; @@ -77,6 +100,25 @@ namespace osu.Game.Graphics.Containers private void load(IBindable beatmap) { Beatmap.BindTo(beatmap); + defaultTiming = new TimingControlPoint + { + BeatLength = default_beat_length, + AutoGenerated = true, + Time = 0 + }; + defaultEffect = new EffectControlPoint + { + Time = 0, + AutoGenerated = true, + KiaiMode = false, + OmitFirstBarLine = false + }; + defaultAmplitudes = new TrackAmplitudes + { + FrequencyAmplitudes = new float[256], + LeftChannel = 0, + RightChannel = 0 + }; } protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) From eab06995d0378d8e984937acd34e3009c9dc0156 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 3 Sep 2019 11:37:56 +0900 Subject: [PATCH 1058/2815] Add some whitespace --- osu.Game/Graphics/Containers/BeatSyncedContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index f6d950c414..370d044ba4 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -100,12 +100,14 @@ namespace osu.Game.Graphics.Containers private void load(IBindable beatmap) { Beatmap.BindTo(beatmap); + defaultTiming = new TimingControlPoint { BeatLength = default_beat_length, AutoGenerated = true, Time = 0 }; + defaultEffect = new EffectControlPoint { Time = 0, @@ -113,6 +115,7 @@ namespace osu.Game.Graphics.Containers KiaiMode = false, OmitFirstBarLine = false }; + defaultAmplitudes = new TrackAmplitudes { FrequencyAmplitudes = new float[256], From 444419b2e6f99fcab6775245a346dd6f6db0c092 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Sep 2019 12:04:49 +0900 Subject: [PATCH 1059/2815] 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 93a9a073a4..743508baf8 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -60,7 +60,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 330018d5cb..03207dfdf7 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -14,7 +14,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 9f8d82ad1e..ec76ceaf95 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -117,7 +117,7 @@ - + From 11dfdc33d519673df52de4a6e09434d0c83d9634 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2019 03:05:45 +0000 Subject: [PATCH 1060/2815] Bump ppy.osu.Game.Resources from 2019.830.0 to 2019.903.0 Bumps [ppy.osu.Game.Resources](https://github.com/ppy/osu-resources) from 2019.830.0 to 2019.903.0. - [Release notes](https://github.com/ppy/osu-resources/releases) - [Commits](https://github.com/ppy/osu-resources/compare/2019.830.0...2019.903.0) Signed-off-by: dependabot-preview[bot] --- 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 93a9a073a4..743508baf8 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -60,7 +60,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 330018d5cb..03207dfdf7 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -14,7 +14,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 9f8d82ad1e..ec76ceaf95 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -117,7 +117,7 @@ - + From 4f3511e8e9637fded836e3f0080b5ba28958ecd8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Sep 2019 12:32:10 +0900 Subject: [PATCH 1061/2815] Fix ring glow lookup being incorrect --- osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs index 00188689dd..30937313fd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Texture = textures.Get("ring-glow"), + Texture = textures.Get("Gameplay/osu/ring-glow"), Blending = BlendingParameters.Additive, Alpha = 0.5f }; From 4cad55cee6a73bb010d0838afeecfa63d38b17a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Sep 2019 13:05:03 +0900 Subject: [PATCH 1062/2815] Move hit windows lookup to DrawableRuleset --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 23 +++++++++++++++++++++++ osu.Game/Screens/Play/HUDOverlay.cs | 5 +---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 0ee9196fb8..a32407d180 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -13,6 +13,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; +using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics.Cursor; using osu.Framework.Input; @@ -390,6 +391,28 @@ namespace osu.Game.Rulesets.UI ///